21 changed files with 1284 additions and 38 deletions
@ -0,0 +1,87 @@
|
||||
package com.mh.user.dto; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat; |
||||
import lombok.Getter; |
||||
import lombok.Setter; |
||||
|
||||
import java.math.BigDecimal; |
||||
import java.util.Date; |
||||
import java.util.StringJoiner; |
||||
|
||||
/** |
||||
* @author LJF |
||||
* @version 1.0 |
||||
* @project EEMCS |
||||
* @description 生活热水监控需要的列表信息 |
||||
* @date 2025-03-14 09:07:46 |
||||
*/ |
||||
@Setter |
||||
@Getter |
||||
public class HotWaterControlZDListVO { |
||||
|
||||
private String cpmId; |
||||
|
||||
private String registerAddr; |
||||
|
||||
private String buildingId; |
||||
|
||||
private String buildingName; |
||||
|
||||
private String deviceType; |
||||
|
||||
private String deviceId; |
||||
|
||||
private String deviceName; |
||||
|
||||
private String curValue; |
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
||||
private Date curTime; |
||||
|
||||
private String paramTypeId; |
||||
|
||||
private String paramTypeGroupId; |
||||
|
||||
private String otherName; |
||||
|
||||
private int orderNum; |
||||
|
||||
private int dlOrderNum; |
||||
|
||||
private int ctOrderNum; |
||||
|
||||
private int digits; |
||||
|
||||
public String getCurValue() { |
||||
return curValue; |
||||
} |
||||
|
||||
public void setCurValue(String curValue) { |
||||
// 判断curValue是否是数字类型
|
||||
boolean isNumeric = curValue!= null && curValue.matches("[0-9]+(\\.[0-9]+)?"); |
||||
if (curValue!= null && isNumeric) { |
||||
// 保留两位小数
|
||||
curValue = new BigDecimal(curValue).setScale(2, BigDecimal.ROUND_HALF_UP).toString(); |
||||
} |
||||
this.curValue = curValue; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return new StringJoiner(", ", HotWaterControlZDListVO.class.getSimpleName() + "[", "]") |
||||
.add("cpmId='" + cpmId + "'") |
||||
.add("buildingId='" + buildingId + "'") |
||||
.add("buildingName='" + buildingName + "'") |
||||
.add("deviceType='" + deviceType + "'") |
||||
.add("deviceId='" + deviceId + "'") |
||||
.add("deviceName='" + deviceName + "'") |
||||
.add("curValue=" + curValue) |
||||
.add("curTime=" + curTime) |
||||
.add("paramTypeId='" + paramTypeId + "'") |
||||
.add("otherName='" + otherName + "'") |
||||
.add("orderNum=" + orderNum) |
||||
.add("dlOrderNum=" + dlOrderNum) |
||||
.add("ctOrderNum=" + ctOrderNum) |
||||
.toString(); |
||||
} |
||||
} |
||||
@ -0,0 +1,362 @@
|
||||
package com.mh.user.utils; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.function.Function; |
||||
|
||||
/** |
||||
* @author LJF |
||||
* @version 1.0 |
||||
* @project CHWS |
||||
* @description 正帝热泵协议解析 |
||||
* @date 2026-05-25 17:15:42 |
||||
*/ |
||||
public class XK814ModbusParamReadUtil { |
||||
/** |
||||
* XK814 冷热水机 Modbus 协议 V1.0 解析器(第三张表) |
||||
* 地址范围:A000 ~ B009, AFFF |
||||
*/ |
||||
// ==================== 地址常量(字符串格式)====================
|
||||
public static final String ADDR_DISPLAY_WATER_TEMP = "A000"; // 40960
|
||||
public static final String ADDR_TANK_TEMP = "A001"; // 40961
|
||||
public static final String ADDR_OUTLET_TEMP = "A002"; // 40962
|
||||
public static final String ADDR_INLET_TEMP = "A003"; // 40963
|
||||
public static final String ADDR_AMBIENT_TEMP = "A004"; // 40964
|
||||
public static final String ADDR_COIL1_TEMP = "A005"; // 40965
|
||||
public static final String ADDR_SUCTION1_TEMP = "A006"; // 40966
|
||||
public static final String ADDR_EXHAUST1_TEMP = "A007"; // 40967
|
||||
public static final String ADDR_VALVE_AFTER1_OR_RETURN = "A008"; // 40968
|
||||
public static final String ADDR_RESERVED_A009 = "A009"; |
||||
public static final String ADDR_RESERVED_A00A = "A00A"; |
||||
public static final String ADDR_MAIN_EXP_VALVE1_OPEN = "A00B"; // 40971
|
||||
public static final String ADDR_AUX_EXP_VALVE1_OPEN = "A00C"; // 40972
|
||||
public static final String ADDR_COMP1_CURRENT = "A00D"; // 40973
|
||||
public static final String ADDR_COIL2_TEMP = "A00E"; // 40974
|
||||
public static final String ADDR_SUCTION2_TEMP = "A00F"; // 40975
|
||||
public static final String ADDR_EXHAUST2_TEMP = "A010"; // 40976
|
||||
public static final String ADDR_VALVE_AFTER2_TEMP = "A011"; // 40977
|
||||
public static final String ADDR_RESERVED_A012 = "A012"; |
||||
public static final String ADDR_RESERVED_A013 = "A013"; |
||||
public static final String ADDR_MAIN_EXP_VALVE2_OPEN = "A014"; // 40980
|
||||
public static final String ADDR_AUX_EXP_VALVE2_OPEN = "A015"; // 40981
|
||||
public static final String ADDR_COMP2_CURRENT = "A016"; // 40982
|
||||
public static final String ADDR_RECOVERABLE_FAULT = "A017"; // 40983
|
||||
public static final String ADDR_UNRECOVERABLE_FAULT = "A018"; // 40984
|
||||
public static final String ADDR_VERSION_INFO = "A019"; // 40985
|
||||
public static final String ADDR_INPUT_SWITCH_STATUS = "A01A"; // 40986
|
||||
public static final String ADDR_WATER_LEVEL_SWITCH = "A01B"; // 40987
|
||||
public static final String ADDR_OUTPUT1_STATUS = "A01C"; // 40988
|
||||
public static final String ADDR_OUTPUT2_STATUS = "A01D"; // 40989
|
||||
public static final String ADDR_OUTPUT3_STATUS = "A01E"; // 40990
|
||||
public static final String ADDR_DIP_SWITCH = "A01F"; // 40991
|
||||
public static final String ADDR_MAINBOARD_TYPE = "A020"; // 40992
|
||||
public static final String ADDR_CONSTANT_TEMP_SET = "AFFF"; // 45055
|
||||
public static final String ADDR_ON_OFF = "B000"; // 45056
|
||||
public static final String ADDR_COOL_SET_TEMP = "B001"; // 45057
|
||||
public static final String ADDR_HEAT_SET_TEMP = "B002"; // 45058
|
||||
public static final String ADDR_MODE = "B003"; // 45059
|
||||
public static final String ADDR_HOT_WATER_SET_TEMP = "B004"; // 45060
|
||||
public static final String ADDR_FORCE_ELECTRIC_HEAT = "B005"; // 45061
|
||||
public static final String ADDR_FORCE_DEFROST = "B006"; // 45062
|
||||
public static final String ADDR_RESERVED_B007 = "B007"; |
||||
public static final String ADDR_RETURN_VALVE_TIMER = "B008"; // 45064
|
||||
public static final String ADDR_SUPPLY_VALVE_TIMER = "B009"; // 45065
|
||||
|
||||
// ==================== 解析器映射表 ====================
|
||||
private static final Map<String, Function<Integer, Object>> PARSERS = new HashMap<>(); |
||||
|
||||
// ==================== 编码器映射表(写操作) ====================
|
||||
private static final Map<String, Function<Object, Integer>> ENCODERS = new HashMap<>(); |
||||
|
||||
static { |
||||
// ---------- TEMP1 类型(无符号byte,低8位有效)----------
|
||||
// 公式:温度 = (raw - 64) / 2,raw=1 表示传感器故障/未接
|
||||
Function<Integer, Object> temp1Parser = v -> parseTemp1(v & 0xFF); |
||||
Function<Object, Integer> temp1Encoder = val -> encodeTemp1((Double) val); |
||||
|
||||
// ---------- TEMP2 类型(无符号byte,低8位有效)----------
|
||||
// 公式:温度 = raw - 32,raw=1 表示传感器故障/未接
|
||||
Function<Integer, Object> temp2Parser = v -> parseTemp2(v & 0xFF); |
||||
Function<Object, Integer> temp2Encoder = val -> encodeTemp2((Integer) val); |
||||
|
||||
// ---------- DIGI1 类型(无符号byte,低8位有效)----------
|
||||
Function<Integer, Object> digi1Parser = v -> v & 0xFF; |
||||
Function<Object, Integer> digi1Encoder = val -> ((Number) val).intValue() & 0xFF ; |
||||
|
||||
// ---------- DIG2 类型(16位无符号整型)----------
|
||||
Function<Integer, Object> dig2Parser = v -> v & 0xFFFF; |
||||
Function<Object, Integer> dig2Encoder = val -> ((Number) val).intValue() & 0xFFFF; |
||||
|
||||
// 地址配置(解析器 + 编码器)
|
||||
// 温度读取地址(只读)
|
||||
String[] temp1Addrs = {ADDR_DISPLAY_WATER_TEMP, ADDR_TANK_TEMP, ADDR_OUTLET_TEMP, |
||||
ADDR_INLET_TEMP, ADDR_AMBIENT_TEMP, ADDR_COIL1_TEMP, ADDR_SUCTION1_TEMP, |
||||
ADDR_VALVE_AFTER1_OR_RETURN, ADDR_COIL2_TEMP, ADDR_SUCTION2_TEMP, |
||||
ADDR_VALVE_AFTER2_TEMP}; |
||||
for (String addr : temp1Addrs) { |
||||
PARSERS.put(addr, temp1Parser); |
||||
// 这些是只读地址,不设置编码器
|
||||
} |
||||
// TEMP2 只读
|
||||
String[] temp2Addrs = {ADDR_EXHAUST1_TEMP, ADDR_EXHAUST2_TEMP}; |
||||
for (String addr : temp2Addrs) { |
||||
PARSERS.put(addr, temp2Parser); |
||||
} |
||||
|
||||
// DIG2 只读
|
||||
String[] dig2Addrs = {ADDR_MAIN_EXP_VALVE1_OPEN, ADDR_AUX_EXP_VALVE1_OPEN, |
||||
ADDR_MAIN_EXP_VALVE2_OPEN, ADDR_AUX_EXP_VALVE2_OPEN}; |
||||
for (String addr : dig2Addrs) { |
||||
PARSERS.put(addr, dig2Parser); |
||||
} |
||||
|
||||
// DIGI1 只读
|
||||
String[] digi1Addrs = {ADDR_COMP1_CURRENT, ADDR_COMP2_CURRENT, |
||||
ADDR_RECOVERABLE_FAULT, ADDR_UNRECOVERABLE_FAULT}; |
||||
for (String addr : digi1Addrs) { |
||||
PARSERS.put(addr, digi1Parser); |
||||
} |
||||
|
||||
// 版本号(特殊解析)
|
||||
PARSERS.put(ADDR_VERSION_INFO, XK814ModbusParamReadUtil::parseVersion); |
||||
// 位域状态
|
||||
PARSERS.put(ADDR_INPUT_SWITCH_STATUS, XK814ModbusParamReadUtil::parseInputSwitchStatus); |
||||
PARSERS.put(ADDR_WATER_LEVEL_SWITCH, XK814ModbusParamReadUtil::parseWaterLevelSwitch); |
||||
PARSERS.put(ADDR_OUTPUT1_STATUS, XK814ModbusParamReadUtil::parseOutput1Status); |
||||
PARSERS.put(ADDR_OUTPUT2_STATUS, XK814ModbusParamReadUtil::parseOutput2Status); |
||||
PARSERS.put(ADDR_OUTPUT3_STATUS, XK814ModbusParamReadUtil::parseOutput3Status); |
||||
PARSERS.put(ADDR_DIP_SWITCH, XK814ModbusParamReadUtil::parseDipSwitch); |
||||
PARSERS.put(ADDR_MAINBOARD_TYPE, XK814ModbusParamReadUtil::parseMainboardType); |
||||
|
||||
// ========== 可读写地址 ==========
|
||||
// AFFFH 恒温设置温度 (TEMP2)
|
||||
PARSERS.put(ADDR_CONSTANT_TEMP_SET, temp2Parser); |
||||
ENCODERS.put(ADDR_CONSTANT_TEMP_SET, temp2Encoder); |
||||
|
||||
// B000H 开关机 (0关机,1开机)
|
||||
PARSERS.put(ADDR_ON_OFF, v -> (v & 0xFF) == 0 ? "关机" : "开机"); |
||||
ENCODERS.put(ADDR_ON_OFF, val -> (val.equals("开机") ? 1 : 0) & 0xFF); |
||||
|
||||
// B001H 制冷设置温度 (TEMP2)
|
||||
PARSERS.put(ADDR_COOL_SET_TEMP, temp2Parser); |
||||
ENCODERS.put(ADDR_COOL_SET_TEMP, temp2Encoder); |
||||
// B002H 制热设置温度 (TEMP2)
|
||||
PARSERS.put(ADDR_HEAT_SET_TEMP, temp2Parser); |
||||
ENCODERS.put(ADDR_HEAT_SET_TEMP, temp2Encoder); |
||||
// B003H 运行模式 (0制热,1制冷,2恒温)
|
||||
PARSERS.put(ADDR_MODE, v -> { |
||||
int mode = v & 0xFF; |
||||
switch (mode) { |
||||
case 0: |
||||
return "制热"; |
||||
case 1: |
||||
return "制冷"; |
||||
case 2: |
||||
return "恒温"; |
||||
default: |
||||
return "未知"; |
||||
} |
||||
}); |
||||
ENCODERS.put(ADDR_MODE, val -> { |
||||
String mode = (String) val; |
||||
switch (mode) { |
||||
case "制热": |
||||
return 0; |
||||
case "制冷": |
||||
return 1; |
||||
case "恒温": |
||||
return 2; |
||||
default: |
||||
throw new IllegalArgumentException("无效模式: " + mode); |
||||
} |
||||
}); |
||||
// B004H 热水设置温度 (TEMP2)
|
||||
PARSERS.put(ADDR_HOT_WATER_SET_TEMP, temp2Parser); |
||||
ENCODERS.put(ADDR_HOT_WATER_SET_TEMP, temp2Encoder); |
||||
// B005H 强制电加热
|
||||
PARSERS.put(ADDR_FORCE_ELECTRIC_HEAT, v -> (v & 0xFF) == 0 ? "取消强制" : "开启强制"); |
||||
ENCODERS.put(ADDR_FORCE_ELECTRIC_HEAT, val -> val.equals("开启强制") ? 1 : 0); |
||||
// B006H 强制除霜
|
||||
PARSERS.put(ADDR_FORCE_DEFROST, v -> (v & 0xFF) == 0 ? "取消强制" : "开启强制"); |
||||
ENCODERS.put(ADDR_FORCE_DEFROST, val -> val.equals("开启强制") ? 1 : 0); |
||||
// B008H 回水阀定时开关 (bit0)
|
||||
PARSERS.put(ADDR_RETURN_VALVE_TIMER, v -> ((v & 01) == 0) ? "关闭" : "开启"); |
||||
ENCODERS.put(ADDR_RETURN_VALVE_TIMER, val -> (val.equals("开启") ? 1 : 0) & 01); |
||||
// B009H 供水阀定时开关 (bit0)
|
||||
PARSERS.put(ADDR_SUPPLY_VALVE_TIMER, v -> ((v & 01) == 0) ? "关闭" : "开启"); |
||||
ENCODERS.put(ADDR_SUPPLY_VALVE_TIMER, val -> (val.equals("开启") ? 1 : 0) & 01); |
||||
} |
||||
|
||||
/** |
||||
* 解析寄存器值 |
||||
* |
||||
* @param address 寄存器地址(字符串格式,如 "A000") |
||||
* @param value 寄存器原始值(0~65535) |
||||
* @return 解析后的物理值,若地址未定义则返回 null |
||||
*/ |
||||
public static Object parse(String address, int value) { |
||||
Function<Integer, Object> parser = PARSERS.get(address); |
||||
if (parser == null) { |
||||
return null; |
||||
} |
||||
return parser.apply(value); |
||||
} |
||||
|
||||
/** |
||||
* 将物理值编码为寄存器值(用于写操作) |
||||
* |
||||
* @param address 寄存器地址(字符串格式,如 "A000") |
||||
* @param physicalValue 物理值(类型需与协议匹配) |
||||
* @return 要写入寄存器的整数值,若地址不支持写或类型错误则抛出异常 |
||||
*/ |
||||
public static int encode(String address, Object physicalValue) { |
||||
Function<Object, Integer> encoder = ENCODERS.get(address); |
||||
if (encoder == null) { |
||||
throw new UnsupportedOperationException("地址 " + address + " 不支持写操作"); |
||||
} |
||||
return encoder.apply(physicalValue); |
||||
} |
||||
|
||||
// ==================== 私有解析/编码辅助方法 ====================
|
||||
|
||||
// TEMP1 解析:温度 = (raw - 64)/2,raw=1 表示故障
|
||||
private static Double parseTemp1(int raw) { |
||||
if (raw == 1) return null; // 传感器故障或未接
|
||||
return (raw - 64) / 2.0; |
||||
} |
||||
|
||||
// TEMP1 编码:给定温度(Double),返回 raw 值
|
||||
private static int encodeTemp1(Double temp) { |
||||
if (temp == null) return 1; // 故障时写入1?协议未明确写故障,一般写有效值
|
||||
int raw = (int) Math.round(temp * 2 + 64); |
||||
// 限制范围 0~255,但协议中有效温度对应 raw 范围 4~? 不做严格限制
|
||||
return Math.min(255, Math.max(0, raw)); |
||||
} |
||||
|
||||
// TEMP2 解析:温度 = raw - 32,raw=1 表示故障
|
||||
private static Integer parseTemp2(int raw) { |
||||
if (raw == 1) return null; |
||||
return raw - 32; |
||||
} |
||||
|
||||
// TEMP2 编码
|
||||
private static int encodeTemp2(Integer temp) { |
||||
if (temp == null) return 1; |
||||
int raw = temp + 32; |
||||
return Math.min(255, Math.max(0, raw)); |
||||
} |
||||
|
||||
// 版本号解析:高8位类别版本,低8位主板版本
|
||||
private static String parseVersion(int value) { |
||||
int high = (value >> 8) & 0xFF; |
||||
int low = value & 0xFF; |
||||
return String.format("版本_类别版本:%d;版本_主板版本:%d", high, low); |
||||
} |
||||
|
||||
// A01AH 输入开关状态
|
||||
private static String parseInputSwitchStatus(int value) { |
||||
StringBuilder sb = new StringBuilder(); |
||||
sb.append("输入开关状态_系统2状态:").append(((value >> 13) & 1) == 0 ? "保温" : "运行").append(";"); |
||||
sb.append("输入开关状态_系统1状态:").append(((value >> 12) & 1) == 0 ? "保温" : "运行").append(";"); |
||||
sb.append("输入开关状态_除霜运行:").append(((value >> 11) & 1) == 0 ? "无" : "开启").append(";"); |
||||
sb.append("输入开关状态_水箱防冻/二级防冻:").append(((value >> 10) & 1) == 0 ? "无" : "开启").append(";"); |
||||
sb.append("输入开关状态_管路防冻:").append(((value >> 9) & 1) == 0 ? "无" : "开启").append(";"); |
||||
sb.append("输入开关状态_末端开关状态:").append(((value >> 8) & 1) == 0 ? "闭合" : "断开").append(";"); |
||||
sb.append("输入开关状态_高压开关1:").append(((value >> 7) & 1) == 0 ? "闭合" : "断开").append(";"); |
||||
sb.append("输入开关状态_高压开关2:").append(((value >> 6) & 1) == 0 ? "闭合" : "断开").append(";"); |
||||
sb.append("输入开关状态_低压开关1:").append(((value >> 5) & 1) == 0 ? "闭合" : "断开").append(";"); |
||||
sb.append("输入开关状态_低压开关2:").append(((value >> 4) & 1) == 0 ? "闭合" : "断开").append(";"); |
||||
sb.append("输入开关状态_联锁开关:").append(((value >> 3) & 1) == 0 ? "闭合" : "断开").append(";"); |
||||
sb.append("输入开关状态_末端开关:").append(((value >> 2) & 1) == 0 ? "闭合" : "断开").append(";"); |
||||
sb.append("输入开关状态_水流开关:").append(((value >> 1) & 1) == 0 ? "闭合" : "断开"); |
||||
return sb.toString(); |
||||
} |
||||
|
||||
// A01BH 水位开关状态
|
||||
private static String parseWaterLevelSwitch(int value) { |
||||
StringBuilder sb = new StringBuilder(); |
||||
sb.append("水位开关状态_高水位开关:").append(((value >> 7) & 1) == 0 ? "闭合" : "断开").append(";"); |
||||
sb.append("水位开关状态_中水位开关:").append(((value >> 6) & 1) == 0 ? "闭合" : "断开").append(";"); |
||||
sb.append("水位开关状态_低水位开关:").append(((value >> 5) & 1) == 0 ? "闭合" : "断开"); |
||||
return sb.toString(); |
||||
} |
||||
|
||||
// A01CH 输出1状态
|
||||
private static String parseOutput1Status(int value) { |
||||
StringBuilder sb = new StringBuilder(); |
||||
sb.append("输出1状态_四通阀2:").append(((value >> 5) & 1) == 0 ? "关闭" : "开启").append(";"); |
||||
sb.append("输出1状态_四通阀1:").append(((value >> 4) & 1) == 0 ? "关闭" : "开启").append(";"); |
||||
sb.append("输出1状态_压缩机2:").append(((value >> 1) & 1) == 0 ? "关闭" : "开启").append(";"); |
||||
sb.append("输出1状态_压缩机1:").append((value & 1) == 0 ? "关闭" : "开启"); |
||||
return sb.toString(); |
||||
} |
||||
|
||||
// A01DH 输出2状态
|
||||
private static String parseOutput2Status(int value) { |
||||
StringBuilder sb = new StringBuilder(); |
||||
sb.append("输出2状态_外风机2:").append(((value >> 5) & 1) == 0 ? "关闭" : "开启").append(";"); |
||||
sb.append("输出2状态_外风机1:").append(((value >> 4) & 1) == 0 ? "关闭" : "开启").append(";"); |
||||
sb.append("输出2状态_循环水泵:").append(((value >> 3) & 1) == 0 ? "关闭" : "开启").append(";"); |
||||
sb.append("输出2状态_曲轴加热:").append(((value >> 2) & 1) == 0 ? "关闭" : "开启").append(";"); |
||||
sb.append("输出2状态_补水阀:").append(((value >> 1) & 1) == 0 ? "关闭" : "开启").append(";"); |
||||
sb.append("输出2状态_电加热:").append((value & 1) == 0 ? "关闭" : "开启"); |
||||
return sb.toString(); |
||||
} |
||||
|
||||
// A01EH 输出3状态
|
||||
private static String parseOutput3Status(int value) { |
||||
StringBuilder sb = new StringBuilder(); |
||||
sb.append("输出3状态_底盘加热:").append(((value >> 7) & 1) == 0 ? "关闭" : "开启").append(";"); |
||||
sb.append("输出3状态_回水阀:").append(((value >> 6) & 1) == 0 ? "关闭" : "开启").append(";"); |
||||
sb.append("输出3状态_末端水泵:").append(((value >> 5) & 1) == 0 ? "关闭" : "开启").append(";"); |
||||
sb.append("输出3状态_增焓阀:").append(((value >> 4) & 1) == 0 ? "关闭" : "开启").append(";"); |
||||
sb.append("输出3状态_供水阀:").append(((value >> 3) & 1) == 0 ? "关闭" : "开启").append(";"); |
||||
sb.append("输出3状态_三通阀:").append(((value >> 2) & 1) == 0 ? "关闭" : "开启"); |
||||
return sb.toString(); |
||||
} |
||||
|
||||
// A01FH 机组信息
|
||||
private static String parseDipSwitch(int value) { |
||||
StringBuilder sb = new StringBuilder(); |
||||
sb.append("机组信息_系统:").append(((value >> 4) & 1) == 0 ? "单系统" : "双系统").append(";"); |
||||
sb.append("机组信息_机型选择:").append(((value >> 3) & 1) == 0 ? "冷热水机" : "热水机").append(";"); |
||||
sb.append("机组信息_主路膨胀阀控制选择:").append(((value >> 2) & 1) == 0 ? "无" : "有").append(";"); |
||||
sb.append("机组信息_电压选择:").append((value & 1) == 0 ? "单相220V" : "三相380V"); |
||||
return sb.toString(); |
||||
} |
||||
|
||||
// A020H 主板类别
|
||||
private static String parseMainboardType(int value) { |
||||
boolean hasConstant = ((value >> 4) & 1) != 0; |
||||
int type = value & 0x0F; |
||||
String typeStr; |
||||
if (type == 1) typeStr = "主板类别_冷热水机"; |
||||
else if (type == 2) typeStr = "主板类别_热水机"; |
||||
else typeStr = "主板类别_未知"; |
||||
return (hasConstant ? "主板类别_有恒温模式;" : "主板类别_无恒温模式;") + typeStr; |
||||
} |
||||
|
||||
// ==================== 测试示例 ====================
|
||||
public static void main(String[] args) { |
||||
// 示例:解析 A000H 显示水温,原始值 110 -> 温度 (110-64)/2 = 23℃
|
||||
System.out.println("显示水温: " + parse(ADDR_DISPLAY_WATER_TEMP, 110) + "℃"); |
||||
System.out.println(ADDR_DISPLAY_WATER_TEMP); |
||||
// 示例:解析 A007H 排气1温度,原始值 33 -> 33-32=1℃
|
||||
System.out.println("排气1温度: " + parse(ADDR_EXHAUST1_TEMP, 33) + "℃"); |
||||
// 示例:解析 B003H 运行模式,原始值 1 -> 制冷
|
||||
System.out.println("运行模式: " + parse("A01D", 1)); |
||||
|
||||
// 编码示例:写恒温设置温度 25℃
|
||||
int encoded = encode(ADDR_CONSTANT_TEMP_SET, 25); // 25+32=57
|
||||
System.out.println("恒温设置温度 25℃ 编码为: " + encoded); |
||||
// 写开关机:开机
|
||||
encoded = encode(ADDR_ON_OFF, "开机"); |
||||
System.out.println("开机编码: " + encoded); |
||||
|
||||
// 测试字符串地址传入
|
||||
System.out.println("\n--- 测试字符串地址 ---"); |
||||
System.out.println("显示水温: " + parse("A000", 110) + "℃"); |
||||
System.out.println("运行模式: " + parse("B003", 1)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,172 @@
|
||||
package com.mh.user.utils; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.function.Function; |
||||
|
||||
/** |
||||
* @author LJF |
||||
* @version 1.0 |
||||
* @project CHWS |
||||
* @description XK814 V1.0 协议通用参数设置(功能码 06) |
||||
* @date 2026-05-25 19:51:30 |
||||
*/ |
||||
public class XK814ModbusParamWriteUtil { |
||||
|
||||
// 地址常量(十六进制字符串)
|
||||
public static final String ADDR_CONSTANT_TEMP_SET = "AFFF"; // 恒温设置温度
|
||||
public static final String ADDR_ON_OFF = "B000"; // 开关机
|
||||
public static final String ADDR_COOL_SET_TEMP = "B001"; // 制冷设置温度
|
||||
public static final String ADDR_HEAT_SET_TEMP = "B002"; // 制热设置温度
|
||||
public static final String ADDR_MODE = "B003"; // 运行模式
|
||||
public static final String ADDR_HOT_WATER_SET_TEMP = "B004"; // 热水设置温度
|
||||
public static final String ADDR_FORCE_ELECTRIC_HEAT = "B005"; // 强制电加热
|
||||
public static final String ADDR_FORCE_DEFROST = "B006"; // 强制除霜
|
||||
public static final String ADDR_RETURN_VALVE_TIMER = "B008"; // 回水阀定时开关
|
||||
public static final String ADDR_SUPPLY_VALVE_TIMER = "B009"; // 供水阀定时开关
|
||||
|
||||
// 编码器映射:地址字符串 -> 物理值转寄存器值(0~65535)的函数
|
||||
private static final Map<String, Function<Object, Integer>> ENCODERS = new HashMap<>(); |
||||
|
||||
static { |
||||
// TEMP2 编码:温度 → raw = temp + 32
|
||||
Function<Object, Integer> temp2Encoder = val -> { |
||||
if (!(val instanceof Integer)) throw new IllegalArgumentException("温度值必须是 Integer 类型"); |
||||
int temp = (Integer) val; |
||||
return temp + 32; |
||||
}; |
||||
|
||||
// 布尔值编码:true→1, false→0
|
||||
Function<Object, Integer> boolEncoder = val -> { |
||||
if (!(val instanceof Boolean)) throw new IllegalArgumentException("开关值必须是 Boolean 类型"); |
||||
return (Boolean) val ? 1 : 0; |
||||
}; |
||||
|
||||
// 模式编码:字符串映射到 0/1/2
|
||||
Function<Object, Integer> modeEncoder = val -> { |
||||
if (!(val instanceof String)) throw new IllegalArgumentException("模式必须是 String 类型"); |
||||
String mode = (String) val; |
||||
switch (mode) { |
||||
case "制热": |
||||
return 0; |
||||
case "制冷": |
||||
return 1; |
||||
case "恒温": |
||||
return 2; |
||||
default: |
||||
throw new IllegalArgumentException("无效模式,必须为 制热/制冷/恒温"); |
||||
} |
||||
}; |
||||
|
||||
ENCODERS.put(ADDR_CONSTANT_TEMP_SET, temp2Encoder); |
||||
ENCODERS.put(ADDR_COOL_SET_TEMP, temp2Encoder); |
||||
ENCODERS.put(ADDR_HEAT_SET_TEMP, temp2Encoder); |
||||
ENCODERS.put(ADDR_HOT_WATER_SET_TEMP, temp2Encoder); |
||||
ENCODERS.put(ADDR_ON_OFF, boolEncoder); |
||||
ENCODERS.put(ADDR_MODE, modeEncoder); |
||||
ENCODERS.put(ADDR_FORCE_ELECTRIC_HEAT, boolEncoder); |
||||
ENCODERS.put(ADDR_FORCE_DEFROST, boolEncoder); |
||||
ENCODERS.put(ADDR_RETURN_VALVE_TIMER, boolEncoder); |
||||
ENCODERS.put(ADDR_SUPPLY_VALVE_TIMER, boolEncoder); |
||||
} |
||||
|
||||
/** |
||||
* 生成 Modbus 06 写单个寄存器的 RTU 报文 |
||||
* |
||||
* @param slaveId 从机地址(1~247) |
||||
* @param address 寄存器地址(十六进制字符串,如 "B000") |
||||
* @param value 物理值(类型需与地址匹配,参考协议) |
||||
* @return 完整的 RTU 报文(字节数组),包含 CRC |
||||
*/ |
||||
public static String buildWriteSingleRegisterMessage(int slaveId, String address, Object value) { |
||||
// 获取编码器
|
||||
String addrUpper = address.toUpperCase(); |
||||
Function<Object, Integer> encoder = ENCODERS.get(addrUpper); |
||||
if (encoder == null) { |
||||
throw new UnsupportedOperationException("地址 " + address + " 不支持写操作"); |
||||
} |
||||
// 编码为寄存器值
|
||||
int regValue = encoder.apply(value); |
||||
// 地址字符串转整数
|
||||
int regAddr = Integer.parseInt(addrUpper, 16); |
||||
// 构建报文(不含 CRC)
|
||||
byte[] pdu = new byte[6]; |
||||
pdu[0] = (byte) (regAddr >> 8); // 地址高字节
|
||||
pdu[1] = (byte) (regAddr & 0xFF); // 地址低字节
|
||||
pdu[2] = (byte) (regValue >> 8); // 值高字节
|
||||
pdu[3] = (byte) (regValue & 0xFF);// 值低字节
|
||||
// 添加从机地址和功能码
|
||||
byte[] frameWithoutCRC = new byte[1 + 1 + 6]; |
||||
frameWithoutCRC[0] = (byte) slaveId; |
||||
frameWithoutCRC[1] = 0x06; // 功能码
|
||||
System.arraycopy(pdu, 0, frameWithoutCRC, 2, 6); |
||||
// 计算 CRC16
|
||||
int crc = calculateCRC(frameWithoutCRC, 0, frameWithoutCRC.length); |
||||
// 完整报文
|
||||
byte[] fullFrame = new byte[frameWithoutCRC.length + 2]; |
||||
System.arraycopy(frameWithoutCRC, 0, fullFrame, 0, frameWithoutCRC.length); |
||||
fullFrame[fullFrame.length - 2] = (byte) (crc & 0xFF); |
||||
fullFrame[fullFrame.length - 1] = (byte) ((crc >> 8) & 0xFF); |
||||
return toHexString(fullFrame); |
||||
} |
||||
|
||||
/** |
||||
* 重载:地址使用整数形式 |
||||
* |
||||
* @param slaveId 从机地址 |
||||
* @param address 寄存器地址(整数,如 0xB000) |
||||
* @param value 物理值 |
||||
* @return 报文 |
||||
*/ |
||||
public static String buildWriteSingleRegisterMessage(int slaveId, int address, Object value) { |
||||
String addrHex = Integer.toHexString(address).toUpperCase(); |
||||
// 补齐为4位十六进制
|
||||
while (addrHex.length() < 4) addrHex = "0" + addrHex; |
||||
return buildWriteSingleRegisterMessage(slaveId, addrHex, value); |
||||
} |
||||
|
||||
// CRC16 (Modbus RTU) 计算
|
||||
private static int calculateCRC(byte[] data, int offset, int len) { |
||||
int crc = 0xFFFF; |
||||
for (int i = offset; i < offset + len; i++) { |
||||
crc ^= (data[i] & 0xFF); |
||||
for (int j = 0; j < 8; j++) { |
||||
if ((crc & 0x0001) != 0) { |
||||
crc = (crc >> 1) ^ 0xA001; |
||||
} else { |
||||
crc = crc >> 1; |
||||
} |
||||
} |
||||
} |
||||
return crc; |
||||
} |
||||
|
||||
// 辅助方法:打印十六进制字符串
|
||||
public static String toHexString(byte[] bytes) { |
||||
StringBuilder sb = new StringBuilder(); |
||||
for (byte b : bytes) { |
||||
sb.append(String.format("%02X ", b)); |
||||
} |
||||
return sb.toString().trim(); |
||||
} |
||||
|
||||
// 示例
|
||||
public static void main(String[] args) { |
||||
// 示例1:从机地址1,设置开机(B000H,true)
|
||||
String msg = buildWriteSingleRegisterMessage(1, ADDR_ON_OFF, true); |
||||
System.out.println("开机报文: " + (msg)); |
||||
|
||||
// 示例2:设置制热温度45℃(B002H,45)
|
||||
msg = buildWriteSingleRegisterMessage(1, ADDR_HEAT_SET_TEMP, 45); |
||||
System.out.println("设置制热温度45℃: " + (msg)); |
||||
|
||||
// 示例3:设置模式为制冷(B003H,"制冷")
|
||||
msg = buildWriteSingleRegisterMessage(1, ADDR_MODE, "制冷"); |
||||
System.out.println("设置制冷模式: " + (msg)); |
||||
|
||||
// 示例4:使用整数地址
|
||||
msg = buildWriteSingleRegisterMessage(1, 0xB005, true); // 强制电加热开启
|
||||
System.out.println("强制电加热开启: " + (msg)); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue