128128COMM_BLOCK_FAILURE_THRESHOLD = 3
129129COMM_BLOCK_FAILURE_WINDOW = 600
130130COMM_RECOVERY_INTERVAL = 300
131+ CONNECT_RETRY_DELAY = 1.0
131132
132133
133134try :
@@ -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