diff --git a/loadvars.sh b/loadvars.sh index 804e456a0..4c7c3f205 100755 --- a/loadvars.sh +++ b/loadvars.sh @@ -1092,7 +1092,7 @@ loadvars(){ fi echo $hausverbrauch > /var/www/html/openWB/ramdisk/hausverbrauch usesimbezug=0 - if [[ $wattbezugmodul == "bezug_solarwatt" ]]|| [[ $wattbezugmodul == "bezug_rct" ]]|| [[ $wattbezugmodul == "bezug_varta" ]] || [[ $wattbezugmodul == "bezug_kostalplenticoreem300haus" ]] || [[ $wattbezugmodul == "bezug_solarlog" ]] ; then + if [[ $wattbezugmodul == "bezug_solarwatt" ]]|| [[ $wattbezugmodul == "bezug_rct" ]]|| [[ $wattbezugmodul == "bezug_kostalplenticoreem300haus" ]] || [[ $wattbezugmodul == "bezug_solarlog" ]] ; then usesimbezug=1 fi if ((usesimbezug == 1)); then @@ -1182,7 +1182,7 @@ loadvars(){ echo "$pvallwh" > /var/www/html/openWB/ramdisk/pvallwh fi - if [[ $speichermodul == "speicher_tesvoltsma" ]] || [[ $speichermodul == "speicher_solarwatt" ]] || [[ $speichermodul == "speicher_rct" ]]|| [[ $speichermodul == "speicher_kostalplenticore" ]] || [[ $speichermodul == "speicher_varta" ]] ; then + if [[ $speichermodul == "speicher_tesvoltsma" ]] || [[ $speichermodul == "speicher_solarwatt" ]] || [[ $speichermodul == "speicher_rct" ]]|| [[ $speichermodul == "speicher_kostalplenticore" ]] ; then ra='^-?[0-9]+$' watt2=$(>"${MYLOGFILE}" 2>&1 +bash "$OPENWBBASEDIR/packages/legacy_run.sh" "modules.devices.varta.device" "counter" "${vartaspeicherip}" >>"${MYLOGFILE}" 2>&1 ret=$? openwbDebugLog ${DMOD} 2 "RET: ${ret}" diff --git a/modules/bezug_varta/varta.py b/modules/bezug_varta/varta.py deleted file mode 100755 index 792a2642a..000000000 --- a/modules/bezug_varta/varta.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python3 -from typing import List -import logging -import struct -import codecs - -from pymodbus.client.sync import ModbusTcpClient - -from helpermodules.cli import run_using_positional_cli_args - -log = logging.getLogger("Varta EVU") - - -def update(ipaddress: str): - with ModbusTcpClient(ipaddress, port=502) as client: - # grid power - resp = client.read_holding_registers(1078, 1, unit=1) - value1 = resp.registers[0] - # ToDo: scale factor in register 2078 SInt16 - # power = reg(1078) * 10 ^ reg(2078) - all = format(value1, '04x') - final = int(struct.unpack('>h', codecs.decode(all, 'hex'))[0])*-1 - log.debug("Result: %s", str(final)) - with open('/var/www/html/openWB/ramdisk/wattbezug', 'w') as f: - f.write(str(final)) - # ToDo: grid frequency in register 1082 as 0.01Hz UInt16 - - -def main(argv: List[str]): - run_using_positional_cli_args(update, argv) diff --git a/modules/speicher_varta/main.sh b/modules/speicher_varta/main.sh index 534f6ab82..ed42c639b 100755 --- a/modules/speicher_varta/main.sh +++ b/modules/speicher_varta/main.sh @@ -1,7 +1,8 @@ #!/bin/bash + OPENWBBASEDIR=$(cd "$(dirname "$0")/../../" && pwd) RAMDISKDIR="${OPENWBBASEDIR}/ramdisk" -#DMOD="BAT" +#DMOD="BATT" DMOD="MAIN" if [ ${DMOD} == "MAIN" ]; then @@ -13,21 +14,9 @@ fi # Auslesen eines Varta Speicher über die integrierte XML-API der Batteroe. if [[ "$usevartamodbus" != "1" ]]; then - speicherwatt=$(curl --connect-timeout 3 -s "$vartaspeicherip/cgi/ems_data.xml" | grep 'P' | sed 's/.*value=//' |tr -d "'/>") - # wenn WR aus bzw. im standby (keine Antwort) ersetze leeren Wert durch eine 0 - ra='^-?[0-9]+$' - if [[ $speicherwatt =~ $ra ]] ; then - echo "$speicherwatt" > "$RAMDISKDIR/speicherleistung" - fi - speichersoc=$(curl --connect-timeout 3 -s "$vartaspeicherip/cgi/ems_data.xml" | grep 'SOC' | sed 's/.*value=//' |tr -d "'/>") - # if [[ $speichersoc -ge "101" ]]; then - speichersoc=$(echo "$speichersoc / 10" |bc) - # fi - if [[ $speichersoc =~ $ra ]] ; then - echo "$speichersoc" > "$RAMDISKDIR/speichersoc" - fi + bash "$OPENWBBASEDIR/packages/legacy_run.sh" "modules.devices.varta.device" "bat_api" "${vartaspeicherip}">>"$MYLOGFILE" 2>&1 else - bash "$OPENWBBASEDIR/packages/legacy_run.sh" "speicher_varta.varta" "${vartaspeicherip}" "${vartaspeicher2ip}" >>"$MYLOGFILE" 2>&1 - ret=$? - openwbDebugLog ${DMOD} 2 "RET: ${ret}" + bash "$OPENWBBASEDIR/packages/legacy_run.sh" "modules.devices.varta.device" "bat_modbus" "${vartaspeicherip}" "${vartaspeicher2ip}" >>"$MYLOGFILE" 2>&1 fi +ret=$? +openwbDebugLog ${DMOD} 2 "RET: ${ret}" diff --git a/modules/speicher_varta/varta.py b/modules/speicher_varta/varta.py deleted file mode 100755 index 5e8d75ad8..000000000 --- a/modules/speicher_varta/varta.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 -from typing import List -import logging -import struct -import codecs - -from pymodbus.client.sync import ModbusTcpClient - -from helpermodules.cli import run_using_positional_cli_args - -log = logging.getLogger("Varta Speicher") - - -def update(ipaddress: str, ip2address: str): - with ModbusTcpClient(ipaddress, port=502) as client: - # battsoc - resp = client.read_holding_registers(1068, 1, unit=1) - value1 = resp.registers[0] - all = format(value1, '04x') - sfinal = int(struct.unpack('>h', codecs.decode(all, 'hex'))[0]) - - # battleistung - resp = client.read_holding_registers(1066, 1, unit=1) - value1 = resp.registers[0] - all = format(value1, '04x') - lfinal = int(struct.unpack('>h', codecs.decode(all, 'hex'))[0]) - - if ip2address != 'none': - with ModbusTcpClient(ip2address, port=502) as client2: - # battsoc - resp = client2.read_holding_registers(1068, 1, unit=1) - value1 = resp.registers[0] - all = format(value1, '04x') - final = int(struct.unpack('>h', codecs.decode(all, 'hex'))[0]) - sfinal = (sfinal+final)/2 - # battleistung - resp = client2.read_holding_registers(1066, 1, unit=1) - value1 = resp.registers[0] - all = format(value1, '04x') - final = int(struct.unpack('>h', codecs.decode(all, 'hex'))[0]) - lfinal = lfinal+final - - with open('/var/www/html/openWB/ramdisk/speichersoc', 'w') as f: - f.write(str(sfinal)) - with open('/var/www/html/openWB/ramdisk/speicherleistung', 'w') as f: - f.write(str(lfinal)) - - -def main(argv: List[str]): - run_using_positional_cli_args(update, argv) diff --git a/packages/modules/devices/varta/__init__.py b/packages/modules/devices/varta/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/packages/modules/devices/varta/bat_api.py b/packages/modules/devices/varta/bat_api.py new file mode 100644 index 000000000..02d6b5085 --- /dev/null +++ b/packages/modules/devices/varta/bat_api.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +import xml.etree.ElementTree as ET + +from dataclass_utils import dataclass_from_dict +from modules.common import req +from modules.common.component_state import BatState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo +from modules.common.simcount import SimCounter +from modules.common.store import get_bat_value_store +from modules.devices.varta.config import VartaBatApiSetup + + +class VartaBatApi: + def __init__(self, device_id: int, component_config: VartaBatApiSetup, device_address: str) -> None: + self.__device_id = device_id + self.component_config = dataclass_from_dict(VartaBatApiSetup, component_config) + self.__device_address = device_address + 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) -> None: + def get_xml_text(attribute_value: str) -> float: + value = None + for element in root[0].iter("var"): + if element.attrib["name"] == attribute_value: + value = element.attrib["value"] + try: + return float(value) + except ValueError: + # Wenn Speicher aus bzw. im Standby (keine Antwort), ersetze leeren Wert durch eine 0. + return 0 + + response = req.get_http_session().get('http://'+self.__device_address+'/cgi/ems_data.xml', + timeout=5) + response.encoding = 'utf-8' + response = response.text.replace("\n", "") + root = ET.fromstring(response) + + power = get_xml_text("P") + soc = get_xml_text("SOC") / 10 + + 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=VartaBatApiSetup) diff --git a/packages/modules/devices/varta/bat_modbus.py b/packages/modules/devices/varta/bat_modbus.py new file mode 100644 index 000000000..4f0f50c02 --- /dev/null +++ b/packages/modules/devices/varta/bat_modbus.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +from dataclass_utils import dataclass_from_dict +from modules.common.component_state import BatState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_bat_value_store +from modules.devices.varta.config import VartaBatModbusSetup + + +class VartaBatModbus: + def __init__(self, device_id: int, component_config: VartaBatModbusSetup) -> None: + self.__device_id = device_id + self.component_config = dataclass_from_dict(VartaBatModbusSetup, 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.component_info = ComponentInfo.from_component_config(self.component_config) + + def update(self, client: ModbusTcpClient_) -> None: + self.set_state(self.get_state(client)) + + def get_state(self, client: ModbusTcpClient_) -> BatState: + soc = client.read_holding_registers(1068, ModbusDataType.INT_16, unit=1) + power = client.read_holding_registers(1066, ModbusDataType.INT_16, unit=1) + return BatState( + power=power, + soc=soc, + ) + + def set_state(self, state: BatState) -> None: + state.imported, state.exported = self.sim_counter.sim_count(state.power) + self.store.set(state) + + +component_descriptor = ComponentDescriptor(configuration_factory=VartaBatModbusSetup) diff --git a/packages/modules/devices/varta/config.py b/packages/modules/devices/varta/config.py new file mode 100644 index 000000000..5b65de219 --- /dev/null +++ b/packages/modules/devices/varta/config.py @@ -0,0 +1,70 @@ +from typing import Optional +from helpermodules.auto_str import auto_str +from modules.common.component_setup import ComponentSetup + + +@auto_str +class VartaConfiguration: + def __init__(self, ip_address: Optional[str] = None): + self.ip_address = ip_address + + +@auto_str +class Varta: + def __init__(self, + name: str = "Varta", + type: str = "varta", + id: int = 0, + configuration: VartaConfiguration = None) -> None: + self.name = name + self.type = type + self.id = id + self.configuration = configuration or VartaConfiguration() + + +@auto_str +class VartaBatApiConfiguration: + def __init__(self): + pass + + +@auto_str +class VartaBatApiSetup(ComponentSetup[VartaBatApiConfiguration]): + def __init__(self, + name: str = "Varta Speicher (Abfrage per API)", + type: str = "bat_api", + id: int = 0, + configuration: VartaBatApiConfiguration = None) -> None: + super().__init__(name, type, id, configuration or VartaBatApiConfiguration()) + + +@auto_str +class VartaBatModbusConfiguration: + def __init__(self): + pass + + +@auto_str +class VartaBatModbusSetup(ComponentSetup[VartaBatModbusConfiguration]): + def __init__(self, + name: str = "Speicher Varta Pulse, Element, Neo, u.a. (Abfrage per Modbus)", + type: str = "bat_modbus", + id: int = 0, + configuration: VartaBatModbusConfiguration = None) -> None: + super().__init__(name, type, id, configuration or VartaBatModbusConfiguration()) + + +@auto_str +class VartaCounterConfiguration: + def __init__(self): + pass + + +@auto_str +class VartaCounterSetup(ComponentSetup[VartaCounterConfiguration]): + def __init__(self, + name: str = "Varta Zähler", + type: str = "counter", + id: int = 0, + configuration: VartaCounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or VartaCounterConfiguration()) diff --git a/packages/modules/devices/varta/counter.py b/packages/modules/devices/varta/counter.py new file mode 100644 index 000000000..a5de2503a --- /dev/null +++ b/packages/modules/devices/varta/counter.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +from dataclass_utils import dataclass_from_dict +from modules.common.component_state import CounterState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_counter_value_store +from modules.devices.varta.config import VartaCounterSetup + + +class VartaCounter: + def __init__(self, device_id: int, component_config: VartaCounterSetup) -> None: + self.__device_id = device_id + self.component_config = dataclass_from_dict(VartaCounterSetup, 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.component_info = ComponentInfo.from_component_config(self.component_config) + + def update(self, client: ModbusTcpClient_): + power = client.read_holding_registers(1078, ModbusDataType.INT_16, unit=1) * -1 + frequency = client.read_holding_registers(1082, ModbusDataType.UINT_16, unit=1) / 100 + imported, exported = self.sim_counter.sim_count(power) + + counter_state = CounterState( + imported=imported, + exported=exported, + power=power, + frequency=frequency + ) + self.store.set(counter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=VartaCounterSetup) diff --git a/packages/modules/devices/varta/device.py b/packages/modules/devices/varta/device.py new file mode 100644 index 000000000..65c6387c7 --- /dev/null +++ b/packages/modules/devices/varta/device.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +import logging +from typing import Iterable, Tuple, Union, List + +from helpermodules.cli import run_using_positional_cli_args +from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_context import SingleComponentUpdateContext +from modules.common.component_state import BatState +from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater +from modules.common.modbus import ModbusTcpClient_ +from modules.devices.varta import bat_api, counter +from modules.devices.varta import bat_modbus +from modules.devices.varta.bat_api import VartaBatApi +from modules.devices.varta.bat_modbus import VartaBatModbus +from modules.devices.varta.config import (Varta, VartaConfiguration, VartaBatApiSetup, VartaBatModbusSetup, + VartaCounterSetup) +from modules.devices.varta.counter import VartaCounter + +log = logging.getLogger(__name__) + + +def create_device(device_config: Varta): + def create_bat_api_component(component_config: VartaBatApiSetup): + return VartaBatApi(device_config.id, component_config, device_config.configuration.ip_address) + + def create_bat_modbus_component(component_config: VartaBatModbusSetup): + return VartaBatModbus(device_config.id, component_config) + + def create_counter_component(component_config: VartaCounterSetup): + return VartaCounter(device_config.id, component_config) + + def update_components(components: Iterable[Union[VartaBatApi, VartaBatModbus, VartaCounter]]): + with client as c: + for component in components: + if isinstance(component, (VartaBatModbus, VartaCounter)): + with SingleComponentUpdateContext(component.component_info): + component.update(c) + for component in components: + if isinstance(component, (VartaBatApi)): + with SingleComponentUpdateContext(component.component_info): + component.update() + + try: + client = ModbusTcpClient_(device_config.configuration.ip_address, 502) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat_api=create_bat_api_component, + bat_modbus=create_bat_modbus_component, + counter=create_counter_component + ), + component_updater=MultiComponentUpdater(update_components) + ) + + +COMPONENT_TYPE_TO_MODULE = { + "bat_api": bat_api, + "bat_modbus": bat_modbus, + "counter": counter +} + + +def create_device_with_components(component_type: str, ip_address: str): + device_config = Varta(configuration=VartaConfiguration(ip_address=ip_address)) + dev = create_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 = None + dev.add_component(component_config) + + log.debug('Varta IP-Adresse: ' + ip_address) + return dev + + +def update_counter(ip_address: str): + create_device_with_components("counter", ip_address).update() + + +def update_bat_api(ip_address: str): + create_device_with_components("bat_api", ip_address).update() + + +def create_modbus_bat(ip_address) -> Tuple[ModbusTcpClient_, VartaBatModbus]: + client = ModbusTcpClient_(ip_address, 502) + bat = VartaBatModbus(None, VartaBatModbusSetup()) + log.debug('Varta IP-Adresse: ' + ip_address) + return client, bat + + +def get_modbus_bat_state(client: ModbusTcpClient_, bat: VartaBatModbus) -> BatState: + with client as c: + with SingleComponentUpdateContext(bat.component_info): + return bat.get_state(c) + + +def update_two_batteries(ip_address: str, ip_address2: str) -> None: + client1, bat1 = create_modbus_bat(ip_address) + bat_state_1 = get_modbus_bat_state(client1, bat1) + if ip_address2 != "none": + client2, bat2 = create_modbus_bat(ip_address2) + bat_state_2 = get_modbus_bat_state(client2, bat2) + soc = (bat_state_1.soc + bat_state_2.soc)/2 + power = bat_state_1.power + bat_state_2.power + else: + soc = bat_state_1.soc + power = bat_state_1.power + + bat1.set_state(BatState(soc=soc, power=power)) + + +def main(argv: List[str]): + run_using_positional_cli_args({"bat_api": update_bat_api, + "bat_modbus": update_two_batteries, "counter": update_counter}, argv) + + +device_descriptor = DeviceDescriptor(configuration_factory=Varta)