Skip to content

Commit f43fc71

Browse files
authored
Solaredge: Move register mapping to initialization method (#2358)
1 parent 94efd45 commit f43fc71

7 files changed

Lines changed: 121 additions & 132 deletions

File tree

packages/modules/common/configurable_device.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def __call__(self, components: Iterable[T_COMPONENT], error_handler: Callable) -
3535
components_list = list(components)
3636
with MultiComponentUpdateContext(components_list, error_handler):
3737
if not components:
38-
raise FaultState.warning("Keine Komponenten konfiguriert")
38+
raise Exception("Keine Komponenten konfiguriert oder Initialisierung fehlgeschlagen")
3939
self.__updater(components_list)
4040

4141

packages/modules/devices/solaredge/solaredge/counter.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22
import logging
3-
from typing import TypedDict, Any
3+
from typing import Dict, TypedDict, Any
44

55
from modules.common import modbus
66
from modules.common.abstract_device import AbstractCounter
@@ -11,13 +11,14 @@
1111
from modules.common.store import get_counter_value_store
1212
from modules.devices.solaredge.solaredge.config import SolaredgeCounterSetup
1313
from modules.devices.solaredge.solaredge.scale import create_scaled_reader
14-
from modules.devices.solaredge.solaredge.meter import SolaredgeMeterRegisters
14+
from modules.devices.solaredge.solaredge.meter import SolaredgeMeterRegisters, set_component_registers
1515

1616
log = logging.getLogger(__name__)
1717

1818

1919
class KwargsDict(TypedDict):
2020
client: modbus.ModbusTcpClient_
21+
components: Dict
2122

2223

2324
class SolaredgeCounter(AbstractCounter):
@@ -30,6 +31,11 @@ def initialize(self) -> None:
3031
self.registers = SolaredgeMeterRegisters()
3132
self.store = get_counter_value_store(self.component_config.id)
3233
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))
34+
35+
components = list(self.kwargs['components'].values())
36+
components.append(self)
37+
set_component_registers(self.component_config, self.__tcp_client, components)
38+
3339
self._read_scaled_int16 = create_scaled_reader(
3440
self.__tcp_client, self.component_config.configuration.modbus_id, ModbusDataType.INT_16
3541
)

packages/modules/devices/solaredge/solaredge/device.py

Lines changed: 4 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22
import logging
3-
from typing import Iterable, Union, List
3+
from typing import Iterable, Union
44

55
from modules.common import modbus
66
from modules.common.abstract_device import DeviceDescriptor
@@ -11,36 +11,11 @@
1111
from modules.devices.solaredge.solaredge.inverter import SolaredgeInverter
1212
from modules.devices.solaredge.solaredge.config import (Solaredge, SolaredgeBatSetup, SolaredgeCounterSetup,
1313
SolaredgeExternalInverterSetup, SolaredgeInverterSetup)
14-
from modules.devices.solaredge.solaredge.meter import SolaredgeMeterRegisters
1514

1615
log = logging.getLogger(__name__)
1716

18-
solaredge_component_classes = Union[SolaredgeBat, SolaredgeCounter,
19-
SolaredgeExternalInverter, SolaredgeInverter]
20-
default_unit_id = 85
21-
synergy_unit_identifier = 160
22-
reconnect_delay = 1.2
23-
24-
25-
def set_component_registers(components: Iterable[solaredge_component_classes],
26-
synergy_units: int,
27-
modbus_id: int) -> None:
28-
meters: List[Union[SolaredgeExternalInverter, SolaredgeCounter, None]] = [None]*3
29-
for component in components:
30-
if (isinstance(component, (SolaredgeExternalInverter, SolaredgeCounter)) and
31-
component.component_config.configuration.modbus_id == modbus_id):
32-
# Registerverschibung nur für Komponenten mit gleicher Modbus-ID, da diese am gleichen Haupt-WR hängen und
33-
# die gleichen Synergy-Units haben.
34-
meters[component.component_config.configuration.meter_id-1] = component
3517

36-
# https://www.solaredge.com/sites/default/files/sunspec-implementation-technical-note.pdf:
37-
# Only enabled meters are readable, i.e. if meter 1 and 3 are enabled, they are readable as 1st meter and 2nd
38-
# meter (and the 3rd meter isn't readable).
39-
for meter_id, meter in enumerate(filter(None, meters), start=1):
40-
log.debug(
41-
"%s: internal meter id: %d, synergy units: %s", meter.component_config.name, meter_id, synergy_units
42-
)
43-
meter.registers = SolaredgeMeterRegisters(meter_id, synergy_units)
18+
reconnect_delay = 1.2
4419

4520

4621
def create_device(device_config: Solaredge):
@@ -52,53 +27,22 @@ def create_bat_component(component_config: SolaredgeBatSetup):
5227

