ZZ-D541SM 桌面超高频 RFID 读写器|标准数采接入文档
适用范围:BioLab / Asoka.Core 标准设备数采接入、实验室样本/耗材/资产 RFID 标签识别、后端 .NET 设备适配器开发。
1. 产品定位
文档定位:本文件是面向项目知识库 / Notion 的正式接入文档,用于指导 ZZ-D541SM 桌面超高频 RFID 读写器的标准化采集、解析、配置、测试与验收。本文不依赖厂家 Windows DLL 作为运行时组件,推荐在 Linux / Docker 后端直接实现串口协议适配器。
1. 设备概述
ZZ-D541SM 是一款桌面式一体化超高频 RFID 读写器,属于 UHF RFID 标签识别设备。它的核心输出不是环境传感值,而是 ISO18000-6C / EPC Gen2 标签的 EPC、可选天线号、可选 RSSI 以及标签存储区数据。
| 项目 | 说明 |
| 设备名称 | ZZ-D541SM 桌面超高频 RFID 读写器 |
| 设备类型 | 桌面式一体化 UHF RFID 读写器 |
| 空中接口 | EPCglobal UHF Class1 Gen2 / ISO18000-6C |
| 主机接口 | RS232,DB9 母头 |
| 默认串口 | 115200 bps,8 数据位,1 停止位,无校验,即 115200-8-N-1 |
| 主机协议 | 厂家私有二进制协议,帧头 `7E 55` |
| 设备族线索 | 与 AnyID / 上海普阅 D540、D541、D543、R540、R548 系列协议高度相关 |
| 推荐接入方式 | .NET 后端实现串口 Adapter / Parser / Transport,不依赖 Windows DLL |
关键定位:
- 设备负责识别 RFID 标签 EPC。
- 业务系统负责把 EPC 映射为样本、试剂、耗材、容器、工装或资产。
- 采集服务需要处理重复上报、多标签、无标签响应、粘包拆包、CRC 校验、标签误操作保护。
2. 适用场景
| 场景 | 接入目标 | 典型事件 |
| 样本登记 | 扫描样本管、冻存盒、样本托盘标签 | `SampleTagDetected` |
| 试剂/耗材识别 | 识别试剂瓶、耗材盒、批次容器 | `ConsumableTagDetected` |
| 实验台工位识别 | 桌面读写器作为固定工位入口 | `WorkbenchRfidRead` |
| 仓储出入库 | 读取货架/周转箱/资产标签 | `InventoryTagRead` |
| 设备联动 | 与 BioLab Gateway Agent / CAP Publisher 联动 | `EquipmentDataCollected` |
不建议把该设备直接作为强实时安全联锁来源。UHF RFID 受标签方向、液体、金属、距离、功率、天线位置影响明显,适合做识别与辅助追踪,关键业务动作需要结合人工确认或其他传感信号。
3. 资料来源
本文基于以下资料和实测结论整理:
| 来源 | 作用 | 优先级 |
| `UHFStation 通信协议说明文档 UM002 V2.8` | 私有串口协议、帧格式、命令码、CRC、主动上传格式 | 最高 |
| ZZ-D541SM 产品手册 | 产品定位、硬件规格、接口、指示灯 | 高 |
| `UFReader / UHFReader.dll API 库函数说明文档 V1.6` | DLL API、结构体、二次开发能力映射 | 中 |
| `UHFReaderDllDemo 使用说明书` | Demo 操作、工作模式、过滤/读写流程 | 中 |
| 串口抓帧与实测帧 | Inventory、无标签/单标签/双标签响应、CRC 验证 | 最高 |
资料结论优先级:
plain实测抓帧 > UM002 V2.8 > DLL API 文档 > Demo 使用说明 > 产品手册推断
注意事项:
- 如早期文档与 UM002 V2.8 冲突,以 UM002 V2.8 和实测抓帧为准。
- UM002 V2.8 中温度命令为
0xD8,不是早期分析中的0xF8。 - UM002 V2.8 中
0xF5、0xF1请求需要带参数00。 - UM002 V2.8 中
0xF6请求需要带天线索引和频点索引。 - 主动上传 TXT 帧使用简单
sum校验,不使用二进制帧的 CRC-16/X25。
4. 接口参数
4.1 硬件接口
| 项目 | 参数 |
| 电源 | +12V \~ +24V DC,推荐 12V |
| 最大功耗 | 约 10W |
| 主机接口 | RS232 |
| 连接器 | DB9 母头 |
| 默认通信 | 115200-8-N-1 |
| 工作频段 | 902–928 MHz,美标 UHF 频段 |
| 射频功率 | 0–33 dBm 可调 |
| 工作温度 | -10°C \~ +65°C |
4.2 DB9 RS232 引脚
| PIN | 功能 |
| ---: | --- |
| 1 | NC |
| 2 | RS232_TXD |
| 3 | RS232_RXD |
| 4 | NC |
| 5 | GND |
| 6–9 | NC |
4.3 串口参数
| 参数 | 推荐值 |
| Port | Linux 常见为 `/dev/ttyUSB0`,Windows 常见为 `COMx` |
| BaudRate | `115200` |
| DataBits | `8` |
| StopBits | `1` |
| Parity | `None` |
| ReadTimeout | 普通命令建议 200–1000 ms,盘点/读写多块可适当放大 |
| CommandInterval | 两个请求帧间隔不小于 50 ms |
故障排查时,如果 115200 无响应,可尝试 38400、9600;如果不知道设备地址,可使用广播地址 FFFF 进行查询或恢复配置。
5. 协议帧格式
ZZ-D541SM 主机通信使用厂家私有二进制协议,帧头固定为 7E 55。
5.1 请求帧格式
plain帧头 2B = 7E 55 帧长度 1B 源地址 2B 目标地址 2B 命令码 1B 保留 1B 参数 N B CRC 2B
| 字段 | 长度 | 说明 |
| --- | ---: | --- |
| 帧头 | 2 bytes | 固定 `7E 55` |
| 帧长度 | 1 byte | 从源地址开始到 CRC 结束,包含 CRC,不包含帧头和长度字节 |
| 源地址 | 2 bytes | 发出本帧的设备地址;主机常用 `00 00` |
| 目标地址 | 2 bytes | 接收本帧的设备地址;实测设备常用 `01 00` |
| 命令码 | 1 byte | 例如 Inventory 为 `10` |
| 保留 | 1 byte | 通常为 `00`;Inventory 中有 bit 级含义 |
| 参数 | N bytes | 随命令变化 |
| CRC | 2 bytes | CRC-16/X25,小端输出 |
5.2 响应帧格式
plain帧头 2B = 7E 55 长度 1B 源地址 2B 目标地址 2B 响应帧标志 1B = 1F 命令码 1B 保留 1B 参数 N B CRC 2B
| 字段 | 长度 | 说明 |
| --- | ---: | --- |
| 帧头 | 2 bytes | 固定 `7E 55` |
| 长度 | 1 byte | 从源地址开始到 CRC 结束,包含 CRC |
| 源地址 | 2 bytes | 设备地址 |
| 目标地址 | 2 bytes | 主机地址 |
| 响应帧标志 | 1 byte | 固定 `1F` |
| 命令码 | 1 byte | 对应请求命令码 |
| 保留 | 1 byte | 通常为 `00` |
| 参数 | N bytes | 状态、数量、EPC、数据区内容等 |
| CRC | 2 bytes | CRC-16/X25,小端输出 |
5.3 超长帧格式
UM002 V2.8 支持超长请求帧和超长响应帧。当长度字段为 00 时,后续通过 2 字节参数长度表示实际参数长度,最长支持 65535 字节。
超长请求帧:
plain7E 55 00 源地址 2B 目标地址 2B 命令码 1B 保留 1B 参数长度 2B 参数 N B CRC 2B
超长响应帧:
plain7E 55 00 源地址 2B 目标地址 2B 1F 命令码 1B 保留 1B 参数长度 2B 参数 N B CRC 2B
当前标准接入优先支持普通帧;如后续涉及大块写标签、多标签大批量结果或特殊命令,再补充超长帧解析。
6. CRC 校验
6.1 二进制帧 CRC-16/X25
Inventory 请求、Inventory 响应、F0 设置射频响应等已验证符合 CRC-16/X25。CRC 是主机串口私有协议层校验,不等于 EPC Bank Word 0 中的标签内部 CRC。
plainCRC-16/X25 参数: poly(normal) = 0x1021 poly(reflected) = 0x8408 init = 0xFFFF refin = true refout = true xorout = 0xFFFF 计算范围 = 从帧长度字段开始,含帧长度,到参数区结束,含参数区 不参与计算 = 帧头 7E 55、CRC 自身、可选 06 帧尾 输出字节序 = 小端,低字节在前
C 参考实现:
c#define PRESET_VALUE 0xFFFF #define POLYNOMIAL 0x8408 uint16_t crc16_x25(const uint8_t *data, uint16_t len) { uint16_t crc = PRESET_VALUE; for (uint16_t i = 0; i < len; i++) { crc ^= data[i]; for (uint8_t j = 0; j < 8; j++) { if (crc & 0x0001) { crc = (crc >> 1) ^ POLYNOMIAL; } else { crc >>= 1; } } crc &= 0xFFFF; } crc = ~crc; return crc & 0xFFFF; }
.NET 参考实现:
c#public static ushort Crc16X25(ReadOnlySpan<byte> data) { ushort crc = 0xFFFF; foreach (var b in data) { crc ^= b; for (var i = 0; i < 8; i++) { crc = (ushort)((crc & 0x0001) != 0 ? (crc >> 1) ^ 0x8408 : crc >> 1); } } return (ushort)~crc; }
写入帧尾时使用小端:
c#var crc = Crc16X25(frameBodyFromLengthToParams); frame.Add((byte)(crc & 0xFF)); frame.Add((byte)((crc >> 8) & 0xFF));
注意:已验证的 30 dBm 设置射频命令帧尾为 D1 5A,并带可选 06 帧尾。该帧暂不符合当前 X25 动态推导结果,应作为固化抓帧使用,后续再通过 UM002 V2.8 / 真实设备继续确认 F0 设置天线参数的完整规则。
6.2 主动上传 TXT 数据与 sum 校验
UM002 V2.8 附录 D 的主动上传 TXT 数据字符格式与 7E 55 二进制帧不同:
- 主动上传 TXT 帧以
aa开始。 - 帧尾为
0D 0A。 - 校验不是 CRC-16/X25,而是 8-bit 累加和。
参考算法:
cu8 Reader_CalTxtCheckSum(u8 *pTxt, u16 len) { u16 i = 0; u8 sum = 0; for (i = 0; i < len; i++) { sum += pTxt[i]; } return sum; }
标准接入要求:
| 协议类型 | 识别方式 | 校验方式 |
| 二进制命令/响应帧 | 帧头 `7E 55` | CRC-16/X25 |
| 主动上传 TXT 帧 | `aa...\r\n` | 8-bit sum |
Parser 必须区分两类协议,不能把 TXT 主动上传帧当作二进制帧解析。
7. 命令清单
7.1 系统控制命令
| 命令码 | 功能 | 标准接入建议 |
| `0xF9` | 获取标签信息 | 可选 |
| `0xD8` | 获取射频模块温度 | 推荐实现;UM002 V2.8 修正为 `D8` |
| `0xF7` | 获取版本信息 | 推荐实现,用于启动自检 |
| `0xF6` | 获取天线反射损耗 | 可选;请求需带天线索引和频点索引 |
| `0xF5` | 读取系统工作参数 | 推荐实现;请求需带参数 `00` |
| `0xF4` | 设置系统工作参数 | 谨慎实现;涉及地址、波特率、工作模式 |
| `0xF2` | 选择天线 | 推荐实现;单天线设备一般选择天线 `0` |
| `0xF1` | 获取天线工作参数 | 推荐实现;请求需带参数 `00` |
| `0xF0` | 设置天线工作参数 | 谨慎实现;30 dBm 已有固化帧例外 |
| `0x04` | 软件复位 | 可选;调试/恢复使用 |
| `0xE9` | 设置输出 | 可选 IO 能力 |
| `0xE8` | 读取 IO 配置参数 | 可选 IO 能力 |
| `0xE7` | 设置 IO 配置参数 | 可选 IO 能力 |
| `0xE6` | 读取输入 | 可选 IO 能力 |
| `0xFA` | 设置心跳时间 | 主动上传/定时模式相关 |
| `0xFB` | 设置所需的上报帧 | 主动上传字段控制 |
| `0xFC` | 获取天线功率 | 推荐实现,优先于解析 F1 中功率 |
| `0xFD` | 设置天线功率 | 后续抓帧确认后实现 |
| `0x0D` | 设置 Q 值参数 | E710 模块相关,可选 |
| `0x0E` | 获取 Q 值参数 | E710 模块相关,可选 |
7.2 ISO18000-6C / EPC Gen2 命令
| 命令码 | 功能 | 关键参数 | 标准接入建议 |
| `0x10` | 盘点标签 | 天线数目 + `[天线索引, 重复次数] * N` | 必须实现 |
| `0x11` | 自定义盘点标签 | Session / Target 等 | 可选 |
| `0x12` | 读取标签存储区 | Bank、起始地址、字长、密码 | 推荐实现 |
| `0x13` | 写入标签存储区 | 密码、Bank、起始地址、字长、数据 | 谨慎实现 |
| `0x14` | 锁定存储区 | Lock 参数 | 高风险,默认禁用 |
| `0x15` | Kill 标签 | Kill 密码 | 高风险,默认禁用 |
| `0x16` | 设置匹配 EPC | 模式、EPC 长度、EPC | 推荐实现,用于防误操作 |
| `0x17` | 读取匹配 EPC | 无参数 | 推荐实现 |
| `0x18` | 设置过滤 | Fun、Target、Action、Bank、StartBit、BitNum、Mask、RFU | 推荐实现 |
| `0x20` | 快速盘点 | Session / Target 等 | 可选 |
8. 常用 HEX 指令
以下指令按源地址 00 00、目标地址 01 00、CRC-16/X25、小端 CRC 整理。未实测命令必须在真实设备上验证响应后再进入生产配置。
| 功能 | 完整 HEX 指令 | 说明 |
| 获取射频模块温度 `0xD8` | `7E550800000100D800DCDF` | UM002 V2.8 修正命令 |
| 获取版本信息 `0xF7` | `7E550800000100F700277F` | 启动自检推荐 |
| 获取系统工作参数 `0xF5` | `7E550900000100F50000BD91` | `F5` 需带参数 `00` |
| 获取天线/射频参数 `0xF1` | `7E550900000100F10000DCF2` | `F1` 需带参数 `00` |
| 获取回波损耗,天线0,频点 `0x07` | `7E550A00000100F6000007F219` | `F6` 需带天线/频点 |
| 获取回波损耗,天线0,频点 `0x1E` | `7E550A00000100F600001EB294` | `F6` 需带天线/频点 |
| 选择天线0 `0xF2` | `7E550900000100F20000B81D` | 单天线接入常用 |
| 软件复位 `0x04` | `7E55080000010004004729` | 调试/恢复 |
| 盘点标签,天线0,重复1次 | `7E550B000001001000010001F81E` | 已验证 Inventory 命令 |
| 盘点标签,返回 Ant + RSSI,天线0,重复1次 | `7E550B000001001003010001353B` | 保留字段 `03` |
| 读取 EPC 区,start=2,word=6,pwd=00000000 | `7E550F000001001200010206000000006AAB` | 读 96-bit EPC 主体 |
| 读取 TID 区,start=0,word=6,pwd=00000000 | `7E550F00000100120002000600000000520B` | 标签芯片 ID |
| 读取 USER 区,start=0,word=6,pwd=00000000 | `7E550F000001001200030006000000008794` | 用户区容量依标签而定 |
| 设置匹配 EPC:`140358806580375076870002` | `7E5516000001001600000C14035880658037507687000298F9` | 防止多标签误操作 |
| 禁止匹配 EPC | `7E550900000100160001F166` | 取消匹配 |
| 读取匹配 EPC | `7E5508000001001700BE96` | 查询当前匹配配置 |
| 禁止过滤 | `7E5509000001001800006367` | 关闭 Mask |
| 获取天线功率 `0xFC` | `7E550800000100FC008F9B` | 推荐实现 |
已验证 30 dBm 设置帧:
plain7E 55 11 00 00 01 00 F0 00 1E 01 07 3B 00 00 00 00 D1 5A 06
说明:
- 该帧用于设置射频功率 30 dBm,已通过真实设备验证。
- 该帧尾
D1 5A暂不按当前 CRC-16/X25 动态重算替换。 - 若需要设置其他功率,优先抓取
0xFD或对应 F0 真实帧再实现。
9. Inventory 请求与响应解析
9.1 标准 Inventory 请求
固定盘点命令:
plain7E 55 0B 00 00 01 00 10 00 01 00 01 F8 1E
字段解析:
plain0B = 长度,从源地址到 CRC,含 CRC 00 00 = 源地址 01 00 = 目标地址 10 = 盘点标签命令 00 = 保留字段,不返回 Ant/RSSI,不读缓冲遗留数据 01 = 天线数目 1 00 = 天线索引 0 01 = 重复次数 1 F8 1E = CRC-16/X25,小端
Inventory 请求帧保留字段 bit 含义:
| Bit | 含义 |
| ---: | --- |
| bit0 | 响应帧返回 RSSI 最大天线值 Ant |
| bit1 | 响应帧返回 RSSI 最大值 Rssi |
| bit2\~bit6 | 预留 |
| bit7 | 读取缓冲区中遗留数据 |
若希望响应带 Ant + RSSI,保留字段置为 03:
plain7E 55 0B 00 00 01 00 10 03 01 00 01 35 3B
9.2 Inventory 响应参数结构
plain参数长度 2B 标签数目 2B [标签长度 1B + EPC xB + Ant 1B 可选 + Rssi 1B 可选] * 标签数目
解析原则:
command必须为0x10。tagCount = 0是正常无标签响应,不应作为错误处理。- EPC 长度字段常见为
0x0C,即 12 bytes / 96-bit EPC,但解析器不能写死 12。 - 是否带 Ant / RSSI 由请求保留字段 bit0 / bit1 决定。
- 多标签响应顺序不稳定,业务层应按集合处理。
- 连续盘点会重复读到同一 EPC,业务层必须去重。
9.3 实测响应示例
无标签响应:
plain7E 55 00 01 00 00 00 1F 10 00 02 00 00 00 B4 E2
解析结果:
json{ "command": "0x10", "tagCount": 0, "epcList": [], "crcOk": true }
单标签响应:
plain7E 55 00 01 00 00 00 1F 10 00 0F 00 01 00 0C 14 03 58 80 65 80 37 50 76 87 00 02 6B 9D
解析结果:
json{ "command": "0x10", "tagCount": 1, "tags": [ { "epcLen": 12, "epc": "140358806580375076870002" } ], "crcOk": true }
双标签响应:
plain7E 55 00 01 00 00 00 1F 10 00 1C 00 02 00 0C 14 03 58 80 65 80 37 50 76 87 00 02 0C 14 03 58 80 65 80 37 50 76 87 00 01 D1 38
解析结果:
json{ "command": "0x10", "tagCount": 2, "tags": [ { "epcLen": 12, "epc": "140358806580375076870002" }, { "epcLen": 12, "epc": "140358806580375076870001" } ], "crcOk": true }
10. 标签存储区与读写边界
ZZ-D541SM 支持 ISO18000-6C / EPC Gen2 标签操作。标签通常包含 4 个 Memory Bank:
| Bank | 名称 | 说明 |
| ---: | --- | --- |
| `0` / `00` | Reserved | Access Password、Kill Password |
| `1` / `01` | EPC | 标签 CRC、PC Word、EPC 主体 |
| `2` / `10` | TID | 标签芯片标识,通常不可修改 |
| `3` / `11` | USER | 用户数据区,容量取决于标签型号 |
EPC Bank 典型结构:
plainWord 0: 标签内部 CRC-16,不等于串口帧 CRC Word 1: PC Word,包含 EPC 长度等控制信息 Word 2+: EPC 数据主体
读取标签存储区 0x12 请求参数:
plain区域 1B 起始地址 1B 字长 1B 密码 4B
写入标签存储区 0x13 请求参数:
plain密码 4B 区域 1B 起始地址 1B 字长 1B 数据 2*xB
工程约束:
- Inventory 响应通常只返回 EPC 主体,不直接返回 EPC Bank 的 Word0 / Word1。
- 读 EPC 主体建议从 EPC Bank Word 2 开始。
- 写 EPC 时不要随意修改 PC Word,否则可能导致 EPC 长度异常。
- TID 区通常不可修改。
- USER 区容量因标签型号而异,写入前必须确认容量。
- 默认 Access Password 常见为
00000000,但应以实际标签为准。 - 写标签、锁标签、Kill 标签前必须使用匹配 EPC 或过滤参数,避免多标签在场时误操作。
11. 过滤与匹配标签
11.1 匹配 EPC
设置匹配 EPC 0x16 参数:
plain模式 1B EPC长度 1B EPC xB
模式:
| 值 | 含义 |
| `00` | 使能匹配 |
| `01` | 禁止匹配 |
示例:匹配 EPC 140358806580375076870002:
plain7E 55 16 00 00 01 00 16 00 00 0C 14 03 58 80 65 80 37 50 76 87 00 02 98 F9
禁止匹配:
plain7E 55 09 00 00 01 00 16 00 01 F1 66
读取匹配 EPC:
plain7E 55 08 00 00 01 00 17 00 BE 96
11.2 过滤参数
设置过滤参数 0x18 参数结构:
plainFun 1B Target 1B Action 2B Bank 1B 起始地址 1B Bit数目 1B Mask值 M B RFU 1B
| 字段 | 说明 |
| Fun | `0x01` 使能,`0x00` 禁止 |
| Target | 0\~4,按 Gen2 Select 语义映射 |
| Action | 0\~7,按 Gen2 Select Action 语义映射 |
| Bank | `0` Reserved、`1` EPC、`2` TID、`3` USER |
| 起始地址 | EPC 主体常见从 `0x20` bit 开始 |
| Bit数目 | 例如 96-bit EPC 为 `0x60` |
| Mask | 过滤值 |
| RFU | 保留 |
禁止过滤完整指令:
plain7E 55 09 00 00 01 00 18 00 00 63 67
12. 响应解析与事件模型
12.1 Parser 设计原则
Parser 需要支持以下能力:
- 串口粘包拆帧:一次
ReadAsync可能包含多帧,也可能只包含半帧。 - 帧头同步:按
7E 55查找帧头,丢弃前导噪声。 - 长度校验:普通帧按
3 + length计算完整帧长度;超长帧按扩展参数长度处理。 - CRC 校验:所有二进制响应帧必须校验 CRC-16/X25。
- 命令匹配:响应帧
command必须与当前请求命令匹配。 - 无标签处理:
tagCount=0返回空集合,不记录为错误。 - EPC 长度动态解析:不能写死 12 bytes。
- 可选字段解析:Ant / RSSI 是否存在取决于 Inventory 请求保留字段。
- 原始帧留痕:日志与事件中可保留 rawFrame,用于后续定位。
12.2 标准 TagRead DTO
c#public sealed class RfidTagRead { public string Epc { get; init; } = string.Empty; public int? AntennaPort { get; init; } public int? Rssi { get; init; } public DateTimeOffset ReadAt { get; init; } public string RawFrame { get; init; } = string.Empty; public bool CrcOk { get; init; } }
12.3 InventoryResult DTO
c#public sealed class RfidInventoryResult { public string EquipmentId { get; init; } = string.Empty; public int TagCount { get; init; } public IReadOnlyList<RfidTagRead> Tags { get; init; } = Array.Empty<RfidTagRead>(); public string RawFrame { get; init; } = string.Empty; public bool CrcOk { get; init; } public DateTimeOffset CollectedAt { get; init; } }
12.4 BioLab 标准事件
建议发布到 BioLab / CAP 的事件模型:
json{ "equipmentId": "ZZ-D541SM-001", "tagEpc": "140358806580375076870002", "timestamp": "2026-06-03T17:20:00+08:00", "antennaPort": null, "rssi": null, "rawFrame": "7E5500010000001F10000F0001000C1403588065803750768700026B9D", "crcOk": true }
字段说明:
| 字段 | 说明 |
| `equipmentId` | 设备唯一编号,由 Nacos 配置下发 |
| `tagEpc` | RFID 标签 EPC,十六进制大写字符串 |
| `timestamp` | 采集时间,建议使用网关本地时间并带时区 |
| `antennaPort` | 可选天线端口;请求保留字段 bit0 开启后解析 |
| `rssi` | 可选 RSSI;请求保留字段 bit1 开启后解析 |
| `rawFrame` | 原始响应帧 HEX,便于审计和排障 |
| `crcOk` | 串口帧 CRC 是否通过 |
12.5 去重与节流
UHF RFID 连续盘点会重复读到同一 EPC,业务层必须去重。
推荐去重键:
plainequipmentId + tagEpc + timeWindow
推荐策略:
| 场景 | 时间窗口 |
| --- | ---: |
| 桌面单标签扫描 | 1–3 秒 |
| 批量盘点 | 0.5–2 秒 |
| 出入库通道 | 3–10 秒,结合业务状态机 |
去重只应抑制重复事件,不应丢弃原始设备日志。原始 TX/RX 帧可进入调试日志或诊断存储,业务事件按去重策略发布。
13. .NET Core 实现方案
13.1 分层架构
plainApplication / Device Service └── IRfidReader / ZZD541DeviceService └── ZZD541SerialAdapter ├── SerialTransport ├── ZZD541FrameBuilder ├── ZZD541FrameParser ├── ZZD541Crc16X25 └── RfidEventPublisher / CAP Publisher
| 层级 | 职责 |
| Transport | 打开/关闭串口,异步读写 byte stream,处理超时和取消 |
| FrameBuilder | 根据命令模型生成 `7E55` 请求帧并计算 CRC |
| FrameParser | 粘包拆帧、长度校验、CRC 校验、响应解析 |
| Adapter | 封装设备命令:Inventory、GetVersion、GetStatus、ReadMemory 等 |
| Device Service | 管理轮询、去重、设备状态、异常恢复 |
| Event Publisher | 转换为 BioLab 标准事件并发布到消息总线/CAP |
13.2 接口契约
c#public interface IRfidReader { Task OpenAsync(RfidConnectionOptions options, CancellationToken cancellationToken = default); Task CloseAsync(CancellationToken cancellationToken = default); Task<RfidInventoryResult> InventoryAsync(RfidInventoryOptions options, CancellationToken cancellationToken = default); Task<RfidReaderStatus> GetStatusAsync(CancellationToken cancellationToken = default); Task SetPowerAsync(int dbm, CancellationToken cancellationToken = default); Task<IReadOnlyList<byte>> ReadMemoryAsync(RfidReadMemoryOptions options, CancellationToken cancellationToken = default); }
连接配置:
c#public sealed class RfidConnectionOptions { public string EquipmentId { get; init; } = string.Empty; public string PortName { get; init; } = "/dev/ttyUSB0"; public int BaudRate { get; init; } = 115200; public int DataBits { get; init; } = 8; public string Parity { get; init; } = "None"; public int StopBits { get; init; } = 1; public ushort SourceAddress { get; init; } = 0x0000; public ushort TargetAddress { get; init; } = 0x0001; public int ReadTimeoutMs { get; init; } = 1000; public int CommandIntervalMs { get; init; } = 50; }
盘点配置:
c#public sealed class RfidInventoryOptions { public byte AntennaIndex { get; init; } = 0; public byte RepeatCount { get; init; } = 1; public bool IncludeAntenna { get; init; } = false; public bool IncludeRssi { get; init; } = false; public bool ReadBufferedData { get; init; } = false; }
13.3 Adapter 工作流程
plain启动服务 1. 从 Nacos 加载设备配置 2. 打开串口 3. 发送 GetVersion / GetSysParams / GetAntParams 自检 4. 按需选择天线、设置功率或读取功率 5. 进入定时 Inventory 轮询 6. 对响应帧做 CRC 校验和 EPC 解析 7. 对 EPC 做时间窗口去重 8. 发布 BioLab 标准事件 9. 异常时按策略重连或降级
13.4 串口粘包拆帧要点
plainBuffer += Serial.Read() while Buffer contains possible frame: find index of 7E 55 discard bytes before header if normal length frame: expectedLength = 3 + length if buffer not enough: wait more bytes frame = buffer[0:expectedLength] verify CRC emit frame if length == 00: parse extended length wait full extended frame
实现注意:
- 不要假设一次串口 read 正好返回一帧。
- 不要在 Parser 中直接发布业务事件;Parser 只负责结构化解析。
- 设备返回 CRC 错误时应记录 rawFrame,并按配置决定是否重试。
- 无响应不等于无标签;无标签应有合法响应,超时才是通信异常。
13.5 运行模式选择
标准接入推荐优先使用命令模式 HEX:
plain工作模式:命令模式 命令格式:HEX 二进制帧 采集方式:服务端定时发送 Inventory
可选支持定时器 / TXT 主动上传模式:
| 模式 | 优点 | 风险 |
| 命令模式 HEX | 可控、易测、适合标准数采 | 需要轮询 |
| 定时器模式 | 设备主动上报 | 需处理 TXT / 二进制主动帧,Demo 可能不显示自动数据 |
| TXT 模式 | 人类可读 | 校验方式不同,字段格式需再次实测 |
14. Nacos 配置
业务配置必须统一由 Nacos 管理,不在本地配置文件中存放业务参数。
建议 DataId:
plainbiolab-device-zzd541.json
建议配置:
json{ "devices": [ { "enabled": true, "equipmentId": "ZZ-D541SM-001", "deviceType": "RFID_UHF_READER", "model": "ZZ-D541SM", "transport": { "type": "SerialPort", "portName": "/dev/ttyUSB0", "baudRate": 115200, "dataBits": 8, "parity": "None", "stopBits": 1, "readTimeoutMs": 1000, "writeTimeoutMs": 1000, "commandIntervalMs": 50 }, "protocol": { "frameHeader": "7E55", "sourceAddress": "0000", "targetAddress": "0100", "crc": "CRC16_X25", "mode": "CommandHex" }, "inventory": { "antennaIndex": 0, "repeatCount": 1, "intervalMs": 500, "includeAntenna": false, "includeRssi": false, "readBufferedData": false, "dedupWindowMs": 2000 }, "startup": { "getVersion": true, "getSystemParameters": true, "getAntennaParameters": true, "selectAntenna": true, "setPowerDbm": null }, "publishing": { "topic": "biolab.device.rfid.tag-read", "publishRawFrame": true } } ] }
配置说明:
| 配置项 | 说明 |
| `equipmentId` | 设备唯一标识,进入事件模型 |
| `transport.portName` | 串口路径,由部署环境决定 |
| `protocol.targetAddress` | 设备地址,当前实测为 `0100` 小端表示 |
| `inventory.includeAntenna` | 对应 Inventory 保留字段 bit0 |
| `inventory.includeRssi` | 对应 Inventory 保留字段 bit1 |
| `inventory.readBufferedData` | 对应 Inventory 保留字段 bit7 |
| `inventory.dedupWindowMs` | 业务事件去重窗口 |
| `startup.setPowerDbm` | 默认 `null`,避免启动时误改射频功率 |
上线建议:
- 首次部署不自动设置功率,只读取版本、系统参数、天线参数。
- 需要设置功率时,应先在测试环境确认对应 HEX 帧。
- 串口端口、轮询间隔、去重窗口、发布 topic 均从 Nacos 下发。
15. 测试验收
15.1 单元测试
| 测试项 | 验收标准 |
| CRC-16/X25 | 已知 Inventory 命令计算得到 `F8 1E` |
| FrameBuilder | 能生成 D8、F7、F5、F1、F2、10、12、16、17、18、FC 等命令帧 |
| NormalFrameParser | 能从粘包 buffer 中拆出完整 `7E55` 帧 |
| CRC 校验 | 对实测无标签、单标签、双标签响应均校验通过 |
| InventoryParser | 正确解析 `tagCount=0/1/2` |
| EPC 长度 | EPC 长度不写死 12 bytes |
| Ant/RSSI 可选字段 | 根据请求保留字段 bit0/bit1 解析 |
| TXT Parser | 若启用主动上传,按 `aa...\r\n` 和 sum 校验处理 |
15.2 集成测试
| 场景 | 操作 | 预期 |
| 串口连接 | 打开 `/dev/ttyUSB0`,115200-8-N-1 | 串口打开成功 |
| 设备自检 | 发送 `F7` 获取版本 | 返回合法响应且 CRC 通过 |
| 系统参数 | 发送 `F5 00` | 返回系统工作参数 |
| 天线参数 | 发送 `F1 00` | 返回天线/射频参数 |
| 单标签盘点 | 放置 1 个标签,发送 Inventory | 返回 1 个 EPC |
| 无标签盘点 | 移除标签,发送 Inventory | 返回 `tagCount=0`,不报错 |
| 多标签盘点 | 放置 2 个以上标签 | 返回多个 EPC,顺序不做业务假设 |
| 去重 | 连续轮询同一标签 | 业务事件在去重窗口内只发布一次 |
| CRC 异常 | 构造错误 CRC 响应 | Parser 拒绝该帧并记录 rawFrame |
| 串口断开 | 拔插 USB 转串口 | 服务进入异常状态并按配置重连 |
15.3 真实设备验收
上线前至少完成以下验收:
- 使用真实 ZZ-D541SM 连接目标部署环境。
- 验证
F7、F5 00、F1 00、Inventory 命令均有合法响应。 - 验证无标签、单标签、双标签响应解析正确。
- 验证
crcOk=true才发布业务事件。 - 验证
equipmentId + EPC + 时间窗口去重生效。 - 验证 Nacos 修改串口、轮询间隔、去重窗口后服务能按预期加载。
- 在真实实验台环境测试标签方向、距离、液体、金属遮挡对读取稳定性的影响。
- 形成设备验收记录:串口号、固件版本、测试标签 EPC、测试时间、通过/失败项。
16. 风险与待确认
| 风险/待确认项 | 影响 | 建议 |
| F0 30 dBm 设置帧 CRC 例外 | 动态设置功率可能失败 | 30 dBm 使用固化帧;其他功率抓帧验证后再开放 |
| UM002 与旧文档字段冲突 | 解析错误或命令错误 | 以 UM002 V2.8 和实测为准 |
| F6 请求必须带天线/频点 | 无参数短帧会失败 | 命令模型中强制天线索引和频点索引 |
| F5/F1 请求需带 `00` | 早期短帧不可用 | FrameBuilder 中固定参数 `00` |
| Inventory 保留字段有 bit 含义 | Ant/RSSI 解析不一致 | 请求选项与 Parser 可选字段严格绑定 |
| 主动上传 TXT 使用 sum 校验 | 若误用 CRC16 会全部校验失败 | 独立 TXT Parser 与校验器 |
| 多标签误写 | 可能写错 EPC/USER/Reserved 区 | 写操作前必须设置匹配 EPC 或过滤 |
| Kill / Lock 高风险 | 标签可能永久不可用 | 默认禁用,仅管理员显式开启 |
| UHF 对液体/金属敏感 | 实验室稳定性波动 | 现场调天线、功率、标签位置,建立 SOP |
| 串口设备名变化 | Docker 重启后端口变化 | 使用 udev 规则或设备映射固定端口 |
| Docker 串口权限 | 容器无法访问 `/dev/ttyUSB0` | 部署时挂载 device,并配置用户权限 |
17. 推荐落地步骤
- 在 Nacos 中新增 ZZ-D541SM 设备配置,先只启用读取版本、读取参数和 Inventory。
- 实现
SerialTransport,支持打开、关闭、异步读写、超时、取消、重连。 - 实现
ZZD541Crc16X25,用已知帧完成单元测试。 - 实现
ZZD541FrameBuilder,覆盖 D8、F7、F5、F1、F2、Inventory、ReadMemory、SetMatchEpc、SetFilter、FC。 - 实现
ZZD541FrameParser,支持粘包拆帧、CRC 校验、Inventory 响应解析。 - 实现
ZZD541SerialAdapter,封装标准命令接口。 - 实现
ZZD541DeviceService,负责轮询、去重、设备状态、异常恢复。 - 接入 CAP / Event Publisher,发布 BioLab 标准 RFID 标签事件。
- 完成单元测试、串口模拟器测试、真实设备验收。
- 上线后保留原始帧诊断日志,观察一段时间后再开放写标签、功率设置等高风险能力。
18. 快速接入检查清单
| 检查项 | 状态 |
| 已确认串口为 RS232 / 115200-8-N-1 | 待填写 |
| 已确认设备地址 / 目标地址 | 待填写 |
| `F7` 获取版本通过 | 待填写 |
| `F5 00` 获取系统参数通过 | 待填写 |
| `F1 00` 获取天线参数通过 | 待填写 |
| Inventory 无标签响应通过 | 待填写 |
| Inventory 单标签响应通过 | 待填写 |
| Inventory 多标签响应通过 | 待填写 |
| CRC-16/X25 单测通过 | 待填写 |
| Nacos 配置已接入 | 待填写 |
| CAP 事件发布已接入 | 待填写 |
| 去重策略已验证 | 待填写 |
| 真实场景稳定性测试完成 | 待填写 |
19. 当前已确认事实
- ZZ-D541SM 使用 RS232 私有协议,默认串口参数为 115200-8-N-1。
- 主机通信帧头为
7E 55。 - UM002 V2.8 是当前最完整的底层协议依据。
- 温度命令为
0xD8。 0xF5与0xF1请求需要带参数00。0xF6请求需要带天线索引和频点索引。- Inventory 请求保留字段 bit0 / bit1 / bit7 分别影响 Ant、RSSI、缓冲遗留数据。
- Inventory 命令
7E550B000001001000010001F81E已验证。 - Inventory 响应 CRC 使用 CRC-16/X25,小端输出。
tagCount=0是正常无标签响应。- EPC 响应必须按
epcLen + epcBytes动态解析。 - 主动上传 TXT 帧与二进制帧不同,使用 8-bit sum 校验。
- BioLab 标准事件建议包含
equipmentId、tagEpc、timestamp、antennaPort、rssi、rawFrame、crcOk。
20. 后续待补充资料
- 抓取
0xFD设置天线功率的真实请求/响应帧,替代 F0 固化功率帧。 - 验证
0xD8温度响应字段结构和单位。 - 验证
0xFC获取天线功率响应结构。 - 验证带 Ant/RSSI 的 Inventory 响应实际字段位置。
- 验证读取 TID、USER 的真实响应样例。
- 验证设置过滤
0x18在不同 Bank、StartBit、BitNum 下的真实效果。 - 验证定时器模式和 TXT 主动上传在 ZZ-D541SM 上的完整字段。
- 明确 Docker 部署时的串口设备映射、权限、udev 固定命名方案。