塔吊TCP
This commit is contained in:
parent
2d3c64dcdb
commit
4ca14f64f7
@ -34,6 +34,14 @@ public class TcpNettyHandler extends SimpleChannelInboundHandler<Object> {
|
||||
|
||||
public static final String PREFIX = "##";
|
||||
public static final String PREFIX1 = "7E";
|
||||
|
||||
public static final String TOWER_PREFIX = "A55A";
|
||||
|
||||
public static final String TOWER_END = "CC33C33C";
|
||||
|
||||
@Autowired
|
||||
private TowerCraneHandler towerCraneHandler;
|
||||
|
||||
@Autowired
|
||||
private HighFormworkSupportService highFormworkSupportService;
|
||||
|
||||
@ -50,7 +58,6 @@ public class TcpNettyHandler extends SimpleChannelInboundHandler<Object> {
|
||||
String str = getString(bytes);
|
||||
str = str.trim();
|
||||
log.info("tcp接收数据 >>> {} ", str);
|
||||
// ctx.writeAndFlush(Unpooled.copiedBuffer("response message", CharsetUtil.UTF_8));
|
||||
if (StringUtils.startsWith(str, HighFormworkSupport.TCP_DATA_PREFIX) && StringUtils.endsWith(str, HighFormworkSupport.TCP_DATA_END)) {
|
||||
log.info("高支模接收数据 >>> {} ", str);
|
||||
//接收高支模数据保存到mysql中
|
||||
@ -113,7 +120,9 @@ public class TcpNettyHandler extends SimpleChannelInboundHandler<Object> {
|
||||
//16进制判断
|
||||
String hexString = readToHexString(bytes);
|
||||
log.info("tcp接收数据(16进制转字符串) >>> {} ", hexString);
|
||||
if (StringUtils.startsWith(hexString, PREFIX1)) {
|
||||
if (StringUtils.startsWith(hexString, TOWER_PREFIX) && StringUtils.endsWith(hexString, TOWER_END)) {
|
||||
towerCraneHandler.parseData(hexString, ctx);
|
||||
} else if (StringUtils.startsWith(hexString, PREFIX1)) {
|
||||
//有害气体
|
||||
poisonousGasDevCurrentDataService.addDataFromTcp(hexString.trim());
|
||||
}
|
||||
|
||||
927
src/main/java/com/zhgd/netty/tcp/handler/TowerCraneHandler.java
Normal file
927
src/main/java/com/zhgd/netty/tcp/handler/TowerCraneHandler.java
Normal file
@ -0,0 +1,927 @@
|
||||
package com.zhgd.netty.tcp.handler;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.zhgd.hj212.hbt212.core.T212Mapper;
|
||||
import com.zhgd.hj212.hbt212.model.CpData;
|
||||
import com.zhgd.hj212.hbt212.model.Data;
|
||||
import com.zhgd.hj212.hbt212.model.Pollution;
|
||||
import com.zhgd.netty.tcp.constant.HighFormworkSupport;
|
||||
import com.zhgd.netty.tcp.service.HighFormworkSupportService;
|
||||
import com.zhgd.xmgl.modules.bigdevice.entity.Tower;
|
||||
import com.zhgd.xmgl.modules.bigdevice.entity.TowerAlarm;
|
||||
import com.zhgd.xmgl.modules.bigdevice.entity.TowerCurrentData;
|
||||
import com.zhgd.xmgl.modules.bigdevice.entity.TowerWorkCycle;
|
||||
import com.zhgd.xmgl.modules.bigdevice.service.ITowerAlarmService;
|
||||
import com.zhgd.xmgl.modules.bigdevice.service.ITowerCurrentDataService;
|
||||
import com.zhgd.xmgl.modules.bigdevice.service.ITowerService;
|
||||
import com.zhgd.xmgl.modules.bigdevice.service.ITowerWorkCycleService;
|
||||
import com.zhgd.xmgl.modules.bigdevice.service.impl.TowerServiceImpl;
|
||||
import com.zhgd.xmgl.modules.environment.entity.DustNoiseData;
|
||||
import com.zhgd.xmgl.modules.environment.service.IDustNoiseDataService;
|
||||
import com.zhgd.xmgl.modules.poisonous.service.IPoisonousGasDevCurrentDataService;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 塔机TCP上传数据解析工具
|
||||
* 遵循规则:大端序、物理量系数换算、时间年份+2000
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class TowerCraneHandler {
|
||||
|
||||
@Autowired
|
||||
private ITowerService towerService;
|
||||
|
||||
@Autowired
|
||||
private ITowerCurrentDataService towerCurrentDataService;
|
||||
|
||||
@Autowired
|
||||
private ITowerAlarmService towerAlarmService;
|
||||
|
||||
@Autowired
|
||||
private ITowerWorkCycleService towerWorkCycleService;
|
||||
|
||||
// 各物理量换算系数
|
||||
private static final double WEIGHT_COEFFICIENT = 0.001; // 吊重:1单位=0.001吨
|
||||
private static final double MOMENT_COEFFICIENT = 0.01; // 力矩:1单位=0.01T.M
|
||||
private static final double HEIGHT_AMPLITUDE_COEFFICIENT = 0.1; // 高度/幅度:1单位=0.1米
|
||||
private static final double ANGLE_COEFFICIENT = 0.1; // 角度:1单位=0.1度
|
||||
|
||||
// 基础换算系数
|
||||
private static final double HEIGHT_AMPLITUDE_WIND_COEFF = 0.1; // 高度/幅度/风速:0.1
|
||||
private static final double WEIGHT_COEFF = 0.001; // 起重量:0.001吨
|
||||
private static final double ROTATE_COEFF = 0.1; // 回转:0.1度
|
||||
private static final int ANGLE_OFFSET = 1500; // 倾角偏移量
|
||||
private static final double ANGLE_COEFF = 100.0; // 倾角系数
|
||||
|
||||
// 固定帧头、帧尾
|
||||
private static final byte[] FRAME_HEAD = {(byte) 0xA5, (byte) 0x5A};
|
||||
|
||||
private static final byte[] FRAME_TYPE = hexStringToByteArray("61");
|
||||
private static final byte[] FRAME_TAIL = {(byte) 0xCC, (byte) 0x33, (byte) 0xC3, (byte) 0x3C};
|
||||
|
||||
/**
|
||||
* 解析塔机十六进制数据字符串
|
||||
* @param hexStr
|
||||
* @return 解析后的字段键值对
|
||||
*/
|
||||
public void parseData(String hexStr, ChannelHandlerContext ctx) {
|
||||
// 1. 十六进制字符串转字节数组
|
||||
byte[] data = hexStringToByteArray(hexStr);
|
||||
// 2. 初始化ByteBuffer,设置大端序(网络字节序)
|
||||
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
// -------------------------- 固定字段解析 --------------------------
|
||||
// 起始符(2字节)
|
||||
byte[] startFlag = new byte[2];
|
||||
buffer.get(startFlag);
|
||||
System.out.println(bytesToHex(startFlag));
|
||||
|
||||
// 命令字(1字节)
|
||||
byte cmd = buffer.get();
|
||||
String format = String.format("%02X", cmd);
|
||||
|
||||
// 塔机编号(1字节)
|
||||
byte towerId = buffer.get();
|
||||
|
||||
// 设备编号(3字节:特殊处理,手动拼接大端序)
|
||||
byte[] deviceIdBytes = new byte[3];
|
||||
buffer.get(deviceIdBytes);
|
||||
int deviceId = ((deviceIdBytes[0] & 0xFF) << 16)
|
||||
| ((deviceIdBytes[1] & 0xFF) << 8)
|
||||
| (deviceIdBytes[2] & 0xFF);
|
||||
Tower tower = towerService.getOne(Wrappers.<Tower>lambdaQuery().eq(Tower::getDevSn, deviceId));
|
||||
if (tower == null) {
|
||||
if (format.equals("60")) {
|
||||
register(hexStr, ctx, 0);
|
||||
}
|
||||
log.info("TCP塔吊不存在:" + deviceId);
|
||||
return;
|
||||
}
|
||||
System.out.println("收到命令" + format);
|
||||
// 循环数据
|
||||
if (format.equals("3D")) {
|
||||
parseWorkCycleData(hexStr, tower);
|
||||
} else if (format.equals("0C")) {
|
||||
parseRealTimeData(hexStr, tower);
|
||||
} else if (format.equals("60")) {
|
||||
register(hexStr, ctx, 1);
|
||||
} else if (format.equals("20")) {
|
||||
parseAlarmData(hexStr, tower);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析命令字20的塔机报警上传数据
|
||||
* @param hexStr 无空格的十六进制字符串(如:A55A200290F72F01140A0F100232110000004100620801000D000005E400000000000000009621CC33C33C)
|
||||
* @return 解析后的字段键值对
|
||||
*/
|
||||
public Map<String, Object> parseAlarmData(String hexStr, Tower tower) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
// 1. 十六进制字符串转字节数组(去除空格)
|
||||
byte[] data = hexStringToByteArray(hexStr.replaceAll("\\s+", ""));
|
||||
// 2. 初始化ByteBuffer,强制大端序(网络字节序)
|
||||
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
// -------------------------- 基础固定字段 --------------------------
|
||||
// 起始符(2字节)
|
||||
byte[] startFlag = new byte[2];
|
||||
buffer.get(startFlag);
|
||||
result.put("起始符", bytesToHex(startFlag));
|
||||
|
||||
// 命令字(1字节)
|
||||
byte cmd = buffer.get();
|
||||
result.put("命令字", String.format("%02X", cmd));
|
||||
if (!"20".equals(result.get("命令字"))) {
|
||||
result.put("解析提示", "非命令字20的报警数据帧");
|
||||
}
|
||||
|
||||
// 塔机编号(1字节,转无符号十进制)
|
||||
byte towerId = buffer.get();
|
||||
result.put("塔机编号", (int) towerId & 0xFF);
|
||||
|
||||
// 设备编号(3字节,手动拼接大端序)
|
||||
byte[] deviceIdBytes = new byte[3];
|
||||
buffer.get(deviceIdBytes);
|
||||
int deviceId = ((deviceIdBytes[0] & 0xFF) << 16)
|
||||
| ((deviceIdBytes[1] & 0xFF) << 8)
|
||||
| (deviceIdBytes[2] & 0xFF);
|
||||
result.put("设备编号", deviceId);
|
||||
|
||||
// 版本号(1字节)
|
||||
byte version = buffer.get();
|
||||
result.put("版本号", (int) version & 0xFF);
|
||||
|
||||
// -------------------------- 时间字段 --------------------------
|
||||
// 时间(6字节:年、月、日、时、分、秒)
|
||||
byte[] timeBytes = new byte[6];
|
||||
buffer.get(timeBytes);
|
||||
String alarmTime = parseTime(timeBytes);
|
||||
result.put("报警数据生成时间", alarmTime);
|
||||
|
||||
// -------------------------- 基础属性字段 --------------------------
|
||||
// 厂家及设备类型(1字节)
|
||||
byte factoryDeviceType = buffer.get();
|
||||
result.put("厂家及设备类型编码", (int) factoryDeviceType & 0xFF);
|
||||
|
||||
// 警报原因(1字节,解析描述)
|
||||
byte alarmReason = buffer.get();
|
||||
int alarmReasonCode = (int) alarmReason & 0xFF;
|
||||
result.put("警报原因编码", alarmReasonCode);
|
||||
// result.put("警报原因描述", parseAlarmReason(alarmReasonCode));
|
||||
|
||||
// -------------------------- 报警时刻的核心数据 --------------------------
|
||||
// 报警时刻高度数据(2字节)
|
||||
short alarmHeightRaw = buffer.getShort();
|
||||
double alarmHeight = alarmHeightRaw * HEIGHT_AMPLITUDE_WIND_COEFF;
|
||||
result.put("报警时刻高度(米)", alarmHeight);
|
||||
|
||||
// 报警时刻幅度数据(2字节)
|
||||
short alarmAmplitudeRaw = buffer.getShort();
|
||||
double alarmAmplitude = alarmAmplitudeRaw * HEIGHT_AMPLITUDE_WIND_COEFF;
|
||||
result.put("报警时刻幅度(米)", alarmAmplitude);
|
||||
|
||||
// 报警时刻回转角度(2字节)
|
||||
short alarmRotateRaw = buffer.getShort();
|
||||
double alarmRotate = alarmRotateRaw * ROTATE_COEFF;
|
||||
result.put("报警时刻回转角度(度)", alarmRotate);
|
||||
|
||||
// 报警时刻起重量数据(2字节)
|
||||
short alarmWeightRaw = buffer.getShort();
|
||||
double alarmWeight = alarmWeightRaw * WEIGHT_COEFF;
|
||||
result.put("报警时刻起重量(吨)", alarmWeight);
|
||||
|
||||
// 报警时刻风速数据(2字节)
|
||||
short alarmWindRaw = buffer.getShort();
|
||||
double alarmWind = alarmWindRaw * HEIGHT_AMPLITUDE_WIND_COEFF;
|
||||
result.put("报警时刻风速(米/秒)", alarmWind);
|
||||
|
||||
// 报警时刻倾角数据(2字节,特殊换算)
|
||||
short alarmAngleRaw = buffer.getShort();
|
||||
double alarmAngle = (alarmAngleRaw - ANGLE_OFFSET) / ANGLE_COEFF;
|
||||
result.put("报警时刻倾角(度)", alarmAngle);
|
||||
|
||||
// -------------------------- 报警时刻百分比字段 --------------------------
|
||||
// 报警时刻重量百分比(1字节)
|
||||
byte alarmWeightPercent = buffer.get();
|
||||
result.put("报警时刻重量百分比(%)", (int) alarmWeightPercent & 0xFF);
|
||||
|
||||
// 报警时刻力矩百分比(1字节)
|
||||
byte alarmMomentPercent = buffer.get();
|
||||
result.put("报警时刻力矩百分比(%)", (int) alarmMomentPercent & 0xFF);
|
||||
|
||||
// 报警时刻风速百分比(1字节)
|
||||
byte alarmWindPercent = buffer.get();
|
||||
result.put("报警时刻风速百分比(%)", (int) alarmWindPercent & 0xFF);
|
||||
|
||||
// 报警时刻倾斜百分比(1字节)
|
||||
byte alarmTiltPercent = buffer.get();
|
||||
result.put("报警时刻倾斜百分比(%)", (int) alarmTiltPercent & 0xFF);
|
||||
|
||||
// -------------------------- 报警时刻制动状态 --------------------------
|
||||
// 报警时刻制动状态(1字节,解析bit位)
|
||||
byte alarmBrakeStatus = buffer.get();
|
||||
int brakeCode = (int) alarmBrakeStatus & 0xFF;
|
||||
result.put("报警时刻制动状态编码", brakeCode);
|
||||
result.put("报警时刻制动状态描述", parseBrakeStatus(brakeCode));
|
||||
|
||||
// -------------------------- 结束符 --------------------------
|
||||
// 结束符(6字节:前2和校验 + 后4固定CC33C33C)
|
||||
byte[] endFlag = new byte[6];
|
||||
buffer.get(endFlag);
|
||||
result.put("结束符", bytesToHex(endFlag));
|
||||
// 提取结束符中的固定帧尾(后4字节)
|
||||
byte[] fixedTail = new byte[4];
|
||||
System.arraycopy(endFlag, 2, fixedTail, 0, 4);
|
||||
result.put("结束符-固定帧尾", bytesToHex(fixedTail));
|
||||
|
||||
TowerAlarm towerAlarm = new TowerAlarm();
|
||||
towerAlarm.setDevSn(tower.getDevSn());
|
||||
towerAlarm.setDevName(tower.getDevName());
|
||||
towerAlarm.setProjectSn(tower.getProjectSn());
|
||||
towerAlarm.setAddTime(DateUtil.parseDateTime(alarmTime));
|
||||
if (alarmReasonCode == 111 || alarmReasonCode == 112 || alarmReasonCode == 113 || alarmReasonCode == 114) {
|
||||
towerAlarm.setMultiAlarm(1);
|
||||
towerAlarm.setAlarmType(1);
|
||||
}
|
||||
if (alarmReasonCode == 211 || alarmReasonCode == 212 || alarmReasonCode == 213 || alarmReasonCode == 214) {
|
||||
towerAlarm.setMultiAlarm(2);
|
||||
towerAlarm.setAlarmType(2);
|
||||
}
|
||||
if (alarmReasonCode == 13) {
|
||||
towerAlarm.setMomentAlarm(1);
|
||||
towerAlarm.setAlarmType(1);
|
||||
}
|
||||
if (alarmReasonCode == 23) {
|
||||
towerAlarm.setMomentAlarm(2);
|
||||
towerAlarm.setAlarmType(2);
|
||||
}
|
||||
if (alarmReasonCode == 14) {
|
||||
towerAlarm.setObliguityAlarm(1);
|
||||
towerAlarm.setAlarmType(1);
|
||||
}
|
||||
if (alarmReasonCode == 24) {
|
||||
towerAlarm.setObliguityAlarm(2);
|
||||
towerAlarm.setAlarmType(2);
|
||||
}
|
||||
if (alarmReasonCode == 15) {
|
||||
towerAlarm.setWindSpeedAlarm(1);
|
||||
towerAlarm.setAlarmType(1);
|
||||
}
|
||||
if (alarmReasonCode == 25) {
|
||||
towerAlarm.setWindSpeedAlarm(2);
|
||||
towerAlarm.setAlarmType(2);
|
||||
}
|
||||
if (alarmReasonCode == 101) {
|
||||
towerAlarm.setMinRangeAlarm(1);
|
||||
towerAlarm.setMaxRangeAlarm(1);
|
||||
towerAlarm.setAlarmType(1);
|
||||
}
|
||||
if (alarmReasonCode == 201) {
|
||||
towerAlarm.setMinRangeAlarm(2);
|
||||
towerAlarm.setMaxRangeAlarm(2);
|
||||
towerAlarm.setAlarmType(2);
|
||||
}
|
||||
if (alarmReasonCode == 102) {
|
||||
towerAlarm.setHeightAlarm(1);
|
||||
towerAlarm.setHeightLowerAlarm(1);
|
||||
towerAlarm.setAlarmType(1);
|
||||
}
|
||||
if (alarmReasonCode == 202) {
|
||||
towerAlarm.setHeightAlarm(2);
|
||||
towerAlarm.setHeightLowerAlarm(2);
|
||||
towerAlarm.setAlarmType(2);
|
||||
}
|
||||
if (alarmReasonCode == 103) {
|
||||
towerAlarm.setPosAngleAlarm(1);
|
||||
towerAlarm.setNegAngleAlarm(1);
|
||||
towerAlarm.setAlarmType(1);
|
||||
}
|
||||
if (alarmReasonCode == 203) {
|
||||
towerAlarm.setPosAngleAlarm(2);
|
||||
towerAlarm.setNegAngleAlarm(2);
|
||||
towerAlarm.setAlarmType(2);
|
||||
}
|
||||
towerAlarmService.saveTowerAlarm(towerAlarm);
|
||||
} catch (Exception e) {
|
||||
log.info("解析异常", e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void register(String hexStr, ChannelHandlerContext ctx, int registerResult) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
byte[] data = hexStringToByteArray(hexStr);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||
buffer.order(ByteOrder.BIG_ENDIAN); // 大端序
|
||||
|
||||
// 1. 解析通用固定字段
|
||||
// 帧头
|
||||
byte[] startFlag = new byte[2];
|
||||
buffer.get(startFlag);
|
||||
result.put("帧头", bytesToHex(startFlag));
|
||||
|
||||
// 帧类型
|
||||
byte frameType = buffer.get();
|
||||
result.put("帧类型", String.format("%02X", frameType));
|
||||
|
||||
// 塔吊编号
|
||||
byte towerId = buffer.get();
|
||||
result.put("塔吊编号", (int) towerId & 0xFF);
|
||||
int towerCode = (int) towerId & 0xFF;
|
||||
|
||||
// 设备编号(3字节)
|
||||
byte[] deviceIdBytes = new byte[3];
|
||||
buffer.get(deviceIdBytes);
|
||||
int deviceId = ((deviceIdBytes[0] & 0xFF) << 16)
|
||||
| ((deviceIdBytes[1] & 0xFF) << 8)
|
||||
| (deviceIdBytes[2] & 0xFF);
|
||||
result.put("设备编号", deviceId);
|
||||
|
||||
// 设备上行60帧:厂家代码 + 和校验 + 帧尾
|
||||
byte factoryCode = buffer.get();
|
||||
result.put("厂家代码", (int) factoryCode & 0xFF);
|
||||
Date date = new Date();
|
||||
// 和校验
|
||||
// 1. 转换各字段为字节数组
|
||||
byte[] towerCraneNoBytes = decimalToFixedLengthBytes(towerCode, 1);
|
||||
byte[] deviceNoBytes = decimalToFixedLengthBytes(deviceId, 3);
|
||||
byte[] registerResultBytes = decimalToFixedLengthBytes(registerResult, 1);
|
||||
byte[] timeBytes = parseTimeToBytes(DateUtil.formatDateTime(date));
|
||||
byte[] uploadIntervalBytes = decimalToFixedLengthBytes(15, 1);
|
||||
// 2. 拼接需要参与和校验的字节数组(除和校验、帧尾外的所有字段)
|
||||
byte[] checkSumSource = concatBytes(
|
||||
FRAME_HEAD, FRAME_TYPE, towerCraneNoBytes,
|
||||
deviceNoBytes, registerResultBytes, timeBytes, uploadIntervalBytes
|
||||
);
|
||||
// 3. 计算和校验(不进位累加,转2字节)
|
||||
byte[] checkSumBytes = calculateCheckSum(checkSumSource);
|
||||
|
||||
|
||||
// 帧尾(4字节)
|
||||
byte[] endFlag = new byte[4];
|
||||
buffer.get(endFlag);
|
||||
result.put("帧尾", bytesToHex(endFlag));
|
||||
|
||||
// 4. 拼接所有字段生成完整帧
|
||||
byte[] fullFrameBytes = concatBytes(
|
||||
FRAME_HEAD, FRAME_TYPE, towerCraneNoBytes,
|
||||
deviceNoBytes, registerResultBytes, timeBytes, uploadIntervalBytes,
|
||||
checkSumBytes, FRAME_TAIL
|
||||
);
|
||||
|
||||
// int year = DateUtil.year(date);
|
||||
// int month = DateUtil.month(date) + 1;
|
||||
// int day = DateUtil.dayOfMonth(date);
|
||||
// int hour = DateUtil.hour(date, true);
|
||||
// int minute = DateUtil.minute(date);
|
||||
// int second = DateUtil.second(date);
|
||||
// String s = generateServerDownFrame(towerCode, deviceId, registerResult, year, month, day, hour, minute, second, 15);
|
||||
// 5. 字节数组转十六进制字符串(大写)
|
||||
String s = byteArrayToHexStringWithSpace(fullFrameBytes);
|
||||
ctx.writeAndFlush(Unpooled.copiedBuffer(s, CharsetUtil.UTF_8));
|
||||
} catch (Exception e) {
|
||||
log.info("解析异常", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节数组转带空格分隔的十六进制字符串(大写)
|
||||
* @param bytes 字节数组
|
||||
* @return 十六进制字符串(每个字节间用空格分隔)
|
||||
*/
|
||||
private static String byteArrayToHexStringWithSpace(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
// 每个字节转两位十六进制,大写
|
||||
sb.append(String.format("%02X", bytes[i]));
|
||||
// 最后一个字节后不加空格
|
||||
if (i != bytes.length - 1) {
|
||||
sb.append(" ");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节数组拼接
|
||||
* @param arrays 待拼接的字节数组
|
||||
* @return 拼接后的字节数组
|
||||
*/
|
||||
private static byte[] concatBytes(byte[]... arrays) {
|
||||
int totalLength = 0;
|
||||
for (byte[] arr : arrays) {
|
||||
totalLength += arr.length;
|
||||
}
|
||||
byte[] result = new byte[totalLength];
|
||||
int pos = 0;
|
||||
for (byte[] arr : arrays) {
|
||||
System.arraycopy(arr, 0, result, pos, arr.length);
|
||||
pos += arr.length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 十进制数转指定长度的字节数组(大端序,不足补0)
|
||||
* @param num 十进制数
|
||||
* @param length 字节长度
|
||||
* @return 字节数组
|
||||
*/
|
||||
private static byte[] decimalToFixedLengthBytes(long num, int length) {
|
||||
byte[] bytes = new byte[length];
|
||||
for (int i = length - 1; i >= 0; i--) {
|
||||
bytes[i] = (byte) (num & 0xFF);
|
||||
num >>= 8;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析时间字符串为6字节数组(日、月、年后两位、时、分、秒)
|
||||
* @param timeStr 时间字符串(yyyy-MM-dd HH:mm:ss)
|
||||
* @return 6字节时间数组
|
||||
*/
|
||||
private static byte[] parseTimeToBytes(String timeStr) {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
LocalDateTime time = LocalDateTime.parse(timeStr, formatter);
|
||||
|
||||
int day = time.getDayOfMonth();
|
||||
int month = time.getMonthValue();
|
||||
int yearLast2 = time.getYear() % 100; // 年取后两位
|
||||
int hour = time.getHour();
|
||||
int minute = time.getMinute();
|
||||
int second = time.getSecond();
|
||||
|
||||
return new byte[]{
|
||||
(byte) yearLast2,
|
||||
(byte) month,
|
||||
(byte) day,
|
||||
(byte) hour,
|
||||
(byte) minute,
|
||||
(byte) second
|
||||
};
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 服务器端生成61帧(下行应答帧)
|
||||
// */
|
||||
// private static String generateServerDownFrame(int towerId, int deviceId, int registerResult,
|
||||
// int year, int month, int day, int hour, int minute, int second,
|
||||
// int uploadInterval) {
|
||||
// ByteBuffer buffer = ByteBuffer.allocate(20); // 2+1+1+3+1+6+1+2 +4(帧尾) = 21,先分配17
|
||||
// buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
//
|
||||
// // 帧头 A55A
|
||||
// buffer.put(FRAME_HEAD);
|
||||
// // 帧类型 61
|
||||
// buffer.put((byte) 0x61);
|
||||
// // 塔吊编号
|
||||
// buffer.put((byte) towerId);
|
||||
// // 设备编号(3字节)
|
||||
// buffer.put((byte) (deviceId >> 16 & 0xFF));
|
||||
// buffer.put((byte) (deviceId >> 8 & 0xFF));
|
||||
// buffer.put((byte) (deviceId & 0xFF));
|
||||
// // 注册结果
|
||||
// buffer.put((byte) registerResult);
|
||||
// // 时间(年-2000)
|
||||
// buffer.put((byte) (year - 2000));
|
||||
// buffer.put((byte) month);
|
||||
// buffer.put((byte) day);
|
||||
// buffer.put((byte) hour);
|
||||
// buffer.put((byte) minute);
|
||||
// buffer.put((byte) second);
|
||||
// // 上传间隔
|
||||
// buffer.put((byte) uploadInterval);
|
||||
//
|
||||
// buffer.put(FRAME_TAIL);
|
||||
// // 拼接帧尾
|
||||
// byte[] frameBytes = buffer.array();
|
||||
// String frameHex = bytesToHex(frameBytes);
|
||||
// log.info(frameHex);
|
||||
// // 3. 将字节数组转为带空格分隔的十六进制字符串(匹配示例格式)
|
||||
// return bytesToHexWithSpace(buffer.array());
|
||||
// }
|
||||
|
||||
/**
|
||||
* 计算和校验:所有字节不进位累加,结果转2字节(低位在前/高位在前?示例中18 08是累加结果)
|
||||
* @param source 参与校验的字节数组
|
||||
* @return 2字节的校验和(示例中累加结果为0x0818,转成字节数组是0x18, 0x08)
|
||||
*/
|
||||
private static byte[] calculateCheckSum(byte[] source) {
|
||||
int sum = 0;
|
||||
// 不进位累加:逐个字节转无符号int后累加
|
||||
for (byte b : source) {
|
||||
sum += (b & 0xFF); // 转无符号,避免负数影响
|
||||
}
|
||||
// 校验和占2字节,取低16位(sum可能超过16位,只保留后两位)
|
||||
sum = sum & 0xFFFF;
|
||||
// 拆分高低位(示例中sum=0x0818,对应字节18 08,即低位在前)
|
||||
byte lowByte = (byte) (sum & 0xFF); // 低位:0x18
|
||||
byte highByte = (byte) ((sum >> 8) & 0xFF); // 高位:0x08
|
||||
return new byte[]{lowByte, highByte};
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节数组转【带空格分隔】的十六进制字符串(核心调整点)
|
||||
* 示例:byte[]{0xA5,0x5A} → "A5 5A"
|
||||
*/
|
||||
private static String bytesToHexWithSpace(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
// 转大写十六进制,补零
|
||||
sb.append(String.format("%02X", bytes[i]));
|
||||
// 最后一个字节不加空格
|
||||
if (i < bytes.length - 1) {
|
||||
sb.append(" ");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析命令字0C的塔机实时数据
|
||||
* @param hexStr 无空格的十六进制字符串
|
||||
* @return 解析结果键值对
|
||||
*/
|
||||
public void parseRealTimeData(String hexStr, Tower tower) {
|
||||
TowerCurrentData towerCurrentData = new TowerCurrentData();
|
||||
towerCurrentData.setDevSn(tower.getDevSn());
|
||||
towerCurrentData.setDevName(tower.getDevName());
|
||||
towerCurrentData.setProjectSn(tower.getProjectSn());
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
// 1. 十六进制转字节数组
|
||||
byte[] data = hexStringToByteArray(hexStr);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||
buffer.order(ByteOrder.BIG_ENDIAN); // 强制大端序
|
||||
|
||||
// -------------------------- 固定字段 --------------------------
|
||||
// 起始符
|
||||
byte[] startFlag = new byte[2];
|
||||
buffer.get(startFlag);
|
||||
result.put("起始符", bytesToHex(startFlag));
|
||||
|
||||
// 命令字
|
||||
byte cmd = buffer.get();
|
||||
result.put("命令字", String.format("%02X", cmd));
|
||||
|
||||
// 塔机编号
|
||||
byte towerId = buffer.get();
|
||||
result.put("塔机编号", (int) towerId & 0xFF);
|
||||
|
||||
// 设备编号(3字节手动拼接)
|
||||
byte[] deviceIdBytes = new byte[3];
|
||||
buffer.get(deviceIdBytes);
|
||||
int deviceId = ((deviceIdBytes[0] & 0xFF) << 16)
|
||||
| ((deviceIdBytes[1] & 0xFF) << 8)
|
||||
| (deviceIdBytes[2] & 0xFF);
|
||||
result.put("设备编号", deviceId);
|
||||
|
||||
// 版本号
|
||||
byte version = buffer.get();
|
||||
result.put("程序版本号", (int) version & 0xFF);
|
||||
|
||||
// -------------------------- 时间字段 --------------------------
|
||||
byte[] timeBytes = new byte[6];
|
||||
buffer.get(timeBytes);
|
||||
String time = parseTime(timeBytes);
|
||||
result.put("时间", time);
|
||||
towerCurrentData.setReciveTime(DateUtil.parseDateTime(time));
|
||||
towerCurrentData.setStartTime(DateUtil.parseDateTime(time));
|
||||
|
||||
// -------------------------- 基础数据 --------------------------
|
||||
// 厂家及设备类型
|
||||
byte factoryType = buffer.get();
|
||||
result.put("厂家及设备类型", (int) factoryType & 0xFF);
|
||||
|
||||
// 高度数据
|
||||
short heightRaw = buffer.getShort();
|
||||
double height = heightRaw * HEIGHT_AMPLITUDE_WIND_COEFF;
|
||||
result.put("高度实时值(米)", height);
|
||||
towerCurrentData.setHeight(String.valueOf(height));
|
||||
|
||||
// 幅度数据
|
||||
short amplitudeRaw = buffer.getShort();
|
||||
double amplitude = amplitudeRaw * HEIGHT_AMPLITUDE_WIND_COEFF;
|
||||
result.put("幅度(米)", amplitude);
|
||||
towerCurrentData.setRanger(String.valueOf(amplitude));
|
||||
|
||||
// 回转角度
|
||||
short rotateRaw = buffer.getShort();
|
||||
double rotate = rotateRaw * ROTATE_COEFF;
|
||||
result.put("回转(度)", rotate);
|
||||
|
||||
// 起重量数据
|
||||
short weightRaw = buffer.getShort();
|
||||
double weight = weightRaw * WEIGHT_COEFF;
|
||||
result.put("重量(吨)", weight);
|
||||
towerCurrentData.setLoading(String.valueOf(weight));
|
||||
|
||||
// 风速数据
|
||||
short windRaw = buffer.getShort();
|
||||
double wind = windRaw * HEIGHT_AMPLITUDE_WIND_COEFF;
|
||||
result.put("风速(米/秒)", wind);
|
||||
towerCurrentData.setWindspeed(String.valueOf(wind));
|
||||
|
||||
// 倾角数据(核心特殊换算)
|
||||
short angleRaw = buffer.getShort();
|
||||
double angle = (angleRaw - ANGLE_OFFSET) / ANGLE_COEFF;
|
||||
result.put("倾角(度)", angle);
|
||||
towerCurrentData.setObliguity(String.valueOf(angle));
|
||||
|
||||
// -------------------------- 百分比字段 --------------------------
|
||||
// 重量百分比
|
||||
byte weightPercent = buffer.get();
|
||||
result.put("重量百分比(%)", (int) weightPercent & 0xFF);
|
||||
towerCurrentData.setLoadRatio(String.valueOf((int) weightPercent & 0xFF));
|
||||
|
||||
// 力矩百分比
|
||||
byte momentPercent = buffer.get();
|
||||
result.put("力矩百分比(%)", (int) momentPercent & 0xFF);
|
||||
towerCurrentData.setTorqueRatio(String.valueOf((int) momentPercent & 0xFF));
|
||||
|
||||
// 风速百分比
|
||||
byte windPercent = buffer.get();
|
||||
result.put("风速百分比(%)", (int) windPercent & 0xFF);
|
||||
|
||||
// 倾斜百分比
|
||||
byte tiltPercent = buffer.get();
|
||||
result.put("倾斜百分比(%)", (int) tiltPercent & 0xFF);
|
||||
|
||||
// -------------------------- 报警/制动 --------------------------
|
||||
// 警报原因
|
||||
byte alarmReason = buffer.get();
|
||||
int alarmCode = (int) alarmReason & 0xFF;
|
||||
result.put("警报原因编码", alarmCode);
|
||||
result.put("警报原因描述", parseAlarmReason(alarmCode));
|
||||
|
||||
// 制动状态
|
||||
byte brakeStatus = buffer.get();
|
||||
int brakeInt = (int) brakeStatus & 0xFF;
|
||||
result.put("制动状态编码", brakeInt);
|
||||
result.put("制动状态描述", parseBrakeStatus(brakeInt));
|
||||
|
||||
// -------------------------- 结束符 --------------------------
|
||||
byte[] endFlag = new byte[6];
|
||||
buffer.get(endFlag);
|
||||
result.put("结束符", bytesToHex(endFlag));
|
||||
towerCurrentDataService.saveTowerCurrentData(towerCurrentData);
|
||||
} catch (Exception e) {
|
||||
log.info("解析异常", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析工作循环数据
|
||||
* @param hexStr 无空格的十六进制字符串(实例:A55A3D0290F72F02111301080A1C211301080A1D2117D300C7012C008200000000000000000082000000000104B52DCC33C33C)
|
||||
* @return 解析后的字段键值对
|
||||
*/
|
||||
public void parseWorkCycleData(String hexStr, Tower tower) {
|
||||
TowerWorkCycle workCycle = new TowerWorkCycle();
|
||||
try {
|
||||
// 1. 十六进制字符串转字节数组
|
||||
byte[] data = hexStringToByteArray(hexStr);
|
||||
// 2. 初始化ByteBuffer,设置大端序(网络字节序)
|
||||
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
// -------------------------- 固定字段解析 --------------------------
|
||||
// 起始符(2字节)
|
||||
byte[] startFlag = new byte[2];
|
||||
buffer.get(startFlag);
|
||||
|
||||
// 命令字(1字节)
|
||||
byte cmd = buffer.get();
|
||||
|
||||
// 塔机编号(1字节)
|
||||
byte towerId = buffer.get();
|
||||
|
||||
// 设备编号(3字节:特殊处理,手动拼接大端序)
|
||||
byte[] deviceIdBytes = new byte[3];
|
||||
buffer.get(deviceIdBytes);
|
||||
int deviceId = ((deviceIdBytes[0] & 0xFF) << 16)
|
||||
| ((deviceIdBytes[1] & 0xFF) << 8)
|
||||
| (deviceIdBytes[2] & 0xFF);
|
||||
workCycle.setDevSn(tower.getDevSn());
|
||||
workCycle.setDevName(tower.getDevName());
|
||||
workCycle.setProjectSn(tower.getProjectSn());
|
||||
|
||||
// 版本号(1字节)
|
||||
byte version = buffer.get();
|
||||
|
||||
// 厂家及设备类型(1字节:位运算解析)
|
||||
byte factoryDeviceType = buffer.get();
|
||||
int typeInt = factoryDeviceType & 0xFF;
|
||||
boolean isLocked = (typeInt & 0x80) != 0; // 最高位(b7):1=锁机,0=未锁机
|
||||
int factoryCode = typeInt & 0x0F; // 低4位:厂家/设备类型编码
|
||||
|
||||
// -------------------------- 时间字段解析 --------------------------
|
||||
// 开始时间(6字节:年、月、日、时、分、秒)
|
||||
byte[] startTimeBytes = new byte[6];
|
||||
buffer.get(startTimeBytes);
|
||||
String startTime = parseTime(startTimeBytes);
|
||||
workCycle.setStartTime(DateUtil.parseDateTime(startTime));
|
||||
|
||||
// 结束时间(6字节:年、月、日、时、分、秒)
|
||||
byte[] endTimeBytes = new byte[6];
|
||||
buffer.get(endTimeBytes);
|
||||
String endTime = parseTime(endTimeBytes);
|
||||
workCycle.setEndTime(DateUtil.parseDateTime(endTime));
|
||||
if (startTime != null && endTime != null) {
|
||||
workCycle.setWorkTime(String.valueOf(DateUtil.between(DateUtil.parseDateTime(startTime), DateUtil.parseDateTime(endTime), DateUnit.SECOND)));
|
||||
}
|
||||
workCycle.setAddTime(new Date());
|
||||
|
||||
// -------------------------- 物理量解析 --------------------------
|
||||
// 本次循环最大吊重(2字节)
|
||||
short maxWeightRaw = buffer.getShort();
|
||||
double maxWeight = maxWeightRaw * WEIGHT_COEFFICIENT;
|
||||
workCycle.setLoading(String.valueOf(maxWeight));
|
||||
|
||||
// 本次循环最大力矩(2字节)
|
||||
short maxMomentRaw = buffer.getShort();
|
||||
double maxMoment = maxMomentRaw * MOMENT_COEFFICIENT;
|
||||
workCycle.setWorkMaxForce(String.valueOf(maxMoment));
|
||||
|
||||
// 最大高度(2字节)
|
||||
short maxHeightRaw = buffer.getShort();
|
||||
double maxHeight = maxHeightRaw * HEIGHT_AMPLITUDE_COEFFICIENT;
|
||||
workCycle.setMaxHeight(String.valueOf(maxHeight));
|
||||
|
||||
// 最小高度(2字节)
|
||||
short minHeightRaw = buffer.getShort();
|
||||
double minHeight = minHeightRaw * HEIGHT_AMPLITUDE_COEFFICIENT;
|
||||
workCycle.setMinHeight(String.valueOf(minHeight));
|
||||
|
||||
// 最大幅度(2字节)
|
||||
short maxAmplitudeRaw = buffer.getShort();
|
||||
double maxAmplitude = maxAmplitudeRaw * HEIGHT_AMPLITUDE_COEFFICIENT;
|
||||
workCycle.setMaxRange(String.valueOf(maxAmplitude));
|
||||
|
||||
// 最小幅度(2字节)
|
||||
short minAmplitudeRaw = buffer.getShort();
|
||||
double minAmplitude = minAmplitudeRaw * HEIGHT_AMPLITUDE_COEFFICIENT;
|
||||
workCycle.setMinRange(String.valueOf(minAmplitude));
|
||||
|
||||
// 起吊点角度(2字节)
|
||||
short liftAngleRaw = buffer.getShort();
|
||||
double liftAngle = liftAngleRaw * ANGLE_COEFFICIENT;
|
||||
workCycle.setSlingStartRotation(String.valueOf(liftAngle));
|
||||
|
||||
// 起吊点幅度(2字节)
|
||||
short liftAmplitudeRaw = buffer.getShort();
|
||||
double liftAmplitude = liftAmplitudeRaw * HEIGHT_AMPLITUDE_COEFFICIENT;
|
||||
workCycle.setSlingStartRange(String.valueOf(liftAmplitude));
|
||||
|
||||
// 起吊点高度(2字节)
|
||||
short liftHeightRaw = buffer.getShort();
|
||||
double liftHeight = liftHeightRaw * HEIGHT_AMPLITUDE_COEFFICIENT;
|
||||
workCycle.setSlingStartHeight(String.valueOf(liftHeight));
|
||||
|
||||
// 卸吊点角度(2字节)
|
||||
short unloadAngleRaw = buffer.getShort();
|
||||
double unloadAngle = unloadAngleRaw * ANGLE_COEFFICIENT;
|
||||
workCycle.setSlingEndRotation(String.valueOf(unloadAngle));
|
||||
|
||||
// 卸吊点幅度(2字节)
|
||||
short unloadAmplitudeRaw = buffer.getShort();
|
||||
double unloadAmplitude = unloadAmplitudeRaw * HEIGHT_AMPLITUDE_COEFFICIENT;
|
||||
workCycle.setSlingEndRange(String.valueOf(unloadAmplitude));
|
||||
|
||||
// 卸吊点高度(2字节)
|
||||
short unloadHeightRaw = buffer.getShort();
|
||||
double unloadHeight = unloadHeightRaw * HEIGHT_AMPLITUDE_COEFFICIENT;
|
||||
workCycle.setSlingEndHeight(String.valueOf(unloadHeight));
|
||||
|
||||
// -------------------------- 结束符解析 --------------------------
|
||||
byte[] endFlag = new byte[6];
|
||||
buffer.get(endFlag);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.info("TCP解析异常", e.getMessage());
|
||||
}
|
||||
towerWorkCycleService.save(workCycle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析警报原因编码
|
||||
*/
|
||||
private String parseAlarmReason(int code) {
|
||||
String alarm = "";
|
||||
switch (code) {
|
||||
case 0 :
|
||||
alarm = "未报警";
|
||||
break;
|
||||
// case 111 -> "碰撞预警";
|
||||
// case 211 -> "碰撞报警";
|
||||
// case 101 -> "幅度限位报警";
|
||||
// case 102 -> "高度限位报警";
|
||||
// case 103 -> "回转限位报警";
|
||||
// case 201 -> "幅度限位预警";
|
||||
// case 202 -> "高度限位预警";
|
||||
// case 203 -> "回转限位预警";
|
||||
// case 12 -> "重量报警";
|
||||
// case 13 -> "力矩报警";
|
||||
// case 14 -> "倾斜报警";
|
||||
// case 15 -> "风速报警";
|
||||
// case 22 -> "重量预警";
|
||||
// case 23 -> "力矩预警";
|
||||
// case 24 -> "倾斜预警";
|
||||
// case 25 -> "风速预警";
|
||||
// default -> "未知报警/预警类型";
|
||||
};
|
||||
return alarm;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析制动状态(8bit:低到高=上下前后左右,1=断开,0=闭合)
|
||||
*/
|
||||
private static String parseBrakeStatus(int code) {
|
||||
if (code == 0) {
|
||||
return "未制动(所有方向闭合)";
|
||||
}
|
||||
StringBuilder desc = new StringBuilder();
|
||||
String[] directions = {"下", "上", "后", "前", "右", "左"}; // 低到高bit0~bit5
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int bit = (code >> i) & 1;
|
||||
desc.append(directions[i]).append(":").append(bit == 1 ? "断开" : "闭合").append(";");
|
||||
}
|
||||
return desc.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析6字节时间(年、月、日、时、分、秒)
|
||||
* @param timeBytes 6字节时间数组
|
||||
* @return 格式化时间字符串:yyyy-MM-dd HH:mm:ss
|
||||
*/
|
||||
private static String parseTime(byte[] timeBytes) {
|
||||
// 年:字节转无符号十进制 + 2000
|
||||
int year = (timeBytes[0] & 0xFF) + 2000;
|
||||
// 月/日/时/分/秒:字节转无符号十进制
|
||||
int month = timeBytes[1] & 0xFF;
|
||||
int day = timeBytes[2] & 0xFF;
|
||||
int hour = timeBytes[3] & 0xFF;
|
||||
int minute = timeBytes[4] & 0xFF;
|
||||
int second = timeBytes[5] & 0xFF;
|
||||
// 格式化(补零)
|
||||
return String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
||||
year, month, day, hour, minute, second);
|
||||
}
|
||||
|
||||
/**
|
||||
* 十六进制字符串转字节数组(去除空格)
|
||||
*/
|
||||
private static byte[] hexStringToByteArray(String hexStr) {
|
||||
hexStr = hexStr.replaceAll("\\s+", ""); // 移除所有空格
|
||||
int len = hexStr.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(hexStr.charAt(i), 16) << 4)
|
||||
+ Character.digit(hexStr.charAt(i + 1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节数组转十六进制字符串(用于输出起始符/结束符)
|
||||
*/
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02X", b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user