5328
def create_counter_component(component_config: SolaredgeCounterSetup):
5429
nonlocal client, device
55-
synergy_units = get_synergy_units(component_config)
56-
counter = SolaredgeCounter(component_config, client=client)
57-
# neue Komponente wird erst nach Instanziierung device.components hinzugefügt
58-
components = list(device.components.values())
59-
components.append(counter)
60-
set_component_registers(components, synergy_units, component_config.configuration.modbus_id)
61-
return counter
30+
return SolaredgeCounter(component_config, client=client, components=device.components)
6231

6332
def create_inverter_component(component_config: SolaredgeInverterSetup):
6433
nonlocal client
6534
return SolaredgeInverter(component_config, client=client, device_id=device_config.id)
6635

6736
def create_external_inverter_component(component_config: SolaredgeExternalInverterSetup):
6837
nonlocal client, device
69-
synergy_units = get_synergy_units(component_config)
70-
external_inverter = SolaredgeExternalInverter(component_config, client=client)
71-
components = list(device.components.values())
72-
components.append(external_inverter)
73-
set_component_registers(components, synergy_units, component_config.configuration.modbus_id)
74-
return external_inverter
38+
return SolaredgeExternalInverter(component_config, client=client, components=device.components)
7539

7640
def update_components(components: Iterable[Union[SolaredgeBat, SolaredgeCounter, SolaredgeInverter]]):
7741
nonlocal client
7842
with client:
7943
for component in components:
8044
component.update()
8145

82-
def get_synergy_units(component_config: Union[SolaredgeBatSetup,
83-
SolaredgeCounterSetup,
84-
SolaredgeInverterSetup,
85-
SolaredgeExternalInverterSetup]) -> None:
86-
nonlocal client
87-
if client.read_holding_registers(40121, modbus.ModbusDataType.UINT_16,
88-
unit=component_config.configuration.modbus_id
89-
) == synergy_unit_identifier:
90-
# Snyergy-Units vom Haupt-WR des angeschlossenen Meters ermitteln. Es kann mehrere Haupt-WR mit
91-
# unterschiedlichen Modbus-IDs im Verbund geben.
92-
log.debug("Synergy Units supported")
93-
synergy_units = int(client.read_holding_registers(
94-
40129, modbus.ModbusDataType.UINT_16,
95-
unit=component_config.configuration.modbus_id)) or 1
96-
log.debug(
97-
f"Synergy Units detected for Modbus ID {component_config.configuration.modbus_id}: {synergy_units}")
98-
else:
99-
synergy_units = 1
100-
return synergy_units
101-
10246
def initializer():
10347
nonlocal client
10448
client = modbus.ModbusTcpClient_(device_config.configuration.ip_address,

packages/modules/devices/solaredge/solaredge/device_test.py

Lines changed: 0 additions & 64 deletions
This file was deleted.

packages/modules/devices/solaredge/solaredge/external_inverter.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22
import logging
3-
from typing import TypedDict, Any
3+
from typing import Dict, TypedDict, Any
44

55
from modules.common import modbus
66
from modules.common.abstract_device import AbstractInverter
@@ -11,13 +11,14 @@
1111
from modules.common.store import get_inverter_value_store
1212
from modules.devices.solaredge.solaredge.config import SolaredgeExternalInverterSetup
1313
from modules.devices.solaredge.solaredge.scale import create_scaled_reader
14-
from modules.devices.solaredge.solaredge.meter import SolaredgeMeterRegisters
14+
from modules.devices.solaredge.solaredge.meter import SolaredgeMeterRegisters, set_component_registers
1515

1616
log = logging.getLogger(__name__)
1717

1818

1919
class KwargsDict(TypedDict):
2020
client: modbus.ModbusTcpClient_
21+
components: Dict
2122

2223

2324
class SolaredgeExternalInverter(AbstractInverter):
@@ -32,6 +33,11 @@ def initialize(self) -> None:
3233
self.registers = SolaredgeMeterRegisters(self.component_config.configuration.meter_id)
3334
self.store = get_inverter_value_store(self.component_config.id)
3435
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))
36+
37+
components = list(self.kwargs['components'].values())
38+
components.append(self)
39+
set_component_registers(self.component_config, self.__tcp_client, components)
40+
3541
self._read_scaled_int16 = create_scaled_reader(
3642
self.__tcp_client, self.component_config.configuration.modbus_id, ModbusDataType.INT_16
3743
)

packages/modules/devices/solaredge/solaredge/meter.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11

22
import logging
3+
from typing import Iterable, Union, List
34

4-
5+
from modules.common import modbus
6+
from modules.devices.solaredge.solaredge.config import (SolaredgeBatSetup, SolaredgeCounterSetup,
7+
SolaredgeExternalInverterSetup, SolaredgeInverterSetup)
58
log = logging.getLogger(__name__)
69

710

