Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions packages/modules/devices/kostal/kostal_plenticore/bat.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#!/usr/bin/env python3
import logging
from typing import Any, Callable, TypedDict
from typing import TypedDict, Any

from modules.common.abstract_device import AbstractBat
from modules.common.component_state import BatState
from modules.common.component_type import ComponentDescriptor
from modules.common.modbus import ModbusDataType
from modules.common.fault_state import ComponentInfo, FaultState
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.kostal.kostal_plenticore.config import KostalPlenticoreBatSetup
Expand All @@ -15,6 +16,8 @@

class KwargsDict(TypedDict):
device_id: int
modbus_id: int
client: ModbusTcpClient_


class KostalPlenticoreBat(AbstractBat):
Expand All @@ -24,29 +27,27 @@ def __init__(self, component_config: KostalPlenticoreBatSetup, **kwargs: Any) ->

def initialize(self) -> None:
self.__device_id: int = self.kwargs['device_id']
self.modbus_id: int = self.kwargs['modbus_id']
self.client: ModbusTcpClient_ = self.kwargs['client']
self.store = get_bat_value_store(self.component_config.id)
self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher")
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))
self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher")

def read_state(self, reader: Callable[[int, ModbusDataType], Any]) -> BatState:
power = reader(582, ModbusDataType.INT_16) * -1
soc = reader(514, ModbusDataType.INT_16)
def update(self) -> None:
power = self.client.read_holding_registers(582, ModbusDataType.INT_16, unit=self.modbus_id) * -1
soc = self.client.read_holding_registers(514, ModbusDataType.INT_16, unit=self.modbus_id)
if power < 0:
power = self.client.read_holding_registers(106, ModbusDataType.FLOAT_32, unit=self.modbus_id) * -1
imported, exported = self.sim_counter.sim_count(power)
log.debug("raw bat power "+str(power))
# Speicherladung muss durch Wandlungsverluste und internen Verbrauch korrigiert werden, sonst
# wird ein falscher Hausverbrauch berechnet. Die Verluste fallen hier unter den Tisch.
if power < 0:
power = reader(106, ModbusDataType.FLOAT_32) * -1

return BatState(
bat_state = BatState(
power=power,
soc=soc,
imported=imported,
exported=exported,
exported=exported
)

def update(self, state):
self.store.set(state)
self.store.set(bat_state)


component_descriptor = ComponentDescriptor(configuration_factory=KostalPlenticoreBatSetup)
45 changes: 25 additions & 20 deletions packages/modules/devices/kostal/kostal_plenticore/counter.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
#!/usr/bin/env python3
from typing import Any, Callable, TypedDict
from typing import TypedDict, Any

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
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.kostal.kostal_plenticore.config import KostalPlenticoreCounterSetup


class KwargsDict(TypedDict):
device_id: int
modbus_id: int
client: ModbusTcpClient_


class KostalPlenticoreCounter(AbstractCounter):
Expand All @@ -21,33 +24,35 @@ def __init__(self, component_config: KostalPlenticoreCounterSetup, **kwargs: Any

def initialize(self) -> None:
self.__device_id: int = self.kwargs['device_id']
self.modbus_id: int = self.kwargs['modbus_id']
self.client: ModbusTcpClient_ = self.kwargs['client']
self.store = get_counter_value_store(self.component_config.id)
self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug")
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))
self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug")

def get_values(self, reader: Callable[[int, ModbusDataType], Any]) -> CounterState:
power_factor = reader(150, ModbusDataType.FLOAT_32)
currents = [reader(register, ModbusDataType.FLOAT_32) for register in [222, 232, 242]]
voltages = [reader(register, ModbusDataType.FLOAT_32) for register in [230, 240, 250]]
powers = [reader(register, ModbusDataType.FLOAT_32) for register in [224, 234, 244]]
power = reader(252, ModbusDataType.FLOAT_32)
frequency = reader(220, ModbusDataType.FLOAT_32)

