|
@@ -0,0 +1,305 @@
|
|
|
|
|
+package com.zksy.telemetry.utils;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.time.Duration;
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
|
+import java.time.ZoneOffset;
|
|
|
|
|
+import java.util.Arrays;
|
|
|
|
|
+
|
|
|
|
|
+public class ProtocolUtils {
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 将2000年起始的时间戳转换为LocalDateTime
|
|
|
|
|
+ */
|
|
|
|
|
+ static LocalDateTime convertToDateTime(int timestamp) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 定义基准时间:2000年1月1日 00:00:00
|
|
|
|
|
+ LocalDateTime baseTime = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
|
|
|
|
|
+ // 累加时间戳(秒数)
|
|
|
|
|
+ return baseTime.plusSeconds(timestamp);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 获取当前时间并转换为2000年起始的时间戳
|
|
|
|
|
+ static int getCurrentTimestampSince2000() {
|
|
|
|
|
+ LocalDateTime now = LocalDateTime.now(ZoneOffset.of("+8"));
|
|
|
|
|
+ LocalDateTime baseTime = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
|
|
|
|
|
+ // 计算时间差(秒数)
|
|
|
|
|
+ return (int) java.time.Duration.between(baseTime, now).getSeconds();
|
|
|
|
|
+ }
|
|
|
|
|
+ public static int calculateCRC16(byte[] data) {
|
|
|
|
|
+ int crc = 0xFFFF;
|
|
|
|
|
+ for (byte b : data) {
|
|
|
|
|
+ crc ^= (b & 0xFF);
|
|
|
|
|
+ for (int i = 0; i < 8; i++) {
|
|
|
|
|
+ if ((crc & 0x0001) != 0) crc = (crc >> 1) ^ 0xA001;
|
|
|
|
|
+ else crc >>= 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ int high = (crc & 0xFF00) >> 8;
|
|
|
|
|
+ int low = crc & 0x00FF;
|
|
|
|
|
+ return (high << 8) | low;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static byte calculateXorCheck(byte[] data) {
|
|
|
|
|
+ byte check = 0x00;
|
|
|
|
|
+ for (byte b : data) check ^= b;
|
|
|
|
|
+ return check;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建上报回应包(对应帧类型 0x31)
|
|
|
|
|
+ * 结构:
|
|
|
|
|
+ * [系统识别码(3)] [整帧长度(2)] [包序号(1)] [帧类型0x31(1)]
|
|
|
|
|
+ * [源地址长度(1)] [源地址(n)] [目的地址长度(1)] [目的地址(m)] [帧校验(1)]
|
|
|
|
|
+ *
|
|
|
|
|
+ * 示例(文档第11页):
|
|
|
|
|
+ * 12 34 56 00 16 81 31 0B 00 00 00 00 00 10 0B 13 81 23 45 67 80 D5
|
|
|
|
|
+ */
|
|
|
|
|
+ public static byte[] buildReportAckPacket(byte[] originalFrame, byte packetSequence) {
|
|
|
|
|
+ int srcLenIndex = 7;
|
|
|
|
|
+ int srcLen = originalFrame[srcLenIndex] & 0xFF;
|
|
|
|
|
+ int srcByteLen = (srcLen + 1) / 2;
|
|
|
|
|
+ int srcStart = srcLenIndex + 1;
|
|
|
|
|
+ int srcEnd = srcStart + srcByteLen;
|
|
|
|
|
+
|
|
|
|
|
+ int dstLenIndex = srcEnd;
|
|
|
|
|
+ int dstLen = originalFrame[dstLenIndex] & 0xFF;
|
|
|
|
|
+ int dstByteLen = (dstLen + 1) / 2;
|
|
|
|
|
+ int dstStart = dstLenIndex + 1;
|
|
|
|
|
+ int dstEnd = dstStart + dstByteLen;
|
|
|
|
|
+
|
|
|
|
|
+ byte[] response = new byte[3 + 2 + 1 + 1 + 1 + dstByteLen + 1 + srcByteLen + 1];
|
|
|
|
|
+ int idx = 0;
|
|
|
|
|
+
|
|
|
|
|
+ // 系统识别码 12 34 56
|
|
|
|
|
+ response[idx++] = 0x12;
|
|
|
|
|
+ response[idx++] = 0x34;
|
|
|
|
|
+ response[idx++] = 0x56;
|
|
|
|
|
+
|
|
|
|
|
+ // 长度(待填充)
|
|
|
|
|
+ int totalLen = response.length;
|
|
|
|
|
+ response[idx++] = (byte) ((totalLen >> 8) & 0xFF);
|
|
|
|
|
+ response[idx++] = (byte) (totalLen & 0xFF);
|
|
|
|
|
+
|
|
|
|
|
+ // 包序号(与原帧一致)
|
|
|
|
|
+ response[idx++] = packetSequence;
|
|
|
|
|
+
|
|
|
|
|
+ // 帧类型:上报回应
|
|
|
|
|
+ response[idx++] = 0x31;
|
|
|
|
|
+
|
|
|
|
|
+ // 源地址 = 原帧的目的地址
|
|
|
|
|
+ response[idx++] = (byte) dstLen;
|
|
|
|
|
+ System.arraycopy(originalFrame, dstStart, response, idx, dstByteLen);
|
|
|
|
|
+ idx += dstByteLen;
|
|
|
|
|
+
|
|
|
|
|
+ // 目的地址 = 原帧的源地址
|
|
|
|
|
+ response[idx++] = (byte) srcLen;
|
|
|
|
|
+ System.arraycopy(originalFrame, srcStart, response, idx, srcByteLen);
|
|
|
|
|
+ idx += srcByteLen;
|
|
|
|
|
+
|
|
|
|
|
+ // 异或校验(从系统识别码到帧内容)
|
|
|
|
|
+ byte xor = calculateXorCheck(Arrays.copyOfRange(response, 0, idx));
|
|
|
|
|
+ response[idx++] = xor;
|
|
|
|
|
+
|
|
|
|
|
+ return response;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建结束通讯回应包(对应帧类型 0x34)
|
|
|
|
|
+ * 结构:
|
|
|
|
|
+ * [系统识别码(3)] [整帧长度(2)] [包序号(0x80)] [帧类型0x34(1)]
|
|
|
|
|
+ * [源地址长度(1)] [源地址(n)] [目的地址长度(1)] [目的地址(m)] [帧校验(1)]
|
|
|
|
|
+ *
|
|
|
|
|
+ * 示例(文档第12页):
|
|
|
|
|
+ * 12 34 56 00 16 80 34 0B 13 81 23 45 67 80 0B 00 00 00 00 00 10 D1
|
|
|
|
|
+ */
|
|
|
|
|
+ public static byte[] buildShutdownAckPacket(byte[] originalFrame) {
|
|
|
|
|
+ // 提取源、目的地址
|
|
|
|
|
+ int srcLenIndex = 7;
|
|
|
|
|
+ int srcLen = originalFrame[srcLenIndex] & 0xFF;
|
|
|
|
|
+ int srcByteLen = (srcLen + 1) / 2;
|
|
|
|
|
+ int srcStart = srcLenIndex + 1;
|
|
|
|
|
+ int srcEnd = srcStart + srcByteLen;
|
|
|
|
|
+
|
|
|
|
|
+ int dstLenIndex = srcEnd;
|
|
|
|
|
+ int dstLen = originalFrame[dstLenIndex] & 0xFF;
|
|
|
|
|
+ int dstByteLen = (dstLen + 1) / 2;
|
|
|
|
|
+ int dstStart = dstLenIndex + 1;
|
|
|
|
|
+ int dstEnd = dstStart + dstByteLen;
|
|
|
|
|
+
|
|
|
|
|
+ // 结束通讯帧包序号固定 0x80
|
|
|
|
|
+ byte packetSeq = (byte) 0x80;
|
|
|
|
|
+
|
|
|
|
|
+ byte[] response = new byte[3 + 2 + 1 + 1 + 1 + dstByteLen + 1 + srcByteLen + 1];
|
|
|
|
|
+ int idx = 0;
|
|
|
|
|
+
|
|
|
|
|
+ // 系统识别码
|
|
|
|
|
+ response[idx++] = 0x12;
|
|
|
|
|
+ response[idx++] = 0x34;
|
|
|
|
|
+ response[idx++] = 0x56;
|
|
|
|
|
+
|
|
|
|
|
+ // 长度
|
|
|
|
|
+ int totalLen = response.length;
|
|
|
|
|
+ response[idx++] = (byte) ((totalLen >> 8) & 0xFF);
|
|
|
|
|
+ response[idx++] = (byte) (totalLen & 0xFF);
|
|
|
|
|
+
|
|
|
|
|
+ // 包序号
|
|
|
|
|
+ response[idx++] = packetSeq;
|
|
|
|
|
+
|
|
|
|
|
+ // 帧类型 0x34
|
|
|
|
|
+ response[idx++] = 0x34;
|
|
|
|
|
+
|
|
|
|
|
+ // 源地址 = 原帧目的地址
|
|
|
|
|
+ response[idx++] = (byte) dstLen;
|
|
|
|
|
+ System.arraycopy(originalFrame, dstStart, response, idx, dstByteLen);
|
|
|
|
|
+ idx += dstByteLen;
|
|
|
|
|
+
|
|
|
|
|
+ // 目的地址 = 原帧源地址
|
|
|
|
|
+ response[idx++] = (byte) srcLen;
|
|
|
|
|
+ System.arraycopy(originalFrame, srcStart, response, idx, srcByteLen);
|
|
|
|
|
+ idx += srcByteLen;
|
|
|
|
|
+
|
|
|
|
|
+ // 异或校验
|
|
|
|
|
+ byte xor = calculateXorCheck(Arrays.copyOfRange(response, 0, idx));
|
|
|
|
|
+ response[idx++] = xor;
|
|
|
|
|
+
|
|
|
|
|
+ return response;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static byte[] buildErrorResponse(String msg) {
|
|
|
|
|
+ byte[] text = msg.getBytes();
|
|
|
|
|
+ byte[] data = new byte[3 + 2 + 1 + text.length + 1];
|
|
|
|
|
+ int idx = 0;
|
|
|
|
|
+ data[idx++] = 0x12;
|
|
|
|
|
+ data[idx++] = 0x34;
|
|
|
|
|
+ data[idx++] = 0x56;
|
|
|
|
|
+
|
|
|
|
|
+ int len = data.length;
|
|
|
|
|
+ data[idx++] = (byte) ((len >> 8) & 0xFF);
|
|
|
|
|
+ data[idx++] = (byte) (len & 0xFF);
|
|
|
|
|
+
|
|
|
|
|
+ data[idx++] = 0x7F; // 错误类型标识
|
|
|
|
|
+ System.arraycopy(text, 0, data, idx, text.length);
|
|
|
|
|
+ idx += text.length;
|
|
|
|
|
+
|
|
|
|
|
+ data[idx++] = calculateXorCheck(Arrays.copyOfRange(data, 0, idx - 1));
|
|
|
|
|
+ return data;
|
|
|
|
|
+ }
|
|
|
|
|
+ public static byte[] buildCustomReplyFrame(byte[] request) throws IOException {
|
|
|
|
|
+ // 校验长度至少20个字节
|
|
|
|
|
+ if (request.length < 20) {
|
|
|
|
|
+ throw new IllegalArgumentException("请求帧长度不足,无法构造应答");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取关键字段
|
|
|
|
|
+ byte[] header = Arrays.copyOfRange(request, 0, 4); // 系统识别码
|
|
|
|
|
+ byte packetSeq = request[5]; // 包序号
|
|
|
|
|
+ byte frameType = request[6]; // 帧类型(0x31)
|
|
|
|
|
+ byte addrLen = request[7]; // 源地址长度(0B)
|
|
|
|
|
+ int srcAddrStart = 8;
|
|
|
|
|
+ int srcAddrEnd = srcAddrStart + 6; // 实际取6字节源地址
|
|
|
|
|
+ int destAddrStart = srcAddrEnd + 1; // 跳过目的地址长度0B
|
|
|
|
|
+ int destAddrEnd = destAddrStart + 6;
|
|
|
|
|
+
|
|
|
|
|
+ byte[] srcAddr = Arrays.copyOfRange(request, srcAddrStart, srcAddrEnd);
|
|
|
|
|
+ byte[] destAddr = Arrays.copyOfRange(request, destAddrStart, destAddrEnd);
|
|
|
|
|
+
|
|
|
|
|
+ // --- 构造应答帧体 ---
|
|
|
|
|
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
|
|
|
+
|
|
|
|
|
+ // 前四字节
|
|
|
|
|
+ bos.write(header, 0, 4);
|
|
|
|
|
+ // 帧长度 0x16
|
|
|
|
|
+ bos.write(new byte[]{0x16});
|
|
|
|
|
+ // 包序号、帧类型
|
|
|
|
|
+ bos.write(packetSeq);
|
|
|
|
|
+ bos.write(frameType);
|
|
|
|
|
+ bos.write(addrLen);
|
|
|
|
|
+
|
|
|
|
|
+ // 交换地址段:目的 → 源
|
|
|
|
|
+ bos.write(destAddr); // 目的地址放前
|
|
|
|
|
+ bos.write(addrLen);
|
|
|
|
|
+ bos.write(srcAddr); // 源地址放后
|
|
|
|
|
+
|
|
|
|
|
+ // 计算异或校验码
|
|
|
|
|
+ byte[] withoutCheck = bos.toByteArray();
|
|
|
|
|
+ byte xor = 0x00;
|
|
|
|
|
+ for (byte b : withoutCheck) xor ^= b;
|
|
|
|
|
+ bos.write(xor);
|
|
|
|
|
+
|
|
|
|
|
+ byte[] result = bos.toByteArray();
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ public static byte[] buildEndReplyFrame(byte[] request) throws IOException {
|
|
|
|
|
+ if (request == null || request.length < 25) {
|
|
|
|
|
+ throw new IllegalArgumentException("请求帧长度不足,无法构造结束应答");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 1 系统识别码
|
|
|
|
|
+ byte[] sysId = Arrays.copyOfRange(request, 0, 3); // 12 34 56
|
|
|
|
|
+
|
|
|
|
|
+ // 2 包序号
|
|
|
|
|
+ byte packetSeq = request[5]; // 81
|
|
|
|
|
+
|
|
|
|
|
+ // 3 源/目的地址
|
|
|
|
|
+ byte srcLen = request[7];
|
|
|
|
|
+ byte[] srcAddr = Arrays.copyOfRange(request, 8, 14);
|
|
|
|
|
+ byte destLen = request[14];
|
|
|
|
|
+ byte[] destAddr = Arrays.copyOfRange(request, 15, 21);
|
|
|
|
|
+
|
|
|
|
|
+ // 4 当前时间转换为从 2000-01-01 开始的秒数
|
|
|
|
|
+ long secondsSince2000 = getSecondsSince2000();
|
|
|
|
|
+ byte[] timestamp = new byte[4];
|
|
|
|
|
+ timestamp[0] = (byte) ((secondsSince2000 >> 24) & 0xFF);
|
|
|
|
|
+ timestamp[1] = (byte) ((secondsSince2000 >> 16) & 0xFF);
|
|
|
|
|
+ timestamp[2] = (byte) ((secondsSince2000 >> 8) & 0xFF);
|
|
|
|
|
+ timestamp[3] = (byte) (secondsSince2000 & 0xFF);
|
|
|
|
|
+
|
|
|
|
|
+ // 5 构造内容区(从包序号开始,到时间戳结束)
|
|
|
|
|
+ ByteArrayOutputStream content = new ByteArrayOutputStream();
|
|
|
|
|
+ content.write(packetSeq); // 包序号
|
|
|
|
|
+ content.write(0x34); // 帧类型(替换成 0x34)
|
|
|
|
|
+ content.write(srcLen); // 源地址长度
|
|
|
|
|
+ content.write(srcAddr); // 源地址
|
|
|
|
|
+ content.write(destLen); // 目的地址长度
|
|
|
|
|
+ content.write(destAddr); // 目的地址
|
|
|
|
|
+ content.write(timestamp); // 当前时间戳
|
|
|
|
|
+ byte[] contentBytes = content.toByteArray();
|
|
|
|
|
+
|
|
|
|
|
+ // 6 计算帧长度(从包序号到时间戳的长度)
|
|
|
|
|
+ int len = contentBytes.length;
|
|
|
|
|
+ byte lenHigh = (byte) ((len >> 8) & 0xFF);
|
|
|
|
|
+ byte lenLow = (byte) (len & 0xFF);
|
|
|
|
|
+
|
|
|
|
|
+ // 7 拼完整帧(不含校验)
|
|
|
|
|
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
|
|
|
+ bos.write(sysId); // 系统识别码
|
|
|
|
|
+ bos.write(lenHigh); // 帧长度高
|
|
|
|
|
+ bos.write(lenLow); // 帧长度低
|
|
|
|
|
+ bos.write(contentBytes);
|
|
|
|
|
+
|
|
|
|
|
+ // 8 计算异或校验
|
|
|
|
|
+ byte[] noCheck = bos.toByteArray();
|
|
|
|
|
+ byte xor = 0x00;
|
|
|
|
|
+ for (byte b : noCheck) xor ^= b;
|
|
|
|
|
+ bos.write(xor); // 校验位
|
|
|
|
|
+
|
|
|
|
|
+ byte[] result = bos.toByteArray();
|
|
|
|
|
+
|
|
|
|
|
+ System.out.println("结束帧应答: " + printHex(result));
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static long getSecondsSince2000() {
|
|
|
|
|
+ LocalDateTime base = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
|
|
|
|
|
+ LocalDateTime now = LocalDateTime.now(ZoneOffset.of("+8"));
|
|
|
|
|
+ return Duration.between(base, now).getSeconds();
|
|
|
|
|
+ }
|
|
|
|
|
+ private static String printHex(byte[] bytes) {
|
|
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
|
|
+ for (byte b : bytes) sb.append(String.format("%02X ", b));
|
|
|
|
|
+ return sb.toString().trim();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|