|
@@ -0,0 +1,163 @@
|
|
|
|
|
+import socket
|
|
|
|
|
+import threading
|
|
|
|
|
+import time
|
|
|
|
|
+import logging
|
|
|
|
|
+from typing import Callable, Optional
|
|
|
|
|
+
|
|
|
|
|
+logger = logging.getLogger(__name__)
|
|
|
|
|
+
|
|
|
|
|
+class RFIDClient:
|
|
|
|
|
+ def __init__(self, config, on_tag_callback: Callable[[Optional[str]], None],
|
|
|
|
|
+ on_state_changed=None, tag_offset: int = -6, tag_length: int = 6, hex_output: bool = True):
|
|
|
|
|
+ self.config = config
|
|
|
|
|
+ self.on_tag_callback = on_tag_callback
|
|
|
|
|
+ self.tag_offset = tag_offset
|
|
|
|
|
+ self.tag_length = tag_length
|
|
|
|
|
+ self.hex_output = hex_output
|
|
|
|
|
+ self.sock: Optional[socket.socket] = None
|
|
|
|
|
+ self.running = False
|
|
|
|
|
+ self._last_tag: Optional[str] = None
|
|
|
|
|
+ self._stable_count = 0
|
|
|
|
|
+ self._last_time = time.time()
|
|
|
|
|
+ self._last_receive_time = time.time() # 最后一次收到数据的时间
|
|
|
|
|
+ self._reconnect_interval = 5 # 重连尝试间隔(秒)
|
|
|
|
|
+ self._last_connect_attempt = 0
|
|
|
|
|
+ self.thread = None
|
|
|
|
|
+ self.buffer = b""
|
|
|
|
|
+ self.on_state_changed = on_state_changed
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ def connect(self) -> bool:
|
|
|
|
|
+ """尝试建立连接,若已连接则直接返回 True,否则尝试重连(有间隔限制)"""
|
|
|
|
|
+ if self.sock:
|
|
|
|
|
+ return True
|
|
|
|
|
+ # 避免频繁重连
|
|
|
|
|
+ if time.time() - self._last_connect_attempt < self._reconnect_interval:
|
|
|
|
|
+ return False
|
|
|
|
|
+ self._last_connect_attempt = time.time()
|
|
|
|
|
+ try:
|
|
|
|
|
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
|
+ self.sock.settimeout(3) # 设置接收超时,避免阻塞
|
|
|
|
|
+ self.sock.connect((self.config.ip, self.config.port))
|
|
|
|
|
+ self.running = True
|
|
|
|
|
+ self.thread = threading.Thread(target=self._read_loop, daemon=True)
|
|
|
|
|
+ self.thread.start()
|
|
|
|
|
+ self._last_receive_time = time.time()
|
|
|
|
|
+ logger.info(f"RFID连接成功 {self.config.ip}:{self.config.port}")
|
|
|
|
|
+ if self.on_state_changed:
|
|
|
|
|
+ self.on_state_changed(True)
|
|
|
|
|
+ return True
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.error(f"RFID连接失败: {e}")
|
|
|
|
|
+ self.sock = None
|
|
|
|
|
+ if self.on_state_changed:
|
|
|
|
|
+ self.on_state_changed(False)
|
|
|
|
|
+ return False
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ def disconnect(self):
|
|
|
|
|
+ """主动断开连接"""
|
|
|
|
|
+ self.running = False
|
|
|
|
|
+ if self.sock:
|
|
|
|
|
+ self.sock.close()
|
|
|
|
|
+ self.sock = None
|
|
|
|
|
+ logger.info("RFID已断开")
|
|
|
|
|
+ if self.on_state_changed:
|
|
|
|
|
+ self.on_state_changed(False)
|
|
|
|
|
+
|
|
|
|
|
+ def _read_loop(self):
|
|
|
|
|
+ while self.running and self.sock:
|
|
|
|
|
+ try:
|
|
|
|
|
+ data = self.sock.recv(1024)
|
|
|
|
|
+ if not data:
|
|
|
|
|
+ break
|
|
|
|
|
+ self._last_receive_time = time.time() # 收到任何数据都更新时间
|
|
|
|
|
+ self.buffer += data
|
|
|
|
|
+ while True:
|
|
|
|
|
+ start = self.buffer.find(b"CT")
|
|
|
|
|
+ if start == -1:
|
|
|
|
|
+ break
|
|
|
|
|
+ next_start = self.buffer.find(b"CT", start + 2)
|
|
|
|
|
+ if next_start == -1:
|
|
|
|
|
+ break
|
|
|
|
|
+ packet = self.buffer[start:next_start]
|
|
|
|
|
+ self.buffer = self.buffer[next_start:]
|
|
|
|
|
+ self._process_packet(packet)
|
|
|
|
|
+ except socket.timeout:
|
|
|
|
|
+ continue # 超时不影响循环
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.error(f"RFID读取错误: {e}")
|
|
|
|
|
+ break
|
|
|
|
|
+ # 退出循环(连接断开)
|
|
|
|
|
+ self.sock = None
|
|
|
|
|
+ logger.info("RFID读取循环结束,将触发重连")
|
|
|
|
|
+ if self.on_state_changed:
|
|
|
|
|
+ self.on_state_changed(False)
|
|
|
|
|
+ self.on_tag_callback(None) # 通知上层
|
|
|
|
|
+
|
|
|
|
|
+ def _process_packet(self, packet: bytes):
|
|
|
|
|
+ hex_str = packet.hex()
|
|
|
|
|
+ logger.debug(f"RFID包 长度 {len(packet)} 十六进制: {hex_str}")
|
|
|
|
|
+
|
|
|
|
|
+ offset = self.tag_offset
|
|
|
|
|
+ length = self.tag_length
|
|
|
|
|
+ if offset < 0:
|
|
|
|
|
+ offset = len(packet) + offset
|
|
|
|
|
+ if offset < 0 or offset + length > len(packet):
|
|
|
|
|
+ logger.debug("提取范围越界,跳过")
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ tag_bytes = packet[offset:offset + length]
|
|
|
|
|
+ if self.hex_output:
|
|
|
|
|
+ tag = tag_bytes.hex().upper()
|
|
|
|
|
+ else:
|
|
|
|
|
+ try:
|
|
|
|
|
+ tag = tag_bytes.decode('ascii')
|
|
|
|
|
+ except UnicodeDecodeError:
|
|
|
|
|
+ tag = tag_bytes.hex().upper()
|
|
|
|
|
+
|
|
|
|
|
+ if len(tag) < 4 or tag.strip('0') == '':
|
|
|
|
|
+ logger.debug("无效卡号(空或全0),忽略")
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ logger.debug(f"提取卡号: {tag}")
|
|
|
|
|
+ self._process_tag(tag)
|
|
|
|
|
+
|
|
|
|
|
+ def _process_tag(self, tag: str):
|
|
|
|
|
+ now = time.time()
|
|
|
|
|
+ if tag == self._last_tag:
|
|
|
|
|
+ self._stable_count += 1
|
|
|
|
|
+ else:
|
|
|
|
|
+ self._last_tag = tag
|
|
|
|
|
+ self._stable_count = 1
|
|
|
|
|
+ self._last_time = now
|
|
|
|
|
+ logger.debug(f"稳定: 标签 {tag}, 计数 {self._stable_count}/{self.config.stability_count}")
|
|
|
|
|
+ if self._stable_count >= self.config.stability_count:
|
|
|
|
|
+ logger.info(f"触发稳定标签: {tag}")
|
|
|
|
|
+ self.on_tag_callback(tag)
|
|
|
|
|
+ self._stable_count = 0
|
|
|
|
|
+
|
|
|
|
|
+ def check_idle(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ 外部定时(每秒)调用的维护方法:
|
|
|
|
|
+ 1. 检查空闲超时(卡离开)
|
|
|
|
|
+ 2. 检查连接状态,若断开或长时间无数据则自动重连
|
|
|
|
|
+ """
|
|
|
|
|
+ # 空闲超时
|
|
|
|
|
+ if self._last_tag and (time.time() - self._last_time) > self.config.stability_time:
|
|
|
|
|
+ logger.info("空闲超时,清空状态")
|
|
|
|
|
+ self._last_tag = None
|
|
|
|
|
+ self._stable_count = 0
|
|
|
|
|
+ self.on_tag_callback(None)
|
|
|
|
|
+
|
|
|
|
|
+ # 连接状态维护
|
|
|
|
|
+ if self.sock is None:
|
|
|
|
|
+ # 如果 socket 已断开,尝试重连
|
|
|
|
|
+ if time.time() - self._last_connect_attempt > self._reconnect_interval:
|
|
|
|
|
+ self.connect()
|
|
|
|
|
+ else:
|
|
|
|
|
+ # 如果 socket 存在但长时间未收到任何数据(10秒),认为连接僵死,主动断开并重连
|
|
|
|
|
+ if (time.time() - self._last_receive_time) > 10:
|
|
|
|
|
+ logger.warning("RFID长时间未收到数据,主动重连")
|
|
|
|
|
+ self.disconnect()
|
|
|
|
|
+ # 下次 check_idle 会触发重连
|