Skip to content

Commit 8ab4793

Browse files
authored
Merge pull request #2082 from rosenrot00/patch-19
Avoid holding Modbus locks during reconnect checks
2 parents 650ad72 + e9e0edd commit 8ab4793

1 file changed

Lines changed: 85 additions & 70 deletions

File tree

custom_components/solax_modbus/__init__.py

Lines changed: 85 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
COMM_BLOCK_FAILURE_THRESHOLD = 3
129129
COMM_BLOCK_FAILURE_WINDOW = 600
130130
COMM_RECOVERY_INTERVAL = 300
131+
CONNECT_RETRY_DELAY = 1.0
131132

132133

133134
try:
@@ -528,6 +529,8 @@ def __init__(
528529
# Fallback dummy client for unrecognized interface types
529530
self._client = SimpleNamespace(connected=False, comm_params=SimpleNamespace(host="", port=""))
530531
self._lock = asyncio.Lock()
532+
self._connect_lock = asyncio.Lock()
533+
self._next_connect_attempt = 0.0
531534
self._name: str = name
532535
# following call will modify and extend client in case old modbus API needs to be used
533536
_LOGGER.debug(f"{name}: using pymodbus version {pymodbus_version_info()}")
@@ -1211,11 +1214,31 @@ def _track_task(self, coro: Any) -> asyncio.Task[Any]:
12111214
async def _check_connection(self) -> bool:
12121215
if getattr(self, "_stopping", False):
12131216
return False
1214-
if not self._client.connected:
1217+
if self._client.connected:
1218+
return True
1219+
now = _mtime.monotonic()
1220+
if now < self._next_connect_attempt:
1221+
return False
1222+
async with self._connect_lock:
1223+
if getattr(self, "_stopping", False):
1224+
return False
1225+
if self._client.connected:
1226+
return True
1227+
now = _mtime.monotonic()
1228+
if now < self._next_connect_attempt:
1229+
return False
12151230
_LOGGER.debug(f"{self._name}: Inverter is not connected, trying to connect")
1216-
await self.async_connect()
1217-
await asyncio.sleep(1)
1218-
return self._client.connected
1231+
try:
1232+
await self.async_connect()
1233+
except Exception as ex:
1234+
self._next_connect_attempt = _mtime.monotonic() + CONNECT_RETRY_DELAY
1235+
_LOGGER.debug(f"{self._name}: connect attempt failed: {ex}")
1236+
return False
1237+
if not self._client.connected:
1238+
self._next_connect_attempt = _mtime.monotonic() + CONNECT_RETRY_DELAY
1239+
return False
1240+
self._next_connect_attempt = 0.0
1241+
return True
12191242

12201243
async def is_online(self) -> bool:
12211244
return self._client.connected and (self.slowdown == 1)
@@ -1233,10 +1256,11 @@ async def async_connect(self) -> None:
12331256

12341257
async def async_read_holding_registers(self, unit: int, address: int, count: int) -> Any:
12351258
"""Read holding registers using high-level pymodbus API."""
1259+
if not await self._check_connection():
1260+
return None
12361261
async with self._lock:
12371262
if getattr(self, "_stopping", False):
12381263
return None
1239-
await self._check_connection()
12401264
if not self._client.connected:
12411265
return None
12421266
try:
@@ -1252,15 +1276,17 @@ async def async_read_holding_registers(self, unit: int, address: int, count: int
12521276
return None
12531277
_LOGGER.debug(f"{self._name}: ModbusException – closing transport and deferring reconnect")
12541278
self._client.close()
1279+
self._next_connect_attempt = _mtime.monotonic() + CONNECT_RETRY_DELAY
12551280
return None
12561281
return resp
12571282

12581283
async def async_read_input_registers(self, unit: int, address: int, count: int) -> Any:
12591284
"""Read input registers using high-level pymodbus API."""
1285+
if not await self._check_connection():
1286+
return None
12601287
async with self._lock:
12611288
if getattr(self, "_stopping", False):
12621289
return None
1263-
await self._check_connection()
12641290
if not self._client.connected:
12651291
return None
12661292
try:
@@ -1276,6 +1302,7 @@ async def async_read_input_registers(self, unit: int, address: int, count: int)
12761302
return None
12771303
_LOGGER.debug(f"{self._name}: ModbusException – closing transport and deferring reconnect")
12781304
self._client.close()
1305+
self._next_connect_attempt = _mtime.monotonic() + CONNECT_RETRY_DELAY
12791306
return None
12801307
return resp
12811308

@@ -1285,8 +1312,11 @@ async def async_lowlevel_write_register(self, unit: int, address: int, payload:
12851312
regs = convert_to_registers(int(payload), DataType.UINT16, self.plugin.order32) # type: ignore[attr-defined]
12861313
else:
12871314
regs = convert_to_registers(int(payload), DataType.INT16, self.plugin.order32) # type: ignore[attr-defined]
1315+
if not await self._check_connection():
1316+
return None
12881317
async with self._lock:
1289-
await self._check_connection()
1318+
if not self._client.connected:
1319+
return None
12901320
try:
12911321
resp = await self._track_task(self._client.write_register(address=address, value=regs[0], **kwargs)) # type: ignore[arg-type]
12921322
# Plugin-level logging hook
@@ -1331,8 +1361,11 @@ async def async_write_registers_single(
13311361
else:
13321362
regs = convert_to_registers(int(payload), DataType.INT16, self.plugin.order32) # type: ignore[attr-defined]
13331363
kwargs = {ADDR_KW: unit} if unit is not None else {}
1364+
if not await self._check_connection():
1365+
return None
13341366
async with self._lock:
1335-
await self._check_connection()
1367+
if not self._client.connected:
1368+
return None
13361369
try:
13371370
resp = await self._track_task(self._client.write_registers(address=address, values=regs, **kwargs)) # type: ignore[arg-type]
13381371
except (ConnectionException, ModbusIOException) as e:
@@ -2293,67 +2326,53 @@ def _hub_closed_now(self, ref_obj: Any) -> None:
22932326
self._hub = None
22942327

22952328
async def async_connect(self, hub: Any = None) -> Any:
2296-
delay = True
2297-
while True:
2298-
# check if strong reference to
2299-
# get one.
2300-
if hub is not None or (self._hub is not None and (hub := self._hub()) is not None):
2301-
port = hub._pb_params.get("port", 0)
2302-
host = hub._pb_params.get("host", port)
2303-
# TODO just wait some time and recheck again if client connected before
2304-
# giving up
2305-
await hub._lock.acquire()
2306-
try:
2307-
if hub._client and hub._client.connected:
2308-
hub._lock.release()
2309-
_LOGGER.debug(
2310-
"Inverter connected at %s:%s",
2311-
host,
2312-
port,
2313-
)
2314-
return hub
2315-
except (TypeError, AttributeError):
2316-
pass
2317-
hub._lock.release()
2318-
if not delay:
2319-
reason = " core modbus hub '{self._core_hub}' not ready" if hub._config_delay else ""
2320-
_LOGGER.warning(f"Unable to connect to Inverter at {host}:{port}.{reason}")
2321-
return None
2322-
else:
2323-
# get hold of current CoreModbusHub object with
2324-
# provided entity name
2329+
if getattr(self, "_stopping", False):
2330+
return None
2331+
now = _mtime.monotonic()
2332+
if now < self._next_connect_attempt:
2333+
return None
2334+
async with self._connect_lock:
2335+
if getattr(self, "_stopping", False):
2336+
return None
2337+
now = _mtime.monotonic()
2338+
if now < self._next_connect_attempt:
2339+
return None
2340+
if hub is None and self._hub is not None:
2341+
hub = self._hub()
2342+
if hub is None:
23252343
try:
23262344
hub = get_core_hub(self._hass, self._core_hub)
23272345
except KeyError:
2328-
_LOGGER.warning(
2329-
f"CoreModbusHub '{self._core_hub}' not available",
2330-
)
2346+
_LOGGER.warning(f"CoreModbusHub '{self._core_hub}' not available")
2347+
self._next_connect_attempt = _mtime.monotonic() + CONNECT_RETRY_DELAY
23312348
return None
2332-
else:
2333-
if hub:
2334-
# update weak reference handle to refer to
2335-
# the actual CoreModbusHub object
2336-
self._hub = WeakRef(hub, self._hub_closed_now)
2337-
continue
2338-
if not delay:
2339-
_LOGGER.warning(
2340-
"Unable to join core modbus %s",
2341-
self._core_hub,
2342-
)
2349+
if not hub:
2350+
_LOGGER.warning("Unable to join core modbus %s", self._core_hub)
2351+
self._next_connect_attempt = _mtime.monotonic() + CONNECT_RETRY_DELAY
23432352
return None
2344-
# wait some time (TODO make configurable) before
2345-
# rechecking if CoreModbusHub object has been created and
2346-
# connected
2347-
delay = False
2348-
await asyncio.sleep(10)
2353+
self._hub = WeakRef(hub, self._hub_closed_now)
2354+
2355+
port = hub._pb_params.get("port", 0)
2356+
host = hub._pb_params.get("host", port)
2357+
try:
2358+
async with hub._lock:
2359+
if hub._client and hub._client.connected:
2360+
_LOGGER.debug("Inverter connected at %s:%s", host, port)
2361+
self._next_connect_attempt = 0.0
2362+
return hub
2363+
except (TypeError, AttributeError):
2364+
pass
2365+
reason = f" core modbus hub '{self._core_hub}' not ready" if getattr(hub, "_config_delay", False) else ""
2366+
_LOGGER.debug(f"Unable to connect to Inverter at {host}:{port}.{reason}")
2367+
self._next_connect_attempt = _mtime.monotonic() + CONNECT_RETRY_DELAY
2368+
return None
23492369

23502370
async def async_read_holding_registers(self, unit: int, address: int, count: int) -> Any:
23512371
"""Read holding registers."""
23522372
kwargs = {ADDR_KW: unit} if unit is not None else {}
23532373
if getattr(self, "_stopping", False):
23542374
return None
2355-
async with self._lock:
2356-
hub = await self._check_connection()
2375+
hub = await self._check_connection()
23572376
try:
23582377
if not hub or getattr(hub, "_config_delay", False):
23592378
return None
@@ -2372,8 +2391,7 @@ async def async_read_input_registers(self, unit: int, address: int, count: int)
23722391
kwargs = {ADDR_KW: unit} if unit is not None else {}
23732392
if getattr(self, "_stopping", False):
23742393
return None
2375-
async with self._lock:
2376-
hub = await self._check_connection()
2394+
hub = await self._check_connection()
23772395
try:
23782396
if not hub or getattr(hub, "_config_delay", False):
23792397
return None
@@ -2398,8 +2416,7 @@ async def async_lowlevel_write_register(self, unit: int, address: int, payload:
23982416
kwargs = {ADDR_KW: unit} if unit is not None else {}
23992417
if getattr(self, "_stopping", False):
24002418
return None
2401-
async with self._lock:
2402-
hub = await self._check_connection()
2419+
hub = await self._check_connection()
24032420
try:
24042421
if not hub or getattr(hub, "_config_delay", False):
24052422
return None
@@ -2428,14 +2445,13 @@ async def async_write_registers_single(
24282445
else:
24292446
regs = convert_to_registers(int(payload), DataType.INT16, self.plugin.order32) # type: ignore[attr-defined]
24302447
kwargs: dict[str, int] = {ADDR_KW: unit} if unit is not None else {}
2431-
async with self._lock:
2432-
hub = await self._check_connection()
2448+
hub = await self._check_connection()
24332449
try:
2434-
if hub._config_delay:
2450+
if not hub or hub._config_delay:
24352451
return None
24362452
async with hub._lock:
24372453
try:
2438-
resp = await self._client.write_registers(address=address, values=regs, **kwargs) # type: ignore[arg-type]
2454+
resp = await self._track_task(hub._client.write_registers(address=address, values=regs, **kwargs))
24392455
except (ConnectionException, ModbusIOException) as e:
24402456
original_message = str(e)
24412457
raise HomeAssistantError(f"Error writing single Modbus registers: {original_message}") from e
@@ -2494,14 +2510,13 @@ async def async_write_registers_multi(self, unit: int, address: int, payload: li
24942510
_LOGGER.error(f"unsupported unit type: {typ} for {key}")
24952511
# for easier debugging, make next line a _LOGGER.info line
24962512
_LOGGER.debug(f"Ready to write multiple registers at 0x{address:02x}: {regs_out}")
2497-
async with self._lock:
2498-
hub = await self._check_connection()
2513+
hub = await self._check_connection()
24992514
try:
2500-
if hub._config_delay:
2515+
if not hub or hub._config_delay:
25012516
return None
25022517
async with hub._lock:
25032518
try:
2504-
resp = await self._client.write_registers(address=address, values=regs_out, **kwargs) # type: ignore[arg-type]
2519+
resp = await self._track_task(hub._client.write_registers(address=address, values=regs_out, **kwargs))
25052520
except (ConnectionException, ModbusIOException) as e:
25062521
original_message = str(e)
25072522
raise HomeAssistantError(f"Error writing multiple Modbus registers: {original_message}") from e

0 commit comments

Comments
 (0)