Skip to content

Commit df591de

Browse files
authored
Merge pull request #2095 from rosenrot00/fixx
Fix local entity state restore after number polling was disabled
2 parents f8ba749 + 350efdc commit df591de

6 files changed

Lines changed: 128 additions & 91 deletions

File tree

custom_components/solax_modbus/__init__.py

Lines changed: 70 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@
129129
COMM_BLOCK_FAILURE_WINDOW = 600
130130
COMM_RECOVERY_INTERVAL = 300
131131
INFLIGHT_CANCEL_TIMEOUT = 2.0
132-
CONNECT_RETRY_DELAY = 1.0
133132

134133

135134
try:
@@ -530,8 +529,6 @@ def __init__(
530529
# Fallback dummy client for unrecognized interface types
531530
self._client = SimpleNamespace(connected=False, comm_params=SimpleNamespace(host="", port=""))
532531
self._lock = asyncio.Lock()
533-
self._connect_lock = asyncio.Lock()
534-
self._next_connect_attempt = 0.0
535532
self._name: str = name
536533
# following call will modify and extend client in case old modbus API needs to be used
537534
_LOGGER.debug(f"{name}: using pymodbus version {pymodbus_version_info()}")
@@ -1228,31 +1225,11 @@ def _is_expected_shutdown_modbus_error(self, ex: BaseException) -> bool:
12281225
async def _check_connection(self) -> bool:
12291226
if getattr(self, "_stopping", False):
12301227
return False
1231-
if self._client.connected:
1232-
return True
1233-
now = _mtime.monotonic()
1234-
if now < self._next_connect_attempt:
1235-
return False
1236-
async with self._connect_lock:
1237-
if getattr(self, "_stopping", False):
1238-
return False
1239-
if self._client.connected:
1240-
return True
1241-
now = _mtime.monotonic()
1242-
if now < self._next_connect_attempt:
1243-
return False
1228+
if not self._client.connected:
12441229
_LOGGER.debug(f"{self._name}: Inverter is not connected, trying to connect")
1245-
try:
1246-
await self.async_connect()
1247-
except Exception as ex:
1248-
self._next_connect_attempt = _mtime.monotonic() + CONNECT_RETRY_DELAY
1249-
_LOGGER.debug(f"{self._name}: connect attempt failed: {ex}")
1250-
return False
1251-
if not self._client.connected:
1252-
self._next_connect_attempt = _mtime.monotonic() + CONNECT_RETRY_DELAY
1253-
return False
1254-
self._next_connect_attempt = 0.0
1255-
return True
1230+
await self.async_connect()
1231+
await asyncio.sleep(1)
1232+
return self._client.connected
12561233

12571234
async def is_online(self) -> bool:
12581235
return self._client.connected and (self.slowdown == 1)
@@ -1270,11 +1247,10 @@ async def async_connect(self) -> None:
12701247

12711248
async def async_read_holding_registers(self, unit: int, address: int, count: int) -> Any:
12721249
"""Read holding registers using high-level pymodbus API."""
1273-
if not await self._check_connection():
1274-
return None
12751250
async with self._lock:
12761251
if getattr(self, "_stopping", False):
12771252
return None
1253+
await self._check_connection()
12781254
if not self._client.connected:
12791255
return None
12801256
try:
@@ -1293,17 +1269,15 @@ async def async_read_holding_registers(self, unit: int, address: int, count: int
12931269
return None
12941270
_LOGGER.debug(f"{self._name}: ModbusException – closing transport and deferring reconnect")
12951271
self._client.close()
1296-
self._next_connect_attempt = _mtime.monotonic() + CONNECT_RETRY_DELAY
12971272
return None
12981273
return resp
12991274

13001275
async def async_read_input_registers(self, unit: int, address: int, count: int) -> Any:
13011276
"""Read input registers using high-level pymodbus API."""
1302-
if not await self._check_connection():
1303-
return None
13041277
async with self._lock:
13051278
if getattr(self, "_stopping", False):
13061279
return None
1280+
await self._check_connection()
13071281
if not self._client.connected:
13081282
return None
13091283
try:
@@ -1322,7 +1296,6 @@ async def async_read_input_registers(self, unit: int, address: int, count: int)
13221296
return None
13231297
_LOGGER.debug(f"{self._name}: ModbusException – closing transport and deferring reconnect")
13241298
self._client.close()
1325-
self._next_connect_attempt = _mtime.monotonic() + CONNECT_RETRY_DELAY
13261299
return None
13271300
return resp
13281301

@@ -1332,11 +1305,8 @@ async def async_lowlevel_write_register(self, unit: int, address: int, payload:
13321305
regs = convert_to_registers(int(payload), DataType.UINT16, self.plugin.order32) # type: ignore[attr-defined]
13331306
else:
13341307
regs = convert_to_registers(int(payload), DataType.INT16, self.plugin.order32) # type: ignore[attr-defined]
1335-
if not await self._check_connection():
1336-
return None
13371308
async with self._lock:
1338-
if not self._client.connected:
1339-
return None
1309+
await self._check_connection()
13401310
try:
13411311
resp = await self._track_task(self._client.write_register(address=address, value=regs[0], **kwargs)) # type: ignore[arg-type]
13421312
# Plugin-level logging hook
@@ -1381,11 +1351,8 @@ async def async_write_registers_single(
13811351
else:
13821352
regs = convert_to_registers(int(payload), DataType.INT16, self.plugin.order32) # type: ignore[attr-defined]
13831353
kwargs = {ADDR_KW: unit} if unit is not None else {}
1384-
if not await self._check_connection():
1385-
return None
13861354
async with self._lock:
1387-
if not self._client.connected:
1388-
return None
1355+
await self._check_connection()
13891356
try:
13901357
resp = await self._track_task(self._client.write_registers(address=address, values=regs, **kwargs)) # type: ignore[arg-type]
13911358
except (ConnectionException, ModbusIOException) as e:
@@ -2346,53 +2313,67 @@ def _hub_closed_now(self, ref_obj: Any) -> None:
23462313
self._hub = None
23472314

23482315
async def async_connect(self, hub: Any = None) -> Any:
2349-
if getattr(self, "_stopping", False):
2350-
return None
2351-
now = _mtime.monotonic()
2352-
if now < self._next_connect_attempt:
2353-
return None
2354-
async with self._connect_lock:
2355-
if getattr(self, "_stopping", False):
2356-
return None
2357-
now = _mtime.monotonic()
2358-
if now < self._next_connect_attempt:
2359-
return None
2360-
if hub is None and self._hub is not None:
2361-
hub = self._hub()
2362-
if hub is None:
2316+
delay = True
2317+
while True:
2318+
# check if strong reference to
2319+
# get one.
2320+
if hub is not None or (self._hub is not None and (hub := self._hub()) is not None):
2321+
port = hub._pb_params.get("port", 0)
2322+
host = hub._pb_params.get("host", port)
2323+
# TODO just wait some time and recheck again if client connected before
2324+
# giving up
2325+
await hub._lock.acquire()
2326+
try:
2327+
if hub._client and hub._client.connected:
2328+
hub._lock.release()
2329+
_LOGGER.debug(
2330+
"Inverter connected at %s:%s",
2331+
host,
2332+
port,
2333+
)
2334+
return hub
2335+
except (TypeError, AttributeError):
2336+
pass
2337+
hub._lock.release()
2338+
if not delay:
2339+
reason = " core modbus hub '{self._core_hub}' not ready" if hub._config_delay else ""
2340+
_LOGGER.warning(f"Unable to connect to Inverter at {host}:{port}.{reason}")
2341+
return None
2342+
else:
2343+
# get hold of current CoreModbusHub object with
2344+
# provided entity name
23632345
try:
23642346
hub = get_core_hub(self._hass, self._core_hub)
23652347
except KeyError:
2366-
_LOGGER.warning(f"CoreModbusHub '{self._core_hub}' not available")
2367-
self._next_connect_attempt = _mtime.monotonic() + CONNECT_RETRY_DELAY
2348+
_LOGGER.warning(
2349+
f"CoreModbusHub '{self._core_hub}' not available",
2350+
)
23682351
return None
2369-
if not hub:
2370-
_LOGGER.warning("Unable to join core modbus %s", self._core_hub)
2371-
self._next_connect_attempt = _mtime.monotonic() + CONNECT_RETRY_DELAY
2352+
else:
2353+
if hub:
2354+
# update weak reference handle to refer to
2355+
# the actual CoreModbusHub object
2356+
self._hub = WeakRef(hub, self._hub_closed_now)
2357+
continue
2358+
if not delay:
2359+
_LOGGER.warning(
2360+
"Unable to join core modbus %s",
2361+
self._core_hub,
2362+
)
23722363
return None
2373-
self._hub = WeakRef(hub, self._hub_closed_now)
2374-
2375-
port = hub._pb_params.get("port", 0)
2376-
host = hub._pb_params.get("host", port)
2377-
try:
2378-
async with hub._lock:
2379-
if hub._client and hub._client.connected:
2380-
_LOGGER.debug("Inverter connected at %s:%s", host, port)
2381-
self._next_connect_attempt = 0.0
2382-
return hub
2383-
except (TypeError, AttributeError):
2384-
pass
2385-
reason = f" core modbus hub '{self._core_hub}' not ready" if getattr(hub, "_config_delay", False) else ""
2386-
_LOGGER.debug(f"Unable to connect to Inverter at {host}:{port}.{reason}")
2387-
self._next_connect_attempt = _mtime.monotonic() + CONNECT_RETRY_DELAY
2388-
return None
2364+
# wait some time (TODO make configurable) before
2365+
# rechecking if CoreModbusHub object has been created and
2366+
# connected
2367+
delay = False
2368+
await asyncio.sleep(10)
23892369

23902370
async def async_read_holding_registers(self, unit: int, address: int, count: int) -> Any:
23912371
"""Read holding registers."""
23922372
kwargs = {ADDR_KW: unit} if unit is not None else {}
23932373
if getattr(self, "_stopping", False):
23942374
return None
2395-
hub = await self._check_connection()
2375+
async with self._lock:
2376+
hub = await self._check_connection()
23962377
try:
23972378
if not hub or getattr(hub, "_config_delay", False):
23982379
return None
@@ -2414,7 +2395,8 @@ async def async_read_input_registers(self, unit: int, address: int, count: int)
24142395
kwargs = {ADDR_KW: unit} if unit is not None else {}
24152396
if getattr(self, "_stopping", False):
24162397
return None
2417-
hub = await self._check_connection()
2398+
async with self._lock:
2399+
hub = await self._check_connection()
24182400
try:
24192401
if not hub or getattr(hub, "_config_delay", False):
24202402
return None
@@ -2442,7 +2424,8 @@ async def async_lowlevel_write_register(self, unit: int, address: int, payload:
24422424
kwargs = {ADDR_KW: unit} if unit is not None else {}
24432425
if getattr(self, "_stopping", False):
24442426
return None
2445-
hub = await self._check_connection()
2427+
async with self._lock:
2428+
hub = await self._check_connection()
24462429
try:
24472430
if not hub or getattr(hub, "_config_delay", False):
24482431
return None
@@ -2471,13 +2454,14 @@ async def async_write_registers_single(
24712454
else:
24722455
regs = convert_to_registers(int(payload), DataType.INT16, self.plugin.order32) # type: ignore[attr-defined]
24732456
kwargs: dict[str, int] = {ADDR_KW: unit} if unit is not None else {}
2474-
hub = await self._check_connection()
2457+
async with self._lock:
2458+
hub = await self._check_connection()
24752459
try:
2476-
if not hub or hub._config_delay:
2460+
if hub._config_delay:
24772461
return None
24782462
async with hub._lock:
24792463
try:
2480-
resp = await self._track_task(hub._client.write_registers(address=address, values=regs, **kwargs))
2464+
resp = await self._client.write_registers(address=address, values=regs, **kwargs) # type: ignore[arg-type]
24812465
except (ConnectionException, ModbusIOException) as e:
24822466
original_message = str(e)
24832467
raise HomeAssistantError(f"Error writing single Modbus registers: {original_message}") from e
@@ -2536,13 +2520,14 @@ async def async_write_registers_multi(self, unit: int, address: int, payload: li
25362520
_LOGGER.error(f"unsupported unit type: {typ} for {key}")
25372521
# for easier debugging, make next line a _LOGGER.info line
25382522
_LOGGER.debug(f"Ready to write multiple registers at 0x{address:02x}: {regs_out}")
2539-
hub = await self._check_connection()
2523+
async with self._lock:
2524+
hub = await self._check_connection()
25402525
try:
2541-
if not hub or hub._config_delay:
2526+
if hub._config_delay:
25422527
return None
25432528
async with hub._lock:
25442529
try:
2545-
resp = await self._track_task(hub._client.write_registers(address=address, values=regs_out, **kwargs))
2530+
resp = await self._client.write_registers(address=address, values=regs_out, **kwargs) # type: ignore[arg-type]
25462531
except (ConnectionException, ModbusIOException) as e:
25472532
original_message = str(e)
25482533
raise HomeAssistantError(f"Error writing multiple Modbus registers: {original_message}") from e

custom_components/solax_modbus/number.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,19 @@ def __init__(
141141

142142
async def async_added_to_hass(self) -> None:
143143
"""Register callbacks."""
144+
if self._write_method == WRITE_DATA_LOCAL:
145+
self.async_on_remove(self.hass.bus.async_listen("solax_modbus_local_data_loaded", self._handle_local_data_loaded))
146+
self.async_write_ha_state()
147+
return
148+
144149
# Skip hub registration for computed/internal entities (those without modbus registers)
145150
if self.entity_description.register is None or self.entity_description.register < 0:
146151
return
147152
await self._hub.async_add_solax_modbus_sensor(self)
148153

149154
async def async_will_remove_from_hass(self) -> None:
155+
if self._write_method == WRITE_DATA_LOCAL or self.entity_description.register is None or self.entity_description.register < 0:
156+
return
150157
await self._hub.async_remove_solax_modbus_sensor(self)
151158

152159
""" remove duplicate declaration
@@ -158,6 +165,12 @@ async def async_set_value(self, native_value: float) -> None:
158165
def modbus_data_updated(self) -> None:
159166
self.async_write_ha_state()
160167

168+
@callback
169+
def _handle_local_data_loaded(self, event: Any) -> None:
170+
if (event.data or {}).get("hub_name") != self._hub._name:
171+
return
172+
self.async_write_ha_state()
173+
161174
@property
162175
def should_poll(self) -> bool:
163176
"""Data is delivered by the hub."""

custom_components/solax_modbus/plugin_solax.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3749,7 +3749,7 @@ def value_function_battery_voltage_cell_difference(initval: int, descr: Any, dat
37493749
2: "Inverter",
37503750
},
37513751
allowedtypes=AC | HYBRID | GEN4 | GEN5,
3752-
modbus_min=101,
3752+
modbus_min=100,
37533753
icon="mdi:dip-switch",
37543754
),
37553755
SolaxModbusSelectEntityDescription(
@@ -4678,7 +4678,7 @@ def value_function_battery_voltage_cell_difference(initval: int, descr: Any, dat
46784678
2: "Inverter",
46794679
},
46804680
allowedtypes=AC | HYBRID | GEN4 | GEN5,
4681-
modbus_min=101,
4681+
modbus_min=100,
46824682
internal=True,
46834683
),
46844684
SolaXModbusSensorEntityDescription(

custom_components/solax_modbus/select.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,18 +112,31 @@ def __init__(
112112

113113
async def async_added_to_hass(self) -> None:
114114
"""Register callbacks."""
115+
if self._write_method == WRITE_DATA_LOCAL:
116+
self.async_on_remove(self.hass.bus.async_listen("solax_modbus_local_data_loaded", self._handle_local_data_loaded))
117+
self.async_write_ha_state()
118+
return
119+
115120
# Skip hub registration for computed/internal entities (those without modbus registers)
116121
if self.entity_description.register is None or self.entity_description.register < 0:
117122
return
118123
await self._hub.async_add_solax_modbus_sensor(self)
119124

120125
async def async_will_remove_from_hass(self) -> None:
126+
if self._write_method == WRITE_DATA_LOCAL or self.entity_description.register is None or self.entity_description.register < 0:
127+
return
121128
await self._hub.async_remove_solax_modbus_sensor(self)
122129

123130
@callback
124131
def modbus_data_updated(self) -> None:
125132
self.async_write_ha_state()
126133

134+
@callback
135+
def _handle_local_data_loaded(self, event: Any) -> None:
136+
if (event.data or {}).get("hub_name") != self._hub._name:
137+
return
138+
self.async_write_ha_state()
139+
127140
@property
128141
def current_option(self) -> str | None:
129142
option_dict = self._option_dict or {}

custom_components/solax_modbus/switch.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from homeassistant.components.switch import SwitchEntity
77
from homeassistant.config_entries import ConfigEntry
88
from homeassistant.const import CONF_NAME
9-
from homeassistant.core import HomeAssistant
9+
from homeassistant.core import HomeAssistant, callback
1010
from homeassistant.helpers.device_registry import DeviceInfo
1111
from homeassistant.helpers.entity_platform import AddEntitiesCallback
1212
from homeassistant.helpers.restore_state import RestoreEntity
@@ -151,11 +151,12 @@ async def async_turn_off(self, **kwargs: Any) -> None:
151151

152152
async def async_added_to_hass(self) -> None:
153153
await super().async_added_to_hass()
154-
# Skip hub registration for computed/internal entities (those without modbus registers)
155-
if self.entity_description.register is None or self.entity_description.register < 0:
156-
return
157154
if self.entity_description.write_method != WRITE_DATA_LOCAL:
158155
return
156+
self.async_on_remove(self.hass.bus.async_listen("solax_modbus_local_data_loaded", self._handle_local_data_loaded))
157+
if self._sensor_key is not None and self._sensor_key in self._hub.data:
158+
self.async_write_ha_state()
159+
return
159160
last_state = await self.async_get_last_state()
160161
if not last_state or last_state.state in ("unknown", "unavailable"):
161162
return
@@ -165,6 +166,12 @@ async def async_added_to_hass(self) -> None:
165166
self._hub.data[self._sensor_key] = 1 if is_on else 0
166167
self.async_write_ha_state()
167168

169+
@callback
170+
def _handle_local_data_loaded(self, event: Any) -> None:
171+
if (event.data or {}).get("hub_name") != self._hub._name:
172+
return
173+
self.async_write_ha_state()
174+
168175
async def _write_switch_to_modbus(self) -> None:
169176
if self.entity_description.write_method == WRITE_DATA_LOCAL:
170177
if self._sensor_key is None:

0 commit comments

Comments
 (0)