modbus_client.py 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import logging
  2. from pymodbus.client import ModbusTcpClient
  3. import threading
  4. import time
  5. from typing import Callable, List
  6. logger = logging.getLogger(__name__)
  7. _MIN_BACKOFF = 5
  8. _MAX_BACKOFF = 60
  9. class ModbusClient:
  10. def __init__(self, config, on_data_callback: Callable[[List[bool]], None]):
  11. self.config = config
  12. self.on_data_callback = on_data_callback
  13. self.client: ModbusTcpClient = None
  14. self.running = False
  15. self.thread = None
  16. self._coil_count = 100
  17. self._backoff = _MIN_BACKOFF
  18. self._last_connect_attempt = 0.0
  19. def connect(self) -> bool:
  20. self._last_connect_attempt = time.time()
  21. try:
  22. self.client = ModbusTcpClient(self.config.ip, port=self.config.port)
  23. self.client.connect()
  24. self._backoff = _MIN_BACKOFF
  25. self.running = True
  26. self.thread = threading.Thread(target=self._poll_loop, daemon=True)
  27. self.thread.start()
  28. logger.info(f"Modbus连接成功 {self.config.ip}:{self.config.port}")
  29. return True
  30. except Exception as e:
  31. self._backoff = min(self._backoff * 2, _MAX_BACKOFF)
  32. logger.error(f"Modbus连接失败,下次{self._backoff}s后重试: {e}")
  33. return False
  34. def disconnect(self):
  35. self.running = False
  36. if self.client:
  37. self.client.close()
  38. self.client = None
  39. logger.info("Modbus已断开")
  40. def _poll_loop(self):
  41. while self.running:
  42. try:
  43. if self.client is None or not self.client.is_socket_open():
  44. logger.warning("Modbus连接断开,尝试重连")
  45. self._try_reconnect()
  46. if self.client is None:
  47. time.sleep(min(self._backoff, self.config.frequency / 1000.0))
  48. continue
  49. result = self.client.read_coils(0, self._coil_count)
  50. if result and not result.isError():
  51. bools = [result.bits[i] for i in range(len(result.bits))]
  52. self.on_data_callback(bools)
  53. except Exception as e:
  54. logger.error(f"Modbus读取错误: {e}")
  55. self.client = None
  56. time.sleep(self.config.frequency / 1000.0)
  57. def _try_reconnect(self):
  58. if time.time() - self._last_connect_attempt < self._backoff:
  59. return
  60. self._last_connect_attempt = time.time()
  61. try:
  62. self.client = ModbusTcpClient(self.config.ip, port=self.config.port)
  63. self.client.connect()
  64. self._backoff = _MIN_BACKOFF
  65. logger.info("Modbus重连成功")
  66. except Exception as e:
  67. self._backoff = min(self._backoff * 2, _MAX_BACKOFF)
  68. logger.error(f"Modbus重连失败,下次{self._backoff}s后重试: {e}")
  69. self.client = None