package com.rfid.uhf288;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import java.util.ArrayList;
|
import java.util.List;
|
|
/**
|
* RFID设备操作模板 - 模块化设计
|
* 参考demo项目,但只包含基本读取功能(类似参考项目zy-wcs-czbrwcs中的功能)
|
* Created by chen.lin on 2026/01/10
|
*
|
* 功能模块:
|
* 1. 连接模块 - 串口连接 / 网络连接(独立)
|
* 2. 读取模块 - 盘点标签(独立)
|
*
|
* 使用方式:
|
* 1. 创建实例
|
* 2. 选择连接方式(串口或网络)
|
* 3. 连接成功后,调用读取功能
|
* 4. 使用完毕后关闭连接
|
*/
|
@Slf4j
|
public class RFIDTemplate {
|
|
// ==================== 基础组件 ====================
|
private Device reader;
|
private int portHandle = -1;
|
private byte[] comAddr = new byte[1];
|
|
// 连接状态
|
private boolean isConnected = false;
|
private String connectionType = ""; // "COM" 或 "NET"
|
|
// ==================== 初始化 ====================
|
|
/**
|
* 构造函数 - 加载DLL库
|
*/
|
public RFIDTemplate() {
|
try {
|
reader = new Device();
|
comAddr[0] = (byte)255; // 默认地址
|
log.info("RFID模板初始化成功");
|
} catch (Exception e) {
|
log.error("RFID模板初始化失败", e);
|
}
|
}
|
|
// ==================== 模块1: 连接模块 ====================
|
|
/**
|
* 【串口连接】打开串口连接
|
*
|
* @param port 串口号 (1=COM1, 2=COM2, 3=COM3...)
|
* @param baud 波特率 (0=9600, 1=19200, 2=38400, 3=57600, 4=115200, 5=230400, 6=460800, 7=921600)
|
* @return true=成功, false=失败
|
*/
|
public boolean connectComPort(int port, byte baud) {
|
if (isConnected) {
|
log.warn("已存在连接,请先关闭当前连接");
|
return false;
|
}
|
|
try {
|
int[] portHandleArray = new int[1];
|
int result = reader.OpenComPort(port, comAddr, baud, portHandleArray);
|
|
if (result == 0) {
|
portHandle = portHandleArray[0];
|
isConnected = true;
|
connectionType = "COM";
|
log.info("========== 串口连接成功 ==========");
|
log.info("串口号: COM{}", port);
|
log.info("端口句柄: {}", portHandle);
|
|
try {
|
int checkAntResult = reader.SetCheckAnt(comAddr, (byte)0x01, portHandle);
|
if (checkAntResult == 0) {
|
// log.info("天线检测开启成功");
|
}
|
int antResult = reader.SetAntennaMultiplexing(comAddr, (byte)0x0F, portHandle);
|
if (antResult == 0) {
|
log.info("天线多路复用配置成功 - 启用天线1,2,3,4 (0x0F)");
|
} else {
|
int antennaResult = reader.SetAntenna(comAddr, (byte)0, (byte)0x0F, (byte)0, portHandle);
|
if (antennaResult == 0) {
|
log.info("天线配置成功 - 启用天线1,2,3,4 (0x0F)");
|
}
|
}
|
} catch (Exception e) {
|
log.warn("配置天线异常: {}", e.getMessage());
|
}
|
return true;
|
} else {
|
log.error("串口连接失败,错误码: 0x{} ({})", String.format("%02X", result & 0xFF), getErrorCodeMessage(result));
|
return false;
|
}
|
} catch (UnsatisfiedLinkError e) {
|
log.error("RFID设备DLL方法链接失败 - OpenComPort", e);
|
log.error("错误信息: {}", e.getMessage());
|
log.error("可能原因:1. DLL版本不匹配 2. 方法签名不匹配 3. DLL未正确加载");
|
throw e; // 重新抛出异常,让调用者处理
|
}
|
}
|
|
/**
|
* 【串口连接】自动尝试多个波特率连接(推荐)
|
*
|
* @param port 串口号 (1=COM1, 2=COM2, 3=COM3...)
|
* @param preferredBaud 首选波特率 (0-7),-1表示使用默认顺序
|
* @return true=成功, false=失败
|
*/
|
public boolean connectComPortAuto(int port, int preferredBaud) {
|
if (isConnected) {
|
log.warn("已存在连接,请先关闭当前连接");
|
return false;
|
}
|
|
// 波特率映射
|
byte[] baudRates = {(byte)0, (byte)1, (byte)2, (byte)3, (byte)4, (byte)5, (byte)6, (byte)7};
|
String[] baudNames = {"9600", "19200", "38400", "57600", "115200", "230400", "460800", "921600"};
|
|
// 默认尝试顺序:57600优先
|
byte[] tryBauds = {(byte)3, (byte)4, (byte)2, (byte)1, (byte)0, (byte)5, (byte)6, (byte)7};
|
|
// 如果指定了首选波特率,将其放在最前面
|
if (preferredBaud >= 0 && preferredBaud <= 7) {
|
byte[] newTryBauds = new byte[tryBauds.length];
|
newTryBauds[0] = (byte)preferredBaud;
|
int idx = 1;
|
for (byte b : tryBauds) {
|
if (b != preferredBaud) {
|
newTryBauds[idx++] = b;
|
}
|
}
|
tryBauds = newTryBauds;
|
}
|
|
log.info("========== 串口连接 ==========");
|
log.info("串口号: COM{}", port);
|
log.info("正在尝试连接...");
|
|
for (int i = 0; i < tryBauds.length; i++) {
|
byte baud = tryBauds[i];
|
String baudName = "";
|
for (int j = 0; j < baudRates.length; j++) {
|
if (baudRates[j] == baud) {
|
baudName = baudNames[j];
|
break;
|
}
|
}
|
|
log.info("尝试波特率: {}bps ... ", baudName);
|
int[] portHandleArray = new int[1];
|
int result = reader.OpenComPort(port, comAddr, baud, portHandleArray);
|
|
if (result == 0) {
|
portHandle = portHandleArray[0];
|
isConnected = true;
|
connectionType = "COM";
|
log.info("✓ 成功!");
|
log.info("连接成功!波特率: {}bps", baudName);
|
log.info("端口句柄: {}", portHandle);
|
return true;
|
} else {
|
log.warn("✗ 失败");
|
}
|
|
try {
|
Thread.sleep(100);
|
} catch (InterruptedException e) {
|
Thread.currentThread().interrupt();
|
}
|
}
|
|
log.error("所有波特率尝试均失败!");
|
return false;
|
}
|
|
/**
|
* 【网络连接】打开网络连接
|
*
|
* @param ipAddr IP地址 (如: 192.168.1.190)
|
* @param port 端口号 (默认6000)
|
* @return true=成功, false=失败
|
*/
|
public boolean connectNetwork(String ipAddr, int port) {
|
if (isConnected) {
|
log.warn("已存在连接,请先关闭当前连接");
|
return false;
|
}
|
|
try {
|
int[] portHandleArray = new int[1];
|
int result = reader.OpenNetPort(port, ipAddr, comAddr, portHandleArray);
|
|
if (result == 0) {
|
portHandle = portHandleArray[0];
|
isConnected = true;
|
connectionType = "NET";
|
log.info("========== 网络连接成功 ==========");
|
log.info("IP地址: {}", ipAddr);
|
log.info("端口号: {}", port);
|
log.info("端口句柄: {}", portHandle);
|
|
try {
|
int checkAntResult = reader.SetCheckAnt(comAddr, (byte)0x01, portHandle);
|
if (checkAntResult == 0) {
|
// log.info("天线检测开启成功");
|
}
|
int antResult = reader.SetAntennaMultiplexing(comAddr, (byte)0x0F, portHandle);
|
if (antResult == 0) {
|
log.info("天线多路复用配置成功 - 启用天线1,2,3,4 (0x0F)");
|
} else {
|
int antennaResult = reader.SetAntenna(comAddr, (byte)0, (byte)0x0F, (byte)0, portHandle);
|
if (antennaResult == 0) {
|
log.info("天线配置成功 - 启用天线1,2,3,4 (0x0F)");
|
}
|
}
|
} catch (Exception e) {
|
log.warn("配置天线异常: {}", e.getMessage());
|
}
|
|
return true;
|
} else {
|
log.error("网络连接失败,错误码: 0x{} ({})", String.format("%02X", result & 0xFF), getErrorCodeMessage(result));
|
log.error("请检查IP地址和端口是否正确");
|
return false;
|
}
|
} catch (UnsatisfiedLinkError e) {
|
log.error("RFID设备DLL方法链接失败 - OpenNetPort", e);
|
log.error("错误信息: {}", e.getMessage());
|
log.error("可能原因:1. DLL版本不匹配 2. 方法签名不匹配 3. DLL未正确加载");
|
throw e; // 重新抛出异常,让调用者处理
|
}
|
}
|
|
/**
|
* 【网络连接】使用默认端口6000连接
|
*
|
* @param ipAddr IP地址 (如: 192.168.1.190)
|
* @return true=成功, false=失败
|
*/
|
public boolean connectNetwork(String ipAddr) {
|
return connectNetwork(ipAddr, 6000);
|
}
|
|
/**
|
* 【连接管理】关闭连接
|
*/
|
public void disconnect() {
|
if (!isConnected) {
|
log.warn("当前没有连接");
|
return;
|
}
|
|
if ("COM".equals(connectionType)) {
|
int result = reader.CloseSpecComPort(portHandle);
|
if (result == 0) {
|
log.info("串口连接已关闭");
|
}
|
} else if ("NET".equals(connectionType)) {
|
int result = reader.CloseNetPort(portHandle);
|
if (result == 0) {
|
log.info("网络连接已关闭");
|
}
|
}
|
|
portHandle = -1;
|
isConnected = false;
|
connectionType = "";
|
}
|
|
/**
|
* 【连接管理】检查连接状态
|
*
|
* @return true=已连接, false=未连接
|
*/
|
public boolean isConnected() {
|
return isConnected && portHandle >= 0;
|
}
|
|
/**
|
* 【天线配置】重新配置天线(动态切换天线时使用)
|
* 当物理天线插拔后,调用此方法重新配置天线,确保所有插上的天线都能被检测到
|
*
|
* @return true=成功, false=失败
|
*/
|
public boolean reconfigureAntenna() {
|
if (!isConnected()) {
|
log.warn("设备未连接,无法配置天线");
|
return false;
|
}
|
|
boolean configured = false;
|
|
try {
|
int checkAntResult = reader.SetCheckAnt(comAddr, (byte)0x01, portHandle);
|
if (checkAntResult == 0) {
|
// log.info("天线检测开启成功");
|
configured = true;
|
}
|
|
int antResult = reader.SetAntennaMultiplexing(comAddr, (byte)0x0F, portHandle);
|
if (antResult == 0) {
|
log.info("天线多路复用配置成功 - 启用天线1,2,3,4 (0x0F)");
|
configured = true;
|
} else {
|
int antennaResult = reader.SetAntenna(comAddr, (byte)0, (byte)0x0F, (byte)0, portHandle);
|
if (antennaResult == 0) {
|
log.info("天线配置成功 - 启用天线1,2,3,4 (0x0F)");
|
configured = true;
|
}
|
}
|
|
return configured;
|
} catch (Exception e) {
|
log.error("重新配置天线异常: {}", e.getMessage(), e);
|
return false;
|
}
|
}
|
|
/**
|
* 【连接管理】获取连接类型
|
*
|
* @return "COM"=串口, "NET"=网络, ""=未连接
|
*/
|
public String getConnectionType() {
|
return connectionType;
|
}
|
|
// ==================== 模块2: 读取模块 ====================
|
|
public List<TagInfo> readTags(int qValue, int session, int scanTime) {
|
List<TagInfo> tagList = new ArrayList<>();
|
if (!isConnected()) {
|
return tagList;
|
}
|
|
byte maskMem = 2;
|
byte[] maskAdr = new byte[2];
|
byte maskLen = 0;
|
byte[] maskData = new byte[256];
|
byte maskFlag = 0;
|
byte adrTID = 0;
|
byte lenTID = 6;
|
byte tidFlag = 0;
|
byte target = 0;
|
byte fastFlag = 0;
|
byte[] pEPCList = new byte[20000];
|
byte[] ant = new byte[1];
|
int[] totallen = new int[1];
|
int[] cardNum = new int[1];
|
|
byte[] antennaValues = {(byte)0x80, (byte)0x81, (byte)0x82, (byte)0x83};
|
int[] antennaNumbers = {1, 2, 3, 4};
|
|
for (int antIdx = 0; antIdx < antennaValues.length; antIdx++) {
|
byte inAnt = antennaValues[antIdx];
|
int expectedAntenna = antennaNumbers[antIdx];
|
|
java.util.Arrays.fill(pEPCList, (byte)0);
|
totallen[0] = 0;
|
cardNum[0] = 0;
|
|
int result = reader.Inventory_G2(comAddr, (byte)qValue, (byte)session, maskMem, maskAdr, maskLen,
|
maskData, maskFlag, adrTID, lenTID, tidFlag, target, inAnt, (byte)scanTime,
|
fastFlag, pEPCList, ant, totallen, cardNum, portHandle);
|
|
if (cardNum[0] > 0 && totallen[0] > 0) {
|
int m = 0;
|
for (int index = 0; index < cardNum[0]; index++) {
|
if (m >= totallen[0] || m >= pEPCList.length) break;
|
|
int epcLen = pEPCList[m++] & 0xFF;
|
if (epcLen <= 0 || epcLen > 32) break;
|
|
if (m + epcLen > totallen[0] || m + epcLen > pEPCList.length) break;
|
|
byte[] epcBytes = new byte[epcLen];
|
for (int n = 0; n < epcLen; n++) {
|
epcBytes[n] = pEPCList[m++];
|
}
|
|
if (m >= totallen[0] || m >= pEPCList.length) break;
|
|
int rssi = pEPCList[m++] & 0xFF;
|
if (rssi > 127) rssi = rssi - 256;
|
|
int antennaValue = 0;
|
if (m < totallen[0] && m < pEPCList.length) {
|
antennaValue = pEPCList[m++] & 0xFF;
|
} else {
|
antennaValue = ant[0] & 0xFF;
|
}
|
|
TagInfo tag = new TagInfo();
|
tag.epc = bytesToHex(epcBytes);
|
tag.rssi = rssi;
|
|
if (antennaValue >= 0x80 && antennaValue <= 0x83) {
|
tag.antenna = antennaValue - 0x80 + 1;
|
} else if (antennaValue >= 1 && antennaValue <= 4) {
|
tag.antenna = antennaValue;
|
} else {
|
tag.antenna = expectedAntenna;
|
}
|
tag.timestamp = System.currentTimeMillis();
|
|
tagList.add(tag);
|
}
|
} else {
|
if (result != 0) {
|
int errorCode = result & 0xFF;
|
if (errorCode == 0xFB) {
|
continue;
|
}
|
if (errorCode == 0x01) {
|
continue;
|
}
|
if (errorCode == 0xF8) {
|
// log.warn("天线{}盘点失败,错误码: 0x{} ({})", expectedAntenna, String.format("%02X", errorCode), getErrorCodeMessage(result));
|
} else {
|
// log.debug("天线{}盘点失败,错误码: 0x{} ({})", expectedAntenna, String.format("%02X", errorCode), getErrorCodeMessage(result));
|
}
|
}
|
}
|
}
|
|
return tagList;
|
}
|
|
public List<TagInfo> readTags() {
|
return readTags(4, 0, 10);
|
}
|
|
// ==================== 工具方法 ====================
|
|
private String bytesToHex(byte[] bytes) {
|
if (bytes == null) return "";
|
StringBuilder sb = new StringBuilder();
|
for (byte b : bytes) {
|
sb.append(String.format("%02X", b & 0xFF));
|
}
|
return sb.toString();
|
}
|
|
private String getErrorCodeMessage(int errorCode) {
|
switch (errorCode & 0xFF) {
|
case 0x00: return "无错误,API调用成功";
|
case 0x01: return "询查时间结束前返回";
|
case 0x02: return "指定的询查时间溢出";
|
case 0x03: return "存储器超限或不被支持的PC值";
|
case 0x04: return "存储器锁定";
|
case 0x05: return "访问密码错误";
|
case 0x09: return "销毁密码错误";
|
case 0x0A: return "销毁密码不能为全0";
|
case 0x0B: return "电子标签不支持该命令或电源不足";
|
case 0x0C: return "对该命令,访问密码不能为0";
|
case 0x0D: return "电子标签已经被设置了读保护,不能再次设置";
|
case 0x0E: return "电子标签没有被设置读保护,不需要解锁";
|
case 0x0F: return "非特定错误,标签不支持特定错误代码";
|
case 0x10: return "有字节空间被锁定,写入失败";
|
case 0x11: return "不能锁定";
|
case 0x12: return "已经锁定,不能再次锁定";
|
case 0x13: return "参数保存失败,但设置的值在读写模块断电前有效";
|
case 0x14: return "无法调整";
|
case 0x15: return "询查时间结束前返回";
|
case 0x17: return "本条消息之后,还有消息";
|
case 0x18: return "读写模块存储空间已满";
|
case 0x19: return "电子不支持该命令或者访问密码不能为0";
|
case 0x30: return "通讯错误";
|
case 0x33: return "通讯繁忙,设备正在执行其他命令";
|
case 0x35: return "端口已打开";
|
case 0x37: return "无效句柄";
|
case 0xF8: return "天线检测错误";
|
case 0xF9: return "命令执行出错";
|
case 0xFA: return "有电子标签,但通信不畅,无法操作";
|
case 0xFB: return "无电子标签可操作";
|
case 0xFC: return "电子标签返回错误代码";
|
case 0xFD: return "命令长度错误";
|
case 0xFE: return "不合法的命令";
|
case 0xFF: return "参数错误";
|
default: return "未知错误码";
|
}
|
}
|
|
// ==================== 标签信息类 ====================
|
|
/**
|
* 标签信息类
|
*/
|
public static class TagInfo {
|
public String epc; // EPC(十六进制字符串)
|
public int rssi; // RSSI(dBm)
|
public int antenna; // 天线号
|
public long timestamp; // 时间戳
|
|
@Override
|
public String toString() {
|
return String.format("EPC: %s, RSSI: %ddBm, 天线: %d", epc, rssi, antenna);
|
}
|
}
|
}
|