作者 钟来

明牛协议解析完成

正在显示 15 个修改的文件 包含 1400 行增加675 行删除
@@ -48,6 +48,8 @@ public class InitPlcConfig { @@ -48,6 +48,8 @@ public class InitPlcConfig {
48 cachPlcConfig.setProtocolType(plc.getProtocolType()); 48 cachPlcConfig.setProtocolType(plc.getProtocolType());
49 cachPlcConfig.setConnectConfig(plc.getConnectConfig()); 49 cachPlcConfig.setConnectConfig(plc.getConnectConfig());
50 cachPlcConfig.setPlcMap(new HashMap()); 50 cachPlcConfig.setPlcMap(new HashMap());
  51 + cachPlcConfig.setZeroBasedAddress(plc.getZeroBasedAddress());
  52 + cachPlcConfig.setByteOrder(plc.getByteOrder());
51 53
52 plcsConfigMap.put(plc.getId(), cachPlcConfig); 54 plcsConfigMap.put(plc.getId(), cachPlcConfig);
53 } 55 }
@@ -5,6 +5,7 @@ import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig; @@ -5,6 +5,7 @@ import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig;
5 import com.zhonglai.luhui.device.modbus.terminal.data.TopicFactoryAdapter; 5 import com.zhonglai.luhui.device.modbus.terminal.data.TopicFactoryAdapter;
6 import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jWrite; 6 import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jWrite;
7 import com.zhonglai.luhui.device.modbus.terminal.modbus.ModbusMasterMessage; 7 import com.zhonglai.luhui.device.modbus.terminal.modbus.ModbusMasterMessage;
  8 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.CachPlcConfig;
