Skip to content

Commit d1a7741

Browse files
committed
RetryUnit_deps
1 parent 99151b6 commit d1a7741

5 files changed

Lines changed: 659 additions & 493 deletions

File tree

‎pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ classifiers = [
2727
"Programming Language :: Python :: 3.12",
2828
"Programming Language :: Python :: 3.13",
2929
"Programming Language :: Python :: 3.14",
30+
"Programming Language :: Python :: 3.15",
3031
]
3132
dependencies = [
3233
"cattrs>=25,<27",

‎src/ha_addon_sunsynk_multi/a_inverter.py‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,7 @@ async def lifecycle_attempt_recovery(self) -> None:
153153

154154
async def read_identity(self) -> Identity:
155155
"""Read device type, protocol, and serial via the Identity Component."""
156-
# Read through Sunsynk, not self.inv.unit: same read_holding_registers
157-
# signature, but with READ_ATTEMPTS retries and the serial flush.
158-
# ponytail: only holds while Identity reads holding registers only. A field
159-
# in another space would need the unit's read_coils/read_input_registers.
160-
identity = Identity(self.inv) # type: ignore[arg-type]
156+
identity = Identity(self.inv.retrying_unit) # type: ignore[arg-type]
161157
await identity.async_update()
162158
self.identity = identity
163159
return identity

‎src/sunsynk/sunsynk.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,23 @@ async def write_registers(self, /, address: int, values: list[int]) -> None:
4949
...
5050

5151

52+
@dataclass(frozen=True, slots=True)
53+
class _RetryingUnit:
54+
"""``HoldingUnit`` facade: FC03/FC16 through ``Sunsynk`` retry policy."""
55+
56+
ss: Sunsynk
57+
58+
@property
59+
def connected(self) -> bool:
60+
return self.ss.unit.connected
61+
62+
async def read_holding_registers(self, address: int, count: int) -> list[int]:
63+
return list(await self.ss.read_holding_registers(address, count))
64+
65+
async def write_registers(self, address: int, values: list[int]) -> None:
66+
await self.ss.unit.write_registers(address, values)
67+
68+
5269
@dataclass(kw_only=True)
5370
class Sunsynk:
5471
"""Sunsync inverter reached through a holding-register unit."""
@@ -66,6 +83,11 @@ class Sunsynk:
6683
allow_gap: int = 2
6784
timeouts: int = 0
6885

86+
@property
87+
def retrying_unit(self) -> HoldingUnit:
88+
"""Unit handle for ``modbus_connection`` Components (retries + serial flush)."""
89+
return _RetryingUnit(self)
90+
6991
@classmethod
7092
def from_url(
7193
cls,

‎src/tests/sunsynk/test_sunsynk.py‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import asyncio
44
import logging
55
from collections.abc import Sequence
6-
from unittest.mock import MagicMock, call, patch
6+
from unittest.mock import AsyncMock, MagicMock, call, patch
77

88
import pytest
99
from modbus_connection import ModbusProtocolError
@@ -24,6 +24,20 @@ def _ss(**kwargs: object) -> Sunsynk:
2424
return Sunsynk(unit=MagicMock(), **kwargs) # type: ignore[arg-type]
2525

2626

27+
async def test_retrying_unit_routes_fc03_through_sunsynk() -> None:
28+
"""retrying_unit uses Sunsynk.read_holding_registers, not the raw unit."""
29+
unit = MagicMock()
30+
unit.connected = True
31+
ss = Sunsynk(unit=unit)
32+
ss.read_holding_registers = AsyncMock(return_value=[0, 1, 2]) # type: ignore[method-assign]
33+
34+
got = await ss.retrying_unit.read_holding_registers(0, 3)
35+
36+
assert got == [0, 1, 2]
37+
ss.read_holding_registers.assert_awaited_once_with(0, 3) # type: ignore[attr-defined]
38+
unit.read_holding_registers.assert_not_called()
39+
40+
2741
@patch("sunsynk.Sunsynk.read_holding_registers")
2842
@patch("sunsynk.Sunsynk.write_register")
2943
async def test_ss_write_sensor(

0 commit comments

Comments
 (0)