From 05e5da7edd1368a2e8f216b3efc6e87eb8d375a2 Mon Sep 17 00:00:00 2001
From: ‘liusuyi’ <1951119284@qq.com>
Date: Mon, 26 Jun 2023 17:24:58 +0800
Subject: [PATCH] 解析雷达报警报文
---
src/main/java/com/ard/utils/tcp/NettyTcpClient.java | 9 +
src/main/java/com/ard/utils/tcp/index.java | 33 ++++
src/main/java/com/ard/utils/SpringTool.java | 17 ++
src/main/resources/logback-spring.xml | 26 +++
src/main/java/com/ard/utils/tcp/NettyTcpClientHandler.java | 324 ++++++++++++++++++++++++++++++++++++++++++++-
src/main/java/com/ard/alarm/tube/service/TubeAlarmService.java | 6
6 files changed, 402 insertions(+), 13 deletions(-)
diff --git a/src/main/java/com/ard/alarm/tube/service/TubeAlarmService.java b/src/main/java/com/ard/alarm/tube/service/TubeAlarmService.java
index 360f727..3106e6a 100644
--- a/src/main/java/com/ard/alarm/tube/service/TubeAlarmService.java
+++ b/src/main/java/com/ard/alarm/tube/service/TubeAlarmService.java
@@ -42,9 +42,9 @@
return;
}
nettyUdpServer.init(udpPort);
- log.info("UDP服务已经启动");
- nettyTcpClient.init("112.98.126.2",1200);
- log.info("TCP客户端已连接");
+ log.info("UDP服务已启动");
+ nettyTcpClient.init("127.0.0.1",1200);
+ log.info("TCP客户端已启动");
}
@Async("alarm")
diff --git a/src/main/java/com/ard/utils/SpringTool.java b/src/main/java/com/ard/utils/SpringTool.java
index 2aff71c..e152dde 100644
--- a/src/main/java/com/ard/utils/SpringTool.java
+++ b/src/main/java/com/ard/utils/SpringTool.java
@@ -32,4 +32,21 @@
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
+
+ /**
+ * Byte字节转Hex
+ * @param b 字节
+ * @return Hex
+ */
+ public static String byteToHex(byte b)
+ {
+ String hexString = Integer.toHexString(b & 0xFF);
+ //由于十六进制是由0~9、A~F来表示1~16,所以如果Byte转换成Hex后如果是<16,就会是一个字符(比如A=10),通常是使用两个字符来表示16进制位的,
+ //假如一个字符的话,遇到字符串11,这到底是1个字节,还是1和1两个字节,容易混淆,如果是补0,那么1和1补充后就是0101,11就表示纯粹的11
+ if (hexString.length() < 2)
+ {
+ hexString = new StringBuilder(String.valueOf(0)).append(hexString).toString();
+ }
+ return hexString.toUpperCase();
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/ard/utils/tcp/NettyTcpClient.java b/src/main/java/com/ard/utils/tcp/NettyTcpClient.java
index 2d01793..2d6c324 100644
--- a/src/main/java/com/ard/utils/tcp/NettyTcpClient.java
+++ b/src/main/java/com/ard/utils/tcp/NettyTcpClient.java
@@ -2,6 +2,7 @@
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
@@ -36,6 +37,14 @@
}
});
ChannelFuture future = bootstrap.connect(host, port).sync();
+ // 添加连接成功的监听器
+ future.addListener((ChannelFutureListener) future1 -> {
+ if (future1.isSuccess()) {
+ log.info("tcp连接成功"+host+":"+port);
+ } else {
+ log.info("tcp连接失败"+host+":"+port);
+ }
+ });
future.channel().closeFuture().sync();
} catch (Exception ex) {
diff --git a/src/main/java/com/ard/utils/tcp/NettyTcpClientHandler.java b/src/main/java/com/ard/utils/tcp/NettyTcpClientHandler.java
index 5770cdc..626aff8 100644
--- a/src/main/java/com/ard/utils/tcp/NettyTcpClientHandler.java
+++ b/src/main/java/com/ard/utils/tcp/NettyTcpClientHandler.java
@@ -1,11 +1,17 @@
package com.ard.utils.tcp;
+import com.ard.utils.SpringTool;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
+import sun.nio.cs.ext.GBK;
import javax.xml.bind.DatatypeConverter;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.*;
/**
* @Description: tcp客户端处理
@@ -14,30 +20,328 @@
* @Date: 2023年06月25日17:02
* @Version: 1.0
**/
-@Slf4j
+@Slf4j(topic = "radar")
public class NettyTcpClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
+
@Override
- protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg){
+ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
// 处理接收到的消息
byte[] byteArray = new byte[msg.readableBytes()];
msg.getBytes(msg.readerIndex(), byteArray);
- String hexString = DatatypeConverter.printHexBinary(byteArray);
-
- log.info("Received: " + hexString);
+ byte[] bytes = receiveCompletePacket(byteArray);
+ if (bytes != null) {
+ String hexString = DatatypeConverter.printHexBinary(bytes);
+ log.info(hexString);
+ processData(bytes);
+ }
}
@Override
- public void channelActive(ChannelHandlerContext ctx) throws Exception {
+ public void channelActive(ChannelHandlerContext ctx) {
// 当客户端连接成功后,发送消息给服务器
- ByteBuf message = ctx.alloc().buffer();
- message.writeBytes("Hello, Server!".getBytes());
- ctx.writeAndFlush(message);
+ TimerTask timerTask = new TimerTask() {
+ @Override
+ public void run() {
+ ByteBuf message = ctx.alloc().buffer();
+ byte[] heart = {0x01, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, (byte) 0x83, (byte) 0x88, 0x5d, 0x71, 0x01, 0x02, 0x00};
+ String hexString = DatatypeConverter.printHexBinary(heart);
+ log.info("发送心跳:" + hexString);
+ message.writeBytes(heart);
+ ctx.writeAndFlush(message);
+ }
+ };
+ Timer timer = new Timer();
+ // timer.schedule(timerTask, 0, 6000);
}
@Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+ log.error("连接异常");
// 发生异常时的处理
cause.printStackTrace();
ctx.close();
}
+
+ public static void processData(byte[] data) {
+ try {
+ data = transferData(data);//去掉包头和包尾及转义
+ String s = DatatypeConverter.printHexBinary(data);
+ log.info(s);
+ byte[] type = Arrays.copyOfRange(data, index.type[0], index.type[1]);//命令类型
+ log.info("命令类型:" + DatatypeConverter.printHexBinary(type));
+
+ byte[] cmdId = Arrays.copyOfRange(data, index.funcc[0], index.funcc[1]);//命令ID
+ log.info("命令ID:" + DatatypeConverter.printHexBinary(cmdId));
+
+ byte[] payloadSize = Arrays.copyOfRange(data, index.payloadSize[0], index.payloadSize[1]);//有效负载大小
+ // log.info("有效负载大小:" + DatatypeConverter.printHexBinary(payloadSize));
+ payloadSize = toLittleEndian(payloadSize);
+ // log.info("有效负载大小(转小端):" + DatatypeConverter.printHexBinary(payloadSize));
+ int payloadSizeToDecimal = byteArrayToDecimal(payloadSize);
+ log.info("有效负载大小(转整型):" + payloadSizeToDecimal);
+
+ if (Arrays.equals(cmdId, new byte[]{0x01})) {
+ byte[] dwTim = Arrays.copyOfRange(data, index.dwTim[0], index.dwTim[1]);
+ // log.info("周视图像的出现时间:" + DatatypeConverter.printHexBinary(dwTim));
+ dwTim = toLittleEndian(dwTim);
+ // log.info("周视图像的出现时间(转小端):" + DatatypeConverter.printHexBinary(dwTim));
+ // log.info("周视图像的出现时间(转整型):" + byteArrayToDecimal(dwTim));
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ long l = byteArrayToDecimal(dwTim);
+ String format = sdf.format(l * 1000);
+ log.info("周视图像的出现时间(转date):" + format);
+
+ byte[] wTargetNum = Arrays.copyOfRange(data, index.wTargetNum[0], index.wTargetNum[1]);
+ wTargetNum = toLittleEndian(wTargetNum);
+ // log.info("目标总点数(转小端):" + DatatypeConverter.printHexBinary(wTargetNum));
+ int targetNum = byteArrayToDecimal(wTargetNum);
+ log.info("目标总点数(转整型):" + targetNum);
+
+ //解析NET_TARGET_UNIT(64是NET_TARGET_HEAD的字节数)
+ int unitIndex = index.acRes[1];
+ log.info("有效负载无头起始位:" + unitIndex);
+
+// byte datum = data[unitIndex];
+// log.info("无头起始字节:" + SpringTool.byteToHex(datum));
+ int UNITNum = (payloadSizeToDecimal - 64) / targetNum;
+ log.info("单个Unit大小:" + UNITNum);
+ for (int i = 0; i < targetNum; i++) {
+ Integer index = unitIndex + UNITNum * i;
+ byte[] dwID = Arrays.copyOfRange(data, index, index + 4);
+ dwID = toLittleEndian(dwID);
+ // log.info("dwID:" + DatatypeConverter.printHexBinary(dwID));
+ int id = byteArrayToDecimal(dwID);
+ log.info("目标ID号:" + id);
+
+ byte[] dwGSum = Arrays.copyOfRange(data, index + 4, index + 8);
+ dwGSum = toLittleEndian(dwGSum);
+ int GSum = byteArrayToDecimal(dwGSum);
+ log.info("目标当前像素灰度和:" + GSum);
+
+ byte[] iDistance = Arrays.copyOfRange(data, index + 8, index + 12);
+ iDistance = toLittleEndian(iDistance);
+ int Distance = byteArrayToDecimal(iDistance);
+ log.info("目标当前距离(m):" + Distance);
+
+ byte[] iTw = Arrays.copyOfRange(data, index + 12, index + 16);
+ iTw = toLittleEndian(iTw);
+ int Tw = byteArrayToDecimal(iTw);
+ log.info("目标当前的像素宽度:" + Tw);
+
+ byte[] iTh = Arrays.copyOfRange(data, index + 16, index + 20);
+ iTh = toLittleEndian(iTh);
+ int Th = byteArrayToDecimal(iTh);
+ log.info("目标当前的像素高度:" + Th);
+
+ byte[] wPxlArea = Arrays.copyOfRange(data, index + 20, index + 22);
+ wPxlArea = toLittleEndian(wPxlArea);
+ int PxlArea = byteArrayToDecimal(wPxlArea);
+ log.info("目标当前像素面积:" + PxlArea);
+
+ byte[] cTrkNum = Arrays.copyOfRange(data, index + 22, index + 23);
+ cTrkNum = toLittleEndian(cTrkNum);
+ int TrkNum = byteArrayToDecimal(cTrkNum);
+ log.info("轨迹点数:" + TrkNum);
+
+ byte[] cStat = Arrays.copyOfRange(data, index + 23, index + 24);
+ cStat = toLittleEndian(cStat);
+ int Stat = byteArrayToDecimal(cStat);
+ log.info("目标当前状态:" + Stat);
+
+ byte[] sVx = Arrays.copyOfRange(data, index + 24, index + 26);
+ sVx = toLittleEndian(sVx);
+ int Vx = byteArrayToDecimal(sVx);
+ log.info("目标当前速度矢量(像素距离)X:" + Vx);
+
+ byte[] sVy = Arrays.copyOfRange(data, index + 26, index + 28);
+ sVy = toLittleEndian(sVy);
+ int Vy = byteArrayToDecimal(sVy);
+ log.info("目标当前速度矢量(像素距离)Y:" + Vy);
+
+ byte[] sAreaNo = Arrays.copyOfRange(data, index + 28, index + 30);
+ sAreaNo = toLittleEndian(sAreaNo);
+ int AreaNo = byteArrayToDecimal(sAreaNo);
+ log.info("目标归属的告警区域号:" + AreaNo);
+
+ byte[] cGrp = Arrays.copyOfRange(data, index + 30, index + 31);
+ cGrp = toLittleEndian(cGrp);
+ int Grp = byteArrayToDecimal(cGrp);
+ log.info("所属组:" + Grp);
+
+ byte[] szName = Arrays.copyOfRange(data, index + 64, index + 96);
+ String str = new String(szName, "GBK");
+ log.info("所属告警区域名称:" + str);
+ }
+ }
+ } catch (Exception ex) {
+ log.error(ex.getMessage());
+ }
+ }
+
+ public static int byteArrayToDecimal(byte[] byteArray) {
+ int decimalValue = 0;
+
+ for (int i = 0; i < byteArray.length; i++) {
+ decimalValue = (decimalValue << 8) | (byteArray[i] & 0xFF);
+ }
+
+ return decimalValue;
+ }
+
+ public static byte[] toLittleEndian(byte[] bigEndianBytes) {
+ byte[] littleEndianBytes = new byte[bigEndianBytes.length];
+
+ for (int i = 0; i < bigEndianBytes.length; i++) {
+ int j = bigEndianBytes.length - i - 1;
+ littleEndianBytes[i] = bigEndianBytes[j];
+ }
+
+ return littleEndianBytes;
+ }
+
+
+ public static Integer processPayloadSize(String payloadSize) {//解析有效负载大小
+ Integer payloadSizeInfo = 0;
+ String[] payloadSizeArr = payloadSize.split("");
+ for (int i = 0; i <= payloadSizeArr.length - 1; i++) {
+ Integer num = null;
+ switch (payloadSizeArr[i]) {
+ case "0":
+ num = 0;
+ break;
+ case "1":
+ num = 1;
+ break;
+ case "2":
+ num = 2;
+ break;
+ case "3":
+ num = 3;
+ break;
+ case "4":
+ num = 4;
+ break;
+ case "5":
+ num = 5;
+ break;
+ case "6":
+ num = 6;
+ break;
+ case "7":
+ num = 7;
+ break;
+ case "8":
+ num = 8;
+ break;
+ case "9":
+ num = 9;
+ break;
+ case "a":
+ num = 10;
+ break;
+ case "b":
+ num = 11;
+ break;
+ case "c":
+ num = 12;
+ break;
+ case "d":
+ num = 13;
+ break;
+ case "e":
+ num = 14;
+ break;
+ case "f":
+ num = 15;
+ break;
+ default:
+ break;
+ }
+ if (i % 2 == 0) {
+ payloadSizeInfo = payloadSizeInfo + num * 16;
+ } else {
+ payloadSizeInfo = payloadSizeInfo + num * 256;
+ }
+ }
+ return payloadSizeInfo;
+ }
+
+ public static Boolean checkPayloadSize(Integer payloadSizeInfo, String data) {//校验有效负载大小
+ Integer payloadSize = (data.length() - 8 - 8) / 2;//去除包头8位和校验8位
+ if (payloadSize.equals(payloadSizeInfo)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // 创建缓冲区列表
+ List<Byte> buffer = new ArrayList<>();
+
+ public byte[] receiveCompletePacket(byte[] receivedData) {
+ // 定义包尾字节序列
+ byte[] packetEnd = {0x01, 0x02, 0x00};
+ // 添加已接收的数据到缓冲区
+ for (byte data : receivedData) {
+ buffer.add(data);
+ }
+ // 检查缓冲区中的数据是否包含完整的包
+ while (buffer.size() >= packetEnd.length) {
+ int endIndex = findPacketEndIndex(buffer, packetEnd);
+ if (endIndex != -1) {
+ // 找到完整的包
+ byte[] packet = extractPacketFromBuffer(buffer, endIndex + packetEnd.length);
+ return packet;
+ } else {
+ // 未找到包尾,继续接收数据
+ break;
+ }
+ }
+
+ // 未找到完整的包
+ return null;
+ }
+
+ public static int findPacketEndIndex(List<Byte> buffer, byte[] packetEnd) {
+ for (int i = 0; i <= buffer.size() - packetEnd.length; i++) {
+ boolean isMatch = true;
+ for (int j = 0; j < packetEnd.length; j++) {
+ if (buffer.get(i + j) != packetEnd[j]) {
+ isMatch = false;
+ break;
+ }
+ }
+ if (isMatch) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static byte[] extractPacketFromBuffer(List<Byte> buffer, int endIndex) {
+ byte[] packet = new byte[endIndex];
+ for (int i = 0; i < endIndex; i++) {
+ packet[i] = buffer.get(i);
+ }
+ buffer.subList(0, endIndex).clear();
+ return packet;
+ }
+
+ //去掉包头和包尾校验及转义
+ public static byte[] transferData(byte[] data) {
+ data = Arrays.copyOfRange(data, 3, data.length);
+ data = Arrays.copyOfRange(data, 0, data.length - 7);
+ String dataStr = DatatypeConverter.printHexBinary(data);
+ if (dataStr.contains("01020201")) {//转义01020201
+ dataStr = dataStr.replaceAll("01020201", "010201");
+ }
+ if (dataStr.contains("01020200")) {//转义01020200
+ dataStr = dataStr.replaceAll("01020200", "010200");
+ }
+ if (dataStr.contains("01020202")) {//转义01020202
+ dataStr = dataStr.replaceAll("01020202", "010202");
+ }
+ data = DatatypeConverter.parseHexBinary(dataStr);
+ return data;
+ }
}
diff --git a/src/main/java/com/ard/utils/tcp/index.java b/src/main/java/com/ard/utils/tcp/index.java
new file mode 100644
index 0000000..c4ae70c
--- /dev/null
+++ b/src/main/java/com/ard/utils/tcp/index.java
@@ -0,0 +1,33 @@
+package com.ard.utils.tcp;
+
+/**
+ * @Description:
+ * @ClassName: domain
+ * @Author: 刘苏义
+ * @Date: 2023年06月26日15:21
+ * @Version: 1.0
+ **/
+public class index {
+ //header
+ public static Integer[] type = {0,1};
+ public static Integer[] funcc = {1,2};
+ public static Integer[] payloadSize = {2,4};
+ //NET_TARGET_HEAD
+ public static Integer[] dwTim = {4,8};
+ public static Integer[] wTargetNum = {8,10};
+ public static Integer[] acRes = {10,68};
+ //NET_TARGET_UNIT
+// public static Integer[] dwID = {68,72};
+// public static Integer[] dwGSum = {};
+// public static Integer[] iDistance = 2;
+// public static Integer[] cTrkNum = 1;
+// public static Integer[] cStat = 1;
+// public static Integer[] sVx = 2;
+// public static Integer[] sVy = 2;
+// public static Integer[] sAreaNo = 2;
+// public static Integer[] cGrp = 1;
+// public static Integer[] acRes1 = 33;
+// public static Integer[] szName = 32;
+// public static Integer[] afTx = 16;
+// public static Integer[] afTy = 16;
+}
diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml
index 7f2dac9..390b765 100644
--- a/src/main/resources/logback-spring.xml
+++ b/src/main/resources/logback-spring.xml
@@ -76,6 +76,28 @@
<onMismatch>DENY</onMismatch>
</filter>
</appender>
+ <!--雷达报警日志输出-->
+ <appender name="radar" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>${log.path}/radar.log</file>
+ <!--循环政策:基于时间创建日志文件-->
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <!--日志文件名格式-->
+ <fileNamePattern>${log.path}/radar.%d{yyyy-MM-dd}.log</fileNamePattern>
+ <!--日志最大的历史60天-->
+ <maxHistory>60</maxHistory>
+ </rollingPolicy>
+ <encoder>
+ <pattern>${log.pattern}</pattern>
+ </encoder>
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <!--过滤的级别-->
+ <level>INFO</level>
+ <!--匹配时的操作:接收(记录)-->
+ <onMatch>ACCEPT</onMatch>
+ <!--不匹配时的操作:拒绝(不记录)-->
+ <onMismatch>DENY</onMismatch>
+ </filter>
+ </appender>
<root level="DEBUG">
<appender-ref ref="console"/>
</root>
@@ -91,5 +113,9 @@
<root level="INFO">
<appender-ref ref="camera"/>
</root>
+ <!--雷达报警日志-->
+ <root level="INFO">
+ <appender-ref ref="radar"/>
+ </root>
</configuration>
--
Gitblit v1.9.3