基于Java实现西克RFID读写
背景
由于自动线需要使用RFID标签来进行业务处理,之前几条线普遍使用过的RFID型号是深圳万全和广州晨控两家国产RFID,以及德国巴鲁夫的RFID,下面会对使用过的几种RFID进行对比。
本次项目由于越南客户要求使用西克RFID来进行处理,所以进行了测试,本次测试所使用的协议为TCP协议,使用的RFID型号为RFH620
使用过的几种高频RFID对比
深圳万全VI-IR610P-HF系列RFID:这款RFID支持多种协议,如TCP、UDP、RS232等协议、支持的使用场景也挺多的,如果只需要进行读取标签信息的话建议使用TCP Server的方式自动接收芯片信息,如果需要使用读写功能建议使用Modbus Tcp来读写芯片信息。
广州晨控FR08系列RFID:FR08系列RFID有E01与E00两种,两种RFID都支持Modbus Tcp协议,但是E01主要是EIP通讯,对Modbus Tcp的支持不是很完善,推荐使用E00的RFID,这款系列的RFID管理工具没有TCP Server的配置,只能是通过Modbus Tcp来进行读写。
德国巴鲁夫BIS018A系列RFID:这款型号的RFID对TCP的支持不是很友好,由于的国外产品,技术支持无法提供上位机相关的对接方式,对于软件开发不是特别友好,主要支持的协议是IOLink协议。这款RFID的配置界面是一个网页,可以通过抓包的方式使用HTTP来请求进行RFID的读写,但是供应商并没有提到过这种方式,经过实测,这款RFID特别容易掉IP,所以不建议使用软件对接这款RFID
德国西克RFH6xx系列RFID:这款RFID支持多种协议,如TCP、RS232等协议、用户可根据管理工具自定义配置读取的信息,如果只需要进行读取标签信息的话建议使用TCP Server的方式自动接收芯片信息,如果需要对芯片进行读写操作,可使用TCP Socket来读写芯片信息。
声明:本人通过几款使用过的RFID进行对比,只是阐述软件对接过程中的感受,具体使用哪种RFID需要结合业务实际考虑
西克RFID配置说明
RFID读写头的配置可以参照手册进行操作,其中有几个关键点需要注意
读取配置
读取配置:读取方式有自动触发、命令触发、条件触发等几种方式,我们常用的方式为自动和命令模式
自动模式:当设置为自动模式时,程序通过TCP连接读写器后,读写器会通过TCP不断发送数据到程序中,发送频率取决于设置的间隔时长
命令模式:当需要通过Socket发送命令到读写器进行读写时,必须设置为命令模式进行读取,在自动模式下发送命令会出现命令不生效的情况,软件会充当TCP Client被动接收读写器发送的数据
数据配置
西克RFID可通过配置数据块自定义TCP返回的数据
数据块中一个块可存储8个数字,可根据业务情况决定使用多少个数据块
网络配置
可设置以太网配置,配置IP地址以及设备通讯模式,经过实测,设备可同时充当客户端与服务端,当配置为客户端时,使用Aux中的端口,也可以通过TCP与设备建立连接,此时设备为服务端状态。
使用Java对RFID标签进行读写
在读取标签之前,我们先看一下通过TCP Socket向西克RFID发送指令的格式
获取UID
通过以上指令我们可以看出,如果需要获取UID的话,只需要发送指令sMN CSGtUID就行了,那么我们通过Java代码的实现则为如下所示
注:命令中的[STX]与[ETX]指的并不是字符,而是0x02(STX,Start of Text)和0x03(ETX,End of Text),用于标识数据包的开始和结束
// 发送获取 UID 的命令并接收响应
private static String sendAndReceiveUid(PrintWriter writer, InputStream inputStream) throws IOException {
String command = STX + "sMN CSGtUID" + ETX + "\n";
System.out.println("发送 UID 命令: " + command.trim());
writer.print(command);
writer.flush();
//处理socket返回的数据,取出UID
return readResponse(inputStream, "CSGtUID", TcpSocketExample::parseUid);
}
读取多个数据块
通过以上命令我们可以看出,读取数据块我们需要发送的指令为sMN CSRdMltBlck,其次就是uid (芯片本身自带的物理ID),bn(开始读取位),nb(结束读取位)三个参数,通过Java代码实现则为以下方式
// 发送读取数据块命令并接收 RFID 数据
private static String sendAndReceiveRfid(PrintWriter writer, InputStream inputStream, String uid, int bn, int nb)
throws IOException {
String command = String.format("%ssMN CSRdMltBlck %s +%d %d%s\n", STX, uid, bn, nb, ETX);
System.out.println("发送读取数据块命令: " + command.trim());
writer.print(command);
writer.flush();
//处理socket返回的信息,提取出数据块的内容
return readResponse(inputStream, "CSRdMltBlck", TcpSocketExample::parseRfid);
}
写入多个数据块
通过以上命令我们可以看出,读取数据块我们需要发送的指令为sMN WrtMltBlck,其次就是uid (芯片本身自带的物理ID),bn(开始写入位),nb(结束写入位),lbc(数据长度),bc(写入的数据)五个参数,通过Java代码实现则为以下方式
// 发送写入数据块命令并确认写入成功
private static String sendAndConfirmWrite(PrintWriter writer, InputStream inputStream,
String uid, int bn, int nb, String data) throws IOException {
//获取数据长度
String lbc = String.format("%02X", data.split(" ").length);
String command = String.format("%ssMN WrtMltBlck %s +%d %d %s %s%s\n", STX, uid, bn, nb, lbc, data, ETX);
System.out.println("发送写入数据块命令: " + command.trim());
writer.print(command);
writer.flush();
//处理socket返回的信息
return readResponse(inputStream, "WrtMltBlck", TcpSocketExample::parseBlockContent);
}
完整代码
通过以上配置说明我们掌握了使用Java读写西克RFID的基本操作,以下为完整代码,替换IP和端口后可直接运行
package com.example;
import cn.hutool.core.util.RandomUtil;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class TcpSocketExample {
private static final String HOST = "192.168.20.12"; // 设备的 IP 地址
private static final int PORT = 2111; // 设备监听的端口号
private static final char STX = 0x02; // 起始字符
private static final char ETX = 0x03; // 结束字符
public static void main(String[] args) {
try (Socket socket = new Socket(HOST, PORT)) {
System.out.println("连接设备成功: " + HOST + ":" + PORT);
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8), true)) {
long startTime = System.currentTimeMillis();
// 1. 发送获取 UID 的命令
String uid = sendAndReceiveUid(writer, inputStream);
if (uid.isEmpty() || Arrays.stream(uid.split(" "))
.allMatch(s -> s.equals("0"))) {
System.err.println("无法获取有效的 UID,程序退出");
return;
}
// 2. 读取数据块
String rfid = sendAndReceiveRfid(writer, inputStream, uid, 0, 1);
System.out.println("读取到的 RFID 数据: " + rfid);
while (true) {
try {
//随机生成16位的RFID号码
String newRfid = RandomUtil.randomNumbers(16);
//处理RFID数据,将RFID两两分开
String writeData = formatDataForWrite(newRfid);
//发送指令写入RFID信息
String result = sendAndConfirmWrite(writer, inputStream, uid, 0, 1, writeData);
if (StringUtils.equals("0", result)) {
System.out.println("写入成功");
} else {
System.out.println("写入失败,结果: " + result);
}
} catch (Exception e) {
System.err.println("写入过程中发生异常: " + e.getMessage());
}
try {
// 暂停两秒
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
System.err.println("线程被中断: " + e.getMessage());
break; // 可能需要退出循环
}
}
long endTime = System.currentTimeMillis();
System.out.println("程序运行时间: " + (endTime - startTime) + " 毫秒");
}
} catch (IOException e) {
System.err.println("设备通信失败: " + e.getMessage());
e.printStackTrace();
}
}
// 发送获取 UID 的命令并接收响应
private static String sendAndReceiveUid(PrintWriter writer, InputStream inputStream) throws IOException {
String command = buildUidCommand();
System.out.println("发送 UID 命令: " + command.trim());
writer.print(command);
writer.flush();
return readResponse(inputStream, "CSGtUID", TcpSocketExample::parseUid);
}
// 发送读取数据块命令并接收 RFID 数据
private static String sendAndReceiveRfid(PrintWriter writer, InputStream inputStream, String uid, int bn, int nb)
throws IOException {
String command = buildReadBlocksCommand(uid, bn, nb);
System.out.println("发送读取数据块命令: " + command.trim());
writer.print(command);
writer.flush();
return readResponse(inputStream, "CSRdMltBlck", TcpSocketExample::parseRfid);
}
// 发送写入数据块命令并确认写入成功
private static String sendAndConfirmWrite(PrintWriter writer, InputStream inputStream,
String uid, int bn, int nb, String data) throws IOException {
String command = buildWriteCommand(uid, bn, nb, data);
System.out.println("发送写入数据块命令: " + command.trim());
writer.print(command);
writer.flush();
return readResponse(inputStream, "WrtMltBlck", TcpSocketExample::parseBlockContent);
}
// 通用方法:读取响应并根据关键字解析
private static String readResponse(InputStream inputStream, String keyword, ResponseParser parser) throws IOException {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
String response = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8).trim();
if (response.contains(keyword)) {
return parser.parse(response);
}
}
return "";
}
// 构建获取 UID 的命令
private static String buildUidCommand() {
return STX + "sMN CSGtUID" + ETX + "\n";
}
// 构建读取数据块的命令
private static String buildReadBlocksCommand(String uid, int bn, int nb) {
return String.format("%ssMN CSRdMltBlck %s +%d %d%s\n", STX, uid, bn, nb, ETX);
}
// 构建写入数据块的命令
private static String buildWriteCommand(String uid, int bn, int nb, String data) {
String lbc = String.format("%02X", data.split(" ").length);
return String.format("%ssMN WrtMltBlck %s +%d %d %s %s%s\n", STX, uid, bn, nb, lbc, data, ETX);
}
// 解析 UID 数据
private static String parseUid(String response) {
String[] parts = response.split(" ");
return IntStream.range(6, parts.length)
.mapToObj(i -> parts[i].trim())
.collect(Collectors.joining(" "));
}
// 解析 RFID 数据
private static String parseRfid(String response) {
String[] parts = response.split(" ");
return IntStream.range(4, parts.length)
.mapToObj(i -> parts[i].trim())
.collect(Collectors.joining());
}
private static String parseBlockContent(String response) {
String[] parts = response.split(" ");
return parts[2];
}
// 格式化待写入的数据为两个字符一组
private static String formatDataForWrite(String data) {
return IntStream.range(0, data.length() / 2)
.mapToObj(i -> data.substring(i * 2, i * 2 + 2))
.collect(Collectors.joining(" "));
}
// 响应解析器函数式接口
@FunctionalInterface
interface ResponseParser {
String parse(String response);
}
}