正在显示
15 个修改的文件
包含
1403 行增加
和
678 行删除
| @@ -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) | ||
| 60 | + { | ||
| 61 | + value = client.readBooleanFromRegisterBit(1, addrStr, zeroBasedAddress); | ||
| 62 | + }else | ||
| 98 | { | 63 | { |
| 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; | 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) { | ||
| 28 | - return master; | ||
| 29 | - } | ||
| 30 | 24 | ||
| 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); | ||
| 37 | - if (config == null) { | ||
| 38 | - throw new IllegalArgumentException("未找到PLC配置: " + id); | ||
| 39 | - } | ||
| 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); | ||
| 45 | - } | 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); | ||
| 46 | } | 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); | ||
| 47 | return master; | 58 | return master; |
| 48 | } | 59 | } |
| 49 | 60 | ||
| 61 | + | ||
| 50 | /** | 62 | /** |
| 51 | - * 创建 Master | 63 | + * 获取或创建 EnhancedModbusTcpClientWithByteOrder(带缓存) |
| 52 | */ | 64 | */ |
| 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("不支持协议"); | ||
| 73 | - } | 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); | ||
| 71 | + if (config == null) { | ||
| 72 | + throw new IllegalArgumentException("未找到 PLC 配置: " + plcId); | ||
| 73 | + } | ||
| 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 字段"); | ||
| 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()); | ||
| 102 | + } | ||
| 103 | + } | ||
| 104 | + | ||
| 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); | ||
| 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 | +} |
-
请 注册 或 登录 后发表评论