rfid_client.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import socket
  2. import threading
  3. import time
  4. import logging
  5. from typing import Callable, Optional
  6. logger = logging.getLogger(__name__)
  7. _MIN_BACKOFF = 5
  8. _MAX_BACKOFF = 60
  9. class RFIDClient:
  10. def __init__(self, config, on_tag_callback: Callable[[Optional[str]], None],
  11. on_state_changed=None, tag_offset: int = -6, tag_length: int = 6, hex_output: bool = True):
  12. self.config = config
  13. self.on_tag_callback = on_tag_callback
  14. self.tag_offset = tag_offset
  15. self.tag_length = tag_length
  16. self.hex_output = hex_output
  17. self.sock: Optional[socket.socket] = None
  18. self.running = False
  19. self._last_tag: Optional[str] = None
  20. self._stable_count = 0
  21. self._last_time = time.time()
  22. self._last_receive_time = time.time() # 最后一次收到数据的时间
  23. self._backoff = _MIN_BACKOFF # 当前退避间隔(秒)
  24. self._last_connect_attempt = 0
  25. self.thread = None
  26. self.buffer = b""
  27. self.on_state_changed = on_state_changed
  28. def connect(self) -> bool:
  29. """尝试建立连接,若已连接则直接返回 True,否则尝试重连(有间隔限制)"""
  30. if self.sock:
  31. return True
  32. # 避免频繁重连
  33. if time.time() - self._last_connect_attempt < self._backoff:
  34. return False
  35. self._last_connect_attempt = time.time()
  36. try:
  37. self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  38. self.sock.settimeout(3) # 设置接收超时,避免阻塞
  39. self.sock.connect((self.config.ip, self.config.port))
  40. self.running = True
  41. self.thread = threading.Thread(target=self._read_loop, daemon=True)
  42. self.thread.start()
  43. self._last_receive_time = time.time()
  44. self._backoff = _MIN_BACKOFF
  45. logger.info(f"RFID连接成功 {self.config.ip}:{self.config.port}")
  46. if self.on_state_changed:
  47. self.on_state_changed(True)
  48. return True
  49. except Exception as e:
  50. self._backoff = min(self._backoff * 2, _MAX_BACKOFF)
  51. logger.error(f"RFID连接失败,下次{self._backoff}s后重试: {e}")
  52. self.sock = None
  53. if self.on_state_changed:
  54. self.on_state_changed(False)
  55. return False
  56. def disconnect(self):
  57. """主动断开连接"""
  58. self.running = False
  59. sock = self.sock
  60. self.sock = None
  61. if sock:
  62. try:
  63. sock.close()
  64. except OSError:
  65. pass
  66. logger.info("RFID已断开")
  67. if self.on_state_changed:
  68. self.on_state_changed(False)
  69. self.on_tag_callback(None) # 通知上层
  70. def _read_loop(self):
  71. while self.running:
  72. sock = self.sock
  73. if sock is None:
  74. break
  75. try:
  76. data = sock.recv(1024)
  77. if not data:
  78. break
  79. self._last_receive_time = time.time() # 收到任何数据都更新时间
  80. self.buffer += data
  81. if len(self.buffer) > 65536:
  82. self.buffer = self.buffer[-32768:]
  83. while True:
  84. start = self.buffer.find(b"CT")
  85. if start == -1:
  86. break
  87. next_start = self.buffer.find(b"CT", start + 2)
  88. if next_start == -1:
  89. break
  90. packet = self.buffer[start:next_start]
  91. self.buffer = self.buffer[next_start:]
  92. self._process_packet(packet)
  93. except socket.timeout:
  94. continue # 超时不影响循环
  95. except OSError:
  96. break
  97. except Exception as e:
  98. logger.error(f"RFID读取错误: {e}")
  99. break
  100. # 退出循环(连接断开)
  101. logger.info("RFID读取循环结束,将触发重连")
  102. if self.on_state_changed:
  103. self.on_state_changed(False)
  104. self.on_tag_callback(None) # 通知上层
  105. def _process_packet(self, packet):
  106. hex_str = packet.hex()
  107. logger.debug(f"RFID包 长度 {len(packet)} 十六进制: {hex_str}")
  108. offset = self.tag_offset
  109. length = self.tag_length
  110. if offset < 0:
  111. offset = len(packet) + offset
  112. if offset < 0 or offset + length > len(packet):
  113. logger.debug("提取范围越界,跳过")
  114. return
  115. tag_bytes = packet[offset:offset + length]
  116. if self.hex_output:
  117. tag = tag_bytes.hex().upper()
  118. else:
  119. try:
  120. tag = tag_bytes.decode('ascii')
  121. except UnicodeDecodeError:
  122. tag = tag_bytes.hex().upper()
  123. if len(tag) < 4 or tag.strip('0') == '':
  124. logger.debug("无效卡号(空或全0),忽略")
  125. return
  126. logger.debug(f"提取卡号: {tag}")
  127. self._process_tag(tag)
  128. def _process_tag(self, tag):
  129. now = time.time()
  130. if tag == self._last_tag:
  131. self._stable_count += 1
  132. else:
  133. self._last_tag = tag
  134. self._stable_count = 1
  135. self._last_time = now
  136. logger.debug(f"稳定: 标签 {tag}, 计数 {self._stable_count}/{self.config.stability_count}")
  137. if self._stable_count >= self.config.stability_count:
  138. logger.info(f"触发稳定标签: {tag}")
  139. self.on_tag_callback(tag)
  140. self._stable_count = 0
  141. def check_idle(self):
  142. """
  143. 外部定时(每秒)调用的维护方法:
  144. 1. 检查空闲超时(卡离开)
  145. 2. 检查连接状态,若断开或长时间无数据则自动重连
  146. """
  147. # 空闲超时
  148. if self._last_tag and (time.time() - self._last_time) > self.config.stability_time:
  149. logger.info("空闲超时,清空状态")
  150. self._last_tag = None
  151. self._stable_count = 0
  152. self.on_tag_callback(None)
  153. # 连接状态维护
  154. if self.sock is None:
  155. # 如果 socket 已断开,尝试重连
  156. if time.time() - self._last_connect_attempt > self._backoff:
  157. self.connect()
  158. else:
  159. # 如果 socket 存在但长时间未收到任何数据(10秒),认为连接僵死,主动断开并重连
  160. if (time.time() - self._last_receive_time) > 10:
  161. logger.warning("RFID长时间未收到数据,主动重连")
  162. self.running = False
  163. sock = self.sock
  164. self.sock = None
  165. if sock:
  166. try:
  167. sock.close()
  168. except OSError:
  169. pass