package com.ard.utils.tcp;
|
|
import com.alibaba.fastjson2.JSON;
|
import com.ard.alarm.radar.domain.ArdEquipRadar;
|
import com.ard.alarm.radar.domain.RadarAlarmInfo;
|
import com.ard.hiksdk.domain.alarmEventInfo;
|
import com.ard.utils.LonlatConver;
|
import com.ard.utils.SpringTool;
|
import com.ard.utils.mqtt.MqttConsumer;
|
import io.netty.buffer.ByteBuf;
|
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.SimpleChannelInboundHandler;
|
import lombok.extern.slf4j.Slf4j;
|
import org.gavaghan.geodesy.Ellipsoid;
|
import org.gavaghan.geodesy.GeodeticCalculator;
|
import org.gavaghan.geodesy.GlobalCoordinates;
|
import sun.nio.cs.ext.GBK;
|
|
import javax.xml.bind.DatatypeConverter;
|
import java.io.UnsupportedEncodingException;
|
import java.nio.ByteBuffer;
|
import java.nio.charset.Charset;
|
import java.nio.charset.StandardCharsets;
|
import java.text.SimpleDateFormat;
|
import java.util.*;
|
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.TimeUnit;
|
|
/**
|
* @Description: tcp客户端处理
|
* @ClassName: NettyTcpClientHandler
|
* @Author: 刘苏义
|
* @Date: 2023年06月25日17:02
|
* @Version: 1.0
|
**/
|
@Slf4j(topic = "radar")
|
public class NettyTcpClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
|
private String host;
|
private Integer port;
|
private Double longitude;
|
private Double lagitude;
|
private String name;
|
|
public NettyTcpClientHandler(ArdEquipRadar ardEquipRadar) {
|
this.host = ardEquipRadar.getIp();
|
this.port = ardEquipRadar.getPort();
|
this.longitude = ardEquipRadar.getLongitude();
|
this.lagitude = ardEquipRadar.getLatitude();
|
this.name=ardEquipRadar.getName();
|
}
|
|
private ChannelHandlerContext context;
|
private ScheduledFuture<?> heartbeatTask;
|
|
@Override
|
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
|
// 处理接收到的消息
|
byte[] byteArray = new byte[msg.readableBytes()];
|
msg.getBytes(msg.readerIndex(), byteArray);
|
byte[] bytes = receiveCompletePacket(byteArray);
|
if (bytes != null) {
|
// String hexString = DatatypeConverter.printHexBinary(bytes);
|
// log.info(hexString);
|
processData(bytes);
|
}
|
}
|
|
@Override
|
public void channelActive(ChannelHandlerContext ctx) {
|
context = ctx;
|
startHeartbeatTask();//开始发送心跳
|
}
|
|
/**
|
* 开始心跳任务
|
*/
|
private void startHeartbeatTask() {
|
heartbeatTask = context.executor().scheduleAtFixedRate(() -> {
|
// 发送心跳消息
|
ByteBuf message = context.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);
|
context.writeAndFlush(message);
|
|
}, 0, 5, TimeUnit.SECONDS);
|
}
|
|
/**
|
* 停止心跳任务
|
*/
|
private void stopHeartbeatTask() {
|
if (heartbeatTask != null) {
|
heartbeatTask.cancel(false);
|
heartbeatTask = null;
|
}
|
}
|
|
@Override
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
log.info("tcp客户端连接异常");
|
// 发生异常时的处理
|
cause.printStackTrace();
|
ctx.close();
|
stopHeartbeatTask();//停止心跳发送
|
}
|
|
/**
|
* 解析报警数据
|
*/
|
public void processData(byte[] data) {
|
try {
|
data = transferData(data);//去掉包头和包尾、校验及转义
|
|
byte[] type = Arrays.copyOfRange(data, 0, 1);//命令类型
|
log.info("命令类型:" + DatatypeConverter.printHexBinary(type));
|
|
byte[] cmdId = Arrays.copyOfRange(data, 1, 2);//命令ID
|
log.info("命令ID:" + DatatypeConverter.printHexBinary(cmdId));
|
|
byte[] payloadSize = Arrays.copyOfRange(data, 2, 4);//有效负载大小
|
payloadSize = toLittleEndian(payloadSize);
|
int payloadSizeToDecimal = byteArrayToDecimal(payloadSize);
|
log.info("有效负载大小(转整型):" + payloadSizeToDecimal);
|
|
if (Arrays.equals(cmdId, new byte[]{0x01})) {
|
byte[] dwTim = Arrays.copyOfRange(data, 4, 8);
|
dwTim = toLittleEndian(dwTim);
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
long l = byteArrayToDecimal(dwTim);
|
String alarmTime = sdf.format(l * 1000);
|
log.info("周视图像的出现时间(转date):" + alarmTime);
|
|
byte[] wTargetNum = Arrays.copyOfRange(data, 8, 10);
|
wTargetNum = toLittleEndian(wTargetNum);
|
int targetNum = byteArrayToDecimal(wTargetNum);
|
log.info("目标总点数(转整型):" + targetNum);
|
|
//解析NET_TARGET_UNIT(64是NET_TARGET_HEAD的字节数)
|
int uintSize = (payloadSizeToDecimal - 64) / targetNum;
|
log.info("单条报警大小:" + uintSize);
|
for (int i = 0; i < targetNum; i++) {
|
|
Integer index = 68 + uintSize * i;
|
byte[] dwID = Arrays.copyOfRange(data, index, index + 4);
|
dwID = toLittleEndian(dwID);
|
int id = byteArrayToDecimal(dwID);
|
// log.info("目标ID号:" + id);
|
|
byte[] iDistance = Arrays.copyOfRange(data, index + 8, index + 12);
|
iDistance = toLittleEndian(iDistance);
|
int Distance = byteArrayToDecimal(iDistance);
|
// log.info("目标当前距离(m):" + Distance);
|
//region 不需要的字段
|
// byte[] dwGSum = Arrays.copyOfRange(data, index + 4, index + 8);
|
// dwGSum = toLittleEndian(dwGSum);
|
// int GSum = byteArrayToDecimal(dwGSum);
|
// log.info("目标当前像素灰度和:" + GSum);
|
// 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);
|
//endregion
|
byte[] szName = Arrays.copyOfRange(data, index + 64, index + 96);
|
int position = findIndexOfDoubleZero(szName);
|
String alarmPointName = "";
|
if (position != -1) {
|
byte[] result = new byte[position];
|
System.arraycopy(szName, 0, result, 0, position);
|
alarmPointName = new String(result, "GBK");
|
} else {
|
alarmPointName = new String(szName, "GBK");
|
}
|
// log.info("所属告警区域名称:" + alarmPointName);
|
byte[] afTx = Arrays.copyOfRange(data, index + 96, index + 100);
|
afTx = toLittleEndian(afTx);
|
float fTx = bytesToFloat(afTx);
|
// log.info("水平角度:" + fTx);
|
byte[] afTy = Arrays.copyOfRange(data, index + 112, index + 116);
|
afTy = toLittleEndian(afTy);
|
float fTy = bytesToFloat(afTy);
|
// log.info("垂直角度:" + fTy);
|
log.info("雷达信息:" + host + "【port】" + port + "【X】" + longitude + "【Y】" + lagitude);
|
Double[] radarXY = {longitude, lagitude};
|
Double[] alarmXY = CalculateCoordinates(radarXY, Distance, (double) fTx);
|
log.info("报警信息:" + "【id】" + id + "【name】" + alarmPointName + "【alarmTime】" + alarmTime + "【distance】" + Distance + "【P】" + fTx + "【T】" + fTy + "【X】" + alarmXY[0] + "【Y】" + alarmXY[1]);
|
RadarAlarmInfo alarmInfo=new RadarAlarmInfo();
|
alarmInfo.setId(id);
|
alarmInfo.setAlarmTime(alarmTime);
|
alarmInfo.setName(alarmPointName+"("+name+")");
|
alarmInfo.setLongitude(alarmXY[0]);
|
alarmInfo.setLagitude(alarmXY[1]);
|
MqttConsumer.publish(2, false, "radar", JSON.toJSONString(alarmInfo));
|
}
|
}
|
} catch (Exception ex) {
|
log.error(ex.getMessage());
|
}
|
}
|
/**
|
* byte数组转float
|
*/
|
private float bytesToFloat(byte[] bytes) {
|
ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
return buffer.getFloat();
|
}
|
|
|
/**
|
* byte数组转整型
|
*/
|
public int byteArrayToDecimal(byte[] byteArray) {
|
int decimalValue = 0;
|
|
for (int i = 0; i < byteArray.length; i++) {
|
decimalValue = (decimalValue << 8) | (byteArray[i] & 0xFF);
|
}
|
|
return decimalValue;
|
}
|
|
/**
|
* 大端转小端
|
*/
|
public 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;
|
}
|
|
|
// 创建缓冲区列表
|
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 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 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 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;
|
}
|
|
/**
|
* 找到00的索引位置
|
*/
|
private int findIndexOfDoubleZero(byte[] bytes) {
|
for (int i = 0; i < bytes.length - 1; i++) {
|
if (bytes[i] == 0x00) {
|
return i;
|
}
|
}
|
return -1;
|
}
|
|
/**
|
* 通过A点坐标,长度和Y轴角度计算B点坐标
|
*/
|
public Double[] CalculateCoordinates(Double[] radarCoordinates, Integer distance, Double angle) {
|
double[] to_wgs84 = LonlatConver.gcj02_To_Wgs84(radarCoordinates[0], radarCoordinates[1]);
|
double Ax = to_wgs84[0]; // A 点的 X 坐标
|
double Ay = to_wgs84[1]; // A 点的 Y 坐标
|
double AB = distance; // AB 的长度
|
double angleWithYAxisDegrees = angle; // AB 与 Y 轴的角度(以度数表示)
|
GeodeticCalculator calculator = new GeodeticCalculator();
|
GlobalCoordinates globalCoordinates = new GlobalCoordinates(Ay, Ax);
|
GlobalCoordinates globalCoordinates1 = calculator.calculateEndingGlobalCoordinates(Ellipsoid.WGS84, globalCoordinates, angleWithYAxisDegrees, AB);
|
return new Double[]{globalCoordinates1.getLongitude(), globalCoordinates1.getLatitude()};
|
}
|
|
}
|