return CounterState(
def update(self) -> None:
power = self.client.read_holding_registers(252, ModbusDataType.FLOAT_32, unit=self.modbus_id)
imported, exported = self.sim_counter.sim_count(power)
power_factor = self.client.read_holding_registers(150, ModbusDataType.FLOAT_32, unit=self.modbus_id)
currents = [self.client.read_holding_registers(
reg, ModbusDataType.FLOAT_32, unit=self.modbus_id) for reg in [222, 232, 242]]
voltages = [self.client.read_holding_registers(
reg, ModbusDataType.FLOAT_32, unit=self.modbus_id) for reg in [230, 240, 250]]
powers = [self.client.read_holding_registers(
reg, ModbusDataType.FLOAT_32, unit=self.modbus_id) for reg in [224, 234, 244]]
frequency = self.client.read_holding_registers(220, ModbusDataType.FLOAT_32, unit=self.modbus_id)

counter_state = CounterState(
powers=powers,
currents=currents,
voltages=voltages,
power=power,
power_factors=[power_factor]*3,
frequency=frequency
frequency=frequency,
imported=imported,
exported=exported
)

def update_imported_exported(self, state: CounterState) -> CounterState:
state.imported, state.exported = self.sim_counter.sim_count(state.power)
return state

def update(self, reader: Callable[[int, ModbusDataType], Any]):
self.store.set(self.update_imported_exported(self.get_values(reader)))
self.store.set(counter_state)


component_descriptor = ComponentDescriptor(configuration_factory=KostalPlenticoreCounterSetup)
88 changes: 32 additions & 56 deletions packages/modules/devices/kostal/kostal_plenticore/device.py
Original file line number Diff line number Diff line change
@@ -1,88 +1,64 @@
# !/usr/bin/env python3
from enum import IntEnum
from typing import Any, Callable, Iterable, Union
from pymodbus.constants import Endian
import functools
#!/usr/bin/env python3
import logging
from typing import Iterable, Union

from modules.common import modbus
from modules.common.abstract_device import DeviceDescriptor
from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater
from modules.common.modbus import ModbusTcpClient_
from modules.devices.kostal.kostal_plenticore.bat import KostalPlenticoreBat
from modules.devices.kostal.kostal_plenticore.inverter import KostalPlenticoreInverter
from modules.devices.kostal.kostal_plenticore.config import (KostalPlenticore, KostalPlenticoreBatSetup,
KostalPlenticoreCounterSetup,
KostalPlenticoreInverterSetup)
from modules.devices.kostal.kostal_plenticore.counter import KostalPlenticoreCounter

from modules.devices.kostal.kostal_plenticore.inverter import KostalPlenticoreInverter
from modules.devices.kostal.kostal_plenticore.config import KostalPlenticore, KostalPlenticoreBatSetup
from modules.devices.kostal.kostal_plenticore.config import KostalPlenticoreCounterSetup, KostalPlenticoreInverterSetup

log = logging.getLogger(__name__)


class LegacyCounterPosition(IntEnum):
HOME_CONSUMPTION = 0
GRID = 1


def update(
components: Iterable[Union[KostalPlenticoreBat, KostalPlenticoreCounter, KostalPlenticoreInverter]],
reader: Callable[[int, modbus.ModbusDataType], Any],
set_inverter_state: bool = True):
battery = next((component for component in components if isinstance(component, KostalPlenticoreBat)), None)
bat_state = battery.read_state(reader) if battery else None
for component in components:
if isinstance(component, KostalPlenticoreInverter):
# Fürs erste nur die WR-Werte nutzen ohne Verlustberechnung.
# power: R575(inverter generation power (actual))
# exported: R320 (Total yield)
inverter_state = component.read_state(reader)
pv_state = inverter_state
if set_inverter_state:
component.update(pv_state)
elif isinstance(component, KostalPlenticoreCounter):
component.update(reader)
if bat_state:
battery.update(bat_state)
if set_inverter_state is False:
return pv_state


def create_device(device_config: KostalPlenticore):
client = None
reader = None

def create_bat_component(component_config: KostalPlenticoreBatSetup):
return KostalPlenticoreBat(component_config, device_id=device_config.id)
nonlocal client
return KostalPlenticoreBat(component_config,
device_id=device_config.id,
modbus_id=device_config.configuration.modbus_id,
client=client)

def create_counter_component(component_config: KostalPlenticoreCounterSetup):
return KostalPlenticoreCounter(component_config, device_id=device_config.id)
nonlocal client
return KostalPlenticoreCounter(component_config,
device_id=device_config.id,
modbus_id=device_config.configuration.modbus_id,
client=client)

def create_inverter_component(component_config: KostalPlenticoreInverterSetup):
return KostalPlenticoreInverter(component_config)
nonlocal client
return KostalPlenticoreInverter(component_config,
device_id=device_config.id,
modbus_id=device_config.configuration.modbus_id,
client=client)

def update_components(
components: Iterable[Union[KostalPlenticoreBat, KostalPlenticoreCounter, KostalPlenticoreInverter]]
):
nonlocal client, reader
components: Iterable[Union[KostalPlenticoreBat, KostalPlenticoreCounter, KostalPlenticoreInverter]]):
nonlocal client
with client:
update(components, reader)
for component in components:
component.update()

