| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- import socket
- import threading
- import time
- import logging
- from typing import Callable, Optional
- logger = logging.getLogger(__name__)
- _MIN_BACKOFF = 5
- _MAX_BACKOFF = 60
- 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._backoff = _MIN_BACKOFF # 当前退避间隔(秒)
- 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._backoff:
- 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.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
- if hasattr(socket, 'TCP_KEEPIDLE'):
- self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 5)
- self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 3)
- self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 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()
- self._backoff = _MIN_BACKOFF
- 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:
- self._backoff = min(self._backoff * 2, _MAX_BACKOFF)
- logger.error(f"RFID连接失败,下次{self._backoff}s后重试: {e}")
- self.sock = None
- if self.on_state_changed:
- self.on_state_changed(False)
- return False
- def disconnect(self):
- """主动断开连接"""
- self.running = False
- sock = self.sock
- self.sock = None
- if sock:
- try:
- sock.close()
- except OSError:
- pass
- logger.info("RFID已断开")
- if self.on_state_changed:
- self.on_state_changed(False)
- self.on_tag_callback(None) # 通知上层
- def _read_loop(self):
- while self.running:
- sock = self.sock
- if sock is None:
- break
- try:
- data = sock.recv(1024)
- if not data:
- break
- self._last_receive_time = time.time() # 收到任何数据都更新时间
- self.buffer += data
- if len(self.buffer) > 65536:
- self.buffer = self.buffer[-32768:]
- 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 OSError:
- break
- except Exception as e:
- logger.error(f"RFID读取错误: {e}")
- break
- # 退出循环(连接断开)
- logger.info("RFID读取循环结束,将触发重连")
- if self.on_state_changed:
- self.on_state_changed(False)
- self.on_tag_callback(None) # 通知上层
- def _process_packet(self, packet):
- 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):
- 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._backoff:
- self.connect()
- else:
- # socket 存在,连接正常,不做额外检测
- # 只在 _read_loop 中 detect socket 异常断开时才会走到 disconnect 逻辑
- pass
|