|
|
@@ -0,0 +1,317 @@
|
|
|
+package com.sooka.sponest.construction.shengchang;
|
|
|
+
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 盛昌协议解析器
|
|
|
+ * 解析注册包、MBUS数据包,构建控制命令
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Component
|
|
|
+public class ShengChangProtocolParser {
|
|
|
+
|
|
|
+ // 注册包固定头
|
|
|
+ private static final byte REGISTER_START = 0x68;
|
|
|
+ private static final byte REGISTER_TYPE = (byte) 0x80;
|
|
|
+ private static final byte REGISTER_END = 0x16;
|
|
|
+
|
|
|
+ // MBUS协议固定值
|
|
|
+ private static final byte MBUS_START = 0x68;
|
|
|
+ private static final byte MBUS_END = 0x16;
|
|
|
+ private static final byte MBUS_SYNC = (byte) 0xFE;
|
|
|
+ private static final byte VALVE_TYPE = 0x42; // 大阀类型
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断是否为注册包
|
|
|
+ */
|
|
|
+ public boolean isRegisterPacket(byte[] data) {
|
|
|
+ if (data.length < 28) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // 检查注册包特征:68 80 ... 80 00 0E 00 0B
|
|
|
+ return data[0] == REGISTER_START &&
|
|
|
+ data[1] == REGISTER_TYPE &&
|
|
|
+ data[9] == REGISTER_TYPE &&
|
|
|
+ data[10] == 0x00 &&
|
|
|
+ data[11] == 0x0E &&
|
|
|
+ data[12] == 0x00 &&
|
|
|
+ data[13] == 0x0B &&
|
|
|
+ data[data.length - 1] == REGISTER_END;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断是否为MBUS数据包
|
|
|
+ */
|
|
|
+ public boolean isMbusPacket(byte[] data) {
|
|
|
+ if (data.length < 10) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // 查找起始码68和结束码16
|
|
|
+ int startIndex = -1;
|
|
|
+ for (int i = 0; i < data.length - 1; i++) {
|
|
|
+ if (data[i] == MBUS_START) {
|
|
|
+ startIndex = i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return startIndex >= 0 && data[data.length - 1] == MBUS_END;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析注册包,提取SIM卡号
|
|
|
+ */
|
|
|
+ public String parseRegisterPacket(byte[] data) {
|
|
|
+ if (!isRegisterPacket(data)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // SIM卡号从索引15开始,到倒数第3个字节(不包括校验和和结束符)
|
|
|
+ int simStart = 15;
|
|
|
+ int simEnd = data.length - 2; // 排除校验和和结束符
|
|
|
+ StringBuilder simNumber = new StringBuilder();
|
|
|
+ for (int i = simStart; i < simEnd; i++) {
|
|
|
+ simNumber.append((char) data[i]);
|
|
|
+ }
|
|
|
+ return simNumber.toString();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("解析注册包失败", e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析MBUS响应数据
|
|
|
+ */
|
|
|
+ public MbusResponse parseMbusResponse(byte[] data) {
|
|
|
+ if (!isMbusPacket(data)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 查找起始码位置
|
|
|
+ int startIndex = -1;
|
|
|
+ for (int i = 0; i < data.length; i++) {
|
|
|
+ if (data[i] == MBUS_START) {
|
|
|
+ startIndex = i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (startIndex < 0 || data.length - startIndex < 13) {
|
|
|
+ log.warn("MBUS数据包太短或找不到起始码");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ MbusResponse response = new MbusResponse();
|
|
|
+
|
|
|
+ // 解析表号(7字节)
|
|
|
+ byte[] meterNo = new byte[7];
|
|
|
+ System.arraycopy(data, startIndex + 2, meterNo, 0, 7);
|
|
|
+ response.setMeterNo(bytesToHex(meterNo));
|
|
|
+
|
|
|
+ // 解析命令字
|
|
|
+ byte command = data[startIndex + 9];
|
|
|
+ response.setCommand(command & 0xFF);
|
|
|
+
|
|
|
+ // 解析长度
|
|
|
+ int length = data[startIndex + 10] & 0xFF;
|
|
|
+ response.setDataLength(length);
|
|
|
+
|
|
|
+ // 解析数据域(根据命令字判断)
|
|
|
+ int cmdType = command & 0xFF;
|
|
|
+
|
|
|
+ // 0x17: 设备主动上报控制状态(只有开度数据)
|
|
|
+ if (cmdType == 0x17 && length >= 4) {
|
|
|
+ int dataStart = startIndex + 14;
|
|
|
+ if (data.length > dataStart) {
|
|
|
+ int opening = data[dataStart] & 0xFF;
|
|
|
+ response.setValveOpening(opening);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 0x81: 读取数据响应,0x97: 阀门控制响应(完整数据)
|
|
|
+ else if ((cmdType == 0x81 || cmdType == 0x97) && length >= 11) {
|
|
|
+ int dataStart = startIndex + 14;
|
|
|
+
|
|
|
+ if (data.length > dataStart) {
|
|
|
+ // 解析阀门开度
|
|
|
+ int opening = data[dataStart] & 0xFF;
|
|
|
+ response.setValveOpening(opening);
|
|
|
+
|
|
|
+ // 解析阀门入口压力(KPa)
|
|
|
+ if (data.length > dataStart + 2) {
|
|
|
+ int inletPressure = (data[dataStart + 1] & 0xFF) | ((data[dataStart + 2] & 0xFF) << 8);
|
|
|
+ response.setInletPressure(inletPressure);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析阀门出口压力(KPa)
|
|
|
+ if (data.length > dataStart + 4) {
|
|
|
+ int outletPressure = (data[dataStart + 3] & 0xFF) | ((data[dataStart + 4] & 0xFF) << 8);
|
|
|
+ response.setOutletPressure(outletPressure);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析供电电压(单位:百毫伏)
|
|
|
+ if (data.length > dataStart + 6) {
|
|
|
+ int voltage = (data[dataStart + 5] & 0xFF) | ((data[dataStart + 6] & 0xFF) << 8);
|
|
|
+ response.setVoltage(voltage / 10.0);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析状态位
|
|
|
+ if (data.length > dataStart + 7) {
|
|
|
+ int status = data[dataStart + 7] & 0xFF;
|
|
|
+ response.setStatus(status);
|
|
|
+ response.setAutoMode((status & 0x80) != 0);
|
|
|
+ response.setLowVoltage((status & 0x40) != 0);
|
|
|
+ response.setCurrentUnderload((status & 0x02) != 0);
|
|
|
+ response.setCurrentOverload((status & 0x01) != 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return response;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("解析MBUS响应失败", e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建阀门控制命令
|
|
|
+ *
|
|
|
+ * @param meterNo 表号(7字节十六进制字符串)
|
|
|
+ * @param openingValue 期望阀门开度(0-100)
|
|
|
+ * @return 命令字节数组
|
|
|
+ */
|
|
|
+ public byte[] buildValveControlCommand(String meterNo, int openingValue) {
|
|
|
+ try {
|
|
|
+ if (openingValue < 0 || openingValue > 100) {
|
|
|
+ throw new IllegalArgumentException("阀门开度必须在0-100之间");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据实际测试:设备需要前导码
|
|
|
+ // 虽然设备上报数据不带前导码,但下发控制命令必须带前导码
|
|
|
+ boolean useSyncCode = true; // 启用前导码
|
|
|
+
|
|
|
+ if (useSyncCode) {
|
|
|
+ // 带前导码格式(协议标准):FE FE 68 42 12 34 56 78 00 11 11 17 04 A0 17 00 32 93 16
|
|
|
+ byte[] command = new byte[19];
|
|
|
+
|
|
|
+ command[0] = MBUS_SYNC;
|
|
|
+ command[1] = MBUS_SYNC;
|
|
|
+ command[2] = MBUS_START;
|
|
|
+ command[3] = VALVE_TYPE;
|
|
|
+
|
|
|
+ byte[] meterBytes = hexStringToBytes(meterNo);
|
|
|
+ System.arraycopy(meterBytes, 0, command, 4, 7);
|
|
|
+
|
|
|
+ command[11] = 0x17; // 命令字
|
|
|
+ command[12] = 0x04; // 长度
|
|
|
+ command[13] = (byte) 0xA0; // 控制域
|
|
|
+ command[14] = 0x17;
|
|
|
+ command[15] = 0x00;
|
|
|
+ command[16] = (byte) (openingValue & 0xFF); // 开度
|
|
|
+ command[17] = calculateChecksum(command, 2, 17); // 校验和
|
|
|
+ command[18] = MBUS_END; // 结束符
|
|
|
+
|
|
|
+ return command;
|
|
|
+ } else {
|
|
|
+ // 无前导码格式(参考设备上报):68 42 64 03 08 21 00 00 00 17 04 A0 17 00 64 70 16
|
|
|
+ byte[] command = new byte[17];
|
|
|
+
|
|
|
+ command[0] = MBUS_START;
|
|
|
+ command[1] = VALVE_TYPE;
|
|
|
+
|
|
|
+ byte[] meterBytes = hexStringToBytes(meterNo);
|
|
|
+ System.arraycopy(meterBytes, 0, command, 2, 7);
|
|
|
+
|
|
|
+ command[9] = 0x17; // 命令字
|
|
|
+ command[10] = 0x04; // 长度
|
|
|
+ command[11] = (byte) 0xA0; // 控制域
|
|
|
+ command[12] = 0x17;
|
|
|
+ command[13] = 0x00;
|
|
|
+ command[14] = (byte) (openingValue & 0xFF); // 开度
|
|
|
+ command[15] = calculateChecksum(command, 0, 15); // 校验和(从0x68开始)
|
|
|
+ command[16] = MBUS_END; // 结束符
|
|
|
+
|
|
|
+ log.info("构建命令 - TCP格式(无前导码,参考设备上报)");
|
|
|
+ return command;
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("构建阀门控制命令失败", e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建读取数据命令
|
|
|
+ *
|
|
|
+ * @param meterNo 表号(7字节十六进制字符串)
|
|
|
+ * @return 命令字节数组
|
|
|
+ */
|
|
|
+ public byte[] buildReadCommand(String meterNo) {
|
|
|
+ try {
|
|
|
+ // 读取命令格式:FE FE 68 42 12 34 56 78 00 11 11 01 03 90 1F 00 93 16
|
|
|
+ byte[] command = new byte[18];
|
|
|
+
|
|
|
+ command[0] = MBUS_SYNC;
|
|
|
+ command[1] = MBUS_SYNC;
|
|
|
+ command[2] = MBUS_START;
|
|
|
+ command[3] = VALVE_TYPE;
|
|
|
+
|
|
|
+ byte[] meterBytes = hexStringToBytes(meterNo);
|
|
|
+ System.arraycopy(meterBytes, 0, command, 4, 7);
|
|
|
+
|
|
|
+ command[11] = 0x01; // 命令字:读取
|
|
|
+ command[12] = 0x03; // 长度
|
|
|
+ command[13] = (byte) 0x90; // 控制域
|
|
|
+ command[14] = 0x1F;
|
|
|
+ command[15] = 0x00;
|
|
|
+ command[16] = calculateChecksum(command, 2, 16); // 校验和
|
|
|
+ command[17] = MBUS_END; // 结束符
|
|
|
+
|
|
|
+ log.info("构建读取命令 - 18字节");
|
|
|
+ return command;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("构建读取命令失败", e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算校验和
|
|
|
+ */
|
|
|
+ private byte calculateChecksum(byte[] data, int start, int end) {
|
|
|
+ int sum = 0;
|
|
|
+ for (int i = start; i < end; i++) {
|
|
|
+ sum += data[i] & 0xFF;
|
|
|
+ }
|
|
|
+ return (byte) (sum & 0xFF);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 十六进制字符串转字节数组
|
|
|
+ */
|
|
|
+ private byte[] hexStringToBytes(String hexString) {
|
|
|
+ // 移除空格
|
|
|
+ hexString = hexString.replace(" ", "");
|
|
|
+ int len = hexString.length();
|
|
|
+ byte[] data = new byte[len / 2];
|
|
|
+ for (int i = 0; i < len; i += 2) {
|
|
|
+ data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
|
|
|
+ + Character.digit(hexString.charAt(i + 1), 16));
|
|
|
+ }
|
|
|
+ return data;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 字节数组转十六进制字符串
|
|
|
+ */
|
|
|
+ private String bytesToHex(byte[] bytes) {
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (byte b : bytes) {
|
|
|
+ sb.append(String.format("%02X", b & 0xFF));
|
|
|
+ }
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+}
|