RFID读写器接入业务系统:从设备心跳、标签去重到异常补传的接口设计
做过几个 RFID 项目之后会发现一个规律:现场最容易出问题的,往往不是"读不到",而是"读到了但系统里不对"。
硬件装上、标签贴好、门禁也通了,结果台账里的资产状态三天两头对不上——同一个标签在系统里生成了十几条出入记录,某台读写器离线两天没人发现,产线断网半小时的数据到现在也没补回来。这些问题不在射频层,而在读写器和业务系统之间的那一层接入代码里。
这篇文章不讲选型,只讲接口设计。把读写器当成一个会掉线、会重复上报、会丢数据的外部数据源,用工程化的方式把数据接稳。
一、设备对接:先把"在线"这件事做扎实

读写器与上位机的通信方式常见有几种,选择取决于部署形态:
| 通信方式 | 适用场景 | 注意点 |
|---|---|---|
| RS-232/485 串口 | 单机、近距离、固定点位 | 需处理串口占用与超时重连 |
| TCP Socket | 局域网内读写器主动上报 | 需自定义心跳与粘包处理 |
| Wiegand | 门禁控制器对接 | 单向、无回执,适合纯开门场景 |
| HTTP 上报 | 读写器直连平台 | 需做限流与鉴权,避免被刷 |
| MQTT | 多设备、弱网、需断线重连 | 推荐:QoS 1 保证至少一次投递 |
推荐 MQTT 的理由很直接:它原生支持断线重连、遗嘱消息(LWT)和服务质量等级。读写器离线时,broker 可以立刻通过遗嘱消息通知平台——这比在平台侧轮询设备状态可靠得多。
即便如此,仍然建议在业务侧保留一层心跳检测:设备在线但数据流卡死(射频模块故障、标签队列积压)的情况,只靠连接状态是发现不了的。
# 读写器心跳与数据流健康检查(Python,可配成定时任务)
import time, socket
def check_reader(host, port, timeout=3):
"""TCP 探活:能连上不代表数据正常,只作为第一层判断"""
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except OSError:
return False
def check_data_flow(reader_id, conn, warn_seconds=120):
"""第二层:最近一条标签上报距今超过阈值,判定数据流异常"""
row = conn.execute(
"SELECT MAX(report_time) FROM tag_event WHERE reader_id = %s", (reader_id,)
).fetchone()
last = row[0]
if last is None:
return "no_data"
gap = (time.time() - last.timestamp())
return "stalled" if gap > warn_seconds else "ok"
# 两层都通过才算健康;任一层异常即告警,避免"连接正常但数据停了"
二、标签重复上报:去重是接入层的必修课
通道门禁这类场景,人员带着资产走过时,读写器每秒可能读取同一标签几十次。如果不做处理,一条"资产出库"会变成几十条重复事件,台账直接被冲垮。
去重的核心是三层过滤,缺一层都会漏:
- 时间窗去重:同一 (读写器, 标签) 在 N 秒内只处理第一条,N 取值与业务有关,出入库一般 3~5 秒足够;
- 状态机判定:只有"在库→出库"这类合法状态迁移才生成业务事件,重复方向直接丢弃;
- 异常过滤:RSSI 低于阈值的读数(天线边缘的偶发读取)不参与判定。
时间窗去重最省事的实现是借助 Redis 的原子操作,避免分布式环境下多实例重复计数:
// 基于 Redis 的时间窗去重(Java + Spring)
public boolean isDuplicate(String readerId, String tagId, int windowSeconds) {
String key = "rfid:dedup:" + readerId + ":" + tagId;
// SETNX:只有第一次设置成功,说明窗口内是首读
Boolean first = redis.opsForValue()
.setIfAbsent(key, "1", windowSeconds, TimeUnit.SECONDS);
return Boolean.FALSE.equals(first); // true 表示重复,直接丢弃
}
这里有个容易忽略的点:去重的 key 必须带读写器维度。只按标签去重,会在两个读写器覆盖范围重叠时误杀——A 读写器读到后,B 读写器的合法读取会被当成重复丢掉。
三、断网与异常补传:数据一条都不能少
工地、仓库、车间这类环境,网络抖动是常态。接入层必须假设"消息一定会丢",并准备补传路径。
补传的设计要点有三个:
一是有本地缓冲。 读写器或边缘网关在断连期间把事件写本地队列(SQLite、文件均可),连接恢复后按序重发。
二是业务侧幂等。 补传意味着同一条事件可能被投递多次,服务端必须靠唯一键拦截。事件表上加一个业务唯一键(如 reader_id + tag_id + event_time 的哈希),重复写入直接忽略。
三是可观测。 补传是否成功、积压是否清空,必须能看到,否则问题会一直沉在水下。
-- 标签事件表:用唯一索引实现幂等,重传天然安全
CREATE TABLE tag_event (
id BIGINT NOT NULL AUTO_INCREMENT,
event_key CHAR(32) NOT NULL COMMENT '业务唯一键:reader+tag+time 哈希',
reader_id VARCHAR(32) NOT NULL,
tag_id VARCHAR(64) NOT NULL,
event_type TINYINT NOT NULL COMMENT '1入库 2出库',
rssi SMALLINT NULL,
report_time DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_event (event_key) -- 关键:重复事件写入即被拦截
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
-- 补传对账:找出"读写器上报过、但业务表里没有"的事件
SELECT e.reader_id, COUNT(*) AS missing_cnt
FROM tag_event e
LEFT JOIN asset_movement m ON m.event_key = e.event_key
WHERE e.report_time >= DATE_SUB(NOW(), INTERVAL 1 DAY)
AND m.id IS NULL
GROUP BY e.reader_id;
第三段代码是补传体系里最有价值的一段:对账。只要每天跑一次,任何"上报了但没落到业务表"的数据都会暴露出来,比事后人工翻记录效率高得多。
四、上线前值得逐项确认的清单
接入层的问题大多在项目上线后才暴露,成本很高。下面的清单建议在联调阶段逐条过:
| 检查项 | 判定标准 |
|---|---|
| 心跳覆盖 | 设备离线、数据流卡死两类异常都能告警 |
| 去重维度 | 已按读写器 + 标签组合去重,重叠覆盖区不误杀 |
| 幂等保障 | 事件表有唯一键,重复投递不产生脏数据 |
| 补传能力 | 断网 ≥ 5 分钟场景实测可完整补齐 |
| 对账机制 | 每日自动比对上报量与业务量,差异可定位到设备 |
| 时钟同步 | 设备与服务端时间偏差在秒级以内(影响事件排序) |
其中"时钟同步"最容易被忽略。读写器长时间运行后时钟漂移,会导致事件顺序错乱——比如"出库"时间戳早于"入库",状态机直接判成异常。
小结
RFID 项目里,射频是硬件的事,数据接得稳不稳是软件的事。把读写器当成一个"不可靠数据源"来设计接口——补上心跳、去重、幂等、对账四道工序,现场问题会少一大半。
实际项目里,如果选的是软硬件同源的方案(像首码这类提供读写器和接入中间件的厂商),设备心跳协议、去重窗口、补传队列这些通常已在中间件层封装好,二次开发只需要对接业务事件表,能省掉相当一部分联调工作。当然,即便厂商封装得再好,前面那张上线清单还是值得自己逐项验一遍——毕竟是自己的数据。
如果你们现场也踩过"数据重复上报"或"断网丢数据"的坑,欢迎在评论区说说具体场景,我挑几个典型的展开写。
- 点赞
- 收藏
- 关注作者
评论(0)