From 274e234c38fadb5674fb4739d546a4dcbfb8a8b2 Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Thu, 1 Aug 2024 07:29:46 +0200 Subject: [PATCH 1/7] add api v2 token --- .../devices/sonnen/sonnenbatterie/bat.py | 6 +- .../devices/sonnen/sonnenbatterie/config.py | 5 +- .../devices/sonnen/sonnenbatterie/counter.py | 6 +- .../devices/sonnen/sonnenbatterie/inverter.py | 6 +- .../modules/devices/sonnenbatterie/device.py | 105 ++++++++++++++++++ 5 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 packages/modules/devices/sonnenbatterie/device.py diff --git a/packages/modules/devices/sonnen/sonnenbatterie/bat.py b/packages/modules/devices/sonnen/sonnenbatterie/bat.py index 5ac3cc8bbb..88442379cb 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/bat.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/bat.py @@ -20,10 +20,12 @@ def __init__(self, device_id: int, device_address: str, device_variant: int, + api_v2_token: str, component_config: Union[Dict, SonnenbatterieBatSetup]) -> None: self.__device_id = device_id self.__device_address = device_address self.__device_variant = device_variant + self.__api_v2_token = api_v2_token self.component_config = dataclass_from_dict(SonnenbatterieBatSetup, 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) @@ -47,7 +49,9 @@ def __update_variant_0(self) -> BatState: def __read_variant_1(self, api: str = "v1"): return req.get_http_session().get( - "http://" + self.__device_address + "/api/" + api + "/status", timeout=5 + "http://" + self.__device_address + "/api/" + api + "/status", + timeout=5, + headers={"Auth-Token": self.__api_v2_token} if api == "v2" else None ).json() def __update_variant_1(self, api: str = "v1") -> BatState: diff --git a/packages/modules/devices/sonnen/sonnenbatterie/config.py b/packages/modules/devices/sonnen/sonnenbatterie/config.py index ea0df8814e..51d719472c 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/config.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/config.py @@ -5,9 +5,10 @@ class SonnenBatterieConfiguration: - def __init__(self, variant: int = 0, ip_address: Optional[str] = None): + def __init__(self, variant: int = 0, ip_address: Optional[str] = None, api_v2_token: Optional[str] = None): self.variant = variant self.ip_address = ip_address + self.api_v2_token = api_v2_token class SonnenBatterie: @@ -44,7 +45,7 @@ def __init__(self): class SonnenbatterieCounterSetup(ComponentSetup[SonnenbatterieCounterConfiguration]): def __init__(self, - name: str = "SonnenBatterie Zähler", + name: str = "SonnenBatterie EVU-Zähler", type: str = "counter", id: int = 0, configuration: SonnenbatterieCounterConfiguration = None) -> None: diff --git a/packages/modules/devices/sonnen/sonnenbatterie/counter.py b/packages/modules/devices/sonnen/sonnenbatterie/counter.py index fa2a266a03..07f995a858 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/counter.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/counter.py @@ -20,10 +20,12 @@ def __init__(self, device_id: int, device_address: str, device_variant: int, + api_v2_token: str, component_config: Union[Dict, SonnenbatterieCounterSetup]) -> None: self.__device_id = device_id self.__device_address = device_address self.__device_variant = device_variant + self.__api_v2_token = api_v2_token self.component_config = dataclass_from_dict(SonnenbatterieCounterSetup, 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) @@ -31,7 +33,9 @@ def __init__(self, def __read_variant_1(self, api: str = "v1"): return req.get_http_session().get( - "http://" + self.__device_address + "/api/" + api + "/status", timeout=5 + "http://" + self.__device_address + "/api/" + api + "/status", + timeout=5, + headers={"Auth-Token": self.__api_v2_token} if api == "v2" else None ).json() def __update_variant_1(self, api: str = "v1") -> CounterState: diff --git a/packages/modules/devices/sonnen/sonnenbatterie/inverter.py b/packages/modules/devices/sonnen/sonnenbatterie/inverter.py index a8b4122d0a..09e58b899b 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/inverter.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/inverter.py @@ -20,10 +20,12 @@ def __init__(self, device_id: int, device_address: str, device_variant: int, + api_v2_token: str, component_config: Union[Dict, SonnenbatterieInverterSetup]) -> None: self.__device_id = device_id self.__device_address = device_address self.__device_variant = device_variant + self.__api_v2_token = api_v2_token self.component_config = dataclass_from_dict(SonnenbatterieInverterSetup, 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) @@ -31,7 +33,9 @@ def __init__(self, def __read_variant_1(self, api: str = "v1"): return req.get_http_session().get( - "http://" + self.__device_address + "/api/" + api + "/status", timeout=5 + "http://" + self.__device_address + "/api/" + api + "/status", + timeout=5, + headers={"Auth-Token": self.__api_v2_token} if api == "v2" else None ).json() def __update_variant_1(self, api: str = "v1") -> InverterState: diff --git a/packages/modules/devices/sonnenbatterie/device.py b/packages/modules/devices/sonnenbatterie/device.py new file mode 100644 index 0000000000..b0b01e1470 --- /dev/null +++ b/packages/modules/devices/sonnenbatterie/device.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +""" Modul zum Auslesen von sonnenBatterie Speichern. +""" +import logging +from typing import Dict, Union, Optional, List + +from dataclass_utils import dataclass_from_dict +from helpermodules.cli import run_using_positional_cli_args +from modules.common.abstract_device import AbstractDevice, DeviceDescriptor +from modules.common.component_context import SingleComponentUpdateContext +from modules.devices.sonnenbatterie import bat, counter, inverter +from modules.devices.sonnenbatterie.config import (SonnenBatterie, SonnenbatterieBatSetup, SonnenbatterieCounterSetup, + SonnenbatterieInverterSetup) +log = logging.getLogger(__name__) + + +sonnenbatterie_component_classes = Union[ + bat.SonnenbatterieBat, + counter.SonnenbatterieCounter, + inverter.SonnenbatterieInverter +] + + +class Device(AbstractDevice): + COMPONENT_TYPE_TO_CLASS = { + "bat": bat.SonnenbatterieBat, + "counter": counter.SonnenbatterieCounter, + "inverter": inverter.SonnenbatterieInverter + } + + def __init__(self, device_config: Union[Dict, SonnenBatterie]) -> None: + self.components = {} # type: Dict[str, sonnenbatterie_component_classes] + try: + self.device_config = dataclass_from_dict(SonnenBatterie, device_config) + except Exception: + log.exception("Fehler im Modul "+self.device_config.name) + + def add_component(self, component_config: Union[Dict, + SonnenbatterieBatSetup, + SonnenbatterieCounterSetup, + SonnenbatterieInverterSetup]) -> 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, + self.device_config.configuration.ip_address, + self.device_config.configuration.variant, + self.device_config.configuration.api_v2_token, + 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: + for component in self.components: + # Auch wenn bei einer Komponente ein Fehler auftritt, sollen alle anderen noch ausgelesen werden. + with SingleComponentUpdateContext(self.components[component].fault_state): + self.components[component].update() + 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 read_legacy(component_type: str, address: str, variant: int, num: Optional[int] = None) -> None: + device_config = SonnenBatterie() + device_config.configuration.ip_address = address + device_config.configuration.variant = variant + dev = Device(device_config) + if component_type in COMPONENT_TYPE_TO_MODULE: + component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() + else: + raise Exception( + "illegal component type " + component_type + ". Allowed values: " + + ','.join(COMPONENT_TYPE_TO_MODULE.keys()) + ) + component_config.id = num + dev.add_component(component_config) + log.debug('SonnenBatterie address: ' + address) + log.debug('SonnenBatterie variant: ' + str(variant)) + dev.update() + + +def main(argv: List[str]): + run_using_positional_cli_args(read_legacy, argv) + + +device_descriptor = DeviceDescriptor(configuration_factory=SonnenBatterie) From 226abb378e9e1c88dcd8c2299179f945b91bcc00 Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Thu, 1 Aug 2024 07:30:10 +0200 Subject: [PATCH 2/7] add consumption counter --- .../devices/sonnen/sonnenbatterie/config.py | 14 +++ .../sonnenbatterie/counter_consumption.py | 116 ++++++++++++++++++ .../modules/devices/sonnenbatterie/device.py | 16 ++- 3 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 packages/modules/devices/sonnenbatterie/counter_consumption.py diff --git a/packages/modules/devices/sonnen/sonnenbatterie/config.py b/packages/modules/devices/sonnen/sonnenbatterie/config.py index 51d719472c..bf9054ed23 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/config.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/config.py @@ -52,6 +52,20 @@ def __init__(self, super().__init__(name, type, id, configuration or SonnenbatterieCounterConfiguration()) +class SonnenbatterieConsumptionCounterConfiguration: + def __init__(self): + pass + + +class SonnenbatterieConsumptionCounterSetup(ComponentSetup[SonnenbatterieCounterConfiguration]): + def __init__(self, + name: str = "SonnenBatterie Verbrauchs-Zähler", + type: str = "counter_consumption", + id: int = 0, + configuration: SonnenbatterieConsumptionCounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or SonnenbatterieConsumptionCounterConfiguration()) + + class SonnenbatterieInverterConfiguration: def __init__(self): pass diff --git a/packages/modules/devices/sonnenbatterie/counter_consumption.py b/packages/modules/devices/sonnenbatterie/counter_consumption.py new file mode 100644 index 0000000000..211fb7b313 --- /dev/null +++ b/packages/modules/devices/sonnenbatterie/counter_consumption.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +import logging +from typing import Dict, Union + +from dataclass_utils import dataclass_from_dict +from modules.common import req +from modules.common.component_state import CounterState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +from modules.common.store import get_counter_value_store +from modules.devices.sonnenbatterie.config import SonnenbatterieConsumptionCounterSetup + +log = logging.getLogger(__name__) + + +class SonnenbatterieConsumptionCounter: + def __init__(self, + device_id: int, + device_address: str, + device_variant: int, + api_v2_token: str, + component_config: Union[Dict, SonnenbatterieConsumptionCounterSetup]) -> None: + self.__device_id = device_id + self.__device_address = device_address + self.__device_variant = device_variant + self.__api_v2_token = api_v2_token + self.component_config = dataclass_from_dict(SonnenbatterieConsumptionCounterSetup, component_config) + self.store = get_counter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + + def __read_variant_3(self): + result = req.get_http_session().get( + "http://" + self.__device_address + "/api/v2/powermeter", + timeout=5, + headers={"Auth-Token": self.__api_v2_token} + ).json() + for channel in result: + if channel["direction"] == "consumption": + return channel + raise ValueError("No consumption channel found") + + def __update_variant_3(self) -> CounterState: + # Auslesen einer Sonnenbatterie 8 oder 10 über die integrierte JSON-API v2 des Batteriesystems + ''' + example data: + [ + { + "a_l1": 0, + "a_l2": 0, + "a_l3": 0, + "channel": 1, + "deviceid": 4, + "direction": "production", + "error": -1, + "kwh_exported": 0, + "kwh_imported": 0, + "v_l1_l2": 0, + "v_l1_n": 0, + "v_l2_l3": 0, + "v_l2_n": 0, + "v_l3_l1": 0, + "v_l3_n": 0, + "va_total": 0, + "var_total": 0, + "w_l1": 0, + "w_l2": 0, + "w_l3": 0, + "w_total": 0 + }, + { + "a_l1": 0, + "a_l2": 0, + "a_l3": 0, + "channel": 2, + "deviceid": 4, + "direction": "consumption", + "error": -1, + "kwh_exported": 0, + "kwh_imported": 0, + "v_l1_l2": 0, + "v_l1_n": 0, + "v_l2_l3": 0, + "v_l2_n": 0, + "v_l3_l1": 0, + "v_l3_n": 0, + "va_total": 0, + "var_total": 0, + "w_l1": 0, + "w_l2": 0, + "w_l3": 0, + "w_total": 0 + } + ] + ''' + counter_state = self.__read_variant_3() + return CounterState( + power=counter_state["w_total"], + powers=[counter_state[f"w_l{phase}"] for phase in range(1, 4)], + currents=[counter_state[f"a_l{phase}"] for phase in range(1, 4)], + voltages=[counter_state[f"v_l{phase}_n"] for phase in range(1, 4)], + imported=counter_state["kwh_imported"], + exported=counter_state["kwh_exported"] + ) + + def update(self) -> None: + log.debug("Variante: " + str(self.__device_variant)) + if self.__device_variant in [0, 1, 2]: + log.debug("Diese Variante bietet keine Verbrauchsdaten!") + elif self.__device_variant == 3: + state = self.__update_variant_3() + else: + raise ValueError("Unbekannte Variante: " + str(self.__device_variant)) + self.store.set(state) + + +component_descriptor = ComponentDescriptor(configuration_factory=SonnenbatterieConsumptionCounterSetup) diff --git a/packages/modules/devices/sonnenbatterie/device.py b/packages/modules/devices/sonnenbatterie/device.py index b0b01e1470..9d6fea9c4e 100644 --- a/packages/modules/devices/sonnenbatterie/device.py +++ b/packages/modules/devices/sonnenbatterie/device.py @@ -8,16 +8,17 @@ from helpermodules.cli import run_using_positional_cli_args from modules.common.abstract_device import AbstractDevice, DeviceDescriptor from modules.common.component_context import SingleComponentUpdateContext -from modules.devices.sonnenbatterie import bat, counter, inverter +from modules.devices.sonnenbatterie import bat, counter, inverter, counter_consumption from modules.devices.sonnenbatterie.config import (SonnenBatterie, SonnenbatterieBatSetup, SonnenbatterieCounterSetup, - SonnenbatterieInverterSetup) + SonnenbatterieInverterSetup, SonnenbatterieConsumptionCounterSetup) log = logging.getLogger(__name__) sonnenbatterie_component_classes = Union[ bat.SonnenbatterieBat, counter.SonnenbatterieCounter, - inverter.SonnenbatterieInverter + inverter.SonnenbatterieInverter, + counter_consumption.SonnenbatterieConsumptionCounter ] @@ -25,7 +26,8 @@ class Device(AbstractDevice): COMPONENT_TYPE_TO_CLASS = { "bat": bat.SonnenbatterieBat, "counter": counter.SonnenbatterieCounter, - "inverter": inverter.SonnenbatterieInverter + "inverter": inverter.SonnenbatterieInverter, + "counter_consumption": counter_consumption.SonnenbatterieConsumptionCounter } def __init__(self, device_config: Union[Dict, SonnenBatterie]) -> None: @@ -38,7 +40,8 @@ def __init__(self, device_config: Union[Dict, SonnenBatterie]) -> None: def add_component(self, component_config: Union[Dict, SonnenbatterieBatSetup, SonnenbatterieCounterSetup, - SonnenbatterieInverterSetup]) -> None: + SonnenbatterieInverterSetup, + SonnenbatterieConsumptionCounterSetup]) -> None: if isinstance(component_config, Dict): component_type = component_config["type"] else: @@ -75,7 +78,8 @@ def update(self) -> None: COMPONENT_TYPE_TO_MODULE = { "bat": bat, "counter": counter, - "inverter": inverter + "inverter": inverter, + "counter_consumption": counter_consumption } From deb8ff9aef1cb05aefcab692d52c0959d4e64b26 Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Mon, 14 Oct 2024 09:10:56 +0200 Subject: [PATCH 3/7] fix apiv2 token after rebase --- packages/modules/devices/sonnen/sonnenbatterie/bat.py | 4 ++-- packages/modules/devices/sonnen/sonnenbatterie/counter.py | 4 ++-- packages/modules/devices/sonnen/sonnenbatterie/device.py | 3 +++ packages/modules/devices/sonnen/sonnenbatterie/inverter.py | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/modules/devices/sonnen/sonnenbatterie/bat.py b/packages/modules/devices/sonnen/sonnenbatterie/bat.py index 88442379cb..ad4804c50c 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/bat.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/bat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Union +from typing import Dict, Optional, Union from dataclass_utils import dataclass_from_dict from modules.common import req @@ -20,7 +20,7 @@ def __init__(self, device_id: int, device_address: str, device_variant: int, - api_v2_token: str, + api_v2_token: Optional[str], component_config: Union[Dict, SonnenbatterieBatSetup]) -> None: self.__device_id = device_id self.__device_address = device_address diff --git a/packages/modules/devices/sonnen/sonnenbatterie/counter.py b/packages/modules/devices/sonnen/sonnenbatterie/counter.py index 07f995a858..a12b909cdc 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/counter.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/counter.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Union +from typing import Dict, Optional, Union from dataclass_utils import dataclass_from_dict from modules.common import req @@ -20,7 +20,7 @@ def __init__(self, device_id: int, device_address: str, device_variant: int, - api_v2_token: str, + api_v2_token: Optional[str], component_config: Union[Dict, SonnenbatterieCounterSetup]) -> None: self.__device_id = device_id self.__device_address = device_address diff --git a/packages/modules/devices/sonnen/sonnenbatterie/device.py b/packages/modules/devices/sonnen/sonnenbatterie/device.py index bfd73cdb81..f44c20b9f8 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/device.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/device.py @@ -21,18 +21,21 @@ def create_bat_component(component_config: SonnenbatterieBatSetup): return SonnenbatterieBat(device_config.id, device_config.configuration.ip_address, device_config.configuration.variant, + device_config.configuration.api_v2_token, component_config) def create_counter_component(component_config: SonnenbatterieCounterSetup): return SonnenbatterieCounter(device_config.id, device_config.configuration.ip_address, device_config.configuration.variant, + device_config.configuration.api_v2_token, component_config) def create_inverter_component(component_config: SonnenbatterieInverterSetup): return SonnenbatterieInverter(device_config.id, device_config.configuration.ip_address, device_config.configuration.variant, + device_config.configuration.api_v2_token, component_config) return ConfigurableDevice( diff --git a/packages/modules/devices/sonnen/sonnenbatterie/inverter.py b/packages/modules/devices/sonnen/sonnenbatterie/inverter.py index 09e58b899b..676b2e4d64 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/inverter.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/inverter.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Union +from typing import Dict, Optional, Union from dataclass_utils import dataclass_from_dict from modules.common import req @@ -19,7 +19,7 @@ class SonnenbatterieInverter(AbstractInverter): def __init__(self, device_id: int, device_address: str, - device_variant: int, + device_variant: Optional[int], api_v2_token: str, component_config: Union[Dict, SonnenbatterieInverterSetup]) -> None: self.__device_id = device_id From 2540c23623abfd5ae83f5176020fc11d07371b1c Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Mon, 14 Oct 2024 09:22:57 +0200 Subject: [PATCH 4/7] fix consumption counter after rebase --- .../sonnenbatterie/counter_consumption.py | 11 +- .../devices/sonnen/sonnenbatterie/device.py | 13 ++- .../modules/devices/sonnenbatterie/device.py | 109 ------------------ 3 files changed, 16 insertions(+), 117 deletions(-) rename packages/modules/devices/{ => sonnen}/sonnenbatterie/counter_consumption.py (93%) delete mode 100644 packages/modules/devices/sonnenbatterie/device.py diff --git a/packages/modules/devices/sonnenbatterie/counter_consumption.py b/packages/modules/devices/sonnen/sonnenbatterie/counter_consumption.py similarity index 93% rename from packages/modules/devices/sonnenbatterie/counter_consumption.py rename to packages/modules/devices/sonnen/sonnenbatterie/counter_consumption.py index 211fb7b313..3b46066f35 100644 --- a/packages/modules/devices/sonnenbatterie/counter_consumption.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/counter_consumption.py @@ -1,26 +1,25 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Union +from typing import Dict, Optional, Union from dataclass_utils import dataclass_from_dict from modules.common import req +from modules.common.abstract_device import AbstractCounter from modules.common.component_state import CounterState from modules.common.component_type import ComponentDescriptor from modules.common.fault_state import ComponentInfo, FaultState from modules.common.store import get_counter_value_store -from modules.devices.sonnenbatterie.config import SonnenbatterieConsumptionCounterSetup +from modules.devices.sonnen.sonnenbatterie.config import SonnenbatterieConsumptionCounterSetup log = logging.getLogger(__name__) -class SonnenbatterieConsumptionCounter: +class SonnenbatterieConsumptionCounter(AbstractCounter): def __init__(self, - device_id: int, device_address: str, device_variant: int, - api_v2_token: str, + api_v2_token: Optional[str], component_config: Union[Dict, SonnenbatterieConsumptionCounterSetup]) -> None: - self.__device_id = device_id self.__device_address = device_address self.__device_variant = device_variant self.__api_v2_token = api_v2_token diff --git a/packages/modules/devices/sonnen/sonnenbatterie/device.py b/packages/modules/devices/sonnen/sonnenbatterie/device.py index f44c20b9f8..13971f70e9 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/device.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/device.py @@ -8,8 +8,10 @@ from modules.devices.sonnen.sonnenbatterie.bat import SonnenbatterieBat from modules.devices.sonnen.sonnenbatterie.config import (SonnenBatterie, SonnenbatterieBatSetup, SonnenbatterieCounterSetup, + SonnenbatterieConsumptionCounterSetup, SonnenbatterieInverterSetup) from modules.devices.sonnen.sonnenbatterie.counter import SonnenbatterieCounter +from modules.devices.sonnen.sonnenbatterie.counter_consumption import SonnenbatterieConsumptionCounter from modules.devices.sonnen.sonnenbatterie.inverter import SonnenbatterieInverter @@ -24,13 +26,19 @@ def create_bat_component(component_config: SonnenbatterieBatSetup): device_config.configuration.api_v2_token, component_config) - def create_counter_component(component_config: SonnenbatterieCounterSetup): + def create_evu_counter_component(component_config: SonnenbatterieCounterSetup): return SonnenbatterieCounter(device_config.id, device_config.configuration.ip_address, device_config.configuration.variant, device_config.configuration.api_v2_token, component_config) + def create_consumption_counter_component(component_config: SonnenbatterieConsumptionCounterSetup): + return SonnenbatterieConsumptionCounter(device_config.configuration.ip_address, + device_config.configuration.variant, + device_config.configuration.api_v2_token, + component_config) + def create_inverter_component(component_config: SonnenbatterieInverterSetup): return SonnenbatterieInverter(device_config.id, device_config.configuration.ip_address, @@ -42,7 +50,8 @@ def create_inverter_component(component_config: SonnenbatterieInverterSetup): device_config=device_config, component_factory=ComponentFactoryByType( bat=create_bat_component, - counter=create_counter_component, + counter=create_evu_counter_component, + counter_consumption=create_consumption_counter_component, inverter=create_inverter_component, ), component_updater=IndependentComponentUpdater(lambda component: component.update()) diff --git a/packages/modules/devices/sonnenbatterie/device.py b/packages/modules/devices/sonnenbatterie/device.py deleted file mode 100644 index 9d6fea9c4e..0000000000 --- a/packages/modules/devices/sonnenbatterie/device.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python3 -""" Modul zum Auslesen von sonnenBatterie Speichern. -""" -import logging -from typing import Dict, Union, Optional, List - -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor -from modules.common.component_context import SingleComponentUpdateContext -from modules.devices.sonnenbatterie import bat, counter, inverter, counter_consumption -from modules.devices.sonnenbatterie.config import (SonnenBatterie, SonnenbatterieBatSetup, SonnenbatterieCounterSetup, - SonnenbatterieInverterSetup, SonnenbatterieConsumptionCounterSetup) -log = logging.getLogger(__name__) - - -sonnenbatterie_component_classes = Union[ - bat.SonnenbatterieBat, - counter.SonnenbatterieCounter, - inverter.SonnenbatterieInverter, - counter_consumption.SonnenbatterieConsumptionCounter -] - - -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "bat": bat.SonnenbatterieBat, - "counter": counter.SonnenbatterieCounter, - "inverter": inverter.SonnenbatterieInverter, - "counter_consumption": counter_consumption.SonnenbatterieConsumptionCounter - } - - def __init__(self, device_config: Union[Dict, SonnenBatterie]) -> None: - self.components = {} # type: Dict[str, sonnenbatterie_component_classes] - try: - self.device_config = dataclass_from_dict(SonnenBatterie, device_config) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) - - def add_component(self, component_config: Union[Dict, - SonnenbatterieBatSetup, - SonnenbatterieCounterSetup, - SonnenbatterieInverterSetup, - SonnenbatterieConsumptionCounterSetup]) -> 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, - self.device_config.configuration.ip_address, - self.device_config.configuration.variant, - self.device_config.configuration.api_v2_token, - 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: - for component in self.components: - # Auch wenn bei einer Komponente ein Fehler auftritt, sollen alle anderen noch ausgelesen werden. - with SingleComponentUpdateContext(self.components[component].fault_state): - self.components[component].update() - 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, - "counter_consumption": counter_consumption -} - - -def read_legacy(component_type: str, address: str, variant: int, num: Optional[int] = None) -> None: - device_config = SonnenBatterie() - device_config.configuration.ip_address = address - device_config.configuration.variant = variant - dev = Device(device_config) - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - component_config.id = num - dev.add_component(component_config) - log.debug('SonnenBatterie address: ' + address) - log.debug('SonnenBatterie variant: ' + str(variant)) - dev.update() - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) - - -device_descriptor = DeviceDescriptor(configuration_factory=SonnenBatterie) From 93190692590213ed6dfd1061a921ef6529e1511b Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Thu, 19 Dec 2024 12:57:58 +0100 Subject: [PATCH 5/7] implement power control --- .../devices/sonnen/sonnenbatterie/bat.py | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/modules/devices/sonnen/sonnenbatterie/bat.py b/packages/modules/devices/sonnen/sonnenbatterie/bat.py index ad4804c50c..4fdeaa930f 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/bat.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/bat.py @@ -47,9 +47,9 @@ def __update_variant_0(self) -> BatState: soc=battery_soc ) - def __read_variant_1(self, api: str = "v1"): + def __read_variant_1(self, api: str = "v1", target: str = "status") -> Dict: return req.get_http_session().get( - "http://" + self.__device_address + "/api/" + api + "/status", + f"http://{self.__device_address}/api/{api}/{target}", timeout=5, headers={"Auth-Token": self.__api_v2_token} if api == "v2" else None ).json() @@ -105,6 +105,32 @@ def __update_variant_1(self, api: str = "v1") -> BatState: exported=exported ) + def __get_json_api_v2_configurations(self) -> Dict: + if self.__device_variant != 3: + raise ValueError("JSON API v2 wird nur für Variante 3 unterstützt!") + return self.__read_variant_1("v2", "configurations") + + def __set_json_api_v2_configurations(self, configuration: Dict) -> None: + if self.__device_variant != 3: + raise ValueError("JSON API v2 wird nur für Variante 3 unterstützt!") + req.get_http_session().put( + f"http://{self.__device_address}/api/v2/configurations", + json=configuration, + headers={"Auth-Token": self.__api_v2_token} + ) + + def __set_json_api_v2_setpoint(self, power_limit: int) -> None: + if self.__device_variant != 3: + raise ValueError("JSON API v2 wird nur für Variante 3 unterstützt!") + command = "charge" + if power_limit < 0: + command = "discharge" + power_limit = -power_limit + req.get_http_session().post( + f"http://{self.__device_address}/api/v2/setpoint/{command}/{power_limit}", + headers={"Auth-Token": self.__api_v2_token, "Content-Type": "application/json"} + ) + def __read_variant_2_element(self, element: str) -> str: response = req.get_http_session().get( 'http://' + self.__device_address + ':7979/rest/devices/battery/' + element, @@ -137,5 +163,23 @@ def update(self) -> None: raise ValueError("Unbekannte Variante: " + str(self.__device_variant)) self.store.set(state) + def set_power_limit(self, power_limit: Optional[int]) -> None: + if self.__device_variant != 3: + raise ValueError("Leistungsvorgabe wird nur für Variante 3 unterstützt!") + operating_mode = self.__get_json_api_v2_configurations()["EM_OperatingMode"] + log.debug(f"Betriebsmodus: aktuell: {operating_mode}") + if power_limit is None: + # Keine Leistungsvorgabe, Betriebsmodus "Eigenverbrauch" aktivieren + if operating_mode == "1": + log.debug("Keine Leistungsvorgabe, aktiviere normale Steuerung durch den Speicher") + self.__set_json_api_v2_configurations({"EM_OperatingMode": "2"}) + else: + # Leistungsvorgabe, Betriebsmodus "Manuell" aktivieren + if operating_mode == "2": + log.debug(f"Leistungsvorgabe: {power_limit}, aktiviere manuelle Steuerung durch openWB") + self.__set_json_api_v2_configurations({"EM_OperatingMode": "1"}) + log.debug(f"Setze Leistungsvorgabe auf: {power_limit}") + self.__set_json_api_v2_setpoint(power_limit) + component_descriptor = ComponentDescriptor(configuration_factory=SonnenbatterieBatSetup) From 0e70896364060897915832a9f752696d3832773f Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Mon, 7 Apr 2025 16:06:26 +0200 Subject: [PATCH 6/7] implement power_limit_controllable --- packages/modules/devices/sonnen/sonnenbatterie/bat.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/modules/devices/sonnen/sonnenbatterie/bat.py b/packages/modules/devices/sonnen/sonnenbatterie/bat.py index 4fdeaa930f..9cdfd483a1 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/bat.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/bat.py @@ -165,7 +165,7 @@ def update(self) -> None: def set_power_limit(self, power_limit: Optional[int]) -> None: if self.__device_variant != 3: - raise ValueError("Leistungsvorgabe wird nur für Variante 3 unterstützt!") + raise ValueError("Leistungsvorgabe wird nur für Variante 'JSON-API v2' unterstützt!") operating_mode = self.__get_json_api_v2_configurations()["EM_OperatingMode"] log.debug(f"Betriebsmodus: aktuell: {operating_mode}") if power_limit is None: @@ -181,5 +181,9 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: log.debug(f"Setze Leistungsvorgabe auf: {power_limit}") self.__set_json_api_v2_setpoint(power_limit) + def power_limit_controllable(self) -> bool: + # Leistungsvorgabe ist nur für Variante 3 (JSON-API v2) möglich + return self.__device_variant == 3 + component_descriptor = ComponentDescriptor(configuration_factory=SonnenbatterieBatSetup) From 929f3d83530fbbbb588dbeccf9066f87edd994b4 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 10 Apr 2025 14:55:56 +0200 Subject: [PATCH 7/7] fix --- .../modules/devices/sonnen/sonnenbatterie/inverter.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/modules/devices/sonnen/sonnenbatterie/inverter.py b/packages/modules/devices/sonnen/sonnenbatterie/inverter.py index 83ec3462bc..f2f584a98e 100644 --- a/packages/modules/devices/sonnen/sonnenbatterie/inverter.py +++ b/packages/modules/devices/sonnen/sonnenbatterie/inverter.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +from typing import Any, Optional, TypedDict +import logging from modules.devices.sonnen.sonnenbatterie.config import SonnenbatterieInverterSetup from modules.common.store import get_inverter_value_store from modules.common.simcount import SimCounter @@ -7,12 +9,6 @@ from modules.common.component_state import InverterState from modules.common.abstract_device import AbstractInverter from modules.common import req -from typing import Any, TypedDict -from typing import Dict, Optional, Union -import logging -<< << << < HEAD -== == == = ->>>>>> > upstream/master log = logging.getLogger(__name__)