!19 绿能模块

* 绿能模块
This commit is contained in:
三丙
2025-08-09 11:00:12 +00:00
parent 3d441d75a3
commit 199711026c
34 changed files with 1122 additions and 50 deletions

View File

@@ -6,6 +6,8 @@
*/
package sanbing.jcpp.infrastructure.util.codec;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class BCDUtil {
private static final String HEX = "0123456789ABCDEF";
@@ -171,4 +173,124 @@ public class BCDUtil {
return sb.toString().toUpperCase();
}
private static final int HOUR_24 = 0x24;
private static final int ZERO_TIME = 0x00;
private static final int BCD_DATE_LENGTH = 8;
private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
/**
* BCD编码的时间转换为LocalDateTime对象
* 格式: YYYYMMDDHHmmss (8字节BCD)
* 示例: 20240315235959
*
* @param bcdBytes BCD编码的时间字节数组必须是8字节
* @return 转换后的LocalDateTime对象如果输入无效则返回null
* @throws IllegalArgumentException 如果输入字节数组长度不是8
*/
public static LocalDateTime bcdToDate(byte[] bcdBytes) {
if (bcdBytes == null || bcdBytes.length != BCD_DATE_LENGTH) {
throw new IllegalArgumentException("BCD date bytes must be 8 bytes long");
}
// 检查是否全为0
boolean allZero = true;
for (int i = 0; i < 7; i++) {
if (bcdBytes[i] != ZERO_TIME) {
allZero = false;
break;
}
}
if (allZero) {
return null;
}
// 使用StringBuilder预分配容量避免扩容
StringBuilder timeStr = new StringBuilder(14);
// 年月日
for (int i = 0; i < 4; i++) {
appendBcdByte(timeStr, bcdBytes[i]);
}
// 小时特殊处理
byte hour = bcdBytes[4];
if ((hour & 0xff) == HOUR_24) {
timeStr.append("00");
} else {
appendBcdByte(timeStr, hour);
}
// 分秒
appendBcdByte(timeStr, bcdBytes[5]);
appendBcdByte(timeStr, bcdBytes[6]);
try {
return LocalDateTime.parse(timeStr.toString(), DATETIME_FORMATTER);
} catch (Exception e) {
return null;
}
}
/**
* 将单个BCD字节追加到StringBuilder
* 性能优化: 直接计算BCD值并追加避免字符串转换
*/
private static void appendBcdByte(StringBuilder sb, byte bcd) {
// 高4位
sb.append(DIGITS[(bcd >> 4) & 0x0F]);
// 低4位
sb.append(DIGITS[bcd & 0x0F]);
}
/**
* LocalDateTime转换为8字节BCD编码
* 格式: YYYYMMDDHHmmss + 0xFF (8字节BCD)
* 示例: 20240315235959FF
*
* @param dateTime 要转换的LocalDateTime对象
* @return 8字节BCD编码的字节数组最后一个字节固定为0xFF如果输入为null则返回null
*/
public static byte[] dateToBcd8(LocalDateTime dateTime) {
if (dateTime == null) {
return null;
}
byte[] bytes = new byte[8];
// 年 (2字节)
int year = dateTime.getYear();
bytes[0] = (byte) ((year / 100) << 4 | (year / 10 % 10));
bytes[1] = (byte) ((year % 10) << 4);
// 月 (与年的最后4位共用一个字节)
int month = dateTime.getMonthValue();
bytes[1] |= (byte) (month / 10);
bytes[2] = (byte) ((month % 10) << 4);
// 日 (与月的最后4位共用一个字节)
int day = dateTime.getDayOfMonth();
bytes[2] |= (byte) (day / 10);
bytes[3] = (byte) ((day % 10) << 4);
// 时 (与日的最后4位共用一个字节)
int hour = dateTime.getHour();
bytes[3] |= (byte) (hour / 10);
bytes[4] = (byte) ((hour % 10) << 4);
// 分 (与时的最后4位共用一个字节)
int minute = dateTime.getMinute();
bytes[4] |= (byte) (minute / 10);
bytes[5] = (byte) ((minute % 10) << 4);
// 秒 (与分的最后4位共用一个字节)
int second = dateTime.getSecond();
bytes[5] |= (byte) (second / 10);
bytes[6] = (byte) ((second % 10) << 4);
// 最后一个字节固定为0xFF
bytes[7] = (byte) 0xFF;
return bytes;
}
}

View File

@@ -71,8 +71,8 @@ public class ByteUtil {
/**
* ByteBuf转byte数组
*
* @param byteBuf
* @return
* @param byteBuf ByteBuf对象
* @return 转换后的字节数组
*/
public static byte[] toBytes(ByteBuf byteBuf) {
int msgLength = byteBuf.readableBytes();
@@ -80,4 +80,48 @@ public class ByteUtil {
byteBuf.readBytes(bytes);
return bytes;
}
/**
* 计算字节数组的累加和如果累加结果超过1字节则只取低8位
*
* 示例:
* byte[] data = {0x01, 0x02, 0x03};
* byte sum = calculateSum(data); // sum = 0x06
*
* byte[] data2 = {(byte)0xFF, (byte)0xFF};
* byte sum2 = calculateSum(data2); // sum2 = (byte)0xFE (254 + 255 = 509, 取低8位为254)
*
* @param data 要计算累加和的字节数组
* @return 累加和的低8位
* @throws IllegalArgumentException 如果输入数组为null
*/
public static byte calculateSum(byte[] data) {
if (data == null) {
throw new IllegalArgumentException("输入数组不能为null");
}
int sum = 0;
for (byte b : data) {
sum += b & 0xFF;
}
return (byte) (sum & 0xFF);
}
/**
* 验证数据的累加和是否与期望值相等
*
* 示例:
* byte[] data = {0x01, 0x02, 0x03};
* boolean valid = verifySum(data, (byte)0x06); // valid = true
*
* @param data 要验证的数据
* @param expectedSum 期望的累加和
* @return 包含验证结果和实际计算出的累加和的键值对
* @throws IllegalArgumentException 如果输入数组为null
*/
public static JCPPPair<Boolean, Byte> verifySum(byte[] data, byte expectedSum) {
byte actualSum = calculateSum(data);
return JCPPPair.of(actualSum == expectedSum, actualSum);
}
}

View File

@@ -190,28 +190,4 @@ public class JacksonUtil {
return node;
}
/**
* 合并两个ObjectNode.
* 如果存在相同的字段优先保留第二个ObjectNode中的值。
*
* @param node1 the first ObjectNode
* @param node2 the second ObjectNode
* @return 合并后的结果
*/
public static ObjectNode merge(ObjectNode node1, ObjectNode node2) {
ObjectNode mergedNode = OBJECT_MAPPER.createObjectNode();
// 把第一个节点的所有字段添加到mergedNode中
node1.fields().forEachRemaining(entry -> {
mergedNode.set(entry.getKey(), entry.getValue());
});
// 把第二个节点的所有字段添加到mergedNode中覆盖相同字段
node2.fields().forEachRemaining(entry -> {
mergedNode.set(entry.getKey(), entry.getValue());
});
return mergedNode;
}
}