塔吊TCP

This commit is contained in:
pengjie 2025-12-19 18:36:26 +08:00
parent 2d3c64dcdb
commit 4ca14f64f7
2 changed files with 938 additions and 2 deletions

View File

@ -34,6 +34,14 @@ public class TcpNettyHandler extends SimpleChannelInboundHandler<Object> {
public static final String PREFIX = "##"; public static final String PREFIX = "##";
public static final String PREFIX1 = "7E"; 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 @Autowired
private HighFormworkSupportService highFormworkSupportService; private HighFormworkSupportService highFormworkSupportService;
@ -50,7 +58,6 @@ public class TcpNettyHandler extends SimpleChannelInboundHandler<Object> {
String str = getString(bytes); String str = getString(bytes);
str = str.trim(); str = str.trim();
log.info("tcp接收数据 >>> {} ", str); 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)) { if (StringUtils.startsWith(str, HighFormworkSupport.TCP_DATA_PREFIX) && StringUtils.endsWith(str, HighFormworkSupport.TCP_DATA_END)) {
log.info("高支模接收数据 >>> {} ", str); log.info("高支模接收数据 >>> {} ", str);
//接收高支模数据保存到mysql中 //接收高支模数据保存到mysql中
@ -113,7 +120,9 @@ public class TcpNettyHandler extends SimpleChannelInboundHandler<Object> {
//16进制判断 //16进制判断
String hexString = readToHexString(bytes); String hexString = readToHexString(bytes);
log.info("tcp接收数据16进制转字符串 >>> {} ", hexString); 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()); poisonousGasDevCurrentDataService.addDataFromTcp(hexString.trim());
} }

View 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; // 最高位b71=锁机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();
}
}