import logging from pymodbus.client import ModbusTcpClient import threading import time from typing import Callable, List logger = logging.getLogger(__name__) _MIN_BACKOFF = 5 _MAX_BACKOFF = 60 class ModbusClient: def __init__(self, config, on_data_callback: Callable[[List[bool]], None]): self.config = config self.on_data_callback = on_data_callback self.client: ModbusTcpClient = None self.running = False self.thread = None self._coil_count = 100 self._backoff = _MIN_BACKOFF self._last_connect_attempt = 0.0 def connect(self) -> bool: self._last_connect_attempt = time.time() try: self.client = ModbusTcpClient(self.config.ip, port=self.config.port) self.client.connect() self._backoff = _MIN_BACKOFF self.running = True self.thread = threading.Thread(target=self._poll_loop, daemon=True) self.thread.start() logger.info(f"Modbus连接成功 {self.config.ip}:{self.config.port}") return True except Exception as e: self._backoff = min(self._backoff * 2, _MAX_BACKOFF) logger.error(f"Modbus连接失败,下次{self._backoff}s后重试: {e}") return False def disconnect(self): self.running = False if self.client: self.client.close() self.client = None logger.info("Modbus已断开") def _poll_loop(self): while self.running: try: if self.client is None or not self.client.is_socket_open(): logger.warning("Modbus连接断开,尝试重连") self._try_reconnect() if self.client is None: time.sleep(min(self._backoff, self.config.frequency / 1000.0)) continue result = self.client.read_coils(0, self._coil_count) if result and not result.isError(): bools = [result.bits[i] for i in range(len(result.bits))] self.on_data_callback(bools) except Exception as e: logger.error(f"Modbus读取错误: {e}") self.client = None time.sleep(self.config.frequency / 1000.0) def _try_reconnect(self): if time.time() - self._last_connect_attempt < self._backoff: return self._last_connect_attempt = time.time() try: self.client = ModbusTcpClient(self.config.ip, port=self.config.port) self.client.connect() self._backoff = _MIN_BACKOFF logger.info("Modbus重连成功") except Exception as e: self._backoff = min(self._backoff * 2, _MAX_BACKOFF) logger.error(f"Modbus重连失败,下次{self._backoff}s后重试: {e}") self.client = None