Fixing reconnect issues after outage

This commit is contained in:
Constantin Pascal
2025-12-30 09:36:58 +02:00
parent 1d0cc328c0
commit a7b68fada7
+47 -9
View File
@@ -221,10 +221,17 @@ class ModbusRTUMonitorHub:
async def _reconnect(self) -> None: async def _reconnect(self) -> None:
"""Reconnect to Modbus gateway after connection loss.""" """Reconnect to Modbus gateway after connection loss."""
# Close existing connection # Close existing connection and null out references
if self._writer: if self._writer:
try:
self._writer.close() self._writer.close()
await self._writer.wait_closed() await self._writer.wait_closed()
except Exception as ex:
_LOGGER.debug("Error closing writer during reconnect: %s", ex)
# Always null out reader/writer to ensure clean state
self._reader = None
self._writer = None
# Wait before reconnecting (30 seconds to prevent DDOS during server outages) # Wait before reconnecting (30 seconds to prevent DDOS during server outages)
await asyncio.sleep(30) await asyncio.sleep(30)
@@ -232,10 +239,15 @@ class ModbusRTUMonitorHub:
# Attempt reconnection # Attempt reconnection
try: try:
await self._connect() await self._connect()
_LOGGER.info("Reconnected to Modbus gateway") _LOGGER.info("Reconnected to Modbus gateway at %s:%s", self.host, self.port)
except Exception as ex: except Exception as ex:
_LOGGER.error("Reconnection failed: %s", ex) _LOGGER.error(
# Will retry on next loop iteration "Reconnection failed for %s:%s - %s (will retry in 30 seconds)",
self.host,
self.port,
ex,
)
# Reader/writer are already None, loop will detect this and retry
async def _monitor_loop(self) -> None: async def _monitor_loop(self) -> None:
"""Main monitoring loop for passive frame reception.""" """Main monitoring loop for passive frame reception."""
@@ -243,13 +255,24 @@ class ModbusRTUMonitorHub:
while True: while True:
try: try:
# Check if reader is valid before attempting to read
if self._reader is None:
# No active connection, attempt reconnect
_LOGGER.info("No active connection to %s:%s, attempting reconnect", self.host, self.port)
await self._reconnect()
# Clear buffer for fresh start after reconnection
buffer.clear()
continue
# Read with timeout to allow graceful shutdown # Read with timeout to allow graceful shutdown
data = await asyncio.wait_for(self._reader.read(1024), timeout=1.0) data = await asyncio.wait_for(self._reader.read(1024), timeout=1.0)
if not data: if not data:
# Connection closed # Connection closed
_LOGGER.warning("Connection closed, attempting reconnect") _LOGGER.warning("Connection closed to %s:%s, attempting reconnect", self.host, self.port)
await self._reconnect() await self._reconnect()
# Clear buffer for fresh start after reconnection
buffer.clear()
continue continue
buffer.extend(data) buffer.extend(data)
@@ -290,8 +313,23 @@ class ModbusRTUMonitorHub:
continue continue
except asyncio.CancelledError: except asyncio.CancelledError:
break break
except (AttributeError, OSError, ConnectionError) as ex:
# Connection-related errors - trigger reconnection
_LOGGER.warning(
"Connection error in monitor loop for %s:%s - %s (will reconnect)",
self.host,
self.port,
ex,
)
# Null out connection and trigger reconnect
self._reader = None
self._writer = None
await self._reconnect()
buffer.clear()
continue
except Exception as ex: except Exception as ex:
_LOGGER.error("Error in monitor loop: %s", ex) # Other unexpected errors
_LOGGER.error("Unexpected error in monitor loop for %s:%s - %s", self.host, self.port, ex)
await asyncio.sleep(5) await asyncio.sleep(5)
async def _handle_frame(self, frame: ModbusRTUFrame) -> None: async def _handle_frame(self, frame: ModbusRTUFrame) -> None:
@@ -632,10 +670,10 @@ class ModbusRTUMonitorHub:
) -> None: ) -> None:
"""Write temperature setpoint to slave using RTU protocol over existing connection.""" """Write temperature setpoint to slave using RTU protocol over existing connection."""
async with self._write_lock: async with self._write_lock:
# Ensure writer is available # Ensure writer is available and not closed
if not self._writer: if not self._writer or self._writer.is_closing():
raise HomeAssistantError( raise HomeAssistantError(
"No active connection to Modbus gateway - cannot write setpoint" f"No active connection to Modbus gateway at {self.host}:{self.port} - cannot write setpoint"
) )
# Scale temperature value (multiply by 10) # Scale temperature value (multiply by 10)