From 74434be79b5eaa266c55cc6ee890a57c7d88d1f2 Mon Sep 17 00:00:00 2001 From: seaspotter Date: Fri, 13 Nov 2026 00:17:51 +0100 Subject: [PATCH 1/4] Solakon One: read the real battery power register, not an undocumented one --- packages/modules/devices/solakon/solakon_one/bat.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/modules/devices/solakon/solakon_one/bat.py b/packages/modules/devices/solakon/solakon_one/bat.py index 2896e75f37..e4e8fc1e9f 100644 --- a/packages/modules/devices/solakon/solakon_one/bat.py +++ b/packages/modules/devices/solakon/solakon_one/bat.py @@ -32,10 +32,8 @@ def initialize(self) -> None: def update(self) -> None: unit = self.component_config.configuration.modbus_id - # Reg 39134 AC Leistung am Stecker, Batterie aus dem Netz aufladen hat positive Werte, - # Leistung aus der Batterie und/oder aus PV ins Netz abgeben hat negative Werte - # Reg 39237 Battery Combined Power - power = self.client.read_holding_registers(39237, ModbusDataType.INT_32, unit=unit) + # Reg 39230 Battery Power, laut Hersteller-Doku positiv beim Laden, negativ beim Entladen + power = self.client.read_holding_registers(39230, ModbusDataType.INT_32, unit=unit) # SoC Ladezustand der Batterie in % soc = self.client.read_holding_registers(39424, ModbusDataType.INT_16, unit=unit) # gesamte DC Ladung der Batterie in Wh From d017620a3c0bf2b8cccc097e8afa4ef7340b47d2 Mon Sep 17 00:00:00 2001 From: seaspotter Date: Fri, 13 Nov 2026 00:37:25 +0100 Subject: [PATCH 2/4] Solakon One: add grid counter, revert inverter to PV-only registers --- .../devices/solakon/solakon_one/config.py | 17 +++++++ .../devices/solakon/solakon_one/counter.py | 50 +++++++++++++++++++ .../devices/solakon/solakon_one/device.py | 10 +++- .../devices/solakon/solakon_one/inverter.py | 14 ++---- 4 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 packages/modules/devices/solakon/solakon_one/counter.py diff --git a/packages/modules/devices/solakon/solakon_one/config.py b/packages/modules/devices/solakon/solakon_one/config.py index 98807b2580..741e016155 100644 --- a/packages/modules/devices/solakon/solakon_one/config.py +++ b/packages/modules/devices/solakon/solakon_one/config.py @@ -58,3 +58,20 @@ def __init__(self, configuration: SolakonOneInverterConfiguration = None, **kwargs) -> None: super().__init__(name, type, id, configuration or SolakonOneInverterConfiguration(), **kwargs) + + +@auto_str +class SolakonOneCounterConfiguration: + def __init__(self, modbus_id: int = 1): + self.modbus_id = modbus_id + + +@auto_str +class SolakonOneCounterSetup(ComponentSetup[SolakonOneCounterConfiguration]): + def __init__(self, + name: str = "Solakon One Zähler", + type: str = "counter", + id: int = 0, + configuration: SolakonOneCounterConfiguration = None, + **kwargs) -> None: + super().__init__(name, type, id, configuration or SolakonOneCounterConfiguration(), **kwargs) diff --git a/packages/modules/devices/solakon/solakon_one/counter.py b/packages/modules/devices/solakon/solakon_one/counter.py new file mode 100644 index 0000000000..9b9107edc7 --- /dev/null +++ b/packages/modules/devices/solakon/solakon_one/counter.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +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, ModbusTcpClient_ +from modules.common.store import get_component_value_store +from modules.devices.solakon.solakon_one.config import SolakonOneCounterSetup +from modules.common.utils.peak_filter import PeakFilter +from modules.common.component_type import ComponentType + + +class KwargsDict(TypedDict): + client: ModbusTcpClient_ + + +class SolakonOneCounter(AbstractCounter): + def __init__(self, component_config: SolakonOneCounterSetup, **kwargs: Any) -> None: + self.component_config = component_config + self.kwargs: KwargsDict = kwargs + + def initialize(self) -> None: + self.client: ModbusTcpClient_ = self.kwargs['client'] + 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) -> None: + unit = self.component_config.configuration.modbus_id + + # Reg 39134 AC Leistung am Stecker (Grid Information laut Hersteller-Doku), Bezug hat positive Werte, + # Einspeisung hat negative Werte + power = self.client.read_holding_registers(39134, ModbusDataType.INT_32, unit=unit) * -1 + + # Gesamte Netzeinspeisung/-bezug in Wh + exported = self.client.read_holding_registers(39621, ModbusDataType.UINT_32, unit=unit) * 10 + imported = self.client.read_holding_registers(39625, ModbusDataType.UINT_32, unit=unit) * 10 + + imported, exported = self.peak_filter.check_values(power, imported, exported) + counter_state = CounterState( + power=power, + imported=imported, + exported=exported + ) + self.store.set(counter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=SolakonOneCounterSetup) diff --git a/packages/modules/devices/solakon/solakon_one/device.py b/packages/modules/devices/solakon/solakon_one/device.py index 3a92b5e420..befe8db2a6 100644 --- a/packages/modules/devices/solakon/solakon_one/device.py +++ b/packages/modules/devices/solakon/solakon_one/device.py @@ -7,8 +7,10 @@ from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater from modules.common.modbus import ModbusTcpClient_ from modules.devices.solakon.solakon_one.bat import SolakonOneBat +from modules.devices.solakon.solakon_one.counter import SolakonOneCounter from modules.devices.solakon.solakon_one.inverter import SolakonOneInverter -from modules.devices.solakon.solakon_one.config import SolakonOne, SolakonOneBatSetup, SolakonOneInverterSetup +from modules.devices.solakon.solakon_one.config import (SolakonOne, SolakonOneBatSetup, SolakonOneCounterSetup, + SolakonOneInverterSetup) log = logging.getLogger(__name__) @@ -22,7 +24,10 @@ def create_bat_component(component_config: SolakonOneBatSetup): def create_inverter_component(component_config: SolakonOneInverterSetup): return SolakonOneInverter(component_config=component_config, client=client) - def update_components(components: Iterable[Union[SolakonOneBat, SolakonOneInverter]]): + def create_counter_component(component_config: SolakonOneCounterSetup): + return SolakonOneCounter(component_config=component_config, client=client) + + def update_components(components: Iterable[Union[SolakonOneBat, SolakonOneInverter, SolakonOneCounter]]): with client: for component in components: with SingleComponentUpdateContext(component.fault_state): @@ -38,6 +43,7 @@ def initializer(): component_factory=ComponentFactoryByType( bat=create_bat_component, inverter=create_inverter_component, + counter=create_counter_component, ), component_updater=MultiComponentUpdater(update_components) ) diff --git a/packages/modules/devices/solakon/solakon_one/inverter.py b/packages/modules/devices/solakon/solakon_one/inverter.py index d8bd5784cf..26c8902eee 100644 --- a/packages/modules/devices/solakon/solakon_one/inverter.py +++ b/packages/modules/devices/solakon/solakon_one/inverter.py @@ -30,18 +30,14 @@ def initialize(self) -> None: def update(self) -> None: unit = self.component_config.configuration.modbus_id - # Reg 39134 AC Leistung am Stecker, Batterie aus dem Heimnetz aufladen hat positive Werte, - # Leistung aus der Batterie und/oder aus PV ins Heimnetz abgeben hat negative Werte - power = self.client.read_holding_registers(39134, ModbusDataType.INT_32, unit=unit) * -1 + # Gesamte DC PV Leistung aller vier MPPT in W + power = self.client.read_holding_registers(39118, ModbusDataType.INT_32, unit=unit) * -1 + # Gesamte DC PV Produktion in Wh + exported = self.client.read_holding_registers(39601, ModbusDataType.UINT_32, unit=unit) * 10 - # Gesamte Energie am Wechselrichter in Wh - exported = self.client.read_holding_registers(39621, ModbusDataType.UINT_32, unit=unit) * 10 - imported = self.client.read_holding_registers(39625, ModbusDataType.UINT_32, unit=unit) * 10 - - imported, exported = self.peak_filter.check_values(power, imported, exported) + _, exported = self.peak_filter.check_values(power, None, exported) inverter_state = InverterState( power=power, - imported=imported, exported=exported ) self.store.set(inverter_state) From ff6cff007f43a6a19d49abd4543f60e293c7a64b Mon Sep 17 00:00:00 2001 From: seaspotter Date: Fri, 13 Nov 2026 00:41:41 +0100 Subject: [PATCH 3/4] Solakon One: simulate inverter's own AC import instead of defaulting to zero --- packages/modules/devices/solakon/solakon_one/device.py | 2 +- .../modules/devices/solakon/solakon_one/inverter.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/modules/devices/solakon/solakon_one/device.py b/packages/modules/devices/solakon/solakon_one/device.py index befe8db2a6..0d893f51c3 100644 --- a/packages/modules/devices/solakon/solakon_one/device.py +++ b/packages/modules/devices/solakon/solakon_one/device.py @@ -22,7 +22,7 @@ def create_bat_component(component_config: SolakonOneBatSetup): return SolakonOneBat(component_config=component_config, client=client) def create_inverter_component(component_config: SolakonOneInverterSetup): - return SolakonOneInverter(component_config=component_config, client=client) + return SolakonOneInverter(component_config=component_config, client=client, device_id=device_config.id) def create_counter_component(component_config: SolakonOneCounterSetup): return SolakonOneCounter(component_config=component_config, client=client) diff --git a/packages/modules/devices/solakon/solakon_one/inverter.py b/packages/modules/devices/solakon/solakon_one/inverter.py index 26c8902eee..aeb6bd6746 100644 --- a/packages/modules/devices/solakon/solakon_one/inverter.py +++ b/packages/modules/devices/solakon/solakon_one/inverter.py @@ -6,6 +6,7 @@ from modules.common.component_type import ComponentDescriptor 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_component_value_store from modules.devices.solakon.solakon_one.config import SolakonOneInverterSetup from modules.common.utils.peak_filter import PeakFilter @@ -14,6 +15,7 @@ class KwargsDict(TypedDict): client: ModbusTcpClient_ + device_id: int class SolakonOneInverter(AbstractInverter): @@ -23,6 +25,8 @@ def __init__(self, component_config: SolakonOneInverterSetup, **kwargs: Any) -> def initialize(self) -> None: self.client: ModbusTcpClient_ = self.kwargs['client'] + self.sim_counter = SimCounter( + self.kwargs['device_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) @@ -34,10 +38,14 @@ def update(self) -> None: power = self.client.read_holding_registers(39118, ModbusDataType.INT_32, unit=unit) * -1 # Gesamte DC PV Produktion in Wh exported = self.client.read_holding_registers(39601, ModbusDataType.UINT_32, unit=unit) * 10 + # Gerät hat keinen Zähler für AC-seitigen Bezug (z.B. Eigenverbrauch des WR selbst) - simulieren, + # damit die Hybrid-Berechnung in PurgeInverterState.fix_hybrid_values() einen Wert hat. + imported, _ = self.sim_counter.sim_count(power) - _, exported = self.peak_filter.check_values(power, None, exported) + imported, exported = self.peak_filter.check_values(power, imported, exported) inverter_state = InverterState( power=power, + imported=imported, exported=exported ) self.store.set(inverter_state) From fe7d80fd76456b52b78281c16d87e4af8a70aadc Mon Sep 17 00:00:00 2001 From: seaspotter Date: Fri, 13 Nov 2026 00:43:04 +0100 Subject: [PATCH 4/4] Solakon One: remove comment --- packages/modules/devices/solakon/solakon_one/inverter.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/modules/devices/solakon/solakon_one/inverter.py b/packages/modules/devices/solakon/solakon_one/inverter.py index aeb6bd6746..ffa1c186b7 100644 --- a/packages/modules/devices/solakon/solakon_one/inverter.py +++ b/packages/modules/devices/solakon/solakon_one/inverter.py @@ -38,8 +38,6 @@ def update(self) -> None: power = self.client.read_holding_registers(39118, ModbusDataType.INT_32, unit=unit) * -1 # Gesamte DC PV Produktion in Wh exported = self.client.read_holding_registers(39601, ModbusDataType.UINT_32, unit=unit) * 10 - # Gerät hat keinen Zähler für AC-seitigen Bezug (z.B. Eigenverbrauch des WR selbst) - simulieren, - # damit die Hybrid-Berechnung in PurgeInverterState.fix_hybrid_values() einen Wert hat. imported, _ = self.sim_counter.sim_count(power) imported, exported = self.peak_filter.check_values(power, imported, exported)