8 import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.Message; 9 import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.Message;
9 import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint; 10 import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
10 import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic; 11 import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
@@ -31,15 +32,16 @@ public class PutTopic extends TopicFactoryAdapter { @@ -31,15 +32,16 @@ public class PutTopic extends TopicFactoryAdapter {
31 { 32 {
32 JSONObject plcCommand = jsonObject.getJSONObject(key); 33 JSONObject plcCommand = jsonObject.getJSONObject(key);
33 String id = key; 34 String id = key;
  35 + CachPlcConfig cachPlcConfig = InitPlcConfig.getPlcsConfigMap().get(id);
34 List<PlcPoint> plcPoints = getPlcPoints(id, plcCommand); 36 List<PlcPoint> plcPoints = getPlcPoints(id, plcCommand);
35 Modbus4jWrite modbus4jWrite = null; 37 Modbus4jWrite modbus4jWrite = null;
36 try { 38 try {
37 modbus4jWrite = new Modbus4jWrite(id); 39 modbus4jWrite = new Modbus4jWrite(id);
38 - modbus4jWrite.batchWrite(plcPoints,true); 40 + modbus4jWrite.batchWrite(plcPoints,cachPlcConfig.getZeroBasedAddress());
39 } catch (Exception e) { 41 } catch (Exception e) {
40 if(null != modbus4jWrite) 42 if(null != modbus4jWrite)
41 { 43 {
42 - ModbusMasterMessage.closeMaster(id); // 销毁旧连接 44 + ModbusMasterMessage.closeMasterByPlcId(id); // 销毁旧连接
43 } 45 }
44 logger.error("写plc异常",e); 46 logger.error("写plc异常",e);
45 try { 47 try {
@@ -6,6 +6,7 @@ import com.zhonglai.luhui.device.modbus.terminal.data.TopicFactoryAdapter; @@ -6,6 +6,7 @@ import com.zhonglai.luhui.device.modbus.terminal.data.TopicFactoryAdapter;
6 import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jRead; 6 import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jRead;
7 import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jWrite; 7 import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jWrite;
8 import com.zhonglai.luhui.device.modbus.terminal.modbus.ModbusMasterMessage; 8 import com.zhonglai.luhui.device.modbus.terminal.modbus.ModbusMasterMessage;
  9 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.CachPlcConfig;
9 import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint; 10 import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
10 import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic; 11 import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
11 import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService; 12 import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
@@ -34,16 +35,18 @@ public class ReadTopic extends TopicFactoryAdapter { @@ -34,16 +35,18 @@ public class ReadTopic extends TopicFactoryAdapter {
34 String plcCommand = jsonObject.getString(key); 35 String plcCommand = jsonObject.getString(key);
35 String id = key; 36 String id = key;
36 List<PlcPoint> plcPoints = getPlcPoints(id, plcCommand); 37 List<PlcPoint> plcPoints = getPlcPoints(id, plcCommand);
  38 + CachPlcConfig cachPlcConfig = InitPlcConfig.getPlcsConfigMap().get(id);
37 Modbus4jRead modbus4jRead = null; 39 Modbus4jRead modbus4jRead = null;
38 try { 40 try {
39 modbus4jRead = new Modbus4jRead(id); 41 modbus4jRead = new Modbus4jRead(id);
40 - Map<String, Object> map = modbus4jRead.batchRead(plcPoints,true); 42 +
  43 + Map<String, Object> map = modbus4jRead.batchRead(plcPoints,cachPlcConfig.getZeroBasedAddress());
41 rJsonObject.put(key, map); 44 rJsonObject.put(key, map);
42 } catch (Exception e) { 45 } catch (Exception e) {
43 logger.error("读plc异常",e); 46 logger.error("读plc异常",e);
44 if(null != modbus4jRead) 47 if(null != modbus4jRead)
45 { 48 {
46 - ModbusMasterMessage.closeMaster(id); // 销毁旧连接 49 + ModbusMasterMessage.closeMasterByPlcId(id); // 销毁旧连接
47 } 50 }
48 return; 51 return;
49 } 52 }
  1 +package com.zhonglai.luhui.device.modbus.terminal.modbus;
  2 +
  3 +import com.serotonin.modbus4j.ModbusMaster;
  4 +import com.serotonin.modbus4j.exception.ModbusTransportException;
  5 +import com.serotonin.modbus4j.msg.*;
  6 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcDataType;
  7 +import org.slf4j.Logger;
  8 +import org.slf4j.LoggerFactory;
  9 +
  10 +import java.nio.ByteBuffer;
  11 +import java.nio.ByteOrder;
  12 +import java.util.concurrent.*;
  13 +import java.util.concurrent.locks.ReentrantLock;
  14 +
  15 +/**
  16 + * 修正并重构后的增强版 Modbus TCP 客户端(字节序与功能码修复)
  17 + */
  18 +public class EnhancedModbusTcpClientWithByteOrder {
  19 + private static final Logger log = LoggerFactory.getLogger(EnhancedModbusTcpClientWithByteOrder.class);
  20 +
  21 + /** 浮点字节序 */
  22 + public enum ByteOrderType { ABCD, CDAB, BADC, DCBA }
  23 +
  24 + private final String host;
  25 + private final int port;
  26 + private final ExecutorService executor;
  27 + private final ReentrantLock connectLock = new ReentrantLock();
  28 +
  29 + // 配置 master 的参数
  30 + private final int timeoutMillis;
  31 + private final int retries;
  32 + private final boolean encapsulated;
  33 +
  34 + public static class ModbusAddress {
  35 + private final int startAddress; // 起始寄存器/线圈地址
  36 + private final int bitIndex; // -1 表示不是位访问
  37 + private final int length; // 连续寄存器/线圈长度
  38 +
  39 + private ModbusAddress(int startAddress, int bitIndex, int length) {
  40 + this.startAddress = startAddress;
  41 + this.bitIndex = bitIndex;
  42 + this.length = length;
  43 + }
  44 +
  45 + public int getStartAddress() { return startAddress; }
  46 + public int getBitIndex() { return bitIndex; }
  47 + public int getLength() { return length; }
  48 +
  49 + /**
  50 + * 支持格式:
  51 + * 40001
  52 + * 40001.01
  53 + * 40001-40003
  54 + */
  55 + public static ModbusAddress parse(String addressStr) {
  56 + addressStr = addressStr.trim();
  57 + if (addressStr.contains("-")) {
  58 + String[] parts = addressStr.split("-");
  59 + if (parts.length != 2) throw new IllegalArgumentException("地址区间格式错误: " + addressStr);
  60 + int start = Integer.parseInt(parts[0].trim());
  61 + int end = Integer.parseInt(parts[1].trim());
  62 + if (end < start) throw new IllegalArgumentException("结束地址不能小于起始地址: " + addressStr);
  63 + return new ModbusAddress(start, -1, end - start + 1);
  64 + } else if (addressStr.contains(".")) {
  65 + String[] parts = addressStr.split("\\.");
  66 + if (parts.length != 2) throw new IllegalArgumentException("位地址格式错误: " + addressStr);
  67 + int reg = Integer.parseInt(parts[0].trim());
  68 + int bit = Integer.parseInt(parts[1].trim()) - 1; // 位从1开始
  69 + if (bit < 0 || bit > 15) throw new IllegalArgumentException("位索引必须在1-16: " + addressStr);
  70 + return new ModbusAddress(reg, bit, 1);
  71 + } else {
  72 + int reg = Integer.parseInt(addressStr);
  73 + return new ModbusAddress(reg, -1, 1);
  74 + }
  75 + }
  76 + }
  77 +
  78 + public EnhancedModbusTcpClientWithByteOrder(String host, int port, int poolSize) {
  79 + this(host, port, poolSize, 3000, 1, true);
  80 + }
  81 +
  82 + public EnhancedModbusTcpClientWithByteOrder(String host, int port, int poolSize, int timeoutMillis, int retries, boolean encapsulated) {
  83 + this.host = host;
  84 + this.port = port;
  85 + this.executor = Executors.newFixedThreadPool(poolSize);
  86 + this.timeoutMillis = timeoutMillis;
  87 + this.retries = retries;
  88 + this.encapsulated = encapsulated;
  89 + }
  90 +
  91 + private ModbusMaster getMaster() throws Exception {
  92 + String key = String.format("tcp:%s:%d", host, port);
  93 + return ModbusConnectionManager.getTcpMaster(host, port, timeoutMillis, retries, encapsulated);
  94 + }
  95 +
  96 + private <T> Future<T> submit(Callable<T> task) { return executor.submit(task); }
  97 +
  98 + /**
  99 + * 将给定地址转换到 0..9999 的 offset。
  100 + * zeroBasedAddress == true 表示用户传入的是从 0 开始的地址(例如 0, 40000 等),
  101 + * 否则表示用户传入的是从 1 开始的地址(常见的 40001 表示 offset 0)。
  102 + */
  103 + private int toOffset(int address, boolean zeroBasedAddress) {
  104 + if (zeroBasedAddress) {
  105 + // 例如 address=40000 -> offset = 40000 % 10000 = 0
  106 + int off = address % 10000;
  107 + return (off + 10000) % 10000;
  108 + } else {
  109 + // one-based input (40001 -> offset 0)
  110 + int off = (address - 1) % 10000;
  111 + return (off + 10000) % 10000;
  112 + }
  113 + }
  114 +
  115 + /**
  116 + * 基于经典 Modbus 地址段做判断:
  117 + * 0xxxx (1..9999) -> Coil (FC1 read)
  118 + * 1xxxx (10001..19999) -> Discrete Input (FC2 read)
  119 + * 3xxxx (30001..39999) -> Input Register (FC4 read)
  120 + * 4xxxx (40001..49999) -> Holding Register (FC3 read, write FC6/16)
  121 + *
  122 + * write 为 true 时,函数尝试返回合适的写功能码(单写/多写)
  123 + */
  124 + private int getFunctionCode(int address, boolean write, Object value) {
  125 + if (address <= 0) throw new IllegalArgumentException("地址必须为正数: " + address);
  126 + int prefix = address / 10000; // 0 -> coils (1..9999), 1 -> discrete inputs, 3 -> input regs, 4 -> holding regs
  127 + switch (prefix) {
  128 + case 0: // 00001 - 09999 => coils
  129 + if (write) {
  130 + if (value instanceof boolean[]) return 15; // Write Multiple Coils
  131 + return 5; // Write Single Coil
  132 + } else {
  133 + return 1; // Read Coils
  134 + }
  135 + case 1: // 10001 - 19999 => discrete input (read-only)
  136 + if (write) throw new IllegalArgumentException("Discrete Input 为只读地址段,不能写: " + address);
  137 + return 2; // Read Discrete Inputs
  138 + case 3: // 30001 - 39999 => input registers (read-only)
  139 + if (write) throw new IllegalArgumentException("Input Register 为只读地址段,不能写: " + address);
  140 + return 4; // Read Input Registers
  141 + case 4: // 40001 - 49999 => holding registers
  142 + if (write) {
  143 + // 16 = Write Multiple Registers, 6 = Write Single Register
  144 + if (value instanceof Number) {
  145 + // 无法仅凭 value 类型精确决定是否使用单寄存器还是多寄存器(例如 INT32 需要 2 寄存器)
  146 + // 所以这里默认多寄存器(16)以通用支持复杂类型;调用处可以选择适配单寄存器写(WriteRegisterRequest)。
  147 + return 16;
  148 + } else if (value instanceof int[] || value instanceof short[] || value instanceof byte[]) {
  149 + return 16;
  150 + } else {
  151 + // For boolean writing to holding register bit - handled separately by API
  152 + return 16;
  153 + }
  154 + } else {
  155 + return 3; // Read Holding Registers
  156 + }
  157 + default:
  158 + throw new IllegalArgumentException("地址超出支持范围: " + address);
  159 + }
  160 + }
  161 +
  162 + private ModbusResponse execute(ModbusRequest req, Class<? extends ModbusResponse> respType) throws Exception {
  163 + ModbusMaster master = getMaster();
  164 + ModbusResponse response = master.send(req);
  165 + if (response == null) throw new ModbusTransportException("设备无响应");
  166 + if (response.isException()) throw new ModbusTransportException("异常响应: " + response.getExceptionMessage());
  167 + return respType.cast(response);
  168 + }
  169 +
  170 + // -------------------- 读接口 --------------------
  171 + public Future<Object> read(int slaveId, int address, PlcDataType dataType, boolean zeroBasedAddress, ByteOrderType floatByteOrder) {
  172 + return submit(() -> {
  173 + int offset = toOffset(address, zeroBasedAddress);
  174 + final ByteOrderType effectiveByteOrder = (floatByteOrder == null) ? ByteOrderType.CDAB : floatByteOrder;
  175 +
  176 + switch (dataType) {
  177 + case BOOLEAN:
  178 + int fcBool = getFunctionCode(address, false, null);
  179 + if (fcBool == 1) return ((ReadCoilsResponse) execute(new ReadCoilsRequest(slaveId, offset, 1), ReadCoilsResponse.class)).getBooleanData()[0];
  180 + if (fcBool == 2) return ((ReadDiscreteInputsResponse) execute(new ReadDiscreteInputsRequest(slaveId, offset, 1), ReadDiscreteInputsResponse.class)).getBooleanData()[0];
  181 + throw new IllegalArgumentException("BOOLEAN 类型无法读取地址:" + address);
  182 +
  183 + case INT16:
  184 + short[] regs16 = readRegisters(slaveId, offset, 1, address);
  185 + // 保持与之前兼容:返回 int(Java 没有 unsigned short,按 signed short 扩展)
  186 + return (int) regs16[0];
  187 +
  188 + case INT32:
  189 + short[] regs32 = readRegisters(slaveId, offset, 2, address);
  190 + return toInt(regs32, effectiveByteOrder);
  191 +
  192 + case INT64:
  193 + short[] regs64 = readRegisters(slaveId, offset, 4, address);
  194 + return toLong(regs64, effectiveByteOrder);
  195 +
  196 + case FLOAT32:
  197 + short[] f32Regs = readRegisters(slaveId, offset, 2, address);
  198 + return toFloat(f32Regs, effectiveByteOrder);
  199 +
  200 + case FLOAT64:
  201 + short[] f64Regs = readRegisters(slaveId, offset, 4, address);
  202 + return toDouble(f64Regs, effectiveByteOrder);
  203 +
  204 + default:
  205 + throw new IllegalArgumentException("未知 DataType:" + dataType);
  206 + }
  207 + });
  208 + }
  209 +
  210 + // -------------------- 写接口 --------------------
  211 + public Future<Void> write(int slaveId, int address, PlcDataType dataType, Object value, boolean zeroBasedAddress, ByteOrderType floatByteOrder) {
  212 + return submit(() -> {
  213 + int offset = toOffset(address, zeroBasedAddress);
  214 + final ByteOrderType effectiveByteOrder = (floatByteOrder == null) ? ByteOrderType.CDAB : floatByteOrder;
  215 +
  216 + switch (dataType) {
  217 + case BOOLEAN:
  218 + if (value instanceof Boolean) {
  219 + execute(new WriteCoilRequest(slaveId, offset, (Boolean) value), WriteCoilResponse.class);
  220 + } else if (value instanceof boolean[]) {
  221 + execute(new WriteCoilsRequest(slaveId, offset, (boolean[]) value), WriteCoilsResponse.class);
  222 + } else throw new IllegalArgumentException("BOOLEAN 写入必须是 Boolean 或 boolean[]");
  223 + return null;
  224 +
  225 + case INT16:
  226 + // 写单个寄存器(用 WriteRegisterRequest -> FC6)
  227 + execute(new WriteRegisterRequest(slaveId, offset, (Long.valueOf(String.valueOf(value))).shortValue()), WriteRegisterResponse.class);
  228 + return null;
  229 +
  230 + case INT32:
  231 + // 写 2 个寄存器(FC16)
  232 + execute(new WriteRegistersRequest(slaveId, offset, toShorts(Integer.parseInt(String.valueOf(value)), effectiveByteOrder)), WriteRegistersResponse.class);
  233 + return null;
  234 +
  235 + case INT64:
  236 + execute(new WriteRegistersRequest(slaveId, offset, toShorts(Long.valueOf(String.valueOf(value)), effectiveByteOrder)), WriteRegistersResponse.class);
  237 + return null;
  238 +
  239 + case FLOAT32:
  240 + execute(new WriteRegistersRequest(slaveId, offset, fromFloat(Float.valueOf(String.valueOf(value)) , effectiveByteOrder)), WriteRegistersResponse.class);
  241 + return null;
  242 +
  243 + case FLOAT64:
  244 + execute(new WriteRegistersRequest(slaveId, offset, fromDouble(Double.valueOf(String.valueOf(value)), effectiveByteOrder)), WriteRegistersResponse.class);
  245 + return null;
  246 +
  247 + default:
  248 + throw new IllegalArgumentException("未知 DataType:" + dataType);
  249 + }
  250 + });
  251 + }
  252 +
  253 + /**
  254 + * 读取 Holding Register 中的某一位(支持 40001.01 格式)
  255 + */
  256 + public boolean readBooleanFromRegisterBit(int slaveId, String addressStr, boolean zeroBasedAddress) throws Exception {
  257 + ModbusAddress addr = ModbusAddress.parse(addressStr);
  258 + if (addr.getBitIndex() < 0) throw new IllegalArgumentException("地址不是位访问: " + addressStr);
  259 +
  260 + int offset = toOffset(addr.getStartAddress(), zeroBasedAddress);
  261 + short[] regs = readRegisters(slaveId, offset, 1, addr.getStartAddress());
  262 + return ((regs[0] >> addr.getBitIndex()) & 0x1) == 1;
  263 + }
  264 +
  265 + /**
  266 + * 写入 Holding Register 中的某一位(支持 40001.01 格式)
  267 + */
  268 + public void writeBooleanToRegisterBit(int slaveId, String addressStr, boolean value, boolean zeroBasedAddress) throws Exception {
  269 + ModbusAddress addr = ModbusAddress.parse(addressStr);
  270 + if (addr.getBitIndex() < 0) throw new IllegalArgumentException("地址不是位访问: " + addressStr);
  271 +
  272 + int offset = toOffset(addr.getStartAddress(), zeroBasedAddress);
  273 + // 读取原寄存器
  274 + short[] regs = readRegisters(slaveId, offset, 1, addr.getStartAddress());
  275 +
  276 + // 修改对应 bit
  277 + if (value) regs[0] |= (1 << addr.getBitIndex());
  278 + else regs[0] &= ~(1 << addr.getBitIndex());
  279 +
  280 + // 写回寄存器(用 WriteRegistersRequest)
  281 + execute(new WriteRegistersRequest(slaveId, offset, regs), WriteRegistersResponse.class);
  282 + }
  283 +
  284 + private short[] readRegisters(int slaveId, int offset, int length, int address) throws Exception {
  285 + int fc = getFunctionCode(address, false, null); // 使用原始地址
  286 + if (fc == 3) return ((ReadHoldingRegistersResponse) execute(new ReadHoldingRegistersRequest(slaveId, offset, length), ReadHoldingRegistersResponse.class)).getShortData();
  287 + if (fc == 4) return ((ReadInputRegistersResponse) execute(new ReadInputRegistersRequest(slaveId, offset, length), ReadInputRegistersResponse.class)).getShortData();
  288 + throw new IllegalArgumentException("寄存器类型无法读取,offset=" + offset + " address=" + address);
  289 + }
  290 +
  291 + // -------------------- 通用字节重排工具 --------------------
  292 + private static byte[] regsToRawBytes(short[] regs) {
  293 + byte[] raw = new byte[regs.length * 2];
  294 + for (int i = 0; i < regs.length; i++) {
  295 + raw[i * 2] = (byte) (regs[i] >> 8);
  296 + raw[i * 2 + 1] = (byte) regs[i];
  297 + }
  298 + return raw;
  299 + }
  300 +
  301 + /**
  302 + * 根据目标字节长度(4 或 8)和字节序类型重排 bytes。
  303 + * 当 bytes.length == 4 或 8 时按表格重排;否则返回原数组拷贝。
  304 + */
  305 + private static byte[] reorderBytesForType(byte[] src, ByteOrderType boType) {
  306 + int len = src.length;
  307 + byte[] dst = new byte[len];
  308 + if (len == 4) {
  309 + int[] map;
  310 + switch (boType) {
  311 + case ABCD: map = new int[]{0,1,2,3}; break;
  312 + case CDAB: map = new int[]{2,3,0,1}; break;
  313 + case BADC: map = new int[]{1,0,3,2}; break;
  314 + case DCBA: map = new int[]{3,2,1,0}; break;
  315 + default: map = new int[]{0,1,2,3};
  316 + }
  317 + for (int i = 0; i < 4; i++) dst[i] = src[map[i]];
  318 + return dst;
  319 + } else if (len == 8) {
  320 + int[] map;
  321 + switch (boType) {
  322 + case ABCD: map = new int[]{0,1,2,3,4,5,6,7}; break;
  323 + case CDAB: map = new int[]{2,3,0,1,6,7,4,5}; break; // swap 2-word pairs
  324 + case BADC: map = new int[]{1,0,3,2,5,4,7,6}; break; // swap bytes in each word
  325 + case DCBA: map = new int[]{7,6,5,4,3,2,1,0}; break; // reverse
  326 + default: map = new int[]{0,1,2,3,4,5,6,7};
  327 + }
  328 + for (int i = 0; i < 8; i++) dst[i] = src[map[i]];
  329 + return dst;
  330 + } else {
  331 + // 不支持的长度,返回原数组副本
  332 + System.arraycopy(src, 0, dst, 0, len);
  333 + return dst;
  334 + }
  335 + }
  336 +
  337 + private static byte[] reorderBytesForRead(byte[] src, ByteOrderType type) {
  338 + int len = src.length;
  339 + byte[] dst = new byte[len];
  340 + int[] m;
  341 +
  342 + if (len == 4) {
  343 + switch (type) {
  344 + case ABCD:
  345 + m = new int[]{0, 1, 2, 3};
  346 + break;
  347 + case CDAB:
  348 + m = new int[]{2, 3, 0, 1};
  349 + break;
  350 + case BADC:
  351 + m = new int[]{1, 0, 3, 2};
  352 + break;
  353 + case DCBA:
  354 + m = new int[]{3, 2, 1, 0};
  355 + break;
  356 + default:
  357 + m = new int[]{0, 1, 2, 3};
  358 + }
  359 + for (int i = 0; i < 4; i++) dst[i] = src[m[i]];
  360 +
  361 + } else if (len == 8) {
  362 + switch (type) {
  363 + case ABCD:
  364 + m = new int[]{0,1,2,3,4,5,6,7};
  365 + break;
  366 + case CDAB:
  367 + m = new int[]{2,3,0,1,6,7,4,5};
  368 + break;
  369 + case BADC:
  370 + m = new int[]{1,0,3,2,5,4,7,6};
  371 + break;
  372 + case DCBA:
  373 + m = new int[]{7,6,5,4,3,2,1,0};
  374 + break;
  375 + default:
  376 + m = new int[]{0,1,2,3,4,5,6,7};
  377 + }
  378 + for (int i = 0; i < 8; i++) dst[i] = src[m[i]];
  379 +
  380 + } else {
  381 + System.arraycopy(src, 0, dst, 0, len);
  382 + }
  383 +
  384 + return dst;
  385 + }
  386 +
  387 + private static byte[] reorderBytesForWrite(byte[] src, ByteOrderType type) {
  388 + int len = src.length;
  389 + byte[] dst = new byte[len];
  390 + int[] m;
  391 +
  392 + if (len == 4) {
  393 + switch (type) {
  394 + case ABCD:
  395 + m = new int[]{0, 1, 2, 3};
  396 + break;
  397 + case CDAB:
  398 + m = new int[]{2, 3, 0, 1};
  399 + break;
  400 + case BADC:
  401 + m = new int[]{1, 0, 3, 2};
  402 + break;
  403 + case DCBA:
  404 + m = new int[]{3, 2, 1, 0};
  405 + break;
  406 + default:
  407 + m = new int[]{0, 1, 2, 3};
  408 + }
  409 + for (int i = 0; i < 4; i++) dst[m[i]] = src[i];
  410 +
  411 + } else if (len == 8) {
  412 + switch (type) {
  413 + case ABCD:
  414 + m = new int[]{0,1,2,3,4,5,6,7};
  415 + break;
  416 + case CDAB:
  417 + m = new int[]{2,3,0,1,6,7,4,5};
  418 + break;
  419 + case BADC:
  420 + m = new int[]{1,0,3,2,5,4,7,6};
  421 + break;
  422 + case DCBA:
  423 + m = new int[]{7,6,5,4,3,2,1,0};
  424 + break;
  425 + default:
  426 + m = new int[]{0,1,2,3,4,5,6,7};
  427 + }
  428 + for (int i = 0; i < 8; i++) dst[m[i]] = src[i];
  429 +
  430 + } else {
  431 + System.arraycopy(src, 0, dst, 0, len);
  432 + }
  433 +
  434 + return dst;
  435 + }
  436 +
  437 + private static short[] rawBytesToRegs(byte[] raw) {
  438 + if (raw.length % 2 != 0) throw new IllegalArgumentException("raw length must be even");
  439 + short[] regs = new short[raw.length / 2];
  440 + for (int i = 0; i < regs.length; i++) {
  441 + regs[i] = (short) (((raw[i * 2] & 0xFF) << 8) | (raw[i * 2 + 1] & 0xFF));
  442 + }
  443 + return regs;
  444 + }
  445 +
  446 + // -------------------- 数值转换实现(统一、可维护) --------------------
  447 + private int toInt(short[] regs, ByteOrderType boType) {
  448 + if (regs.length == 1) {
  449 + return regs[0]; // 单寄存器,保留 signed short 扩展
  450 + }
  451 + if (regs.length < 2) throw new IllegalArgumentException("regs 长度必须 >= 2");
  452 + byte[] raw = regsToRawBytes(new short[]{regs[0], regs[1]});
  453 + byte[] reordered = reorderBytesForRead(raw, boType);
  454 + return ByteBuffer.wrap(reordered).order(ByteOrder.BIG_ENDIAN).getInt();
  455 + }
  456 +
  457 + private long toLong(short[] regs, ByteOrderType boType) {
  458 + if (regs.length != 4) throw new IllegalArgumentException("regs 长度必须为4");
  459 + byte[] raw = regsToRawBytes(regs);
  460 + byte[] reordered = reorderBytesForRead(raw, boType);
  461 + return ByteBuffer.wrap(reordered).order(ByteOrder.BIG_ENDIAN).getLong();
  462 + }
  463 +
  464 + private float toFloat(short[] regs, ByteOrderType boType) {
  465 + if (regs.length < 2) throw new IllegalArgumentException("regs 长度必须 >= 2");
  466 + byte[] raw = regsToRawBytes(new short[]{regs[0], regs[1]});
  467 + byte[] reordered = reorderBytesForRead(raw, boType);
  468 + return ByteBuffer.wrap(reordered).order(ByteOrder.BIG_ENDIAN).getFloat();
  469 + }
  470 +
  471 + private double toDouble(short[] regs, ByteOrderType boType) {
  472 + if (regs.length != 4) throw new IllegalArgumentException("regs 长度必须为4");
  473 + byte[] raw = regsToRawBytes(regs);
  474 + byte[] reordered = reorderBytesForRead(raw, boType);
  475 + return ByteBuffer.wrap(reordered).order(ByteOrder.BIG_ENDIAN).getDouble();
  476 + }
  477 +
  478 + // 从基本类型生成寄存器(short[])
  479 + private short[] fromFloat(float value, ByteOrderType boType) {
  480 + byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(value).array();
  481 + // inverse: 根据字节序先获得 raw 字节,应当把 `bytes` 视为网络顺序(ABCD),
  482 + // 因此要把 `bytes` 按相反的 reordering 写回 regs:
  483 + byte[] raw = reorderBytesForWrite(bytes, boType); // 使其成为 "寄存器大端序" 的原始 bytes
  484 + return rawBytesToRegs(raw);
  485 + }
  486 +
  487 + private short[] fromDouble(double value, ByteOrderType boType) {
  488 + byte[] bytes = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putDouble(value).array();
  489 + byte[] raw = reorderBytesForWrite(bytes, boType);
  490 + return rawBytesToRegs(raw);
  491 + }
  492 +
  493 + private short[] toShorts(int value, ByteOrderType boType) {
  494 + byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(value).array();
  495 + byte[] raw = reorderBytesForWrite(bytes, boType);
  496 + return rawBytesToRegs(raw);
  497 + }
  498 +
  499 + private short[] toShorts(long value, ByteOrderType boType) {
  500 + byte[] bytes = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putLong(value).array();
  501 + byte[] raw = reorderBytesForWrite(bytes, boType);
  502 + return rawBytesToRegs(raw);
  503 + }
  504 +
  505 + /**
  506 + * 返回用于逆向重排的类型(因为我们上面 reorderBytesForType 的定义是“从 regs->字节->按字节序重排得到目标顺序”)。
  507 + * 当我们有一个按网络顺序(BIG_ENDIAN)的字节数组(例如 ByteBuffer.putInt 产生的),
  508 + * 为得到写入寄存器的 raw bytes(每个寄存器高字节在前),需要对这个数组做 inverse reorder。
  509 + */
  510 + private static ByteOrderType inverseOrder(ByteOrderType boType) {
  511 + switch (boType) {
  512 + case ABCD: return ByteOrderType.ABCD;
  513 + case CDAB: return ByteOrderType.CDAB; // 自反映射在 reorderBytesForType 中是对称的(因为 map 的逆映射与 map 本身一致)
  514 + case BADC: return ByteOrderType.BADC;
  515 + case DCBA: return ByteOrderType.DCBA;
  516 + default: return ByteOrderType.CDAB;
  517 + }
  518 + }
  519 +
  520 + // -------------------- 关闭 --------------------
  521 + public void close() {
  522 + executor.shutdown();
  523 + try {
  524 + if (!executor.awaitTermination(3, TimeUnit.SECONDS)) {
  525 + executor.shutdownNow();
  526 + }
  527 + } catch (InterruptedException e) {
  528 + Thread.currentThread().interrupt();
  529 + executor.shutdownNow();
  530 + }
  531 + log.info("EnhancedModbusTcpClientWithByteOrder closed");
  532 + }
  533 +
  534 + // -------------------- 简单 main 测试示例 --------------------
  535 + public static void main(String[] args) throws Exception {
  536 + EnhancedModbusTcpClientWithByteOrder client =
  537 + new EnhancedModbusTcpClientWithByteOrder("127.0.0.1", 2010, 10);
  538 +
  539 +// // 示例:读取 10001 (coil) — 注意 zeroBasedAddress 参数语义
  540 +// Future<Object> bit1 = client.read(1, 10001, PlcDataType.BOOLEAN, false, ByteOrderType.CDAB);
  541 +// System.out.println("10001 = " + bit1.get());
  542 +//
  543 +// // 写入 FLOAT32
  544 +// client.write(1, 40001,PlcDataType.FLOAT32, 38.5f, false, ByteOrderType.CDAB);
  545 +// // 读取 FLOAT32
  546 +// Float temp = (Float) client.read(1, 40001,PlcDataType.FLOAT32, false, ByteOrderType.CDAB).get();
  547 +// System.out.println("40001 FLOAT32 = " + temp);
  548 +// // 写入 FLOAT64
  549 +// client.write(1, 40003,PlcDataType.FLOAT64, 102.23, false, ByteOrderType.CDAB);
  550 +// // 读取 FLOAT64
  551 +// Double pressure = (Double) client.read(1, 40003,PlcDataType.FLOAT64, false, ByteOrderType.CDAB).get();
  552 +// System.out.println("40003 FLOAT64 = " + pressure);
  553 +// // 写入 FLOAT32
  554 +// client.write(1, 40053, PlcDataType.FLOAT32, 56.5f, false, ByteOrderType.CDAB);
  555 +// Float one = (Float) client.read(1, 40053,PlcDataType.FLOAT32, false, ByteOrderType.CDAB).get();
  556 +// System.out.println("40008 INT16 = " + one);
  557 +// // 写入 40009 寄存器的第 2 位
  558 + client.writeBooleanToRegisterBit(1, "40051.13", true, false);
  559 +// // 读取 40009 寄存器的第 2 位
  560 +// boolean bitValue = client.readBooleanFromRegisterBit(1, "40009.02", false);
  561 +// System.out.println("40009.01 = " + bitValue);
  562 +
  563 + // 写入 FLOAT32
  564 +// client.write(1, 40053,PlcDataType.FLOAT32, 9, false, ByteOrderType.ABCD);
  565 +// System.out.println(client.read(1, 40053, PlcDataType.FLOAT32, false, ByteOrderType.ABCD).get());
  566 +
  567 +// System.out.println(client.read(1, 10001,PlcDataType.BOOLEAN, false, ByteOrderType.CDAB).get());
  568 +// System.out.println(client.read(1, 10002,PlcDataType.BOOLEAN, false, ByteOrderType.CDAB).get());
  569 +// System.out.println(client.read(1, 10003,PlcDataType.BOOLEAN, false, ByteOrderType.CDAB).get());
  570 + client.close();
  571 + }
  572 +}
@@ -8,6 +8,7 @@ import com.serotonin.modbus4j.exception.ErrorResponseException; @@ -8,6 +8,7 @@ import com.serotonin.modbus4j.exception.ErrorResponseException;
8 import com.serotonin.modbus4j.exception.ModbusInitException; 8 import com.serotonin.modbus4j.exception.ModbusInitException;
9 import com.serotonin.modbus4j.exception.ModbusTransportException; 9 import com.serotonin.modbus4j.exception.ModbusTransportException;
10 import com.serotonin.modbus4j.locator.BaseLocator; 10 import com.serotonin.modbus4j.locator.BaseLocator;
  11 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcDataType;
11 import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint; 12 import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
12 import com.zhonglai.luhui.device.modbus.terminal.task.CollectPlcDataTask; 13 import com.zhonglai.luhui.device.modbus.terminal.task.CollectPlcDataTask;
13 import org.slf4j.Logger; 14 import org.slf4j.Logger;
@@ -18,6 +19,7 @@ import java.io.IOException; @@ -18,6 +19,7 @@ import java.io.IOException;
18 import java.util.HashMap; 19 import java.util.HashMap;
19 import java.util.List; 20 import java.util.List;
20 import java.util.Map; 21 import java.util.Map;
  22 +import java.util.concurrent.Future;
21 23
22 /** 24 /**
23 * modbus通讯工具类,采用modbus4j实现 25 * modbus通讯工具类,采用modbus4j实现
@@ -46,94 +48,20 @@ public class Modbus4jRead { @@ -46,94 +48,20 @@ public class Modbus4jRead {
46 /** 48 /**
47 * 批量读取点位 49 * 批量读取点位
48 */ 50 */
49 - public Map<String, Object> batchRead(List<PlcPoint> plcPoints, boolean zeroBasedAddress) throws ModbusTransportException, ErrorResponseException {  
50 - BatchRead<String> batch = new BatchRead<>();  
51 -  
52 - for (PlcPoint plcPoint : plcPoints) {  
53 - String key = plcPoint.getSystem();  
54 -  
55 - // 解析 Modbus 地址,生成 BaseLocator  
56 - BaseLocator<?> locator = ModbusAddressParser.parseLocator(  
57 - plcPoint.getAddress(),  
58 - plcPoint.getDataType(),  
59 - plcPoint.getSlaveId(),  
60 - zeroBasedAddress,  
61 - plcPoint.getOrder()  
62 - );  
63 -  
64 - batch.addLocator(key, locator);  
65 - }  
66 -  
67 - batch.setContiguousRequests(false);  
68 -  
69 - // 发送批量请求  
70 - BatchResults<String> results = null;  
71 - try {  
72 - results = ModbusMasterMessage.getMaster( id).send(batch);  
73 - } catch (Exception e) {  
74 - logger.error("第一次批量读取异常,尝试一次", e);  
75 - if (e instanceof ModbusInitException || e instanceof ModbusTransportException || e.getCause() instanceof IOException) {  
76 - ModbusMasterMessage.closeMaster(id);  
77 - try {  
78 - results = ModbusMasterMessage.getMaster( id).send(batch);  
79 - } catch (Exception ex) {  
80 - logger.error("第二次批量读取异常,不处理", ex);  
81 - return null;  
82 - }  
83 - } else {  
84 - logger.info("第一次批量读不是联系异常,不尝试了");  
85 - return null;  
86 - }  
87 -  
88 - }  
89 -  
90 - // 转换成 JSON 对象 51 + public Map<String, Object> batchRead( List<PlcPoint> plcPoints, boolean zeroBasedAddress) throws Exception {
  52 + EnhancedModbusTcpClientWithByteOrder client = ModbusMasterMessage.getEnhancedModbusTcpClientWithByteOrder(id);
91 Map<String, Object> jsonMap = new HashMap<>(); 53 Map<String, Object> jsonMap = new HashMap<>();
92 for (PlcPoint plcPoint : plcPoints) { 54 for (PlcPoint plcPoint : plcPoints) {
93 -  
94 - // 输出所有结果  
95 String key = plcPoint.getSystem(); 55 String key = plcPoint.getSystem();
96 - Object value = results.getValue(key);  
97 - switch (plcPoint.getDataType()) 56 + Object value;
  57 + String addrStr = plcPoint.getAddress();
  58 + EnhancedModbusTcpClientWithByteOrder.ModbusAddress addr = EnhancedModbusTcpClientWithByteOrder.ModbusAddress.parse(addrStr);
  59 + if (addr.getBitIndex()>-1)
98 { 60 {
99 - case FLOAT32:  
100 - if (value instanceof Integer) {  
101 - int raw = (int) value;  
102 - switch (plcPoint.getOrder()) {  
103 - case "ABCD":  
104 - value = ModbusAddressParser.intToFloatABCD(raw);  
105 - break;  
106 - case "BADC":  
107 - value = ModbusAddressParser.intToFloatBADC(raw);  
108 - break;  
109 - case "CDAB":  
110 - value = ModbusAddressParser.intToFloatCDAB(raw);  
111 - break;  
112 - case "DCBA":  
113 - value = ModbusAddressParser.intToFloatDCBA(raw);  
114 - break;  
115 - }  
116 - }  
117 - break;  
118 - case INT32:  
119 - if (value instanceof Integer) {  
120 - int raw = (int) value;  
121 - switch (plcPoint.getOrder()) {  
122 - case "ABCD":  
123 - value = ModbusAddressParser.int32ABCD(raw);  
124 - break;  
125 - case "BADC":  
126 - value = ModbusAddressParser.int32BADC(raw);  
127 - break;  
128 - case "CDAB":  
129 - value = ModbusAddressParser.int32CDAB(raw);  
130 - break;  
131 - case "DCBA":  
132 - value = ModbusAddressParser.int32DCBA(raw);  
133 - break;  
134 - }  
135 - }  
136 - break; 61 + value = client.readBooleanFromRegisterBit(1, addrStr, zeroBasedAddress);
  62 + }else
  63 + {
  64 + value = client.read(1, addr.getStartAddress(),plcPoint.getDataType(),zeroBasedAddress,null == plcPoint.getOrder()? EnhancedModbusTcpClientWithByteOrder.ByteOrderType.ABCD: EnhancedModbusTcpClientWithByteOrder.ByteOrderType.valueOf(plcPoint.getOrder())).get();
137 } 65 }
138 jsonMap.put(key, value); 66 jsonMap.put(key, value);
139 } 67 }
@@ -34,120 +34,24 @@ public class Modbus4jWrite { @@ -34,120 +34,24 @@ public class Modbus4jWrite {
34 public Modbus4jWrite(String id) throws Exception { 34 public Modbus4jWrite(String id) throws Exception {
35 this.id = id; 35 this.id = id;
36 } 36 }
37 - private final Object writeLock = new Object(); // 全局串行化写锁  
38 37
39 public void batchWrite(List<PlcPoint> points, boolean zeroBasedAddress) throws Exception { 38 public void batchWrite(List<PlcPoint> points, boolean zeroBasedAddress) throws Exception {
40 if (points == null || points.isEmpty()) return; 39 if (points == null || points.isEmpty()) return;
41 40
42 - synchronized (writeLock) {  
43 - ModbusMaster master = ModbusMasterMessage.getMaster(id);  
44 -  
45 - try {  
46 - sendBatch(master, points, zeroBasedAddress);  
47 - } catch (Exception e) {  
48 - log.info("第一次批量写入异常,尝试重连后再写入", e);  
49 -  
50 - if (e instanceof ModbusInitException || e instanceof ModbusTransportException || e.getCause() instanceof IOException) {  
51 - ModbusMasterMessage.closeMaster(id);  
52 - master = ModbusMasterMessage.getMaster(id);  
53 - sendBatch(master, points, zeroBasedAddress);  
54 - } else {  
55 - log.info("第二次批量写入异常,不处理", e);  
56 - }  
57 - }  
58 - }  
59 - }  
60 - // 批量写入封装  
61 - private void sendBatch(ModbusMaster master, List<PlcPoint> points, boolean zeroBasedAddress) throws Exception {  
62 -  
63 - for (PlcPoint point : points) {  
64 - BaseLocator<?> locator = ModbusAddressParser.parseLocator(  
65 - point.getAddress(),  
66 - point.getDataType(),  
67 - point.getSlaveId(),  
68 - zeroBasedAddress,  
69 - point.getOrder()  
70 - );  
71 -  
72 - Object value = convertValue(point);  
73 -  
74 - if (point.getDataType() == PlcDataType.BIT && point.getAddress().contains(".")) {  
75 - // ⚡ 先读寄存器,再修改 bit 位,再写回  
76 - int offset = ModbusAddressParser.parseOffset(point.getAddress(), zeroBasedAddress);  
77 - int bitIndex = ModbusAddressParser.parseBitIndex(point.getAddress());  
78 - if (bitIndex < 0) {  
79 - throw new IllegalArgumentException("无效的 bit 地址: " + point.getAddress());  
80 - }  
81 -  
82 - // 读 holding register  
83 - int rawValue = master.getValue(  
84 - BaseLocator.holdingRegister(point.getSlaveId(), offset, DataType.TWO_BYTE_INT_UNSIGNED)  
85 - ).intValue();  
86 -  
87 - // 修改 bit  
88 - boolean bitVal = (Boolean) value;  
89 - if (bitVal) {  
90 - rawValue = rawValue | (1 << bitIndex); // 置位  
91 - } else {  
92 - rawValue = rawValue & ~(1 << bitIndex); // 清位  
93 - }  
94 -  
95 - // 写回  
96 - master.setValue(  
97 - ModbusAddressParser.parseLocator(  
98 - point.getAddress().split("\\.")[0],  
99 - PlcDataType.INT16,  
100 - point.getSlaveId(),  
101 - zeroBasedAddress,  
102 - point.getOrder()  
103 - ),  
104 - rawValue  
105 - );  
106 -  
107 - log.info("写入成功(Bit): {}[{}] = {}", point.getName(), point.getAddress(), value);  
108 -  
109 - } else {  
110 - // ⚡ 其它类型照旧  
111 - master.setValue(locator, value);  
112 - log.info("写入成功: {}[{}] = {}", point.getName(), point.getAddress(), value);  
113 - }  
114 - }  
115 - }  
116 -  
117 -  
118 - // 值转换逻辑沿用你的 writePoint  
119 - private Object convertValue(PlcPoint point) {  
120 - Object value = point.getValue();  
121 - if (value instanceof String) {  
122 - String strVal = (String) value;  
123 - switch (point.getDataType()) {  
124 - case BIT: return Boolean.parseBoolean(strVal);  
125 - case INT16: return Short.parseShort(strVal);  
126 - case UINT16: return Integer.parseInt(strVal);  
127 - case INT32:  
128 - int raw = Integer.parseInt(strVal);  
129 - switch (point.getOrder()) {  
130 - case "ABCD": return ModbusAddressParser.int32ToABCD(raw);  
131 - case "BADC": return ModbusAddressParser.int32ToBADC(raw);  
132 - case "CDAB": return ModbusAddressParser.int32ToCDAB(raw);  
133 - case "DCBA": return ModbusAddressParser.int32ToDCBA(raw);  
134 - default: return raw;  
135 - }  
136 - case INT64: return Long.parseLong(strVal);  
137 - case FLOAT32:  
138 - float fvalue = Float.parseFloat(strVal);  
139 - switch (point.getOrder()) {  
140 - case "ABCD": return ModbusAddressParser.floatToIntABCD(fvalue);  
141 - case "BADC": return ModbusAddressParser.floatToIntBADC(fvalue);  
142 - case "CDAB": return ModbusAddressParser.floatToIntCDAB(fvalue);  
143 - case "DCBA": return ModbusAddressParser.floatToIntDCBA(fvalue);  
144 - default: return fvalue;  
145 - }  
146 - case DOUBLE64: return Double.parseDouble(strVal);  
147 - default: throw new IllegalArgumentException("不支持的数据类型: " + point.getDataType()); 41 + EnhancedModbusTcpClientWithByteOrder client = ModbusMasterMessage.getEnhancedModbusTcpClientWithByteOrder(id);
  42 + for (PlcPoint plcPoint : points)
  43 + {
  44 + EnhancedModbusTcpClientWithByteOrder.ModbusAddress addr = EnhancedModbusTcpClientWithByteOrder.ModbusAddress.parse(plcPoint.getAddress());
  45 + if (addr.getBitIndex()>-1)
  46 + {
  47 + log.info("BIT写入数据:addr {},plcPoint {}",addr,plcPoint);
  48 + client.writeBooleanToRegisterBit(1, plcPoint.getAddress(), Boolean.valueOf(String.valueOf(plcPoint.getValue())), zeroBasedAddress);
  49 + }else
  50 + {
  51 + log.info(plcPoint.getDataType()+"写入数据:addr {},plcPoint {}",addr,plcPoint);
  52 + client.write(1, addr.getStartAddress(),plcPoint.getDataType(),plcPoint.getValue(), zeroBasedAddress, null == plcPoint.getOrder()? EnhancedModbusTcpClientWithByteOrder.ByteOrderType.CDAB: EnhancedModbusTcpClientWithByteOrder.ByteOrderType.valueOf(plcPoint.getOrder()));
148 } 53 }
149 } 54 }
150 - return value;  
151 } 55 }
152 56
153 } 57 }
1 -package com.zhonglai.luhui.device.modbus.terminal.modbus;  
2 -  
3 -import com.serotonin.modbus4j.locator.BaseLocator;  
4 -import com.serotonin.modbus4j.code.DataType;  
5 -import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcDataType;  
6 -  
7 -import java.nio.ByteBuffer;  
8 -import java.nio.ByteOrder;  
9 -  
10 -public class ModbusAddressParser {  
11 -  
12 - /**  
13 - * 解析 Modbus 地址,生成 BaseLocator  
14 - * @param rawAddress 原始地址 (40001, 40001.01, 40003-40004)  
15 - * @param type 数据类型 (BIT, INT16, UINT16, INT32, INT64, FLOAT32, DOUBLE64)  
16 - * @param slaveId 从站ID  
17 - * @return BaseLocator 对象  
18 - */  
19 - public static BaseLocator<?> parseLocator(String rawAddress, PlcDataType type, int slaveId, boolean zeroBasedAddress,String order) {  
20 - int offset = parseOffset(rawAddress,zeroBasedAddress);  
21 - int bitIndex = parseBitIndex(rawAddress);  
22 -  
23 - switch (type) {  
24 - case BIT:  
25 - if (rawAddress.startsWith("0")) {  
26 - // Coil (线圈输出)  
27 - return BaseLocator.coilStatus(slaveId, offset);  
28 - } else if (rawAddress.startsWith("1")) {  
29 - // Discrete Input (离散量输入)  
30 - return BaseLocator.inputStatus(slaveId, offset);  
31 - } else if (rawAddress.startsWith("3") && bitIndex >= 0) {  
32 - // Input Register 内部 bit 位  
33 - return BaseLocator.inputRegisterBit(slaveId, offset, bitIndex);  
34 - } else if (rawAddress.startsWith("4") && bitIndex >= 0) {  
35 - // Holding Register 内部 bit 位  
36 - return BaseLocator.holdingRegisterBit(slaveId, offset, bitIndex);  
37 - } else {  
38 - throw new IllegalArgumentException("地址不支持 BIT 类型: " + rawAddress);  
39 - }  
40 -  
41 - case INT16:  
42 - if (rawAddress.startsWith("3")) {  
43 - return BaseLocator.inputRegister(slaveId, offset, DataType.TWO_BYTE_INT_SIGNED);  
44 - } else if (rawAddress.startsWith("4")) {  
45 - return BaseLocator.holdingRegister(slaveId, offset, DataType.TWO_BYTE_INT_SIGNED);  
46 - }  
47 - break;  
48 -  
49 - case UINT16:  
50 - if (rawAddress.startsWith("3")) {  
51 - return BaseLocator.inputRegister(slaveId, offset, DataType.TWO_BYTE_INT_UNSIGNED);  
52 - } else if (rawAddress.startsWith("4")) {  
53 - return BaseLocator.holdingRegister(slaveId, offset, DataType.TWO_BYTE_INT_UNSIGNED);  
54 - }  
55 - break;  
56 -  
57 - case INT32:  
58 - if (rawAddress.startsWith("3")) {  
59 - return BaseLocator.inputRegister(slaveId, offset, DataType.FOUR_BYTE_INT_SIGNED);  
60 - } else if (rawAddress.startsWith("4")) {  
61 - return BaseLocator.holdingRegister(slaveId, offset, DataType.FOUR_BYTE_INT_SIGNED);  
62 - }  
63 - break;  
64 -  
65 - case INT64:  
66 - if (rawAddress.startsWith("3")) {  
67 - return BaseLocator.inputRegister(slaveId, offset, DataType.EIGHT_BYTE_INT_SIGNED);  
68 - } else if (rawAddress.startsWith("4")) {  
69 - return BaseLocator.holdingRegister(slaveId, offset, DataType.EIGHT_BYTE_INT_SIGNED);  
70 - }  
71 - break;  
72 -  
73 - case FLOAT32:  
74 - if (order == null) {  
75 - order = "ABCD"; // 默认大端  
76 - }  
77 - int floatDataType = DataType.FOUR_BYTE_FLOAT;  
78 - switch (order) {  
79 - case "ABCD": // 大端 IEEE754  
80 - floatDataType = DataType.FOUR_BYTE_FLOAT;  
81 - break;  
82 - case "CDAB": // 寄存器交换  
83 - floatDataType = DataType.FOUR_BYTE_FLOAT_SWAPPED;  
84 - break;  
85 - case "BADC": // 字节交换  
86 - floatDataType = DataType.FOUR_BYTE_FLOAT_SWAPPED_INVERTED;  
87 - break;  
88 - case "DCBA": // 全反转  
89 - floatDataType = DataType.FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED;  
90 - // ⚠ modbus4j 没有直接 DCBA 的 float,可以用 int SWAP-SWAP 再手动转 float  
91 - break;  
92 - }  
93 - if (rawAddress.startsWith("3")) {  
94 - return BaseLocator.inputRegister(slaveId, offset, floatDataType);  
95 - } else if (rawAddress.startsWith("4")) {  
96 - return BaseLocator.holdingRegister(slaveId, offset, floatDataType);  
97 - }  
98 - break;  
99 -  
100 - case DOUBLE64:  
101 - if (rawAddress.startsWith("3")) {  
102 - return BaseLocator.inputRegister(slaveId, offset, DataType.EIGHT_BYTE_FLOAT);  
103 - } else if (rawAddress.startsWith("4")) {  
104 - return BaseLocator.holdingRegister(slaveId, offset, DataType.EIGHT_BYTE_FLOAT);  
105 - }  
106 - break;  
107 -  
108 - default:  
109 - throw new IllegalArgumentException("不支持的数据类型: " + type);  
110 - }  
111 -  
112 - throw new IllegalArgumentException("地址和数据类型不匹配: " + rawAddress + " -> " + type);  
113 - }  
114 -  
115 - /**  
116 - * 解析寄存器地址(不包含小数点后的 bit 部分)  
117 - * @param addressStr e.g. "40050.06" 或 "40050"  
118 - * @param zeroBasedAddress 是否零基地址  
119 - * @return int registerAddress  
120 - */  
121 - public static int parseRegisterAddress(String addressStr, boolean zeroBasedAddress) {  
122 - String[] parts = addressStr.split("\\.");  
123 - int baseAddr = Integer.parseInt(parts[0]);  
124 -  
125 - // 去掉40001/30001等逻辑前缀,Modbus4j用的是零基  
126 - if (baseAddr >= 40001 && baseAddr <= 49999) {  
127 - baseAddr = baseAddr - 40001;  
128 - } else if (baseAddr >= 30001 && baseAddr <= 39999) {  
129 - baseAddr = baseAddr - 30001;  
130 - } else if (baseAddr >= 1 && baseAddr <= 9999) {  
131 - baseAddr = baseAddr - 1;  
132 - }  
133 -  
134 - if (!zeroBasedAddress) {  
135 - baseAddr = baseAddr + 1; // 用户配置非零基,则+1  
136 - }  
137 -  
138 - return baseAddr;  
139 - }  
140 -  
141 - /** 解析寄存器偏移量 */  
142 - public static int parseOffset(String rawAddress, boolean zeroBasedAddress) {  
143 - if (rawAddress == null || rawAddress.isEmpty()) {  
144 - throw new IllegalArgumentException("地址不能为空");  
145 - }  
146 -  
147 - // 处理区间地址:40003-40004  
148 - if (rawAddress.contains("-")) {  
149 - String[] parts = rawAddress.split("-");  
150 - rawAddress = parts[0]; // 取起始地址  
151 - }  
152 -  
153 - // 去掉小数点后部分:40001.01 -> 40001  
154 - String basePart = rawAddress.split("\\.")[0];  
155 - int address = Integer.parseInt(basePart);  
156 -  
157 - int offset;  
158 - if (address >= 1 && address <= 9999) { // Coil (0xxxx)  
159 - offset = address - 1;  
160 - } else if (address >= 10001 && address <= 19999) { // Discrete Input (1xxxx)  
161 - offset = address - 10001;  
162 - } else if (address >= 30001 && address <= 39999) { // Input Register (3xxxx)  
163 - offset = address - 30001;  
164 - } else if (address >= 40001 && address <= 49999) { // Holding Register (4xxxx)  
165 - offset = address - 40001;  
166 - } else {  
167 - throw new IllegalArgumentException("无效的Modbus地址: " + rawAddress);  
168 - }  
169 -  
170 - // 根据 zeroBasedAddress 参数调整偏移  
171 - if (!zeroBasedAddress) {  
172 - offset += 1;  
173 - }  
174 -  
175 - return offset;  
176 - }  
177 -  
178 -  
179 - /** 解析 bit 索引 */  
180 - public static int parseBitIndex(String rawAddress) {  
181 - if (rawAddress.contains(".")) {  
182 - String[] parts = rawAddress.split("\\.");  
183 - return Integer.parseInt(parts[1]) - 1; // 转成从0开始  
184 - }  
185 - return -1; // 没有 bit 位  
186 - }  
187 -  
188 - public static float intToFloatDCBA(int value) {  
189 - byte[] bytes = new byte[4];  
190 - bytes[0] = (byte) (value & 0xFF);  
191 - bytes[1] = (byte) ((value >> 8) & 0xFF);  
192 - bytes[2] = (byte) ((value >> 16) & 0xFF);  
193 - bytes[3] = (byte) ((value >> 24) & 0xFF);  
194 -  
195 - // 反转成 DCBA  
196 - byte tmp;  
197 - tmp = bytes[0]; bytes[0] = bytes[3]; bytes[3] = tmp;  
198 - tmp = bytes[1]; bytes[1] = bytes[2]; bytes[2] = tmp;  
199 -  
200 - return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();  
201 - }  
202 -  
203 - public static int floatToIntDCBA(float f) {  
204 - byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(f).array();  
205 -  
206 - // 反转成 DCBA  
207 - byte tmp;  
208 - tmp = bytes[0]; bytes[0] = bytes[3]; bytes[3] = tmp;  
209 - tmp = bytes[1]; bytes[1] = bytes[2]; bytes[2] = tmp;  
210 -  
211 - return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();  
212 - }  
213 -  
214 -  
215 - public static float intToFloatABCD(int value) {  
216 - byte[] bytes = intToBytes(value);  
217 - return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();  
218 - }  
219 -  
220 - public static float intToFloatBADC(int value) {  
221 - byte[] bytes = intToBytes(value);  
222 - swap(bytes, 0, 1);  
223 - swap(bytes, 2, 3);  
224 - return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();  
225 - }  
226 -  
227 - public static float intToFloatCDAB(int value) {  
228 - byte[] bytes = intToBytes(value);  
229 - swap(bytes, 0, 2);  
230 - swap(bytes, 1, 3);  
231 - return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();  
232 - }  
233 -  
234 - // ========== 32位整型的不同字节序 ==========  
235 - public static int int32ABCD(int value) {  
236 - return value; // 默认就是ABCD  
237 - }  
238 -  
239 - public static int int32BADC(int value) {  
240 - byte[] bytes = intToBytes(value);  
241 - swap(bytes, 0, 1);  
242 - swap(bytes, 2, 3);  
243 - return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();  
244 - }  
245 -  
246 - public static int int32CDAB(int value) {  
247 - byte[] bytes = intToBytes(value);  
248 - swap(bytes, 0, 2);  
249 - swap(bytes, 1, 3);  
250 - return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();  
251 - }  
252 -  
253 - public static int int32DCBA(int value) {  
254 - byte[] bytes = intToBytes(value);  
255 - reverse(bytes);  
256 - return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();  
257 - }  
258 -  
259 - // ========== 通用工具 ==========  
260 - private static byte[] intToBytes(int value) {  
261 - return new byte[] {  
262 - (byte) ((value >> 24) & 0xFF),  
263 - (byte) ((value >> 16) & 0xFF),  
264 - (byte) ((value >> 8) & 0xFF),  
265 - (byte) (value & 0xFF)  
266 - };  
267 - }  
268 -  
269 - private static void swap(byte[] arr, int i, int j) {  
270 - byte tmp = arr[i];  
271 - arr[i] = arr[j];  
272 - arr[j] = tmp;  
273 - }  
274 -  
275 - private static void reverse(byte[] arr) {  
276 - for (int i = 0, j = arr.length - 1; i < j; i++, j--) {  
277 - swap(arr, i, j);  
278 - }  
279 - }  
280 -  
281 -  
282 - // ========== Float 写入 ==========  
283 - public static int floatToIntABCD(float f) {  
284 - byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(f).array();  
285 - return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();  
286 - }  
287 -  
288 - public static int floatToIntBADC(float f) {  
289 - byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(f).array();  
290 - swap(bytes, 0, 1);  
291 - swap(bytes, 2, 3);  
292 - return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();  
293 - }  
294 -  
295 - public static int floatToIntCDAB(float f) {  
296 - byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(f).array();  
297 - swap(bytes, 0, 2);  
298 - swap(bytes, 1, 3);  
299 - return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();  
300 - }  
301 -  
302 -  
303 - // ========== Int32 写入 ==========  
304 - public static int int32ToABCD(int value) {  
305 - return value; // 默认  
306 - }  
307 -  
308 - public static int int32ToBADC(int value) {  
309 - byte[] bytes = intToBytes(value);  
310 - swap(bytes, 0, 1);  
311 - swap(bytes, 2, 3);  
312 - return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();  
313 - }  
314 -  
315 - public static int int32ToCDAB(int value) {  
316 - byte[] bytes = intToBytes(value);  
317 - swap(bytes, 0, 2);  
318 - swap(bytes, 1, 3);  
319 - return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();  
320 - }  
321 -  
322 - public static int int32ToDCBA(int value) {  
323 - byte[] bytes = intToBytes(value);  
324 - reverse(bytes);  
325 - return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();  
326 - }  
327 -  
328 - private short[] toRegisters(Object value, PlcDataType dataType, String order) {  
329 - switch (dataType) {  
330 - case INT16:  
331 - return new short[]{(Short) value};  
332 - case UINT16:  
333 - return new short[]{((Integer) value).shortValue()};  
334 - case INT32:  
335 - int intVal = (Integer) value;  
336 - return int32ToShorts(intVal, order);  
337 - case FLOAT32:  
338 - float fVal = (Float) value;  
339 - return floatToShorts(fVal, order);  
340 - case DOUBLE64:  
341 - double dVal = (Double) value;  
342 - return doubleToShorts(dVal, order);  
343 - default:  
344 - throw new IllegalArgumentException("不支持的数据类型: " + dataType);  
345 - }  
346 - }  
347 -  
348 - // INT32 转 short[2]  
349 - private short[] int32ToShorts(int value, String order) {  
350 - short high = (short) ((value >> 16) & 0xFFFF);  
351 - short low = (short) (value & 0xFFFF);  
352 - if ("ABCD".equals(order)) return new short[]{high, low};  
353 - if ("BADC".equals(order)) return new short[]{(short)((high & 0xFF) << 8 | (high >> 8 & 0xFF)),  
354 - (short)((low & 0xFF) << 8 | (low >> 8 & 0xFF))};  
355 - if ("CDAB".equals(order)) return new short[]{low, high};  
356 - if ("DCBA".equals(order)) return new short[]{(short)((low & 0xFF) << 8 | (low >> 8 & 0xFF)),  
357 - (short)((high & 0xFF) << 8 | (high >> 8 & 0xFF))};  
358 - return new short[]{high, low};  
359 - }  
360 -  
361 - // FLOAT32 转 short[2]  
362 - private short[] floatToShorts(float f, String order) {  
363 - int bits = Float.floatToIntBits(f);  
364 - return int32ToShorts(bits, order);  
365 - }  
366 -  
367 - // DOUBLE64 转 short[4]  
368 - private short[] doubleToShorts(double d, String order) {  
369 - long bits = Double.doubleToLongBits(d);  
370 - short[] regs = new short[4];  
371 - regs[0] = (short)((bits >> 48) & 0xFFFF);  
372 - regs[1] = (short)((bits >> 32) & 0xFFFF);  
373 - regs[2] = (short)((bits >> 16) & 0xFFFF);  
374 - regs[3] = (short)(bits & 0xFFFF);  
375 -  
376 - switch (order) {  
377 - case "ABCD": return regs;  
378 - case "BADC": return new short[]{regs[1], regs[0], regs[3], regs[2]};  
379 - case "CDAB": return new short[]{regs[2], regs[3], regs[0], regs[1]};  
380 - case "DCBA": return new short[]{regs[3], regs[2], regs[1], regs[0]};  
381 - default: return regs;  
382 - }  
383 - }  
384 -  
385 -  
386 -}  
  1 +package com.zhonglai.luhui.device.modbus.terminal.modbus;
  2 +
  3 +import com.serotonin.modbus4j.ModbusFactory;
  4 +import com.serotonin.modbus4j.ModbusMaster;
  5 +import com.serotonin.modbus4j.ip.IpParameters;
  6 +import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.JSerialCommWrapper;
  7 +import com.alibaba.fastjson.JSONObject;
  8 +import org.slf4j.Logger;
  9 +import org.slf4j.LoggerFactory;
  10 +
  11 +import java.io.File;
  12 +import java.util.Objects;
  13 +import java.util.concurrent.ConcurrentHashMap;
  14 +
  15 +/**
  16 + * 统一管理 ModbusMaster 的连接池/缓存(按 key 缓存)
  17 + * key 建议:tcp -> "tcp:host:port" ; rtu/ascii/udp -> 使用 PLC id 或可序列化配置信息
  18 + */
  19 +public class ModbusConnectionManager {
  20 + private static final Logger log = LoggerFactory.getLogger(ModbusConnectionManager.class);
  21 + private static final ModbusFactory factory = new ModbusFactory();
  22 +
  23 + // key -> ModbusMaster
  24 + private static final ConcurrentHashMap<String, ModbusMaster> masterCache = new ConcurrentHashMap<>();
  25 +
  26 + // key -> lock object for double-check init
  27 + private static final ConcurrentHashMap<String, Object> lockMap = new ConcurrentHashMap<>();
  28 +
  29 + private ModbusConnectionManager() {}
  30 +
  31 + /**
  32 + * 获取或创建 TCP Master(host + port 作为 key)
  33 + */
  34 + public static ModbusMaster getTcpMaster(String host, int port, int timeout, int retries, boolean encapsulated) throws Exception {
  35 + String key = String.format("tcp:%s:%d", Objects.requireNonNull(host), port);
  36 + ModbusMaster m = masterCache.get(key);
  37 + if (m != null) return m;
  38 +
  39 + Object lock = lockMap.computeIfAbsent(key, k -> new Object());
  40 + synchronized (lock) {
  41 + m = masterCache.get(key);
  42 + if (m != null) return m;
  43 +
  44 + IpParameters params = new IpParameters();
  45 + params.setHost(host);
  46 + params.setPort(port);
  47 + // encapsulated param controls MBAP or not (depends on device); keep configurable
  48 + m = factory.createTcpMaster(params, encapsulated);
  49 + m.setTimeout(timeout);
  50 + m.setRetries(retries);
  51 + try {
  52 + // set custom IO log file if needed
  53 + m.setIoLog(new MyIOLog(new File("logs/modbus.log")));
  54 + } catch (Exception e) {
  55 + // 忽略日志初始化失败
  56 + }
  57 + m.init();
  58 + masterCache.put(key, m);
  59 + log.info("Created TCP ModbusMaster [{}]", key);
  60 + return m;
  61 + }
  62 + }
  63 +
  64 + /**
  65 + * 通用创建接口 —— 通过传入 protocolType 与 connectConfig(用于兼容 InitPlcConfig)
  66 + * 支持:TCP / RTU / ASCII / UDP (与原 createMaster 保持一致)
  67 + *
  68 + * protocolType: "TCP" / "RTU" / "ASCII" / "UDP"
  69 + * connectConfig: JSONObject(与原代码互操作)
  70 + */
  71 + public static ModbusMaster getOrCreateMaster(String key, String protocolType, JSONObject connectConfig, int timeout, int retries) throws Exception {
  72 + ModbusMaster m = masterCache.get(key);
  73 + if (m != null) return m;
  74 +
  75 + Object lock = lockMap.computeIfAbsent(key, k -> new Object());
  76 + synchronized (lock) {
  77 + m = masterCache.get(key);
  78 + if (m != null) return m;
  79 +
  80 + ModbusMaster created;
  81 + switch (protocolType) {
  82 + case "TCP": {
  83 + IpParameters parameters = JSONObject.parseObject(connectConfig.toJSONString(), IpParameters.class);
  84 + created = factory.createTcpMaster(parameters, true);
  85 + break;
  86 + }
  87 + case "RTU": {
  88 + JSerialCommWrapper wrapper = JSONObject.parseObject(connectConfig.toJSONString(), JSerialCommWrapper.class);
  89 + created = factory.createRtuMaster(wrapper);
  90 + break;
  91 + }
  92 + case "ASCII": {
  93 + JSerialCommWrapper wrapper = JSONObject.parseObject(connectConfig.toJSONString(), JSerialCommWrapper.class);
  94 + created = factory.createAsciiMaster(wrapper);
  95 + break;
  96 + }
  97 + case "UDP": {
  98 + IpParameters parameters = JSONObject.parseObject(connectConfig.toJSONString(), IpParameters.class);
  99 + created = factory.createUdpMaster(parameters);
  100 + break;
  101 + }
  102 + default:
  103 + throw new IllegalArgumentException("不支持的协议类型: " + protocolType);
  104 + }
  105 +
  106 + created.setTimeout(timeout);
  107 + created.setRetries(retries);
  108 + try {
  109 + created.setIoLog(new MyIOLog(new File("logs/modbus.log")));
  110 + } catch (Exception ignored) {}
  111 + created.init();
  112 +
  113 + masterCache.put(key, created);
  114 + log.info("Created ModbusMaster [{}] protocol={}", key, protocolType);
  115 + return created;
  116 + }
  117 + }
  118 +
  119 + /**
  120 + * 关闭并移除指定 key 的 master
  121 + */
  122 + public static void closeMaster(String key) {
  123 + Object lock = lockMap.computeIfAbsent(key, k -> new Object());
  124 + synchronized (lock) {
  125 + ModbusMaster m = masterCache.remove(key);
  126 + if (m != null) {
  127 + try {
  128 + m.destroy();
  129 + log.info("Closed ModbusMaster [{}]", key);
  130 + } catch (Exception e) {
  131 + log.warn("Close ModbusMaster [{}] failed: {}", key, e.getMessage());
  132 + }
  133 + }
  134 + lockMap.remove(key);
  135 + }
  136 + }
  137 +
  138 + /**
  139 + * 关闭所有 master
  140 + */
  141 + public static void closeAll() {
  142 + masterCache.forEach((k, v) -> {
  143 + try { v.destroy(); } catch (Exception ignored) {}
  144 + });
  145 + masterCache.clear();
  146 + lockMap.clear();
  147 + log.info("Closed all ModbusMaster instances");
  148 + }
  149 +}
1 package com.zhonglai.luhui.device.modbus.terminal.modbus; 1 package com.zhonglai.luhui.device.modbus.terminal.modbus;
2 2
3 -import com.serotonin.modbus4j.ModbusFactory;  
4 -import com.serotonin.modbus4j.ModbusMaster;  
5 import com.alibaba.fastjson.JSONObject; 3 import com.alibaba.fastjson.JSONObject;
6 -import com.serotonin.modbus4j.ip.IpParameters;  
7 -import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;  
8 -import com.serotonin.modbus4j.msg.ReadHoldingRegistersResponse; 4 +import com.serotonin.modbus4j.ModbusMaster;
9 import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig; 5 import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig;
10 import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.CachPlcConfig; 6 import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.CachPlcConfig;
11 -import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.JSerialCommWrapper;  
12 import org.slf4j.Logger; 7 import org.slf4j.Logger;
13 import org.slf4j.LoggerFactory; 8 import org.slf4j.LoggerFactory;
14 9
15 -import java.io.File;  
16 import java.util.concurrent.ConcurrentHashMap; 10 import java.util.concurrent.ConcurrentHashMap;
17 11
  12 +/**
  13 + * 通过 PLC ID 获取 ModbusMaster 或增强的 Modbus TCP Client。
  14 + * 所有 ModbusMaster 交由 ModbusConnectionManager 管理生命周期。
  15 + *
  16 + * 支持:
  17 + * - PLC ID 生成唯一 key
  18 + * - 安全缓存 Enhanced 客户端
  19 + * - 自动加载字节序
  20 + */
18 public class ModbusMasterMessage { 21 public class ModbusMasterMessage {
  22 +
19 private static final Logger log = LoggerFactory.getLogger(ModbusMasterMessage.class); 23 private static final Logger log = LoggerFactory.getLogger(ModbusMasterMessage.class);
20 - private static final ConcurrentHashMap<String, ModbusMaster> masterCache = new ConcurrentHashMap<>();  
21 - private static final ConcurrentHashMap<String, Object> lockMap = new ConcurrentHashMap<>();  
22 - private static final ModbusFactory factory = new ModbusFactory();  
23 -  
24 - /** 获取或创建 ModbusMaster,不做连接性检测 */  
25 - public static ModbusMaster getMaster(String id) throws Exception {  
26 - ModbusMaster master = masterCache.get(id);  
27 - if (master != null) { 24 +
  25 + /**
  26 + * 记录 key 与 plcId 的映射(目前主要用于 debug,可扩展)
  27 + */
  28 + private static final ConcurrentHashMap<String, String> keyToPlcId = new ConcurrentHashMap<>();
  29 +
  30 + /**
  31 + * 缓存增强版 TCP 客户端,避免多次创建造成连接池重复。
  32 + */
  33 + private static final ConcurrentHashMap<String, EnhancedModbusTcpClientWithByteOrder> enhancedCache =
  34 + new ConcurrentHashMap<>();
  35 +
  36 +
  37 + /**
  38 + * 根据 PLC ID 获取 ModbusMaster,如不存在则由 ConnectionManager 创建。
  39 + */
  40 + public static ModbusMaster getMaster(String plcId) throws Exception {
  41 +
  42 + CachPlcConfig config = InitPlcConfig.getPlcSystems(plcId);
  43 + if (config == null) {
  44 + throw new IllegalArgumentException("未找到 PLC 配置: " + plcId);
  45 + }
  46 +
  47 + String key = "modbus:master:" + plcId;
  48 +
  49 + ModbusMaster master = ModbusConnectionManager.getOrCreateMaster(
  50 + key,
  51 + config.getProtocolType().name(),
  52 + config.getConnectConfig(),
  53 + 3000,
  54 + 1
  55 + );
  56 +
  57 + keyToPlcId.put(key, plcId);
28 return master; 58 return master;
29 } 59 }
30 60
31 - // double-check locking 保证只创建一次  
32 - Object lock = lockMap.computeIfAbsent(id, k -> new Object());  
33 - synchronized (lock) {  
34 - master = masterCache.get(id);  
35 - if (master == null) {  
36 - CachPlcConfig config = InitPlcConfig.getPlcSystems(id); 61 +
  62 + /**
  63 + * 获取或创建 EnhancedModbusTcpClientWithByteOrder(带缓存)
  64 + */
  65 + public static EnhancedModbusTcpClientWithByteOrder getEnhancedModbusTcpClientWithByteOrder(String plcId)
  66 + throws Exception {
  67 +
  68 + return enhancedCache.computeIfAbsent(plcId, id -> {
  69 + try {
  70 + CachPlcConfig config = InitPlcConfig.getPlcSystems(plcId);
37 if (config == null) { 71 if (config == null) {
38 - throw new IllegalArgumentException("未找到PLC配置: " + id); 72 + throw new IllegalArgumentException("未找到 PLC 配置: " + plcId);
39 } 73 }
40 - master = createMaster(config);  
41 - master.setIoLog(new MyIOLog(new File("logs/modbus.log")));  
42 - master.init();  
43 - masterCache.put(id, master);  
44 - log.info("ModbusMaster[{}] 已创建", id); 74 +
  75 + JSONObject conn = config.getConnectConfig();
  76 + String host = conn.getString("host");
  77 +
  78 + // 修复 bug:原代码误写为 "post"
  79 + Integer portObj = conn.getInteger("port");
  80 + if (portObj == null) {
  81 + throw new IllegalArgumentException("PLC " + plcId + " 缺少 port 字段");
45 } 82 }
  83 + int port = portObj;
  84 +
  85 + int poolSize = conn.getInteger("poolSize") != null && conn.getInteger("poolSize") > 0
  86 + ? conn.getInteger("poolSize")
  87 + : 10;
  88 +
  89 + EnhancedModbusTcpClientWithByteOrder client =
  90 + new EnhancedModbusTcpClientWithByteOrder(host, port, poolSize);
  91 +
  92 + // 处理字节序设置
  93 + if (config.getByteOrder() != null) {
  94 + try {
  95 + EnhancedModbusTcpClientWithByteOrder.ByteOrderType order =
  96 + EnhancedModbusTcpClientWithByteOrder.ByteOrderType.valueOf(
  97 + config.getByteOrder().toUpperCase()
  98 + );
  99 + } catch (Exception e) {
  100 + log.warn("PLC {} 字节序配置不正确: {},使用默认字节序",
  101 + plcId, config.getByteOrder());
46 } 102 }
47 - return master;  
48 } 103 }
49 104
50 - /**  
51 - * 创建 Master  
52 - */  
53 - private static ModbusMaster createMaster(CachPlcConfig config) throws Exception {  
54 - System.out.println("config:"+JSONObject.toJSONString(config));  
55 - JSONObject connectConfig = config.getConnectConfig();  
56 - switch (config.getProtocolType()) {  
57 - case TCP:  
58 - IpParameters parameters = JSONObject.parseObject(connectConfig.toJSONString(), IpParameters.class);  
59 -// parameters.setEncapsulated(false);  
60 - return factory.createTcpMaster(parameters  
61 - , true);  
62 - case RTU:  
63 - return factory.createRtuMaster(  
64 - JSONObject.parseObject(connectConfig.toJSONString(), JSerialCommWrapper.class));  
65 - case ASCII:  
66 - return factory.createAsciiMaster(  
67 - JSONObject.parseObject(connectConfig.toJSONString(), JSerialCommWrapper.class));  
68 - case UDP:  
69 - return factory.createUdpMaster(  
70 - JSONObject.parseObject(connectConfig.toJSONString(), IpParameters.class));  
71 - default:  
72 - throw new Exception("不支持协议"); 105 + log.info("为 PLC {} 创建并缓存 EnhancedModbusTcpClientWithByteOrder: {}:{} (poolSize={})",
  106 + plcId, host, port, poolSize);
  107 +
  108 + return client;
  109 +
  110 + } catch (Exception e) {
  111 + log.error("创建 EnhancedModbusTcpClientWithByteOrder 失败 (PLC {}): {}", plcId, e.getMessage(), e);
  112 + throw new RuntimeException(e);
73 } 113 }
  114 + });
74 } 115 }
75 116
  117 +
76 /** 118 /**
77 - * 关闭 Master 119 + * 关闭指定 PLC 的 ModbusMaster
78 */ 120 */
79 - public static void closeMaster(String id) {  
80 - Object lock = lockMap.computeIfAbsent(id, k -> new Object());  
81 - synchronized (lock) {  
82 - ModbusMaster master = masterCache.remove(id);  
83 - if (master != null) master.destroy();  
84 - lockMap.remove(id);  
85 - } 121 + public static void closeMasterByPlcId(String plcId) {
  122 + String key = "modbus:master:" + plcId;
  123 + ModbusConnectionManager.closeMaster(key);
  124 + keyToPlcId.remove(key);
86 } 125 }
87 126
  127 +
88 /** 128 /**
89 - * 关闭所有 Master 129 + * 关闭所有 ModbusMaster,同时清空映射缓存
90 */ 130 */
91 public static void closeAll() { 131 public static void closeAll() {
92 - if(null != masterCache)  
93 - {  
94 - masterCache.values().forEach(ModbusMaster::destroy);  
95 - masterCache.clear();  
96 - }  
97 - if (null != lockMap)  
98 - {  
99 - lockMap.clear();  
100 - }  
101 - 132 + ModbusConnectionManager.closeAll();
  133 + keyToPlcId.clear();
102 } 134 }
103 } 135 }
104 -  
@@ -8,10 +8,20 @@ import java.util.Map; @@ -8,10 +8,20 @@ import java.util.Map;
8 public class CachPlcConfig { 8 public class CachPlcConfig {
9 private String id; 9 private String id;
10 private String systemName; 10 private String systemName;
  11 + private String byteOrder;
  12 + private Boolean zeroBasedAddress;
11 private ProtocolType protocolType; 13 private ProtocolType protocolType;
12 private JSONObject connectConfig; 14 private JSONObject connectConfig;
13 private Map<String, PlcPoint> plcMap; 15 private Map<String, PlcPoint> plcMap;
14 16
  17 + public Boolean getZeroBasedAddress() {
  18 + return zeroBasedAddress;
  19 + }
  20 +
  21 + public void setZeroBasedAddress(Boolean zeroBasedAddress) {
  22 + this.zeroBasedAddress = zeroBasedAddress;
  23 + }
  24 +
15 public String getSystemName() { 25 public String getSystemName() {
16 return systemName; 26 return systemName;
17 } 27 }
@@ -51,4 +61,12 @@ public class CachPlcConfig { @@ -51,4 +61,12 @@ public class CachPlcConfig {
51 public void setId(String id) { 61 public void setId(String id) {
52 this.id = id; 62 this.id = id;
53 } 63 }
  64 +
  65 + public String getByteOrder() {
  66 + return byteOrder;
  67 + }
  68 +
  69 + public void setByteOrder(String byteOrder) {
  70 + this.byteOrder = byteOrder;
  71 + }
54 } 72 }
@@ -3,14 +3,12 @@ package com.zhonglai.luhui.device.modbus.terminal.modbus.dto; @@ -3,14 +3,12 @@ package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
3 import com.fasterxml.jackson.annotation.JsonCreator; 3 import com.fasterxml.jackson.annotation.JsonCreator;
4 4
5 public enum PlcDataType { 5 public enum PlcDataType {
6 - BIT, // 线圈/开关量  
7 - INPUT_BIT, // 输入离散量 6 + BOOLEAN,
8 INT16, 7 INT16,
9 - UINT16,  
10 - INT32,  
11 - INT64, 8 + INT32, // 新增
  9 + INT64, // 新增
12 FLOAT32, 10 FLOAT32,
13 - DOUBLE64; 11 + FLOAT64;
14 12
15 //忽略大小写 13 //忽略大小写
16 @JsonCreator 14 @JsonCreator
@@ -18,13 +16,11 @@ public enum PlcDataType { @@ -18,13 +16,11 @@ public enum PlcDataType {
18 if (value == null) return null; 16 if (value == null) return null;
19 switch (value.toUpperCase()) { 17 switch (value.toUpperCase()) {
20 case "BIT": 18 case "BIT":
21 - return BIT; 19 + return BOOLEAN;
22 case "INT": 20 case "INT":
  21 + return INT16;
23 case "INT16": 22 case "INT16":
24 return INT16; 23 return INT16;
25 - case "UINT":  
26 - case "UINT16":  
27 - return UINT16;  
28 case "LONG": // 兼容 long 24 case "LONG": // 兼容 long
29 return INT32; 25 return INT32;
30 case "INT32": 26 case "INT32":
@@ -32,14 +28,15 @@ public enum PlcDataType { @@ -32,14 +28,15 @@ public enum PlcDataType {
32 case "INT64": 28 case "INT64":
33 return INT64; 29 return INT64;
34 case "FLOAT": 30 case "FLOAT":
  31 + return FLOAT32;
35 case "FLOAT32": 32 case "FLOAT32":
36 return FLOAT32; 33 return FLOAT32;
37 case "DOUBLE": 34 case "DOUBLE":
38 - case "DOUBLE64":  
39 - return DOUBLE64;  
40 - case "INPUT_BIT":  
41 - case "INPUT":  
42 - return INPUT_BIT; 35 + return FLOAT32;
  36 + case "BOOLEAN":
  37 + return BOOLEAN;
  38 + case "FLOAT64":
  39 + return FLOAT64;
43 default: 40 default:
44 throw new IllegalArgumentException("未知的枚举值: " + value); 41 throw new IllegalArgumentException("未知的枚举值: " + value);
45 } 42 }
@@ -6,9 +6,25 @@ import java.util.List; @@ -6,9 +6,25 @@ import java.util.List;
6 public class PlcSystem { 6 public class PlcSystem {
7 private String id; 7 private String id;
8 private String systemName; 8 private String systemName;
  9 + private String byteOrder;
  10 + private Boolean zeroBasedAddress;
9 private ProtocolType protocolType; 11 private ProtocolType protocolType;
10 private JSONObject connectConfig; 12 private JSONObject connectConfig;
11 private List<PlcPoint> points; 13 private List<PlcPoint> points;
  14 + public Boolean getZeroBasedAddress() {
  15 + return zeroBasedAddress;
  16 + }
  17 +
  18 + public void setZeroBasedAddress(Boolean zeroBasedAddress) {
  19 + this.zeroBasedAddress = zeroBasedAddress;
  20 + }
  21 + public String getByteOrder() {
  22 + return byteOrder;
  23 + }
  24 +
  25 + public void setByteOrder(String byteOrder) {
  26 + this.byteOrder = byteOrder;
  27 + }
12 28
13 public String getSystemName() { 29 public String getSystemName() {
14 return systemName; 30 return systemName;
@@ -62,7 +62,7 @@ public class CollectPlcDataTask { @@ -62,7 +62,7 @@ public class CollectPlcDataTask {
62 count++; 62 count++;
63 63
64 if (count == maxDataLenth) { 64 if (count == maxDataLenth) {
65 - boolean success = subMqttData(mqttService, plcId, plcPoints); 65 + boolean success = subMqttData(mqttService, plcId, plcPoints,cachPlcConfig.getZeroBasedAddress());
66 System.out.println("plc " + plcId + " 发送数据 " + plcPoints.size() + " 条"); 66 System.out.println("plc " + plcId + " 发送数据 " + plcPoints.size() + " 条");
67 67
68 if (!success) { 68 if (!success) {
@@ -85,7 +85,7 @@ public class CollectPlcDataTask { @@ -85,7 +85,7 @@ public class CollectPlcDataTask {
85 85
86 // 处理剩余未满一批的数据 86 // 处理剩余未满一批的数据
87 if (!plcPoints.isEmpty()) { 87 if (!plcPoints.isEmpty()) {
88 - boolean success = subMqttData(mqttService, plcId, plcPoints); 88 + boolean success = subMqttData(mqttService, plcId, plcPoints,cachPlcConfig.getZeroBasedAddress());
89 logger.info("plc " + plcId + " 发送数据 " + plcPoints.size() + " 条"); 89 logger.info("plc " + plcId + " 发送数据 " + plcPoints.size() + " 条");
90 if (!success) { 90 if (!success) {
91 logger.info("plc " + plcId + " 数据发送失败,本批次丢弃"); 91 logger.info("plc " + plcId + " 数据发送失败,本批次丢弃");
@@ -94,11 +94,11 @@ public class CollectPlcDataTask { @@ -94,11 +94,11 @@ public class CollectPlcDataTask {
94 } 94 }
95 95
96 96
97 - private boolean subMqttData(MqttService mqttService, String plcId, List<PlcPoint> plcPoints) 97 + private boolean subMqttData(MqttService mqttService, String plcId, List<PlcPoint> plcPoints, boolean zeroBasedAddress)
98 { 98 {
99 //通知 99 //通知
100 try { 100 try {
101 - Map<String, Object> datMap = new Modbus4jRead(plcId).batchRead(plcPoints,true); 101 + Map<String, Object> datMap = new Modbus4jRead(plcId).batchRead(plcPoints,zeroBasedAddress);
102 JSONObject mqttSendData = new JSONObject(); 102 JSONObject mqttSendData = new JSONObject();
103 mqttSendData.put(plcId+"", datMap); 103 mqttSendData.put(plcId+"", datMap);
104 String rdata = JSONObject.toJSONString(mqttSendData); 104 String rdata = JSONObject.toJSONString(mqttSendData);
@@ -107,7 +107,7 @@ public class CollectPlcDataTask { @@ -107,7 +107,7 @@ public class CollectPlcDataTask {
107 if (e instanceof ModbusTransportException ) 107 if (e instanceof ModbusTransportException )
108 { 108 {
109 logger.error("plc通讯超时:"+plcId); 109 logger.error("plc通讯超时:"+plcId);
110 - ModbusMasterMessage.closeMaster(plcId); // 销毁旧连接 110 + ModbusMasterMessage.closeMasterByPlcId(plcId); // 销毁旧连接
111 return false; 111 return false;
112 }else 112 }else
113 if(e instanceof MqttException) 113 if(e instanceof MqttException)
@@ -117,7 +117,7 @@ public class CollectPlcDataTask { @@ -117,7 +117,7 @@ public class CollectPlcDataTask {
117 if (e instanceof ErrorResponseException) 117 if (e instanceof ErrorResponseException)
118 { 118 {
119 logger.error("plc返回指令异常",e); 119 logger.error("plc返回指令异常",e);
120 - ModbusMasterMessage.closeMaster(plcId); // 销毁旧连接 120 + ModbusMasterMessage.closeMasterByPlcId(plcId); // 销毁旧连接
121 return false; 121 return false;
122 }else{ 122 }else{
123 throw new RuntimeException(e); 123 throw new RuntimeException(e);
  1 +{
  2 + "plcs": [
  3 + {
  4 + "id": "2_1",
  5 + "systemName": "成鱼系统1",
  6 + "protocolType": "TCP",
  7 + "byteOrder": "CBAD",
  8 + "connectConfig": { "host": "192.168.2.11", "port": 2010,"poolSize": 10},
  9 + "points": [
  10 + {"name": "自动", "system": "zd", "address": "10001", "dataType": "bit"},
  11 + {"name": "远程", "system": "yc", "address": "10002", "dataType": "bit"},
  12 + {"name": "补水泵启动", "system": "bsbqd", "address": "10003", "dataType": "bit"},
  13 + {"name": "水泵1运行", "system": "sb1", "address": "10004", "dataType": "bit"},
  14 + {"name": "水泵2运行", "system": "sb2", "address": "10005", "dataType": "bit"},
  15 + {"name": "氧锥泵1运行", "system": "yzb1yx", "address": "10006", "dataType": "bit"},
  16 + {"name": "氧锥泵2运行", "system": "yzb2yx", "address": "10007", "dataType": "bit"},
  17 + {"name": "氧锥泵3运行", "system": "yzb3yx", "address": "10008", "dataType": "bit"},
  18 + {"name": "氧锥泵4运行", "system": "yzb4yx", "address": "10009", "dataType": "bit"},
  19 + {"name": "排污泵运行", "system": "pwb", "address": "10010", "dataType": "bit"},
  20 + {"name": "微滤机电源合闸", "system": "wlj", "address": "10011", "dataType": "bit"},
  21 + {"name": "紫外灯电源合闸", "system": "zwd", "address": "10012", "dataType": "bit"},
  22 + {"name": "微滤池高液位", "system": "wlq", "address": "10013", "dataType": "bit"},
  23 + {"name": "微滤池低液位", "system": "wld", "address": "10014", "dataType": "bit"},
  24 + {"name": "蝶阀1开到位", "system": "df1kdw", "address": "10015", "dataType": "bit"},
  25 + {"name": "蝶阀1关到位", "system": "df1gdw", "address": "10016", "dataType": "bit"},
  26 + {"name": "蝶阀2开到位", "system": "df2kdw", "address": "10017", "dataType": "bit"},
  27 + {"name": "蝶阀2关到位", "system": "df2gdw", "address": "10018", "dataType": "bit"},
  28 + {"name": "蝶阀3开到位", "system": "df3kdw", "address": "10019", "dataType": "bit"},
  29 + {"name": "蝶阀3关到位", "system": "df3gdw", "address": "10020", "dataType": "bit"},
  30 + {"name": "蝶阀4开到位", "system": "df4kdw", "address": "10021", "dataType": "bit"},
  31 + {"name": "蝶阀4关到位", "system": "df4gdw", "address": "10022", "dataType": "bit"},
  32 + {"name": "蝶阀5开到位", "system": "df5kdw", "address": "10023", "dataType": "bit"},
  33 + {"name": "蝶阀5关到位", "system": "df5gdw", "address": "10024", "dataType": "bit"},
  34 + {"name": "蝶阀6开到位", "system": "df6kdw", "address": "10025", "dataType": "bit"},
  35 + {"name": "蝶阀6关到位", "system": "df6gdw", "address": "10026", "dataType": "bit"},
  36 + {"name": "蝶阀7开到位", "system": "df7kdw", "address": "10027", "dataType": "bit"},
  37 + {"name": "蝶阀7关到位", "system": "df7gdw", "address": "10028", "dataType": "bit"},
  38 + {"name": "蝶阀8开到位", "system": "df8kdw", "address": "10029", "dataType": "bit"},
  39 + {"name": "蝶阀8关到位", "system": "df8gdw", "address": "10030", "dataType": "bit"},
  40 + {"name": "循环水泵运行", "system": "xhsbyx", "address": "10031", "dataType": "bit"},
  41 + {"name": "系统报警", "system": "xtbj", "address": "00001", "dataType": "bit"},
  42 + {"name": "水泵1故障", "system": "sb1gz", "address": "40001.09", "dataType": "bit"},
  43 + {"name": "水泵2故障", "system": "sb2gz", "address": "40001.10", "dataType": "bit"},
  44 + {"name": "氧锥泵1故障", "system": "yzb1gz", "address": "40001.11", "dataType": "bit"},
  45 + {"name": "氧锥泵2故障", "system": "yzb2gz", "address": "40001.12", "dataType": "bit"},
  46 + {"name": "氧锥泵3故障", "system": "yzb3gz", "address": "40001.13", "dataType": "bit"},
  47 + {"name": "氧锥泵4故障", "system": "yzb4gz", "address": "40001.14", "dataType": "bit"},
  48 + {"name": "排污泵故障", "system": "pwb_gz", "address": "40001.15", "dataType": "bit"},
  49 + {"name": "排污阀1开不到位", "system": "pwf1kbdw", "address": "40001.01", "dataType": "bit"},
  50 + {"name": "排污阀1关不到位", "system": "pwf1gbdw", "address": "40001.02", "dataType": "bit"},
  51 + {"name": "排污阀2开不到位", "system": "pwf2kbdw", "address": "40001.03", "dataType": "bit"},
  52 + {"name": "排污阀2关不到位", "system": "pwf2gbdw", "address": "40001.04", "dataType": "bit"},
  53 + {"name": "排污阀3开不到位", "system": "pwf3kbdw", "address": "40001.05", "dataType": "bit"},
  54 + {"name": "排污阀3关不到位", "system": "pwf3gbdw", "address": "40001.06", "dataType": "bit"},
  55 + {"name": "排污阀4开不到位", "system": "pwf4kbdw", "address": "40001.07", "dataType": "bit"},
  56 + {"name": "排污阀4关不到位", "system": "pwf4gbdw", "address": "40001.08", "dataType": "bit"},
  57 + {"name": "排污阀5开不到位", "system": "pwf5kbdw", "address": "40002.09", "dataType": "bit"},
  58 + {"name": "排污阀5关不到位", "system": "pwf5gbdw", "address": "40002.10", "dataType": "bit"},
  59 + {"name": "排污阀6开不到位", "system": "pwf6kbdw", "address": "40002.11", "dataType": "bit"},
  60 + {"name": "排污阀6关不到位", "system": "pwf6gbdw", "address": "40002.12", "dataType": "bit"},
  61 + {"name": "排污阀7开不到位", "system": "pwf7kbdw", "address": "40002.13", "dataType": "bit"},
  62 + {"name": "排污阀7关不到位", "system": "pwf7gbdw", "address": "40002.14", "dataType": "bit"},
  63 + {"name": "排污阀8开不到位", "system": "pwf8kbdw", "address": "40002.15", "dataType": "bit"},
  64 + {"name": "排污阀8关不到位", "system": "pwf8gbdw", "address": "40002.16", "dataType": "bit"},
  65 + {"name": "补水高液位超时", "system": "bsgywdcs", "address": "40002.03", "dataType": "bit"},
  66 + {"name": "微滤池高液位超时", "system": "wlcgywdcs", "address": "40002.04", "dataType": "bit"},
  67 + {"name": "微滤机跳闸", "system": "wljtz", "address": "40002.05", "dataType": "bit"},
  68 + {"name": "紫外杀菌灯跳闸故障", "system": "zwsjdtz", "address": "40002.06", "dataType": "bit"},
  69 + {"name": "溶氧超限报警", "system": "rycxbj", "address": "40002.07", "dataType": "bit"},
  70 + {"name": "微滤池低液位长时间不消失报警", "system": "wlcdywbcsbj", "address": "40002.08", "dataType": "bit"},
  71 + {"name": "溶氧值", "system": "ryz", "address": "40003-40004","order": "ABCD", "dataType": "float32"},
  72 + {"name": "温度值", "system": "wdz", "address": "40005-40006","order": "ABCD", "dataType": "float32"},
  73 + {"name": "电能值", "system": "dnz", "address": "40007-40008","order": "ABCD", "dataType": "float32"},
  74 + {"name": "当前氧锥泵运行台数", "system": "dqyzb", "address": "40009", "dataType": "int"},
  75 + {"name": "氧锥泵1运行时间", "system": "yzb1_sj", "address": "40011-40012","order": "ABCD", "dataType": "int32"},
  76 + {"name": "氧锥泵2运行时间", "system": "yzb2_sj", "address": "40013-40014","order": "ABCD", "dataType": "int32"},
  77 + {"name": "氧锥泵3运行时间", "system": "yzb3_sj", "address": "40015-40016","order": "ABCD", "dataType": "int32"},
  78 + {"name": "氧锥泵4运行时间", "system": "yzb4_sj", "address": "40017-40018","order": "ABCD", "dataType": "int32"},
  79 + {"name": "生化池水温", "system": "shcsw", "address": "40019-40020","order": "ABCD", "dataType": "float32"},
  80 + {"name": "循环水泵故障", "system": "xhsb_gz", "address": "40021.09", "dataType": "bit"},
  81 + {"name": "生化池水温低限报警", "system": "shcsw_dx_bj", "address": "40021.10", "dataType": "bit"},
  82 + {"name": "生化池水温高限报警", "system": "shcsw_gx_bj", "address": "40021.11", "dataType": "bit"},
  83 +
  84 + {"name": "排污阀1开OR关", "system": "pwf1_or", "address": "40051.01", "dataType": "bit"},
  85 + {"name": "排污阀2开OR关", "system": "pwf2_or", "address": "40051.02", "dataType": "bit"},
  86 + {"name": "排污阀3开OR关", "system": "pwf3_or", "address": "40051.03", "dataType": "bit"},
  87 + {"name": "排污阀4开OR关", "system": "pwf4_or", "address": "40051.04", "dataType": "bit"},
  88 + {"name": "排污阀5开OR关", "system": "pwf5_or", "address": "40051.05", "dataType": "bit"},
  89 + {"name": "排污阀6开OR关", "system": "pwf6_or", "address": "40051.06", "dataType": "bit"},
  90 + {"name": "排污阀7开OR关", "system": "pwf7_or", "address": "40051.07", "dataType": "bit"},
  91 + {"name": "排污阀8开OR关", "system": "pwf8_or", "address": "40051.08", "dataType": "bit"},
  92 +
  93 + {"name": "水泵1启动", "system": "sb1start", "address": "40051.09", "dataType": "bit"},
  94 + {"name": "水泵2启动", "system": "sb2start", "address": "40051.10", "dataType": "bit"},
  95 + {"name": "氧锥泵1启动", "system": "yzb1_qd", "address": "40051.11", "dataType": "bit"},
  96 + {"name": "氧锥泵2启动", "system": "yzb2_qd", "address": "40051.12", "dataType": "bit"},
  97 + {"name": "氧锥泵3启动", "system": "yzb3_qd", "address": "40051.13", "dataType": "bit"},
  98 + {"name": "氧锥泵4启动", "system": "yzb4_qd", "address": "40051.14", "dataType": "bit"},
  99 + {"name": "排污泵启动", "system": "pwb_qd", "address": "40051.15", "dataType": "bit"},
  100 +
  101 + {"name": "水泵1停止", "system": "sb1stop", "address": "40052.01", "dataType": "bit"},
  102 + {"name": "水泵2停止", "system": "sb2stop", "address": "40052.02", "dataType": "bit"},
  103 + {"name": "氧锥泵1停止", "system": "yzb1_tz", "address": "40052.03", "dataType": "bit"},
  104 + {"name": "氧锥泵2停止", "system": "yzb2_tz", "address": "40052.04", "dataType": "bit"},
  105 + {"name": "氧锥泵3停止", "system": "yzb3_tz", "address": "40052.05", "dataType": "bit"},
  106 + {"name": "氧锥泵4停止", "system": "yzb4_tz", "address": "40052.06", "dataType": "bit"},
  107 + {"name": "排污泵停止", "system": "pwb_tz", "address": "40052.07", "dataType": "bit"},
  108 +
  109 + {"name": "清报警", "system": "qbj", "address": "40052.09", "dataType": "bit"},
  110 + {"name": "累计时间清零", "system": "ljtq", "address": "40052.10", "dataType": "bit"},
  111 + {"name": "溶氧上限报警设定值", "system": "rysjup", "address": "40053-40054","order": "ABCD", "dataType": "float32"},
  112 + {"name": "溶氧下限报警设定值", "system": "rysjdown", "address": "40055-40056","order": "ABCD", "dataType": "float32"}
  113 + ]
  114 + },
  115 + {
  116 + "id": "2_2",
  117 + "systemName": "成鱼系统2",
  118 + "protocolType": "TCP",
  119 + "byteOrder": "CBAD",
  120 + "connectConfig": { "host": "192.168.2.2", "port": 2001,"poolSize": 10},
  121 + "points": [
  122 + {"name": "自动", "system": "zd", "address": "10001", "dataType": "bit"},
  123 + {"name": "远程", "system": "yc", "address": "10002", "dataType": "bit"},
  124 + {"name": "补水泵启动", "system": "bsbqd", "address": "10003", "dataType": "bit"},
  125 + {"name": "水泵1运行", "system": "sb1", "address": "10004", "dataType": "bit"},
  126 + {"name": "水泵2运行", "system": "sb2", "address": "10005", "dataType": "bit"},
  127 + {"name": "氧锥泵1运行", "system": "yzb1yx", "address": "10006", "dataType": "bit"},
  128 + {"name": "氧锥泵2运行", "system": "yzb2yx", "address": "10007", "dataType": "bit"},
  129 + {"name": "氧锥泵3运行", "system": "yzb3yx", "address": "10008", "dataType": "bit"},
  130 + {"name": "氧锥泵4运行", "system": "yzb4yx", "address": "10009", "dataType": "bit"},
  131 + {"name": "排污泵运行", "system": "pwb", "address": "10010", "dataType": "bit"},
  132 + {"name": "微滤机电源合闸", "system": "wlj", "address": "10011", "dataType": "bit"},
  133 + {"name": "紫外灯电源合闸", "system": "zwd", "address": "10012", "dataType": "bit"},
  134 + {"name": "微滤池高液位", "system": "wlq", "address": "10013", "dataType": "bit"},
  135 + {"name": "微滤池低液位", "system": "wld", "address": "10014", "dataType": "bit"},
  136 + {"name": "蝶阀1开到位", "system": "df1kdw", "address": "10015", "dataType": "bit"},
  137 + {"name": "蝶阀1关到位", "system": "df1gdw", "address": "10016", "dataType": "bit"},
  138 + {"name": "蝶阀2开到位", "system": "df2kdw", "address": "10017", "dataType": "bit"},
  139 + {"name": "蝶阀2关到位", "system": "df2gdw", "address": "10018", "dataType": "bit"},
  140 + {"name": "蝶阀3开到位", "system": "df3kdw", "address": "10019", "dataType": "bit"},
  141 + {"name": "蝶阀3关到位", "system": "df3gdw", "address": "10020", "dataType": "bit"},
  142 + {"name": "蝶阀4开到位", "system": "df4kdw", "address": "10021", "dataType": "bit"},
  143 + {"name": "蝶阀4关到位", "system": "df4gdw", "address": "10022", "dataType": "bit"},
  144 + {"name": "蝶阀5开到位", "system": "df5kdw", "address": "10023", "dataType": "bit"},
  145 + {"name": "蝶阀5关到位", "system": "df5gdw", "address": "10024", "dataType": "bit"},
  146 + {"name": "蝶阀6开到位", "system": "df6kdw", "address": "10025", "dataType": "bit"},
  147 + {"name": "蝶阀6关到位", "system": "df6gdw", "address": "10026", "dataType": "bit"},
  148 + {"name": "蝶阀7开到位", "system": "df7kdw", "address": "10027", "dataType": "bit"},
  149 + {"name": "蝶阀7关到位", "system": "df7gdw", "address": "10028", "dataType": "bit"},
  150 + {"name": "蝶阀8开到位", "system": "df8kdw", "address": "10029", "dataType": "bit"},
  151 + {"name": "蝶阀8关到位", "system": "df8gdw", "address": "10030", "dataType": "bit"},
  152 + {"name": "循环水泵运行", "system": "xhsbyx", "address": "10031", "dataType": "bit"},
  153 + {"name": "系统报警", "system": "xtbj", "address": "00001", "dataType": "bit"},
  154 + {"name": "水泵1故障", "system": "sb1gz", "address": "40001.09", "dataType": "bit"},
  155 + {"name": "水泵2故障", "system": "sb2gz", "address": "40001.10", "dataType": "bit"},
  156 + {"name": "氧锥泵1故障", "system": "yzb1gz", "address": "40001.11", "dataType": "bit"},
  157 + {"name": "氧锥泵2故障", "system": "yzb2gz", "address": "40001.12", "dataType": "bit"},
  158 + {"name": "氧锥泵3故障", "system": "yzb3gz", "address": "40001.13", "dataType": "bit"},
  159 + {"name": "氧锥泵4故障", "system": "yzb4gz", "address": "40001.14", "dataType": "bit"},
  160 + {"name": "排污泵故障", "system": "pwb_gz", "address": "40001.15", "dataType": "bit"},
  161 + {"name": "排污阀1开不到位", "system": "pwf1kbdw", "address": "40001.01", "dataType": "bit"},
  162 + {"name": "排污阀1关不到位", "system": "pwf1gbdw", "address": "40001.02", "dataType": "bit"},
  163 + {"name": "排污阀2开不到位", "system": "pwf2kbdw", "address": "40001.03", "dataType": "bit"},
  164 + {"name": "排污阀2关不到位", "system": "pwf2gbdw", "address": "40001.04", "dataType": "bit"},
  165 + {"name": "排污阀3开不到位", "system": "pwf3kbdw", "address": "40001.05", "dataType": "bit"},
  166 + {"name": "排污阀3关不到位", "system": "pwf3gbdw", "address": "40001.06", "dataType": "bit"},
  167 + {"name": "排污阀4开不到位", "system": "pwf4kbdw", "address": "40001.07", "dataType": "bit"},
  168 + {"name": "排污阀4关不到位", "system": "pwf4gbdw", "address": "40001.08", "dataType": "bit"},
  169 + {"name": "排污阀5开不到位", "system": "pwf5kbdw", "address": "40002.09", "dataType": "bit"},
  170 + {"name": "排污阀5关不到位", "system": "pwf5gbdw", "address": "40002.10", "dataType": "bit"},
  171 + {"name": "排污阀6开不到位", "system": "pwf6kbdw", "address": "40002.11", "dataType": "bit"},
  172 + {"name": "排污阀6关不到位", "system": "pwf6gbdw", "address": "40002.12", "dataType": "bit"},
  173 + {"name": "排污阀7开不到位", "system": "pwf7kbdw", "address": "40002.13", "dataType": "bit"},
  174 + {"name": "排污阀7关不到位", "system": "pwf7gbdw", "address": "40002.14", "dataType": "bit"},
  175 + {"name": "排污阀8开不到位", "system": "pwf8kbdw", "address": "40002.15", "dataType": "bit"},
  176 + {"name": "排污阀8关不到位", "system": "pwf8gbdw", "address": "40002.16", "dataType": "bit"},
  177 + {"name": "补水高液位超时", "system": "bsgywdcs", "address": "40002.03", "dataType": "bit"},
  178 + {"name": "微滤池高液位超时", "system": "wlcgywdcs", "address": "40002.04", "dataType": "bit"},
  179 + {"name": "微滤机跳闸", "system": "wljtz", "address": "40002.05", "dataType": "bit"},
  180 + {"name": "紫外杀菌灯跳闸故障", "system": "zwsjdtz", "address": "40002.06", "dataType": "bit"},
  181 + {"name": "溶氧超限报警", "system": "rycxbj", "address": "40002.07", "dataType": "bit"},
  182 + {"name": "微滤池低液位长时间不消失报警", "system": "wldc", "address": "40002.08", "dataType": "bit"},
  183 + {"name": "溶氧值", "system": "ryz", "address": "40003-40004","order": "ABCD", "dataType": "float32"},
  184 + {"name": "温度值", "system": "wdz", "address": "40005-40006","order": "ABCD", "dataType": "float32"},
  185 + {"name": "电能值", "system": "dnz", "address": "40007-40008","order": "ABCD", "dataType": "float32"},
  186 + {"name": "当前氧锥泵运行台数", "system": "dqyzb", "address": "40009", "dataType": "int"},
  187 + {"name": "氧锥泵1运行时间", "system": "yzb1_sj", "address": "40011-40012","order": "ABCD", "dataType": "int32"},
  188 + {"name": "氧锥泵2运行时间", "system": "yzb2_sj", "address": "40013-40014","order": "ABCD", "dataType": "int32"},
  189 + {"name": "氧锥泵3运行时间", "system": "yzb3_sj", "address": "40015-40016","order": "ABCD", "dataType": "int32"},
  190 + {"name": "氧锥泵4运行时间", "system": "yzb4_sj", "address": "40017-40018","order": "ABCD", "dataType": "int32"},
  191 + {"name": "生化池水温", "system": "shcsw", "address": "40019-40020","order": "ABCD", "dataType": "float32"},
  192 + {"name": "循环水泵故障", "system": "xhsb_gz", "address": "40021.09", "dataType": "bit"},
  193 + {"name": "生化池水温低限报警", "system": "shcsw_dx_bj", "address": "40021.10", "dataType": "bit"},
  194 + {"name": "生化池水温高限报警", "system": "shcsw_gx_bj", "address": "40021.11", "dataType": "bit"},
  195 + {"name": "排污阀1开OR关", "system": "pwf1_or", "address": "40051.01", "dataType": "bit"},
  196 + {"name": "排污阀2开OR关", "system": "pwf2_or", "address": "40051.02", "dataType": "bit"},
  197 + {"name": "排污阀3开OR关", "system": "pwf3_or", "address": "40051.03", "dataType": "bit"},
  198 + {"name": "排污阀4开OR关", "system": "pwf4_or", "address": "40051.04", "dataType": "bit"},
  199 + {"name": "排污阀5开OR关", "system": "pwf5_or", "address": "40051.05", "dataType": "bit"},
  200 + {"name": "排污阀6开OR关", "system": "pwf6_or", "address": "40051.06", "dataType": "bit"},
  201 + {"name": "排污阀7开OR关", "system": "pwf7_or", "address": "40051.07", "dataType": "bit"},
  202 + {"name": "排污阀8开OR关", "system": "pwf8_or", "address": "40051.08", "dataType": "bit"},
  203 +
  204 + {"name": "水泵1启动", "system": "sb1start", "address": "40051.09", "dataType": "bit"},
  205 + {"name": "水泵2启动", "system": "sb2start", "address": "40051.10", "dataType": "bit"},
  206 + {"name": "氧锥泵1启动", "system": "yzb1_qd", "address": "40051.11", "dataType": "bit"},
  207 + {"name": "氧锥泵2启动", "system": "yzb2_qd", "address": "40051.12", "dataType": "bit"},
  208 + {"name": "氧锥泵3启动", "system": "yzb3_qd", "address": "40051.13", "dataType": "bit"},
  209 + {"name": "氧锥泵4启动", "system": "yzb4_qd", "address": "40051.14", "dataType": "bit"},
  210 + {"name": "排污泵启动", "system": "pwb_qd", "address": "40051.15", "dataType": "bit"},
  211 +
  212 + {"name": "水泵1停止", "system": "sb1stop", "address": "40052.01", "dataType": "bit"},
  213 + {"name": "水泵2停止", "system": "sb2stop", "address": "40052.02", "dataType": "bit"},
  214 + {"name": "氧锥泵1停止", "system": "yzb1_tz", "address": "40052.03", "dataType": "bit"},
  215 + {"name": "氧锥泵2停止", "system": "yzb2_tz", "address": "40052.04", "dataType": "bit"},
  216 + {"name": "氧锥泵3停止", "system": "yzb3_tz", "address": "40052.05", "dataType": "bit"},
  217 + {"name": "氧锥泵4停止", "system": "yzb4_tz", "address": "40052.06", "dataType": "bit"},
  218 + {"name": "排污泵停止", "system": "pwb_tz", "address": "40052.07", "dataType": "bit"},
  219 +
  220 + {"name": "清报警", "system": "qbj", "address": "40052.09", "dataType": "bit"},
  221 + {"name": "累计时间清零", "system": "ljtq", "address": "40052.10", "dataType": "bit"},
  222 + {"name": "溶氧上限报警设定值", "system": "rysjup", "address": "40053-40054","order": "ABCD", "dataType": "float32"},
  223 + {"name": "溶氧下限报警设定值", "system": "rysjdown", "address": "40055-40056","order": "ABCD", "dataType": "float32"}
  224 + ]
  225 + },
  226 + {
  227 + "id": "2_3",
  228 + "systemName": "源水处理区",
  229 + "protocolType": "TCP",
  230 + "byteOrder": "CBAD",
  231 + "connectConfig": { "host": "192.168.2.5", "port": 2004,"poolSize": 10},
  232 + "points": [
  233 + {"name": "自动", "system": "zd", "address": "10001", "dataType": "bit"},
  234 + {"name": "远程", "system": "yc", "address": "10002", "dataType": "bit"},
  235 + {"name": "水源泵1启动", "system": "syp1", "address": "10003", "dataType": "bit"},
  236 + {"name": "水源泵2启动", "system": "syp2", "address": "10004", "dataType": "bit"},
  237 + {"name": "水源泵3启动", "system": "syp3", "address": "10005", "dataType": "bit"},
  238 + {"name": "风机1启动", "system": "fj1", "address": "10006", "dataType": "bit"},
  239 + {"name": "风机2启动", "system": "fj2", "address": "10007", "dataType": "bit"},
  240 + {"name": "紫外灯电源合闸", "system": "zwd", "address": "10008", "dataType": "bit"},
  241 + {"name": "生化池高液位", "system": "shg", "address": "10009", "dataType": "bit"},
  242 + {"name": "生化池低液位", "system": "shd", "address": "10010", "dataType": "bit"},
  243 + {"name": "系统报警", "system": "xtbj", "address": "00001", "dataType": "bit"},
  244 +
  245 + {"name": "电能值", "system": "dnz", "address": "40007-40008","order": "ABCD", "dataType": "float"},
  246 + {"name": "当前水源泵启动台数", "system": "dqsy", "address": "40009", "dataType": "int"},
  247 + {"name": "当前风机运行台数", "system": "dqfj", "address": "40010", "dataType": "int"},
  248 + {"name": "水源泵1运行时间", "system": "syp1sj", "address": "40011-40012","order": "ABCD", "dataType": "long"},
  249 + {"name": "水源泵2运行时间", "system": "syp2sj", "address": "40013-40014","order": "ABCD", "dataType": "long"},
  250 + {"name": "水源泵3运行时间", "system": "syp3sj", "address": "40015-40016","order": "ABCD", "dataType": "long"},
  251 + {"name": "风机1运行时间", "system": "fj1t", "address": "40017-40018","order": "ABCD", "dataType": "long"},
  252 + {"name": "风机2运行时间", "system": "fj2t", "address": "40019-40020","order": "ABCD", "dataType": "long"},
  253 +
  254 + {"name": "水源泵1故障", "system": "syp1g", "address": "40001.09", "dataType": "bit"},
  255 + {"name": "水源泵2故障", "system": "syp2g", "address": "40001.10", "dataType": "bit"},
  256 + {"name": "水源泵3故障", "system": "syp3g", "address": "40001.11", "dataType": "bit"},
  257 + {"name": "风机1故障", "system": "fj1g", "address": "40001.12", "dataType": "bit"},
  258 + {"name": "风机2故障", "system": "fj2g", "address": "40001.13", "dataType": "bit"},
  259 + {"name": "紫外杀菌灯跳闸故障", "system": "zwsjdtz", "address": "40001.14", "dataType": "bit"},
  260 +
  261 + {"name": "水源泵1启动", "system": "syp1s", "address": "40051.01", "dataType": "bit"},
  262 + {"name": "水源泵2启动", "system": "syp2s", "address": "40051.02", "dataType": "bit"},
  263 + {"name": "水源泵3启动", "system": "syp3s", "address": "40051.03", "dataType": "bit"},
  264 + {"name": "风机1启动", "system": "fj1s", "address": "40051.04", "dataType": "bit"},
  265 + {"name": "风机2启动", "system": "fj2s", "address": "40051.05", "dataType": "bit"},
  266 + {"name": "水源泵1停止", "system": "syp1t", "address": "40051.09", "dataType": "bit"},
  267 + {"name": "水源泵2停止", "system": "syp2t", "address": "40051.10", "dataType": "bit"},
  268 + {"name": "水源泵3停止", "system": "syp3t", "address": "40051.11", "dataType": "bit"},
  269 + {"name": "风机1停止", "system": "fj1p", "address": "40051.12", "dataType": "bit"},
  270 + {"name": "风机2停止", "system": "fj2p", "address": "40051.13", "dataType": "bit"},
  271 + {"name": "清报警", "system": "qbj", "address": "40052.01", "dataType": "bit"},
  272 + {"name": "累计时间清零", "system": "ljtq", "address": "40052.02", "dataType": "bit"}
  273 + ]
  274 + },
  275 + {
  276 + "id": "2_4",
  277 + "systemName": "育苗系统",
  278 + "protocolType": "TCP",
  279 + "byteOrder": "CBAD",
  280 + "connectConfig": { "host": "192.168.2.4", "port": 2002,"poolSize": 10},
  281 + "points": [
  282 + {"name": "手动/自动", "system": "sdz", "address": "10001", "dataType": "bit"},
  283 + {"name": "本地/远程", "system": "bdyy", "address": "10002", "dataType": "bit"},
  284 + {"name": "水泵1运行", "system": "sb1", "address": "10004", "dataType": "bit"},
  285 + {"name": "水泵2运行", "system": "sb2", "address": "10005", "dataType": "bit"},
  286 + {"name": "风机1运行", "system": "fj1", "address": "10006", "dataType": "bit"},
  287 + {"name": "风机2运行", "system": "fj2", "address": "10007", "dataType": "bit"},
  288 + {"name": "热源泵1电源合闸", "system": "ryb1", "address": "10008", "dataType": "bit"},
  289 + {"name": "热源泵2电源合闸", "system": "ryb2", "address": "10009", "dataType": "bit"},
  290 + {"name": "微滤机电源合闸", "system": "wlj", "address": "10010", "dataType": "bit"},
  291 + {"name": "紫外灯电源合闸", "system": "zwd", "address": "10011", "dataType": "bit"},
  292 + {"name": "补水池高液位", "system": "bsc", "address": "10012", "dataType": "bit"},
  293 + {"name": "微滤池高液位", "system": "wlq", "address": "10013", "dataType": "bit"},
  294 + {"name": "微滤池低液位", "system": "wld", "address": "10015", "dataType": "bit"},
  295 + {"name": "系统报警", "system": "xtbj", "address": "00001", "dataType": "bit"},
  296 +
  297 + {"name": "溶氧值", "system": "ryz", "address": "40003-40004","order": "ABCD", "dataType": "float32"},
  298 + {"name": "温度值", "system": "wdz", "address": "40005-40006","order": "ABCD", "dataType": "float32"},
  299 + {"name": "电能值", "system": "dnz", "address": "40007-40008","order": "ABCD", "dataType": "float32"},
  300 + {"name": "当前风机运行台数", "system": "dqfj", "address": "40009", "dataType": "int"},
  301 + {"name": "风机1运行时间", "system": "fj1t", "address": "40011-40012","order": "ABCD", "dataType": "long"},
  302 + {"name": "风机2运行时间", "system": "fj2t", "address": "40013-40014","order": "ABCD", "dataType": "long"},
  303 +
  304 + {"name": "水泵1故障", "system": "sb1gz", "address": "40001.10", "dataType": "bit"},
  305 + {"name": "水泵2故障", "system": "sb2gz", "address": "40001.11", "dataType": "bit"},
  306 + {"name": "风机1故障", "system": "fj1g", "address": "40001.12", "dataType": "bit"},
  307 + {"name": "风机2故障", "system": "fj2g", "address": "40001.13", "dataType": "bit"},
  308 + {"name": "热泵1跳闸", "system": "rb1tz", "address": "40001.16", "dataType": "bit"},
  309 + {"name": "热泵2跳闸", "system": "rb2tz", "address": "40001.01", "dataType": "bit"},
  310 + {"name": "微滤机跳闸", "system": "wljtz", "address": "40001.02", "dataType": "bit"},
  311 + {"name": "紫外杀菌灯跳闸故障", "system": "zwsjdtz", "address": "40001.03", "dataType": "bit"},
  312 + {"name": "补水上液位超时", "system": "bssywcs", "address": "40001.04", "dataType": "bit"},
  313 + {"name": "微滤池上液位超时", "system": "wlcsywcs", "address": "40001.05", "dataType": "bit"},
  314 + {"name": "溶氧超限报警", "system": "rycxbj", "address": "40001.06", "dataType": "bit"},
  315 + {"name": "补水泵3故障(没有)", "system": "bsb3g", "address": "40001.07", "dataType": "bit"},
  316 + {"name": "微滤池低液位长时间不消失报警", "system": "wldc", "address": "40001.08", "dataType": "bit"},
  317 +
  318 + {"name": "水泵1启动", "system": "sb1start", "address": "40051.01", "dataType": "bit"},
  319 + {"name": "水泵2启动", "system": "sb2start", "address": "40051.02", "dataType": "bit"},
  320 + {"name": "风机1启动", "system": "fj1s", "address": "40051.03", "dataType": "bit"},
  321 + {"name": "风机2启动", "system": "fj2s", "address": "40051.04", "dataType": "bit"},
  322 + {"name": "补水泵3启动", "system": "bsb3s", "address": "40051.05", "dataType": "bit"},
  323 + {"name": "水泵1停止", "system": "sb1stop", "address": "40051.09", "dataType": "bit"},
  324 + {"name": "水泵2停止", "system": "sb2stop", "address": "40051.10", "dataType": "bit"},
  325 + {"name": "风机1停止", "system": "fj1p", "address": "40051.11", "dataType": "bit"},
  326 + {"name": "风机2停止", "system": "fj2p", "address": "40051.12", "dataType": "bit"},
  327 + {"name": "补水泵3停止", "system": "bsb3p", "address": "40051.13", "dataType": "bit"},
  328 +
  329 + {"name": "清报警", "system": "qbj", "address": "40052.01", "dataType": "bit"},
  330 + {"name": "累计时间清零", "system": "ljtq", "address": "40052.02", "dataType": "bit"},
  331 +
  332 + {"name": "溶氧上限报警设定值", "system": "rysjup", "address": "40053-40054","order": "ABCD", "dataType": "float32"},
  333 + {"name": "溶氧下限报警设定值", "system": "rysjdown", "address": "40055-40056","order": "ABCD", "dataType": "float32"}
  334 + ]
  335 + },
  336 + {
  337 + "id": "2_5",
  338 + "systemName": "设备房系统",
  339 + "protocolType": "TCP",
  340 + "byteOrder": "CBAD",
  341 + "connectConfig": { "host": "192.168.2.3", "port": 2003,"poolSize": 10},
  342 + "points": [
  343 + {"name": "自动", "system": "zd", "address": "10001", "dataType": "bit"},
  344 + {"name": "远程", "system": "yc", "address": "10002", "dataType": "bit"},
  345 + {"name": "风机1运行", "system": "fj1", "address": "10003", "dataType": "bit"},
  346 + {"name": "风机2运行", "system": "fj2", "address": "10004", "dataType": "bit"},
  347 + {"name": "风机3运行", "system": "fj3", "address": "10005", "dataType": "bit"},
  348 + {"name": "风机4运行", "system": "fj4", "address": "10006", "dataType": "bit"},
  349 + {"name": "补水泵1运行", "system": "bsb1", "address": "10007", "dataType": "bit"},
  350 + {"name": "补水泵2运行", "system": "bsb2", "address": "10008", "dataType": "bit"},
  351 + {"name": "热泵1电源合闸", "system": "rb1", "address": "10010", "dataType": "bit"},
  352 + {"name": "热泵2电源合闸", "system": "rb2", "address": "10011", "dataType": "bit"},
  353 + {"name": "空压机电源合闸", "system": "kyj", "address": "10012", "dataType": "bit"},
  354 + {"name": "补水阀1开到位", "system": "bsf1", "address": "10013", "dataType": "bit"},
  355 + {"name": "补水阀2开到位", "system": "bsf2", "address": "10014", "dataType": "bit"},
  356 + {"name": "补水阀1关到位", "system": "bsf1g", "address": "10016", "dataType": "bit"},
  357 + {"name": "补水阀2关到位", "system": "bsf2g", "address": "10017", "dataType": "bit"},
  358 + {"name": "补水1高液位", "system": "bsg1", "address": "10019", "dataType": "bit"},
  359 + {"name": "补水2高液位", "system": "bsg2", "address": "10020", "dataType": "bit"},
  360 + {"name": "系统报警", "system": "xtbj", "address": "00001", "dataType": "bit"},
  361 +
  362 +
  363 + {"name": "电能值", "system": "dnz", "address": "40007-40008","order": "ABCD", "dataType": "float"},
  364 + {"name": "当前风机运行台数", "system": "dqfj", "address": "40009", "dataType": "int"},
  365 + {"name": "风机1运行时间", "system": "fj1t", "address": "40011-40012","order": "ABCD", "dataType": "long"},
  366 + {"name": "风机2运行时间", "system": "fj2t", "address": "40013-40014","order": "ABCD", "dataType": "long"},
  367 + {"name": "风机3运行时间", "system": "fj3t", "address": "40015-40016","order": "ABCD", "dataType": "long"},
  368 + {"name": "风机4运行时间", "system": "fj4t", "address": "40017-40018","order": "ABCD", "dataType": "long"},
  369 +
  370 + {"name": "风机1故障", "system": "fj1g", "address": "40001.09", "dataType": "bit"},
  371 + {"name": "风机2故障", "system": "fj2g", "address": "40001.10", "dataType": "bit"},
  372 + {"name": "风机3故障", "system": "fj3g", "address": "40001.11", "dataType": "bit"},
  373 + {"name": "风机4故障", "system": "fj4g", "address": "40001.12", "dataType": "bit"},
  374 + {"name": "补水泵1故障", "system": "bsb1g", "address": "40001.13", "dataType": "bit"},
  375 + {"name": "补水泵2故障", "system": "bsb2g", "address": "40001.14", "dataType": "bit"},
  376 + {"name": "热泵1跳闸故障", "system": "rb1g", "address": "40001.16", "dataType": "bit"},
  377 + {"name": "热泵2跳闸故障", "system": "rb2g", "address": "40001.01", "dataType": "bit"},
  378 + {"name": "补水阀1开不到位", "system": "bsf1b", "address": "40001.02", "dataType": "bit"},
  379 + {"name": "补水阀1关不到位", "system": "bsf1bg", "address": "40001.03", "dataType": "bit"},
  380 + {"name": "补水阀2开不到位", "system": "bsf2b", "address": "40001.04", "dataType": "bit"},
  381 + {"name": "补水阀2关不到位", "system": "bsf2bg", "address": "40001.05", "dataType": "bit"},
  382 + {"name": "空压机跳闸故障", "system": "kyjg", "address": "40001.08", "dataType": "bit"},
  383 +
  384 + {"name": "补水阀1开OR关", "system": "bsf1c", "address": "40051.01", "dataType": "bit"},
  385 + {"name": "补水阀2开OR关", "system": "bsf2c", "address": "40051.02", "dataType": "bit"},
  386 + {"name": "风机1启动", "system": "fj1s", "address": "40051.09", "dataType": "bit"},
  387 + {"name": "风机2启动", "system": "fj2s", "address": "40051.10", "dataType": "bit"},
  388 + {"name": "风机3启动", "system": "fj3s", "address": "40051.11", "dataType": "bit"},
  389 + {"name": "风机4启动", "system": "fj4s", "address": "40051.12", "dataType": "bit"},
  390 + {"name": "补水泵1启动", "system": "bsb1s", "address": "40051.13", "dataType": "bit"},
  391 + {"name": "补水泵2启动", "system": "bsb2s", "address": "40051.14", "dataType": "bit"},
  392 + {"name": "风机1停止", "system": "fj1p", "address": "40052.01", "dataType": "bit"},
  393 + {"name": "风机2停止", "system": "fj2p", "address": "40052.02", "dataType": "bit"},
  394 + {"name": "风机3停止", "system": "fj3p", "address": "40052.03", "dataType": "bit"},
  395 + {"name": "风机4停止", "system": "fj4p", "address": "40052.04", "dataType": "bit"},
  396 + {"name": "补水泵1停止", "system": "bsb1p", "address": "40052.05", "dataType": "bit"},
  397 + {"name": "补水泵2停止", "system": "bsb2p", "address": "40052.06", "dataType": "bit"},
  398 + {"name": "清报警", "system": "qbj", "address": "40052.09", "dataType": "bit"},
  399 + {"name": "累计时间清零", "system": "ljtq", "address": "40052.10", "dataType": "bit"}
  400 + ]
  401 + }
  402 + ]
  403 +}
1 -{}  
  1 +{
  2 + "plcs": [
  3 + {
  4 + "id": "2_1",
  5 + "systemName": "成鱼系统1",
  6 + "protocolType": "TCP",
  7 + "byteOrder": "CBAD",
  8 + "zeroBasedAddress": false,
  9 + "connectConfig": { "host": "127.0.0.1", "port": 2010,"poolSize": 10},
  10 + "points": [
  11 + {"name": "自动", "system": "zd", "address": "10001", "dataType": "bit"},
  12 + {"name": "远程", "system": "yc", "address": "10002", "dataType": "bit"},
  13 + {"name": "补水泵启动", "system": "bsbqd", "address": "10003", "dataType": "bit"},
  14 + {"name": "水泵1运行", "system": "sb1", "address": "10004", "dataType": "bit"},
  15 + {"name": "水泵2运行", "system": "sb2", "address": "10005", "dataType": "bit"},
  16 + {"name": "氧锥泵1运行", "system": "yzb1yx", "address": "10006", "dataType": "bit"},
  17 + {"name": "氧锥泵2运行", "system": "yzb2yx", "address": "10007", "dataType": "bit"},
  18 + {"name": "氧锥泵3运行", "system": "yzb3yx", "address": "10008", "dataType": "bit"},
  19 + {"name": "氧锥泵4运行", "system": "yzb4yx", "address": "10009", "dataType": "bit"},
  20 + {"name": "排污泵运行", "system": "pwb", "address": "10010", "dataType": "bit"},
  21 + {"name": "微滤机电源合闸", "system": "wlj", "address": "10011", "dataType": "bit"},
  22 + {"name": "紫外灯电源合闸", "system": "zwd", "address": "10012", "dataType": "bit"},
  23 + {"name": "微滤池高液位", "system": "wlq", "address": "10013", "dataType": "bit"},
  24 + {"name": "微滤池低液位", "system": "wld", "address": "10014", "dataType": "bit"},
  25 + {"name": "蝶阀1开到位", "system": "df1kdw", "address": "10015", "dataType": "bit"},
  26 + {"name": "蝶阀1关到位", "system": "df1gdw", "address": "10016", "dataType": "bit"},
  27 + {"name": "蝶阀2开到位", "system": "df2kdw", "address": "10017", "dataType": "bit"},
  28 + {"name": "蝶阀2关到位", "system": "df2gdw", "address": "10018", "dataType": "bit"},
  29 + {"name": "蝶阀3开到位", "system": "df3kdw", "address": "10019", "dataType": "bit"},
  30 + {"name": "蝶阀3关到位", "system": "df3gdw", "address": "10020", "dataType": "bit"},
  31 + {"name": "蝶阀4开到位", "system": "df4kdw", "address": "10021", "dataType": "bit"},
  32 + {"name": "蝶阀4关到位", "system": "df4gdw", "address": "10022", "dataType": "bit"},
  33 + {"name": "蝶阀5开到位", "system": "df5kdw", "address": "10023", "dataType": "bit"},
  34 + {"name": "蝶阀5关到位", "system": "df5gdw", "address": "10024", "dataType": "bit"},
  35 + {"name": "蝶阀6开到位", "system": "df6kdw", "address": "10025", "dataType": "bit"},
  36 + {"name": "蝶阀6关到位", "system": "df6gdw", "address": "10026", "dataType": "bit"},
  37 + {"name": "蝶阀7开到位", "system": "df7kdw", "address": "10027", "dataType": "bit"},
  38 + {"name": "蝶阀7关到位", "system": "df7gdw", "address": "10028", "dataType": "bit"},
  39 + {"name": "蝶阀8开到位", "system": "df8kdw", "address": "10029", "dataType": "bit"},
  40 + {"name": "蝶阀8关到位", "system": "df8gdw", "address": "10030", "dataType": "bit"},
  41 + {"name": "循环水泵运行", "system": "xhsbyx", "address": "10031", "dataType": "bit"},
  42 + {"name": "系统报警", "system": "xtbj", "address": "00001", "dataType": "bit"},
  43 + {"name": "水泵1故障", "system": "sb1gz", "address": "40001.09", "dataType": "bit"},
  44 + {"name": "水泵2故障", "system": "sb2gz", "address": "40001.10", "dataType": "bit"},
  45 + {"name": "氧锥泵1故障", "system": "yzb1gz", "address": "40001.11", "dataType": "bit"},
  46 + {"name": "氧锥泵2故障", "system": "yzb2gz", "address": "40001.12", "dataType": "bit"},
  47 + {"name": "氧锥泵3故障", "system": "yzb3gz", "address": "40001.13", "dataType": "bit"},
  48 + {"name": "氧锥泵4故障", "system": "yzb4gz", "address": "40001.14", "dataType": "bit"},
  49 + {"name": "排污泵故障", "system": "pwb_gz", "address": "40001.15", "dataType": "bit"},
  50 + {"name": "排污阀1开不到位", "system": "pwf1kbdw", "address": "40001.01", "dataType": "bit"},
  51 + {"name": "排污阀1关不到位", "system": "pwf1gbdw", "address": "40001.02", "dataType": "bit"},
  52 + {"name": "排污阀2开不到位", "system": "pwf2kbdw", "address": "40001.03", "dataType": "bit"},
  53 + {"name": "排污阀2关不到位", "system": "pwf2gbdw", "address": "40001.04", "dataType": "bit"},
  54 + {"name": "排污阀3开不到位", "system": "pwf3kbdw", "address": "40001.05", "dataType": "bit"},
  55 + {"name": "排污阀3关不到位", "system": "pwf3gbdw", "address": "40001.06", "dataType": "bit"},
  56 + {"name": "排污阀4开不到位", "system": "pwf4kbdw", "address": "40001.07", "dataType": "bit"},
  57 + {"name": "排污阀4关不到位", "system": "pwf4gbdw", "address": "40001.08", "dataType": "bit"},
  58 + {"name": "排污阀5开不到位", "system": "pwf5kbdw", "address": "40002.09", "dataType": "bit"},
  59 + {"name": "排污阀5关不到位", "system": "pwf5gbdw", "address": "40002.10", "dataType": "bit"},
  60 + {"name": "排污阀6开不到位", "system": "pwf6kbdw", "address": "40002.11", "dataType": "bit"},
  61 + {"name": "排污阀6关不到位", "system": "pwf6gbdw", "address": "40002.12", "dataType": "bit"},
  62 + {"name": "排污阀7开不到位", "system": "pwf7kbdw", "address": "40002.13", "dataType": "bit"},
  63 + {"name": "排污阀7关不到位", "system": "pwf7gbdw", "address": "40002.14", "dataType": "bit"},
  64 + {"name": "排污阀8开不到位", "system": "pwf8kbdw", "address": "40002.15", "dataType": "bit"},
  65 + {"name": "排污阀8关不到位", "system": "pwf8gbdw", "address": "40002.16", "dataType": "bit"},
  66 + {"name": "补水高液位超时", "system": "bsgywdcs", "address": "40002.03", "dataType": "bit"},
  67 + {"name": "微滤池高液位超时", "system": "wlcgywdcs", "address": "40002.04", "dataType": "bit"},
  68 + {"name": "微滤机跳闸", "system": "wljtz", "address": "40002.05", "dataType": "bit"},
  69 + {"name": "紫外杀菌灯跳闸故障", "system": "zwsjdtz", "address": "40002.06", "dataType": "bit"},
  70 + {"name": "溶氧超限报警", "system": "rycxbj", "address": "40002.07", "dataType": "bit"},
  71 + {"name": "微滤池低液位长时间不消失报警", "system": "wlcdywbcsbj", "address": "40002.08", "dataType": "bit"},
  72 + {"name": "溶氧值", "system": "ryz", "address": "40003-40004","order": "ABCD", "dataType": "float32"},
  73 + {"name": "温度值", "system": "wdz", "address": "40005-40006","order": "ABCD", "dataType": "float32"},
  74 + {"name": "电能值", "system": "dnz", "address": "40007-40008","order": "ABCD", "dataType": "float32"},
  75 + {"name": "当前氧锥泵运行台数", "system": "dqyzb", "address": "40009", "dataType": "int"},
  76 + {"name": "氧锥泵1运行时间", "system": "yzb1_sj", "address": "40011-40012","order": "ABCD", "dataType": "int32"},
  77 + {"name": "氧锥泵2运行时间", "system": "yzb2_sj", "address": "40013-40014","order": "ABCD", "dataType": "int32"},
  78 + {"name": "氧锥泵3运行时间", "system": "yzb3_sj", "address": "40015-40016","order": "ABCD", "dataType": "int32"},
  79 + {"name": "氧锥泵4运行时间", "system": "yzb4_sj", "address": "40017-40018","order": "ABCD", "dataType": "int32"},
  80 + {"name": "生化池水温", "system": "shcsw", "address": "40019-40020","order": "ABCD", "dataType": "float32"},
  81 + {"name": "循环水泵故障", "system": "xhsb_gz", "address": "40021.09", "dataType": "bit"},
  82 + {"name": "生化池水温低限报警", "system": "shcsw_dx_bj", "address": "40021.10", "dataType": "bit"},
  83 + {"name": "生化池水温高限报警", "system": "shcsw_gx_bj", "address": "40021.11", "dataType": "bit"}
  84 + ]
  85 + }
  86 + ]
  87 +}