diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index a6803941fd..c35ba3b974 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -58,7 +58,7 @@ class UpdateConfig: - DATASTORE_VERSION = 138 + DATASTORE_VERSION = 139 valid_topic = [ "^openWB/bat/config/bat_control_activated$", @@ -3500,3 +3500,29 @@ def upgrade(topic: str, payload) -> Optional[dict]: return {topic: payload_device} self._loop_all_received_topics(upgrade) self._append_datastore_version(138) + + def upgrade_datastore_139(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if re.search("^openWB/system/device/[0-9]+/config$", topic) is not None: + payload_device = decode_payload(payload) + if payload_device.get("type") in ("solarbank_4_e5000", "solarbank_max_ac"): + old_type = payload_device["type"] + payload_device["type"] = "solarbank" + if old_type == "solarbank_max_ac": + index = get_index(topic) + for topic_component, payload_component in self.all_received_topics.items(): + if re.search(f"^openWB/system/device/{index}/component/[0-9]+/config$", + topic_component) is not None: + payload_component = decode_payload(payload_component) + if payload_component.get("type") == ComponentType.COUNTER.value: + pub_system_message( + payload_device, + "Die Solarbank Max AC unterstuetzt den Zaehler nicht mehr als eigene " + "Komponente. Bitte lege den Anker SOLIX Smart Meter Gen 2 als " + "eigenstaendiges Geraet mit eigener IP-Adresse an und entferne die " + "bisherige Zaehler-Komponente unter Einstellungen -> Konfiguration -> " + "Geraete manuell.", MessageType.WARNING) + break + return {topic: payload_device} + self._loop_all_received_topics(upgrade) + self._append_datastore_version(139) diff --git a/packages/modules/devices/anker/smartmeter_gen2/counter.py b/packages/modules/devices/anker/smartmeter_gen2/counter.py index 44c098ecb3..83237c8566 100644 --- a/packages/modules/devices/anker/smartmeter_gen2/counter.py +++ b/packages/modules/devices/anker/smartmeter_gen2/counter.py @@ -34,16 +34,19 @@ def update(self): unit = self.device_config.configuration.modbus_id power = self.client.read_input_registers(10644, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) * -1 + wordorder=Endian.Big, unit=unit) powers = self.client.read_input_registers(10638, [ModbusDataType.INT_32] * 3, - wordorder=Endian.Little, unit=unit) + wordorder=Endian.Big, unit=unit) voltages = self.client.read_input_registers(10632, [ModbusDataType.UINT_16] * 3, - wordorder=Endian.Little, unit=unit) - currents = self.client.read_input_registers(10666, [ModbusDataType.INT_16] * 3, - wordorder=Endian.Little, unit=unit) + wordorder=Endian.Big, unit=unit) + currents = self.client.read_input_registers(10635, [ModbusDataType.INT_16] * 3, + wordorder=Endian.Big, unit=unit) + + # Currents hat keine eigene Vorzeichen (getestet), daher kommen die Vorzeichen aus + # den powers-Werten voltages = [value / 10 for value in voltages] - currents = [value / -100 for value in currents] + currents = [abs(c) / 100 * (1 if p >= 0 else -1) for c, p in zip(currents, powers)] self.peak_filter.check_values(power) imported, exported = self.sim_counter.sim_count(power) diff --git a/packages/modules/devices/anker/solarbank_4_e5000/__init__.py b/packages/modules/devices/anker/solarbank/__init__.py similarity index 100% rename from packages/modules/devices/anker/solarbank_4_e5000/__init__.py rename to packages/modules/devices/anker/solarbank/__init__.py diff --git a/packages/modules/devices/anker/solarbank_max_ac/bat.py b/packages/modules/devices/anker/solarbank/bat.py similarity index 92% rename from packages/modules/devices/anker/solarbank_max_ac/bat.py rename to packages/modules/devices/anker/solarbank/bat.py index 1c53d95c9c..75d44a6ba3 100644 --- a/packages/modules/devices/anker/solarbank_max_ac/bat.py +++ b/packages/modules/devices/anker/solarbank/bat.py @@ -9,7 +9,7 @@ from modules.common.modbus import ModbusDataType, Endian, ModbusTcpClient_ from modules.common.simcount import SimCounter from modules.common.store import get_component_value_store -from modules.devices.anker.solarbank_max_ac.config import Anker, AnkerBatSetup +from modules.devices.anker.solarbank.config import Anker, AnkerBatSetup from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType @@ -39,7 +39,7 @@ def update(self) -> None: unit = self.device_config.configuration.modbus_id power = self.client.read_input_registers(10008, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) * -1 + wordorder=Endian.Big, unit=unit) * -1 soc = self.client.read_input_registers(10014, ModbusDataType.UINT_16, unit=unit) self.peak_filter.check_values(power) @@ -65,9 +65,6 @@ def set_power_limit(self, power_limit: Optional[int]) -> None: self.client.write_register(10064, 3, data_type=ModbusDataType.UINT_16, unit=unit) self.last_mode = 'limited' - # Berechne power value: 0 = stop, != 0 = multipliziere mit -1 - # Laut Doku ist der min Wert 100W, ggf. noch Anpassung für power_limit=0 notwendig - power_value = 0 if power_limit == 0 else int(power_limit) * -1 self.client.write_register(10071, power_value, data_type=ModbusDataType.INT_32, unit=unit) log.debug("Aktive Batteriesteuerung angefordert, angeforderte Leistung: {power_value} W") diff --git a/packages/modules/devices/anker/solarbank_4_e5000/config.py b/packages/modules/devices/anker/solarbank/config.py similarity index 93% rename from packages/modules/devices/anker/solarbank_4_e5000/config.py rename to packages/modules/devices/anker/solarbank/config.py index 380788057e..bcd9102ed6 100644 --- a/packages/modules/devices/anker/solarbank_4_e5000/config.py +++ b/packages/modules/devices/anker/solarbank/config.py @@ -16,8 +16,8 @@ def __init__(self, class Anker: def __init__(self, - name: str = "Anker SOLIX Solarbank 4 E5000 Pro", - type: str = "solarbank_4_e5000", + name: str = "Anker SOLIX Solarbank", + type: str = "solarbank", id: int = 0, configuration: AnkerConfiguration = None) -> None: self.name = name diff --git a/packages/modules/devices/anker/solarbank_4_e5000/device.py b/packages/modules/devices/anker/solarbank/devie.py similarity index 86% rename from packages/modules/devices/anker/solarbank_4_e5000/device.py rename to packages/modules/devices/anker/solarbank/devie.py index 397f174198..2629539ebb 100644 --- a/packages/modules/devices/anker/solarbank_4_e5000/device.py +++ b/packages/modules/devices/anker/solarbank/devie.py @@ -6,9 +6,9 @@ from modules.common.component_context import SingleComponentUpdateContext from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.common.modbus import ModbusTcpClient_ -from modules.devices.anker.solarbank_4_e5000.bat import AnkerBat -from modules.devices.anker.solarbank_4_e5000.config import Anker, AnkerBatSetup, AnkerInverterSetup -from modules.devices.anker.solarbank_4_e5000.inverter import AnkerInverter +from modules.devices.anker.solarbank.bat import AnkerBat +from modules.devices.anker.solarbank.config import Anker, AnkerBatSetup, AnkerInverterSetup +from modules.devices.anker.solarbank.inverter import AnkerInverter log = logging.getLogger(__name__) diff --git a/packages/modules/devices/anker/solarbank_max_ac/inverter.py b/packages/modules/devices/anker/solarbank/inverter.py similarity index 78% rename from packages/modules/devices/anker/solarbank_max_ac/inverter.py rename to packages/modules/devices/anker/solarbank/inverter.py index b4d152bdc5..9efe89fcc0 100644 --- a/packages/modules/devices/anker/solarbank_max_ac/inverter.py +++ b/packages/modules/devices/anker/solarbank/inverter.py @@ -8,7 +8,7 @@ from modules.common.modbus import ModbusDataType, Endian, ModbusTcpClient_ from modules.common.simcount import SimCounter from modules.common.store import get_component_value_store -from modules.devices.anker.solarbank_max_ac.config import Anker, AnkerInverterSetup +from modules.devices.anker.solarbank.config import Anker, AnkerInverterSetup from modules.common.utils.peak_filter import PeakFilter from modules.common.component_type import ComponentType @@ -34,18 +34,13 @@ def initialize(self) -> None: def update(self) -> None: unit = self.device_config.configuration.modbus_id - # Register 10002 ist die PV_power also die DC Leistung - # Register 10010 ist "Load_power" unklar ob dies wirklich die AC Leistung des Inverters ist - power = self.client.read_input_registers(10010, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) * -1 - dc_power = self.client.read_input_registers(10002, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) * -1 + power = self.client.read_input_registers(10002, ModbusDataType.INT_32, + wordorder=Endian.Big, unit=unit) * -1 self.peak_filter.check_values(power) imported, exported = self.sim_counter.sim_count(power) inverter_state = InverterState( power=power, - dc_power=dc_power, imported=imported, exported=exported ) diff --git a/packages/modules/devices/anker/solarbank_4_e5000/bat.py b/packages/modules/devices/anker/solarbank_4_e5000/bat.py deleted file mode 100644 index 28145c291a..0000000000 --- a/packages/modules/devices/anker/solarbank_4_e5000/bat.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 -import logging -from typing import Any, Optional, TypedDict - -from modules.common.abstract_device import AbstractBat -from modules.common.component_state import BatState -from modules.common.component_type import ComponentDescriptor -from modules.common.fault_state import ComponentInfo, FaultState -from modules.common.modbus import ModbusDataType, Endian, ModbusTcpClient_ -from modules.common.simcount import SimCounter -from modules.common.store import get_component_value_store -from modules.devices.anker.solarbank_4_e5000.config import Anker, AnkerBatSetup -from modules.common.utils.peak_filter import PeakFilter -from modules.common.component_type import ComponentType - -log = logging.getLogger(__name__) - -# Rohwert der Leistungsregister / POWER_GAIN = Watt (Gerät liefert mW statt W) -POWER_GAIN = 1000 - - -class KwargsDict(TypedDict): - device_config: Anker - client: ModbusTcpClient_ - - -class AnkerBat(AbstractBat): - def __init__(self, component_config: AnkerBatSetup, **kwargs: Any) -> None: - self.component_config = component_config - self.kwargs: KwargsDict = kwargs - - def initialize(self) -> None: - self.device_config: Anker = self.kwargs['device_config'] - self.client: ModbusTcpClient_ = self.kwargs['client'] - self.sim_counter = SimCounter(self.device_config.id, self.component_config.id, self.component_config.type) - self.store = get_component_value_store(self.component_config.type, self.component_config.id) - self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) - self.peak_filter = PeakFilter(ComponentType.BAT, self.component_config.id, self.fault_state) - self.last_mode = 'Undefined' - - def update(self) -> None: - unit = self.device_config.configuration.modbus_id - - power = self.client.read_input_registers(10008, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) * -1 / POWER_GAIN - soc = self.client.read_input_registers(10014, ModbusDataType.UINT_16, unit=unit) - - self.peak_filter.check_values(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) - - def set_power_limit(self, power_limit: Optional[int]) -> None: - unit = self.device_config.configuration.modbus_id - - if power_limit is None: - log.debug("Keine Batteriesteuerung, Selbstregelung durch Wechselrichter") - if self.last_mode is not None: - self.client.write_register(10064, 0, data_type=ModbusDataType.UINT_16, unit=unit) - self.last_mode = None - else: - if self.last_mode != 'limited': - self.client.write_register(10064, 3, data_type=ModbusDataType.UINT_16, unit=unit) - self.last_mode = 'limited' - - # Berechne power value: 0 = stop, != 0 = multipliziere mit -1 - # Laut Doku ist der min Wert 100W, ggf. noch Anpassung für power_limit=0 notwendig - # ACHTUNG: POWER_GAIN hier nur nach Symmetrie-Annahme angewendet (Leseregister sind - # nachweislich in mW). Vor Praxiseinsatz unbedingt mit einem kleinen, unkritischen - # Grenzwert verifizieren, dass das Schreibregister ebenfalls mW statt W erwartet - - # sonst wird ggf. eine 1000x falsche Leistung angefordert. - power_value = 0 if power_limit == 0 else int(power_limit) * -1 * POWER_GAIN - self.client.write_register(10071, power_value, data_type=ModbusDataType.INT_32, unit=unit) - log.debug("Aktive Batteriesteuerung angefordert, angeforderte Leistung: {power_value} W") - - def power_limit_controllable(self) -> bool: - return True - - -component_descriptor = ComponentDescriptor(configuration_factory=AnkerBatSetup) diff --git a/packages/modules/devices/anker/solarbank_4_e5000/inverter.py b/packages/modules/devices/anker/solarbank_4_e5000/inverter.py deleted file mode 100644 index 8e76e1a314..0000000000 --- a/packages/modules/devices/anker/solarbank_4_e5000/inverter.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -from typing import TypedDict, Any - -from modules.common.abstract_device import AbstractInverter -from modules.common.component_state import InverterState -from modules.common.component_type import ComponentDescriptor -from modules.common.fault_state import ComponentInfo, FaultState -from modules.common.modbus import ModbusDataType, Endian, ModbusTcpClient_ -from modules.common.simcount import SimCounter -from modules.common.store import get_component_value_store -from modules.devices.anker.solarbank_4_e5000.config import Anker, AnkerInverterSetup -from modules.common.utils.peak_filter import PeakFilter -from modules.common.component_type import ComponentType - -# Rohwert der Leistungsregister / POWER_GAIN = Watt (Gerät liefert mW statt W) -POWER_GAIN = 1000 - - -class KwargsDict(TypedDict): - device_config: Anker - client: ModbusTcpClient_ - - -class AnkerInverter(AbstractInverter): - def __init__(self, component_config: AnkerInverterSetup, **kwargs: Any) -> None: - self.component_config = component_config - self.kwargs: KwargsDict = kwargs - - def initialize(self) -> None: - self.device_config: Anker = self.kwargs['device_config'] - self.client: ModbusTcpClient_ = self.kwargs['client'] - self.sim_counter = SimCounter(self.device_config.id, self.component_config.id, self.component_config.type) - self.store = get_component_value_store(self.component_config.type, self.component_config.id) - self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) - self.peak_filter = PeakFilter(ComponentType.INVERTER, self.component_config.id, self.fault_state) - - def update(self) -> None: - unit = self.device_config.configuration.modbus_id - - # Register 10002 ist die PV_power also die DC Leistung - # Register 10010 ist "Load_power" unklar ob dies wirklich die AC Leistung des Inverters ist - power = self.client.read_input_registers(10010, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) * -1 / POWER_GAIN - dc_power = self.client.read_input_registers(10002, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) * -1 / POWER_GAIN - - self.peak_filter.check_values(power) - imported, exported = self.sim_counter.sim_count(power) - inverter_state = InverterState( - power=power, - dc_power=dc_power, - imported=imported, - exported=exported - ) - self.store.set(inverter_state) - - -component_descriptor = ComponentDescriptor(configuration_factory=AnkerInverterSetup) diff --git a/packages/modules/devices/anker/solarbank_max_ac/__init__.py b/packages/modules/devices/anker/solarbank_max_ac/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/modules/devices/anker/solarbank_max_ac/config.py b/packages/modules/devices/anker/solarbank_max_ac/config.py deleted file mode 100644 index 4e8a704d25..0000000000 --- a/packages/modules/devices/anker/solarbank_max_ac/config.py +++ /dev/null @@ -1,69 +0,0 @@ -from typing import Optional - -from modules.common.component_setup import ComponentSetup -from ..vendor import vendor_descriptor - - -class AnkerConfiguration: - def __init__(self, - ip_address: Optional[str] = None, - port: int = 502, - modbus_id: int = 1): - self.ip_address = ip_address - self.port = port - self.modbus_id = modbus_id - - -class Anker: - def __init__(self, - name: str = "Anker SOLIX Solarbank Max AC", - type: str = "solarbank_max_ac", - id: int = 0, - configuration: AnkerConfiguration = None) -> None: - self.name = name - self.type = type - self.vendor = vendor_descriptor.configuration_factory().type - self.id = id - self.configuration = configuration or AnkerConfiguration() - - -class AnkerBatConfiguration: - def __init__(self): - pass - - -class AnkerBatSetup(ComponentSetup[AnkerBatConfiguration]): - def __init__(self, - name: str = "Anker Speicher", - type: str = "bat", - id: int = 0, - configuration: AnkerBatConfiguration = None) -> None: - super().__init__(name, type, id, configuration or AnkerBatConfiguration()) - - -class AnkerCounterConfiguration: - def __init__(self): - pass - - -class AnkerCounterSetup(ComponentSetup[AnkerCounterConfiguration]): - def __init__(self, - name: str = "Anker Zähler", - type: str = "counter", - id: int = 0, - configuration: AnkerCounterConfiguration = None) -> None: - super().__init__(name, type, id, configuration or AnkerCounterConfiguration()) - - -class AnkerInverterConfiguration: - def __init__(self): - pass - - -class AnkerInverterSetup(ComponentSetup[AnkerInverterConfiguration]): - def __init__(self, - name: str = "Anker Wechselrichter", - type: str = "inverter", - id: int = 0, - configuration: AnkerInverterConfiguration = None) -> None: - super().__init__(name, type, id, configuration or AnkerInverterConfiguration()) diff --git a/packages/modules/devices/anker/solarbank_max_ac/counter.py b/packages/modules/devices/anker/solarbank_max_ac/counter.py deleted file mode 100644 index 5e54deb3a0..0000000000 --- a/packages/modules/devices/anker/solarbank_max_ac/counter.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python3 -from typing import Any, TypedDict - -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.modbus import ModbusDataType, Endian, ModbusTcpClient_ -from modules.common.simcount import SimCounter -from modules.common.store import get_component_value_store -from modules.devices.anker.solarbank_max_ac.config import Anker, AnkerCounterSetup -from modules.common.utils.peak_filter import PeakFilter -from modules.common.component_type import ComponentType - - -class KwargsDict(TypedDict): - device_config: Anker - client: ModbusTcpClient_ - - -class AnkerCounter(AbstractCounter): - def __init__(self, component_config: AnkerCounterSetup, **kwargs: Any) -> None: - self.component_config = component_config - self.kwargs: KwargsDict = kwargs - - def initialize(self) -> None: - self.device_config: Anker = self.kwargs['device_config'] - self.client: ModbusTcpClient_ = self.kwargs['client'] - self.sim_counter = SimCounter(self.device_config.id, self.component_config.id, self.component_config.type) - self.store = get_component_value_store(self.component_config.type, self.component_config.id) - self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) - self.peak_filter = PeakFilter(ComponentType.COUNTER, self.component_config.id, self.fault_state) - - def update(self): - unit = self.device_config.configuration.modbus_id - - power = self.client.read_input_registers(10644, ModbusDataType.INT_32, - wordorder=Endian.Little, unit=unit) * -1 - powers = self.client.read_input_registers(10638, [ModbusDataType.INT_32] * 3, - wordorder=Endian.Little, unit=unit) - voltages = self.client.read_input_registers(10632, [ModbusDataType.UINT_16] * 3, - wordorder=Endian.Little, unit=unit) - currents = self.client.read_input_registers(10666, [ModbusDataType.INT_16] * 3, - wordorder=Endian.Little, unit=unit) - - voltages = [value / 10 for value in voltages] - currents = [value / -100 for value in currents] - - self.peak_filter.check_values(power) - imported, exported = self.sim_counter.sim_count(power) - counter_state = CounterState( - imported=imported, - exported=exported, - power=power, - powers=powers, - voltages=voltages, - currents=currents - ) - self.store.set(counter_state) - - -component_descriptor = ComponentDescriptor(configuration_factory=AnkerCounterSetup) diff --git a/packages/modules/devices/anker/solarbank_max_ac/device.py b/packages/modules/devices/anker/solarbank_max_ac/device.py deleted file mode 100644 index 4cd00f9ca7..0000000000 --- a/packages/modules/devices/anker/solarbank_max_ac/device.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python3 -import logging -from typing import Iterable, Union - -from modules.common.abstract_device import DeviceDescriptor -from modules.common.component_context import SingleComponentUpdateContext -from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater -from modules.common.modbus import ModbusTcpClient_ -from modules.devices.anker.solarbank_max_ac.bat import AnkerBat -from modules.devices.anker.solarbank_max_ac.config import Anker, AnkerBatSetup, AnkerCounterSetup, AnkerInverterSetup -from modules.devices.anker.solarbank_max_ac.counter import AnkerCounter -from modules.devices.anker.solarbank_max_ac.inverter import AnkerInverter - -log = logging.getLogger(__name__) - - -def create_device(device_config: Anker): - client = None - - def create_bat_component(component_config: AnkerBatSetup): - return AnkerBat(component_config, device_config=device_config, client=client) - - def create_counter_component(component_config: AnkerCounterSetup): - return AnkerCounter(component_config, device_config=device_config, client=client) - - def create_inverter_component(component_config: AnkerInverterSetup): - return AnkerInverter(component_config, device_config=device_config, client=client) - - def update_components(components: Iterable[Union[AnkerBat, AnkerCounter, AnkerInverter]]): - with client: - for component in components: - with SingleComponentUpdateContext(component.fault_state): - component.update() - - def initializer(): - nonlocal client - client = ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) - - return ConfigurableDevice( - device_config=device_config, - initializer=initializer, - component_factory=ComponentFactoryByType( - bat=create_bat_component, - counter=create_counter_component, - inverter=create_inverter_component, - ), - component_updater=MultiComponentUpdater(update_components) - ) - - -device_descriptor = DeviceDescriptor(configuration_factory=Anker)