ProtocolUtils.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. package com.zksy.telemetry.utils;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.IOException;
  4. import java.time.Duration;
  5. import java.time.LocalDateTime;
  6. import java.time.ZoneOffset;
  7. import java.util.Arrays;
  8. import java.util.logging.Logger;
  9. public class ProtocolUtils {
  10. private static final Logger logger = Logger.getLogger(ProtocolUtils.class.getName());
  11. /**
  12. * 将2000年起始的时间戳转换为LocalDateTime
  13. */
  14. static LocalDateTime convertToDateTime(int timestamp) {
  15. try {
  16. // 定义基准时间:2000年1月1日 00:00:00
  17. LocalDateTime baseTime = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
  18. // 累加时间戳(秒数)
  19. return baseTime.plusSeconds(timestamp);
  20. } catch (Exception e) {
  21. return null;
  22. }
  23. }
  24. // 获取当前时间并转换为2000年起始的时间戳
  25. static int getCurrentTimestampSince2000() {
  26. LocalDateTime now = LocalDateTime.now(ZoneOffset.of("+8"));
  27. LocalDateTime baseTime = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
  28. // 计算时间差(秒数)
  29. return (int) java.time.Duration.between(baseTime, now).getSeconds();
  30. }
  31. public static int calculateCRC16(byte[] data) {
  32. int crc = 0xFFFF;
  33. for (byte b : data) {
  34. crc ^= (b & 0xFF);
  35. for (int i = 0; i < 8; i++) {
  36. if ((crc & 0x0001) != 0) crc = (crc >> 1) ^ 0xA001;
  37. else crc >>= 1;
  38. }
  39. }
  40. int high = (crc & 0xFF00) >> 8;
  41. int low = crc & 0x00FF;
  42. return (high << 8) | low;
  43. }
  44. public static byte calculateXorCheck(byte[] data) {
  45. byte check = 0x00;
  46. for (byte b : data) check ^= b;
  47. return check;
  48. }
  49. /**
  50. * 构建上报回应包(对应帧类型 0x31)
  51. * 结构:
  52. * [系统识别码(3)] [整帧长度(2)] [包序号(1)] [帧类型0x31(1)]
  53. * [源地址长度(1)] [源地址(n)] [目的地址长度(1)] [目的地址(m)] [帧校验(1)]
  54. *
  55. * 示例(文档第11页):
  56. * 12 34 56 00 16 81 31 0B 00 00 00 00 00 10 0B 13 81 23 45 67 80 D5
  57. */
  58. public static byte[] buildReportAckPacket(byte[] originalFrame, byte packetSequence) {
  59. int srcLenIndex = 7;
  60. int srcLen = originalFrame[srcLenIndex] & 0xFF;
  61. int srcByteLen = (srcLen + 1) / 2;
  62. int srcStart = srcLenIndex + 1;
  63. int srcEnd = srcStart + srcByteLen;
  64. int dstLenIndex = srcEnd;
  65. int dstLen = originalFrame[dstLenIndex] & 0xFF;
  66. int dstByteLen = (dstLen + 1) / 2;
  67. int dstStart = dstLenIndex + 1;
  68. int dstEnd = dstStart + dstByteLen;
  69. byte[] response = new byte[3 + 2 + 1 + 1 + 1 + dstByteLen + 1 + srcByteLen + 1];
  70. int idx = 0;
  71. // 系统识别码 12 34 56
  72. response[idx++] = 0x12;
  73. response[idx++] = 0x34;
  74. response[idx++] = 0x56;
  75. // 长度(待填充)
  76. int totalLen = response.length;
  77. response[idx++] = (byte) ((totalLen >> 8) & 0xFF);
  78. response[idx++] = (byte) (totalLen & 0xFF);
  79. // 包序号(与原帧一致)
  80. response[idx++] = packetSequence;
  81. // 帧类型:上报回应
  82. response[idx++] = 0x31;
  83. // 源地址 = 原帧的目的地址
  84. response[idx++] = (byte) dstLen;
  85. System.arraycopy(originalFrame, dstStart, response, idx, dstByteLen);
  86. idx += dstByteLen;
  87. // 目的地址 = 原帧的源地址
  88. response[idx++] = (byte) srcLen;
  89. System.arraycopy(originalFrame, srcStart, response, idx, srcByteLen);
  90. idx += srcByteLen;
  91. // 异或校验(从系统识别码到帧内容)
  92. byte xor = calculateXorCheck(Arrays.copyOfRange(response, 0, idx));
  93. response[idx++] = xor;
  94. return response;
  95. }
  96. /**
  97. * 构建结束通讯回应包(对应帧类型 0x34)
  98. * 结构:
  99. * [系统识别码(3)] [整帧长度(2)] [包序号(0x80)] [帧类型0x34(1)]
  100. * [源地址长度(1)] [源地址(n)] [目的地址长度(1)] [目的地址(m)] [帧校验(1)]
  101. *
  102. * 示例(文档第12页):
  103. * 12 34 56 00 16 80 34 0B 13 81 23 45 67 80 0B 00 00 00 00 00 10 D1
  104. */
  105. public static byte[] buildShutdownAckPacket(byte[] originalFrame) {
  106. // 步骤1:动态提取地址信息(对齐validateMessage)
  107. int[] addrInfo = extractAddrInfo(originalFrame);
  108. int srcLenFlag = addrInfo[0];
  109. int srcAddrByteLen = addrInfo[1];
  110. int srcAddrStart = addrInfo[2];
  111. int srcAddrEnd = addrInfo[3];
  112. int destLenFlag = addrInfo[4];
  113. int destAddrByteLen = addrInfo[5];
  114. int destAddrStart = addrInfo[6];
  115. int destAddrEnd = addrInfo[7];
  116. // 步骤2:提取地址字节
  117. byte[] srcAddr = Arrays.copyOfRange(originalFrame, srcAddrStart, srcAddrEnd);
  118. byte[] destAddr = Arrays.copyOfRange(originalFrame, destAddrStart, destAddrEnd);
  119. // 步骤3:构造应答帧
  120. int totalLen = 3 + 2 + 1 + 1 + 1 + destAddrByteLen + 1 + srcAddrByteLen + 1;
  121. byte[] response = new byte[totalLen];
  122. int idx = 0;
  123. // 系统识别码
  124. response[idx++] = 0x12;
  125. response[idx++] = 0x34;
  126. response[idx++] = 0x56;
  127. // 长度
  128. response[idx++] = (byte) ((totalLen >> 8) & 0xFF);
  129. response[idx++] = (byte) (totalLen & 0xFF);
  130. // 包序号0x80 + 帧类型0x34
  131. response[idx++] = (byte) 0x80;
  132. response[idx++] = 0x34;
  133. // 源地址=原目的地址(动态)
  134. response[idx++] = (byte) destLenFlag;
  135. System.arraycopy(destAddr, 0, response, idx, destAddrByteLen);
  136. idx += destAddrByteLen;
  137. // 目的地址=原源地址(动态)
  138. response[idx++] = (byte) srcLenFlag;
  139. System.arraycopy(srcAddr, 0, response, idx, srcAddrByteLen);
  140. idx += srcAddrByteLen;
  141. // 异或校验
  142. byte xor = calculateXorCheck(Arrays.copyOfRange(response, 0, idx));
  143. response[idx++] = xor;
  144. return response;
  145. }
  146. public static byte[] buildCustomReplyFrame(byte[] request) throws IOException {
  147. // 步骤1:动态提取地址信息(对齐validateMessage)
  148. int[] addrInfo = extractAddrInfo(request);
  149. int srcLenFlag = addrInfo[0];
  150. int srcAddrByteLen = addrInfo[1]; // 原源地址长度 m
  151. int srcAddrStart = addrInfo[2];
  152. int srcAddrEnd = addrInfo[3];
  153. int destLenFlag = addrInfo[4];
  154. int destAddrByteLen = addrInfo[5]; // 原目的地址长度 n
  155. int destAddrStart = addrInfo[6];
  156. int destAddrEnd = addrInfo[7];
  157. // 步骤2:提取地址字节
  158. byte[] srcAddr = Arrays.copyOfRange(request, srcAddrStart, srcAddrEnd);
  159. byte[] destAddr = Arrays.copyOfRange(request, destAddrStart, destAddrEnd);
  160. // 步骤3:固定字段提取
  161. byte[] sysId = Arrays.copyOfRange(request, 0, 3); // 系统识别码(3字节)
  162. byte packetSeq = request[5]; // 包序号(1字节)
  163. byte frameType = request[6]; // 帧类型0x31(1字节)
  164. // 帧长度字段值 = 系统识别码(3字节)+2(自身) + 1(包序号) + 1(帧类型) + 1(源地址标识) + n(源地址) + 1(目的地址标识) + m(目的地址)
  165. int frameLenValue = 3 + 2 + 1 + 1 + 1 + destAddrByteLen + 1 + srcAddrByteLen+1;
  166. // 步骤4:构造帧体(直接写入正确长度,无需占位回填)
  167. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  168. bos.write(sysId); // 系统识别码(3字节)
  169. // 写入帧长度字段(包含自身2字节,高字节在前)
  170. bos.write((byte) ((frameLenValue >> 8) & 0xFF));
  171. bos.write((byte) (frameLenValue & 0xFF));
  172. // 步骤5:地址交换(动态长度)
  173. bos.write(packetSeq); // 包序号
  174. bos.write(frameType); // 帧类型
  175. bos.write(destLenFlag); // 应答源地址标识=原目的标识
  176. bos.write(destAddr); // 应答源地址=原目的地址(n字节)
  177. bos.write(srcLenFlag); // 应答目的地址标识=原源标识
  178. bos.write(srcAddr); // 应答目的地址=原源地址(m字节)
  179. // 步骤6:计算异或校验(仅计算不含校验位的帧体)
  180. byte[] tempFrame = bos.toByteArray();
  181. byte xor = calculateXorCheck(tempFrame);
  182. // 步骤7:拼接最终帧(帧体 + 异或校验位)
  183. ByteArrayOutputStream finalBos = new ByteArrayOutputStream();
  184. finalBos.write(tempFrame);
  185. finalBos.write(xor);
  186. return finalBos.toByteArray();
  187. }
  188. public static byte[] buildEndReplyFrame(byte[] request) throws IOException {
  189. // 步骤1:动态提取地址信息(对齐validateMessage)
  190. int[] addrInfo = extractAddrInfo(request);
  191. int srcLenFlag = addrInfo[0];
  192. int srcAddrByteLen = addrInfo[1]; // 源地址长度 n
  193. int srcAddrStart = addrInfo[2];
  194. int srcAddrEnd = addrInfo[3];
  195. int destLenFlag = addrInfo[4];
  196. int destAddrByteLen = addrInfo[5]; // 目的地址长度 m
  197. int destAddrStart = addrInfo[6];
  198. int destAddrEnd = addrInfo[7];
  199. // 步骤2:提取地址字节
  200. byte[] srcAddr = Arrays.copyOfRange(request, srcAddrStart, srcAddrEnd);
  201. byte[] destAddr = Arrays.copyOfRange(request, destAddrStart, destAddrEnd);
  202. // 步骤3:固定字段提取
  203. byte[] sysId = Arrays.copyOfRange(request, 0, 3); // 系统识别码(3字节)
  204. byte packetSeq = request[5]; // 包序号(1字节)
  205. // 步骤4:时间戳(4字节)
  206. long secondsSince2000 = getSecondsSince2000();
  207. byte[] timestamp = new byte[4];
  208. timestamp[0] = (byte) ((secondsSince2000 >> 24) & 0xFF);
  209. timestamp[1] = (byte) ((secondsSince2000 >> 16) & 0xFF);
  210. timestamp[2] = (byte) ((secondsSince2000 >> 8) & 0xFF);
  211. timestamp[3] = (byte) (secondsSince2000 & 0xFF);
  212. // ========== 核心修正:计算包含自身2字节的帧长度字段值 ==========
  213. // 帧长度字段值 = 2(自身) + 1(包序号) + 1(帧类型) + 1(源地址标识) + n(源地址) + 1(目的地址标识) + m(目的地址) + 4(时间戳)
  214. int frameLenValue =3+ 2 + 1 + 1 + 1 + srcAddrByteLen + 1 + destAddrByteLen + 4+1;
  215. // 拆分高低字节
  216. byte lenHigh = (byte) ((frameLenValue >> 8) & 0xFF);
  217. byte lenLow = (byte) (frameLenValue & 0xFF);
  218. // 步骤5:构造内容区(包序号到时间戳,不含长度字段)
  219. ByteArrayOutputStream content = new ByteArrayOutputStream();
  220. content.write(0X80); // 包序号(1字节)
  221. content.write(0x34); // 帧类型0x34(1字节)
  222. content.write(destLenFlag);// 目的地址标识(1字节)
  223. content.write(destAddr); // 目的地址(m字节)
  224. content.write(srcLenFlag); // 源地址标识(1字节)srcLenFlag
  225. content.write(srcAddr); // 源地址(n字节)
  226. content.write(timestamp); // 时间戳(4字节)
  227. byte[] contentBytes = content.toByteArray();
  228. // 步骤6:拼帧(系统识别码 + 长度字段 + 内容区)
  229. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  230. bos.write(sysId); // 系统识别码(3字节)
  231. bos.write(lenHigh); // 长度高字节(包含自身2字节的总长度)
  232. bos.write(lenLow); // 长度低字节
  233. bos.write(contentBytes);
  234. // 步骤7:计算并添加异或校验(仅计算不含校验位的帧体)
  235. byte[] noCheck = bos.toByteArray();
  236. byte xor = calculateXorCheck(noCheck);
  237. bos.write(xor);
  238. byte[] result = bos.toByteArray();
  239. System.out.println("结束帧应答: " + printHex(result));
  240. return result;
  241. }
  242. private static long getSecondsSince2000() {
  243. LocalDateTime base = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
  244. LocalDateTime now = LocalDateTime.now(ZoneOffset.of("+8"));
  245. return Duration.between(base, now).getSeconds();
  246. }
  247. private static String printHex(byte[] bytes) {
  248. StringBuilder sb = new StringBuilder();
  249. for (byte b : bytes) sb.append(String.format("%02X ", b));
  250. return sb.toString().trim();
  251. }
  252. /**
  253. * 核心工具方法:根据地址标识计算地址字节长度
  254. * 规则:标识值转10进制 → 奇数+1 → 除以2
  255. */
  256. private static int calculateAddrByteLength(int lenFlag) {
  257. int temp = lenFlag;
  258. // 奇数则+1
  259. if (temp % 2 != 0) {
  260. temp += 1;
  261. }
  262. // 除以2得到字节长度
  263. return temp / 2;
  264. }
  265. /**
  266. * 核心工具:正向解析源/目的地址信息(贴合实际报文结构)
  267. * 逻辑:
  268. * 1. 正向定位源地址标识(索引7)、源地址、目的地址标识、目的地址
  269. * 2. 找 0x01 2C 验证地址段结束位置,确保解析合法
  270. * @return 数组格式:[srcLenFlag, srcAddrByteLen, srcAddrStart, srcAddrEnd, destLenFlag, destAddrByteLen, destAddrStart, destAddrEnd]
  271. */
  272. private static int[] extractAddrInfo(byte[] request) {
  273. if (request == null || request.length < 25) { // 适配实际报文最小长度(82字节)
  274. throw new IllegalArgumentException("请求帧长度不足,无法解析地址信息,长度=" + (request == null ? 0 : request.length));
  275. }
  276. // ========== 步骤1:正向定位核心索引(贴合协议固定结构) ==========
  277. int srcLenIndex = 7; // 源地址标识固定在索引7
  278. int srcLenFlag = request[srcLenIndex] & 0xFF;
  279. int srcAddrByteLen = calculateAddrByteLength(srcLenFlag);
  280. int srcAddrStart = srcLenIndex + 1;
  281. int srcAddrEnd = srcAddrStart + srcAddrByteLen;
  282. // 目的地址标识 = 源地址结束索引
  283. int destLenIndex = srcAddrEnd;
  284. int destLenFlag = request[destLenIndex] & 0xFF;
  285. int destAddrByteLen = calculateAddrByteLength(destLenFlag);
  286. int destAddrStart = destLenIndex + 1;
  287. int destAddrEnd = destAddrStart + destAddrByteLen;
  288. // ========== 步骤2:验证地址段结束位置(找 0x01 2C) ==========
  289. int deviceCodeIndex = destAddrEnd;
  290. // 校验设备编码+功能码是否为 0x01 2C(地址段后必须紧跟)
  291. if (deviceCodeIndex + 1 >= request.length
  292. || request[deviceCodeIndex] != 0x01
  293. || request[deviceCodeIndex + 1] != 0x2C) {
  294. throw new IllegalArgumentException("地址段结束后未找到 0x01 2C 序列,地址解析异常:设备编码索引=" + deviceCodeIndex);
  295. }
  296. // ========== 步骤3:范围最终校验 ==========
  297. // 源地址范围校验
  298. if (srcAddrStart < 8 || srcAddrEnd > destLenIndex) {
  299. throw new IllegalArgumentException(
  300. String.format("源地址范围异常:标识索引=%d 标识值=%d 计算长度=%d 起始=%d 结束=%d",
  301. srcLenIndex, srcLenFlag, srcAddrByteLen, srcAddrStart, srcAddrEnd));
  302. }
  303. // 目的地址范围校验
  304. if (destAddrStart < (destLenIndex + 1) || destAddrEnd > deviceCodeIndex) {
  305. throw new IllegalArgumentException(
  306. String.format("目的地址范围异常:标识索引=%d 标识值=%d 计算长度=%d 起始=%d 结束=%d",
  307. destLenIndex, destLenFlag, destAddrByteLen, destAddrStart, destAddrEnd));
  308. }
  309. return new int[]{
  310. srcLenFlag, srcAddrByteLen, srcAddrStart, srcAddrEnd,
  311. destLenFlag, destAddrByteLen, destAddrStart, destAddrEnd
  312. };
  313. }
  314. }