11+
synergy_unit_identifier = 160
12+
13+
814
class SolaredgeMeterRegisters:
915
def __init__(self, internal_meter_id: int = 1, synergy_units: int = 1):
1016
# 40206: Total Real Power (sum of active phases)
@@ -53,3 +59,55 @@ def _update_offset_synergy_units(self, synergy_units: int) -> None:
5359
def _add_offset(self, offset: int) -> None:
5460
for name, value in self.__dict__.items():
5561
setattr(self, name, value+offset)
62+
63+
64+
def _set_registers(components: Iterable,
65+
synergy_units: int,
66+
modbus_id: int) -> None:
67+
meters: List = [None]*3
68+
for component in components:
69+
if (isinstance(component.component_config, (SolaredgeCounterSetup, SolaredgeExternalInverterSetup)) and
70+
component.component_config.configuration.modbus_id == modbus_id):
71+
# Registerverschibung nur für Komponenten mit gleicher Modbus-ID, da diese am gleichen Haupt-WR hängen
72+
# und die gleichen Synergy-Units haben.
73+
meters[component.component_config.configuration.meter_id-1] = component
74+
75+
# https://www.solaredge.com/sites/default/files/sunspec-implementation-technical-note.pdf:
76+
# Only enabled meters are readable, i.e. if meter 1 and 3 are enabled, they are readable as 1st meter and 2nd
77+
# meter (and the 3rd meter isn't readable).
78+
for meter_id, meter in enumerate(filter(None, meters), start=1):
79+
log.debug(
80+
"%s: internal meter id: %d, synergy units: %s", meter.component_config.name, meter_id, synergy_units
81+
)
82+
meter.registers = SolaredgeMeterRegisters(meter_id, synergy_units)
83+
84+
85+
def _get_synergy_units(component_config: Union[SolaredgeBatSetup,
86+
SolaredgeCounterSetup,
87+
SolaredgeInverterSetup,
88+
SolaredgeExternalInverterSetup],
89+
client) -> int:
90+
if client.read_holding_registers(40121, modbus.ModbusDataType.UINT_16,
91+
unit=component_config.configuration.modbus_id
92+
) == synergy_unit_identifier:
93+
# Snyergy-Units vom Haupt-WR des angeschlossenen Meters ermitteln. Es kann mehrere Haupt-WR mit
94+
# unterschiedlichen Modbus-IDs im Verbund geben.
95+
log.debug("Synergy Units supported")
96+
synergy_units = int(client.read_holding_registers(
97+
40129, modbus.ModbusDataType.UINT_16,
98+
unit=component_config.configuration.modbus_id)) or 1
99+
log.debug(
100+
f"Synergy Units detected for Modbus ID {component_config.configuration.modbus_id}: {synergy_units}")
101+
else:
102+
synergy_units = 1
103+
return synergy_units
104+
105+
106+
def set_component_registers(component_config: Union[SolaredgeBatSetup,
107+
SolaredgeCounterSetup,
108+
SolaredgeInverterSetup,
109+
SolaredgeExternalInverterSetup],
110+
client,
111+
components: Iterable) -> None:
112+
synergy_units = _get_synergy_units(component_config, client)
113+
_set_registers(components, synergy_units, component_config.configuration.modbus_id)

packages/modules/devices/solaredge/solaredge/meter_test.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
from typing import NamedTuple
1+
from typing import List, NamedTuple
2+
from unittest.mock import Mock
23
import pytest
34

4-
from modules.devices.solaredge.solaredge.meter import SolaredgeMeterRegisters
5+
from modules.devices.solaredge.solaredge.config import SolaredgeCounterSetup
6+
from modules.devices.solaredge.solaredge.counter import SolaredgeCounter
7+
from modules.devices.solaredge.solaredge.meter import SolaredgeMeterRegisters, _set_registers
58

69

710
Params = NamedTuple("Params", [("meter_id", int),
@@ -24,3 +27,39 @@ def test_meter(params: Params):
2427

2528
# assert
2629
assert registers.powers == params.expected_power_register
30+
31+
32+
Params = NamedTuple("Params", [("configured_meter_ids", List[int])])
33+
34+
35+
@pytest.mark.parametrize(["params"], [
36+
pytest.param(
37+
Params(configured_meter_ids=[1, 2]),
38+
id="ids unchanged if meter_ids are continuous starting from 1"
39+
),
40+
pytest.param(
41+
Params(configured_meter_ids=[2, 3]),
42+
id="ids move forward if not starting at 1"
43+
),
44+
pytest.param(
45+
Params(configured_meter_ids=[1, 3]),
46+
id="gaps in ids are closed"
47+
)
48+
])
49+
def test_set_component_registers_assigns_effective_meter_regs(params: Params):
50+
# setup
51+
components_list = []
52+
for meter_id in params.configured_meter_ids:
53+
counter = SolaredgeCounter(component_config=Mock(spec=SolaredgeCounterSetup,
54+
configuration=Mock(meter_id=meter_id, modbus_id=1)))
55+
counter.component_config.configure_mock(name="counter")
56+
components_list.append(counter)
57+
58+
for counter in components_list:
59+
counter.registers = SolaredgeMeterRegisters(internal_meter_id=counter.component_config.configuration.meter_id)
60+
# execution
61+
_set_registers(components_list, synergy_units=1, modbus_id=1)
62+
63+
# evaluation
64+
assert components_list[0].registers.powers == 40206
65+
assert components_list[1].registers.powers == 40380

0 commit comments

Comments
 (0)