def initializer():
nonlocal client, reader
client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port)
reader = _create_reader(client, device_config.configuration.modbus_id)
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),
bat=create_bat_component,
counter=create_counter_component,
inverter=create_inverter_component,
),
component_updater=MultiComponentUpdater(update_components)
)


def _create_reader(tcp_client: modbus.ModbusTcpClient_, modbus_id: int) -> Callable[[int, modbus.ModbusDataType], Any]:
return functools.partial(tcp_client.read_holding_registers, unit=modbus_id, wordorder=Endian.Little)


device_descriptor = DeviceDescriptor(configuration_factory=KostalPlenticore)
35 changes: 21 additions & 14 deletions packages/modules/devices/kostal/kostal_plenticore/inverter.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,44 @@
#!/usr/bin/env python3
from typing import Any, Callable
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
from modules.common.modbus import ModbusDataType, ModbusTcpClient_
from modules.common.simcount import SimCounter
from modules.common.store import get_inverter_value_store
from modules.devices.kostal.kostal_plenticore.config import KostalPlenticoreInverterSetup


class KwargsDict(TypedDict):
device_id: int
modbus_id: int
client: ModbusTcpClient_


class KostalPlenticoreInverter(AbstractInverter):
def __init__(self, component_config: KostalPlenticoreInverterSetup) -> None:
def __init__(self, component_config: KostalPlenticoreInverterSetup, **kwargs: Any) -> None:
self.component_config = component_config
self.kwargs: KwargsDict = kwargs

def initialize(self) -> None:
self.__device_id: int = self.kwargs['device_id']
self.modbus_id: int = self.kwargs['modbus_id']
self.client: ModbusTcpClient_ = self.kwargs['client']
self.store = get_inverter_value_store(self.component_config.id)
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))
self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="pv")

def read_state(self, reader: Callable[[int, ModbusDataType], Any]) -> InverterState:
# PV-Anlage kann nichts verbrauchen, also ggf. Register-/Rundungsfehler korrigieren.
power = reader(575, ModbusDataType.INT_16) * -1
exported = reader(320, ModbusDataType.FLOAT_32)
def update(self) -> None:
power = self.client.read_holding_registers(575, ModbusDataType.INT_16, unit=self.modbus_id) * -1
exported = self.client.read_holding_registers(320, ModbusDataType.FLOAT_32, unit=self.modbus_id)

return InverterState(
inverter_state = InverterState(
power=power,
exported=exported
)

def dc_in_string_1_2(self, reader: Callable[[int, ModbusDataType], Any]):
return reader(260, ModbusDataType.FLOAT_32) + reader(270, ModbusDataType.FLOAT_32)

def update(self, state):
self.store.set(state)
self.store.set(inverter_state)


component_descriptor = ComponentDescriptor(configuration_factory=KostalPlenticoreInverterSetup)