From ff487f1b054ca8cc763cce81864d4e756434f629 Mon Sep 17 00:00:00 2001 From: zq Date: Fri, 19 Dec 2025 11:38:04 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9B=86=E6=88=90modbus=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E8=AE=BE=E5=A4=87=E8=AF=BB=E5=8F=96=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E4=BB=A3=E7=A0=81-=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=96=B9=E5=BC=8F=E8=AF=BB=E5=8F=96=EF=BC=9B=20PCS?= =?UTF-8?q?=E5=BC=80=E5=85=B3=E6=9C=BA=E5=8A=9F=E8=83=BD=E9=80=9A=E8=BF=87?= =?UTF-8?q?modbus=E8=BF=9E=E6=8E=A5=E8=AE=BE=E5=A4=87=E5=8F=91=E9=80=81?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E5=91=BD=E4=BB=A4=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-local.yml | 2 + .../src/main/resources/application-prod.yml | 2 + ems-admin/src/main/resources/application.yml | 2 + ems-admin/src/main/resources/logback.xml | 2 +- ems-common/pom.xml | 6 + .../modbus/Modbus4jConnectionManager.java | 61 +++ .../common/core/modbus/ModbusProcessor.java | 384 ++++++++++++++++++ .../modbus/PooledModbusMasterFactory.java | 58 +++ .../core/modbus/domain/DeviceConfig.java | 91 +++++ .../common/core/modbus/domain/TagConfig.java | 78 ++++ .../core/modbus/domain/WriteTagConfig.java | 22 + .../com/xzzn/common/enums/ModBusType.java | 57 +++ .../xzzn/common/enums/PcsControlCommand.java | 45 ++ .../com/xzzn/common/enums/RegisterType.java | 8 + .../com/xzzn/quartz/config/ScheduledTask.java | 34 ++ .../com/xzzn/quartz/task/ModbusPoller.java | 307 +++++++------- .../com/xzzn/ems/domain/EmsBatteryData.java | 21 +- .../java/com/xzzn/ems/domain/EmsPcsData.java | 64 ++- .../ems/domain/vo/DeviceUpdateRequest.java | 14 + .../xzzn/ems/mapper/EmsPointMatchMapper.java | 14 +- .../impl/DeviceDataProcessServiceImpl.java | 95 +++-- .../impl/EmsDeviceSettingServiceImpl.java | 83 +++- .../mapper/ems/EmsBatteryDataDayMapper.xml | 9 +- .../resources/mapper/ems/EmsPcsDataMapper.xml | 30 +- .../mapper/ems/EmsPointMatchMapper.xml | 7 +- pom.xml | 6 + 26 files changed, 1286 insertions(+), 216 deletions(-) create mode 100644 ems-common/src/main/java/com/xzzn/common/core/modbus/Modbus4jConnectionManager.java create mode 100644 ems-common/src/main/java/com/xzzn/common/core/modbus/ModbusProcessor.java create mode 100644 ems-common/src/main/java/com/xzzn/common/core/modbus/PooledModbusMasterFactory.java create mode 100644 ems-common/src/main/java/com/xzzn/common/core/modbus/domain/DeviceConfig.java create mode 100644 ems-common/src/main/java/com/xzzn/common/core/modbus/domain/TagConfig.java create mode 100644 ems-common/src/main/java/com/xzzn/common/core/modbus/domain/WriteTagConfig.java create mode 100644 ems-common/src/main/java/com/xzzn/common/enums/ModBusType.java create mode 100644 ems-common/src/main/java/com/xzzn/common/enums/PcsControlCommand.java create mode 100644 ems-common/src/main/java/com/xzzn/common/enums/RegisterType.java create mode 100644 ems-quartz/src/main/java/com/xzzn/quartz/config/ScheduledTask.java diff --git a/ems-admin/src/main/resources/application-local.yml b/ems-admin/src/main/resources/application-local.yml index ac9c414..7654dc9 100644 --- a/ems-admin/src/main/resources/application-local.yml +++ b/ems-admin/src/main/resources/application-local.yml @@ -194,6 +194,8 @@ mqtt: connection-timeout: 15 keep-alive-interval: 30 automatic-reconnect: true + topic: 021_DDS_01_UP + siteId: 021_DDS_01 modbus: pool: diff --git a/ems-admin/src/main/resources/application-prod.yml b/ems-admin/src/main/resources/application-prod.yml index 00b6232..fee9ed4 100644 --- a/ems-admin/src/main/resources/application-prod.yml +++ b/ems-admin/src/main/resources/application-prod.yml @@ -194,6 +194,8 @@ mqtt: connection-timeout: 15 keep-alive-interval: 30 automatic-reconnect: true + topic: + siteId: modbus: pool: diff --git a/ems-admin/src/main/resources/application.yml b/ems-admin/src/main/resources/application.yml index 41a1ccc..cf6c040 100644 --- a/ems-admin/src/main/resources/application.yml +++ b/ems-admin/src/main/resources/application.yml @@ -196,6 +196,8 @@ mqtt: connection-timeout: 15 keep-alive-interval: 30 automatic-reconnect: true + topic: + siteId: modbus: pool: diff --git a/ems-admin/src/main/resources/logback.xml b/ems-admin/src/main/resources/logback.xml index 45e383b..8f26067 100644 --- a/ems-admin/src/main/resources/logback.xml +++ b/ems-admin/src/main/resources/logback.xml @@ -1,7 +1,7 @@ - + diff --git a/ems-common/pom.xml b/ems-common/pom.xml index 393c33e..9bade5d 100644 --- a/ems-common/pom.xml +++ b/ems-common/pom.xml @@ -119,6 +119,12 @@ javax.servlet-api + + + com.infiniteautomation + modbus4j + + \ No newline at end of file diff --git a/ems-common/src/main/java/com/xzzn/common/core/modbus/Modbus4jConnectionManager.java b/ems-common/src/main/java/com/xzzn/common/core/modbus/Modbus4jConnectionManager.java new file mode 100644 index 0000000..0234a14 --- /dev/null +++ b/ems-common/src/main/java/com/xzzn/common/core/modbus/Modbus4jConnectionManager.java @@ -0,0 +1,61 @@ +package com.xzzn.common.core.modbus; + +import com.serotonin.modbus4j.ModbusMaster; +import com.xzzn.common.core.modbus.domain.DeviceConfig; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.annotation.PreDestroy; + +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class Modbus4jConnectionManager { + private static final Logger logger = LoggerFactory.getLogger(Modbus4jConnectionManager.class); + + private final Map> connectionPools = new ConcurrentHashMap<>(); + + + public ModbusMaster borrowMaster(DeviceConfig config) throws Exception { + String poolKey = getPoolKey(config); + GenericObjectPool pool = connectionPools.computeIfAbsent(poolKey, key -> { + PooledModbusMasterFactory factory = new PooledModbusMasterFactory(config.getHost(), config.getPort()); + GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig<>(); + poolConfig.setMaxTotal(5); // 池中最大连接数 + poolConfig.setMinIdle(1); // 最小空闲连接数 + poolConfig.setTestOnBorrow(true); // 借用时测试连接有效性 + + return new GenericObjectPool<>(factory, poolConfig); + }); + return pool.borrowObject(); + } + + + + + public void returnMaster(DeviceConfig config, ModbusMaster master) { + if (master == null) { + return; + } + String poolKey = getPoolKey(config); + GenericObjectPool pool = connectionPools.get(poolKey); + if (pool != null) { + pool.returnObject(master); + } + } + + private String getPoolKey(DeviceConfig config) { + return config.getHost() + ":" + config.getPort(); + } + + @PreDestroy + public void shutdown() { + connectionPools.values().forEach(GenericObjectPool::close); + } + +} diff --git a/ems-common/src/main/java/com/xzzn/common/core/modbus/ModbusProcessor.java b/ems-common/src/main/java/com/xzzn/common/core/modbus/ModbusProcessor.java new file mode 100644 index 0000000..3153a44 --- /dev/null +++ b/ems-common/src/main/java/com/xzzn/common/core/modbus/ModbusProcessor.java @@ -0,0 +1,384 @@ +package com.xzzn.common.core.modbus; + +import com.serotonin.modbus4j.BatchRead; +import com.serotonin.modbus4j.BatchResults; +import com.serotonin.modbus4j.ModbusMaster; +import com.serotonin.modbus4j.code.DataType; +import com.serotonin.modbus4j.exception.ErrorResponseException; +import com.serotonin.modbus4j.exception.ModbusTransportException; +import com.serotonin.modbus4j.locator.BaseLocator; +import com.serotonin.modbus4j.msg.WriteCoilRequest; +import com.serotonin.modbus4j.msg.WriteCoilResponse; +import com.serotonin.modbus4j.msg.WriteCoilsRequest; +import com.serotonin.modbus4j.msg.WriteCoilsResponse; +import com.serotonin.modbus4j.msg.WriteRegisterRequest; +import com.serotonin.modbus4j.msg.WriteRegisterResponse; +import com.serotonin.modbus4j.msg.WriteRegistersRequest; +import com.serotonin.modbus4j.msg.WriteRegistersResponse; +import com.xzzn.common.core.modbus.domain.DeviceConfig; +import com.xzzn.common.core.modbus.domain.TagConfig; +import com.xzzn.common.core.modbus.domain.WriteTagConfig; +import com.xzzn.common.core.redis.RedisCache; +import com.xzzn.common.enums.ModBusType; +import com.xzzn.common.enums.RegisterType; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import static com.xzzn.common.enums.RegisterType.COIL; +import static com.xzzn.common.enums.RegisterType.DISCRETE_INPUT; + +@Service +public class ModbusProcessor { + + private static final Logger logger = LoggerFactory.getLogger(ModbusProcessor.class); + + @Autowired + private RedisCache redisCache; + @Autowired + private Modbus4jConnectionManager connectionManager; + + public boolean writeDataToDevice(DeviceConfig config) { + ModbusMaster master = null; + boolean result = true; + try { + master = connectionManager.borrowMaster(config); + // 设置了Modbus通信的超时时间为3000毫秒(3秒)。当主设备与从设备通信时,若在3秒内未收到响应,则认为通信超时并抛出异常。这有助于避免长时间等待无响应的设备。 + master.setTimeout(5000); + writeTagValue(master, config, config.getWriteTags()); + } catch (Exception e) { + logger.error("Failed to borrow connection or write to devices '{}'", config.getDeviceName(), e); + result = false; + } + finally { + // 关键:无论成功与否,都必须将连接归还到池中 + if (master != null) { + connectionManager.returnMaster(config, master); + } + } + return result; + } + + public void writeTagValue(ModbusMaster master, DeviceConfig config, List tags) { + tags.forEach(tag -> { + Map type = ModBusType.REGISTER_TYPE; + int firstDigit = Integer.parseInt(tag.getAddress().substring(0, 1)); + int address = 0; + int addressLength = tag.getAddress().length(); + int exp = (int) Math.pow(10, addressLength-1); + if (firstDigit != 0){ + int digit = Integer.parseInt(tag.getAddress()); + address = digit % (exp); + }else { + address = Integer.parseInt(tag.getAddress()); + } + RegisterType registerType = type.get(firstDigit); + logger.info("Register type: {}, address: {}, firstDigit: {}", registerType, tag.getAddress(), firstDigit); + switch (registerType) { + case COIL: { + writeCoilRequest(master, config.getSlaveId(), address, Boolean.parseBoolean(String.valueOf(tag.getValue()))); + break; + } + case HOLDING_REGISTER: { + writeRegisterRequest(master, config.getSlaveId(), address, Integer.parseInt(String.valueOf(tag.getValue()))); + break; + } + default: + logger.error("Unsupported register type: {}", registerType); + break; + } + }); + } + + public static void writeCoilRequest(ModbusMaster master, int slaveId, int address, boolean value) { + try { + WriteCoilRequest request = new WriteCoilRequest(slaveId, address, value); + WriteCoilResponse response = (WriteCoilResponse)master.send(request); + if (response.isException()) { + logger.info("Write coil failed: " + response.getExceptionMessage()); + } else { + logger.info("Write coil successful"); + } + } catch (Exception e) { + logger.error("Failed to write coil value '{}' to address '{}'", value, address, e); + } + } + + public static void writeCoilsRequest(ModbusMaster master, int slaveId, int address, boolean[] values) { + try { + WriteCoilsRequest request = new WriteCoilsRequest(slaveId, address, values); + WriteCoilsResponse response = (WriteCoilsResponse)master.send(request); + if (response.isException()) { + logger.info("Write coils failed: " + response.getExceptionMessage()); + } else { + logger.info("Write coils successful"); + } + } catch (Exception e) { + logger.error("Failed to write coils value '{}' to address '{}'", values, address, e); + } + } + + public static void writeRegisterRequest(ModbusMaster master, int slaveId, int address, int value) { + try { + WriteRegisterRequest request = new WriteRegisterRequest(slaveId, address, value); + WriteRegisterResponse response = (WriteRegisterResponse)master.send(request); + if (response.isException()) { + logger.info("Write register failed: " + response.getExceptionMessage()); + } else { + logger.info("Write register successful"); + } + } catch (Exception e) { + logger.error("Failed to write register value '{}' to address '{}'", value, address, e); + } + } + + public static void writeRegistersRequest(ModbusMaster master, int slaveId, int address, short[] values) { + try { + WriteRegistersRequest request = new WriteRegistersRequest(slaveId, address, values); + WriteRegistersResponse response = (WriteRegistersResponse)master.send(request); + if (response.isException()) { + logger.info("Write registers failed: " + response.getExceptionMessage()); + } else { + logger.info("Write registers successful"); + } + } catch (Exception e) { + logger.error("Failed to write registers value '{}' to address '{}'", values, address, e); + } + } + + public static void setCoilValue(ModbusMaster master, int slaveId, int address, int value) { + // 写入单个线圈(从站ID, 地址, 布尔值) + BaseLocator coilLocator = BaseLocator.coilStatus(slaveId, address); + try { + // 写入布尔值到线圈 + master.setValue(coilLocator, value); + logger.info("set coil successful"); + } catch (ModbusTransportException | ErrorResponseException e) { + logger.error("Failed to set coil value '{}' to address '{}'", value, address, e); + } + } + + public static void setRegisterValue(ModbusMaster master, int slaveId, int address, int value) { + // 写入单个保持寄存器(从站, 地址, 16位整数) + BaseLocator holdingLocator = BaseLocator.holdingRegister(slaveId, address, DataType.TWO_BYTE_INT_UNSIGNED); + try { + // 写入整数值到保持寄存器 + master.setValue(holdingLocator, value); + logger.info("set register successful"); + } catch (ModbusTransportException | ErrorResponseException e) { + logger.error("Failed to set register value '{}' to address '{}'", value, address, e); + } + } + + + public Map readDataFromDevice(DeviceConfig config, ModbusMaster master) { + Map deviceData = new HashMap<>(); +// ModbusMaster master = null; // 将master的声明提前 + try { + master = connectionManager.borrowMaster(config); +// 设置了Modbus通信的超时时间为3000毫秒(3秒)。当主设备与从设备通信时,若在3秒内未收到响应,则认为通信超时并抛出异常。这有助于避免长时间等待无响应的设备。 + master.setTimeout(5000); + BatchResults results = readTagValues(master, config.getSlaveId(), config.getTags()); + for (TagConfig tag : config.getTags()) { + if (Objects.equals(tag.getDataType(), "FOUR_BYTE_FLOAT_DBCA")){ + Object value = results.getValue(tag.getKey()); + value = convertValueToFloat(value); + deviceData.put(tag.getKey(), value); + }else { + deviceData.put(tag.getKey(), results.getValue(tag.getKey())); + } + + +// try { +// Object value = readTagValue(master, config.getSlaveId(), tag); +// if (value != null) { +// deviceData.put(tag.getKey(), value); +// } +// } catch (Exception e) { +// logger.error("Failed to read tag '{}' from devices '{}'", tag.getKey(), config.getDeviceName(), e); +// } + } + } catch (Exception e) { + logger.error("Failed to borrow connection or read from devices '{}'", config.getDeviceName(), e); + } + finally { + // 关键:无论成功与否,都必须将连接归还到池中 + if (master != null) { + connectionManager.returnMaster(config, master); + } + } + String deviceNumber = config.getDeviceNumber(); + redisCache.setCacheObject(deviceNumber, deviceData); + return deviceData; + } + + private Object readTagValue(ModbusMaster master, int slaveId, TagConfig tag) throws Exception { + Object value; + Map type = ModBusType.REGISTER_TYPE; + Map DATA_LENGTH = ModBusType.LENGTH; + int firstDigit = Integer.parseInt(tag.getAddress().substring(0, 1)); + int address = 0; + int addressLength = tag.getAddress().length(); + int exp = (int) Math.pow(10, addressLength-1); + if (firstDigit != 0){ + int digit = Integer.parseInt(tag.getAddress()); + address = digit % (exp); + }else { + address = Integer.parseInt(tag.getAddress()); + } + RegisterType registerType = type.get(firstDigit); + int dataLength = DATA_LENGTH.get(tag.getDataType()); + // 增加配置校验和警告 + if ((Objects.equals(registerType, COIL) || Objects.equals(registerType, DISCRETE_INPUT))) { + logger.warn("Configuration warning for tag '{}': ModBusType is {} but DataType is {}. DataType should be BOOLEAN. Proceeding with BOOLEAN read.", + tag.getKey(), registerType, tag.getDataType()); + } + try { + switch (registerType) { + case COIL: { + + BaseLocator loc = BaseLocator.coilStatus(slaveId, address); + value = master.getValue(loc); + logger.info("读取线圈{}成功: {}",tag.getAddress(), value); + break; + } + case DISCRETE_INPUT: { + BaseLocator loc = BaseLocator.inputStatus(slaveId, address); + value = master.getValue(loc); + logger.info("读取输入{}成功: {}",tag.getAddress(), value); + break; + } + case HOLDING_REGISTER: { + if (dataLength == 28){ + BaseLocator locator = BaseLocator.holdingRegister(slaveId, address, 4); + value = master.getValue(locator); + //将 value 转换为二进制数据 + value = convertValueToFloat(value); + }else{ + BaseLocator locator = BaseLocator.holdingRegister(slaveId, address, dataLength); + value = master.getValue(locator); + } + logger.info("读取保持寄存器{}成功: {}",tag.getAddress(), value); + break; + } + case INPUT_REGISTER: { + BaseLocator locator = BaseLocator.inputRegister(slaveId, address, dataLength); + value= master.getValue(locator); + logger.info("读取输入寄存器{}成功: {}",tag.getAddress(), value); + + + break; + } + default: + logger.error("Unsupported register type: {}", registerType); + value = null; + break; + } + }catch (Exception e){ + logger.error("Failed to read tag '{}'", tag.getKey(), e); + value = null; + } + return value; + } + + + private BatchResults readTagValues(ModbusMaster master, int slaveId, List tags) throws Exception { + try { + BatchRead batch = new BatchRead<>(); + tags.forEach(tag -> { + Map type = ModBusType.REGISTER_TYPE; + Map DATA_LENGTH = ModBusType.LENGTH; + int firstDigit = Integer.parseInt(tag.getAddress().substring(0, 1)); + int address = 0; + int addressLength = tag.getAddress().length(); + int exp = (int) Math.pow(10, addressLength-1); + if (firstDigit != 0){ + int digit = Integer.parseInt(tag.getAddress()); + address = digit % (exp); + }else { + address = Integer.parseInt(tag.getAddress()); + } + RegisterType registerType = type.get(firstDigit); + int dataLength = DATA_LENGTH.get(tag.getDataType()); + switch (registerType) { + case COIL: { + BaseLocator loc = BaseLocator.coilStatus(slaveId, address); + batch.addLocator(tag.getKey(), loc); + break; + } + case DISCRETE_INPUT: { + BaseLocator loc = BaseLocator.inputStatus(slaveId, address); + batch.addLocator(tag.getKey(), loc); + break; + } + case HOLDING_REGISTER: { + logger.info("HOLDING_REGISTER: {}",tag.getAddress()); + if (dataLength == 28){ + BaseLocator locator = BaseLocator.holdingRegister(slaveId, address, 4); + batch.addLocator(tag.getKey(), locator); + }else { + BaseLocator loc = BaseLocator.holdingRegister(slaveId, address, dataLength); + batch.addLocator(tag.getKey(), loc); + } + break; + } + case INPUT_REGISTER: { + logger.info("INPUT_REGISTER: {}",tag.getAddress()); + BaseLocator loc = BaseLocator.inputRegister(slaveId, address, dataLength); + batch.addLocator(tag.getKey(), loc); + break; + } + } + }); + + BatchResults results = master.send(batch); + for (TagConfig tag : tags){ + if (tag.getBit()!=null){ + logger.info("批处理读取寄存器成功: {}",tag.getAddress() +"(" + tag.getBit() + "):" + results.getValue(tag.getKey())); + + }else { + logger.info("批处理读取寄存器成功: {}",tag.getAddress() + ":" + results.getValue(tag.getKey())); + } + } + return results; + }catch (Exception e){ + logger.error("Failed to read master '{}'", slaveId, e); + throw new Exception(e); + } + } + + public static float convertValueToFloat(Object value) { + if (!(value instanceof Number)) { + throw new IllegalArgumentException("Input must be a Number"); + } + + int intValue = ((Number) value).intValue(); + String binaryData = Integer.toBinaryString(intValue); + + // 补足32位二进制字符串 + if (binaryData.length() < 32) { + binaryData = String.format("%32s", binaryData).replace(' ', '0'); + } + + // 分割为4个字节并重组为 dcba 格式 + byte[] reorderedBytes = new byte[4]; + for (int i = 0; i < 4; i++) { + int start = i * 8; + String byteStr = binaryData.substring(start, start + 8); + reorderedBytes[3 - i] = (byte) Integer.parseInt(byteStr, 2); + } + + // 使用 ByteBuffer 提高可读性和安全性 + return Float.intBitsToFloat( + java.nio.ByteBuffer.wrap(reorderedBytes).getInt() + ); + } + +} diff --git a/ems-common/src/main/java/com/xzzn/common/core/modbus/PooledModbusMasterFactory.java b/ems-common/src/main/java/com/xzzn/common/core/modbus/PooledModbusMasterFactory.java new file mode 100644 index 0000000..9e77cd2 --- /dev/null +++ b/ems-common/src/main/java/com/xzzn/common/core/modbus/PooledModbusMasterFactory.java @@ -0,0 +1,58 @@ +package com.xzzn.common.core.modbus; + + +import com.serotonin.modbus4j.ModbusFactory; +import com.serotonin.modbus4j.ModbusMaster; +import com.serotonin.modbus4j.ip.IpParameters; + +import org.apache.commons.pool2.BasePooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; + +public class PooledModbusMasterFactory extends BasePooledObjectFactory { + + private final String host; + private final int port; + + public PooledModbusMasterFactory(String host, int port) { + this.host = host; + this.port = port; + } + + @Override + public ModbusMaster create() throws Exception { + IpParameters params = new IpParameters(); + params.setHost(this.host); + params.setPort(this.port); + params.setEncapsulated(false); + + ModbusMaster master = new ModbusFactory().createTcpMaster(params, true); + master.init(); + return master; + } + + @Override + public PooledObject wrap(ModbusMaster master) { + return new DefaultPooledObject<>(master); + } + + @Override + public void destroyObject(PooledObject p) throws Exception { + if (p.getObject() != null) { + p.getObject().destroy(); + } + super.destroyObject(p); + } + + @Override + public boolean validateObject(PooledObject p) { + // testConnectivity() 方法在 modbus4j 中不存在。 + // 一个真正的验证需要执行一次读操作,但这需要 slaveId,而工厂是通用的,不知道 slaveId。 + // 因此,我们使用一个较弱的验证,只检查 master 对象是否已初始化。 + // 这无法检测到被远程设备断开的连接,错误将在实际读取操作中被捕获。 + if (p.getObject() == null) { + return false; + } + return p.getObject().isInitialized(); + } +} diff --git a/ems-common/src/main/java/com/xzzn/common/core/modbus/domain/DeviceConfig.java b/ems-common/src/main/java/com/xzzn/common/core/modbus/domain/DeviceConfig.java new file mode 100644 index 0000000..cbcc2ce --- /dev/null +++ b/ems-common/src/main/java/com/xzzn/common/core/modbus/domain/DeviceConfig.java @@ -0,0 +1,91 @@ +package com.xzzn.common.core.modbus.domain; + + +import java.util.List; + + +public class DeviceConfig { + + + private String deviceNumber; + private String deviceName; + private String host; + private int port; + private long time; + private int slaveId; + private boolean enabled; + private List tags; + private List writeTags; + + public String getDeviceNumber() { + return deviceNumber; + } + + public void setDeviceNumber(String deviceNumber) { + this.deviceNumber = deviceNumber; + } + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public long getTime() { + return time; + } + + public void setTime(long time) { + this.time = time; + } + + public int getSlaveId() { + return slaveId; + } + + public void setSlaveId(int slaveId) { + this.slaveId = slaveId; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } + + public List getWriteTags() { + return writeTags; + } + + public void setWriteTags(List writeTags) { + this.writeTags = writeTags; + } +} \ No newline at end of file diff --git a/ems-common/src/main/java/com/xzzn/common/core/modbus/domain/TagConfig.java b/ems-common/src/main/java/com/xzzn/common/core/modbus/domain/TagConfig.java new file mode 100644 index 0000000..7e5c3d2 --- /dev/null +++ b/ems-common/src/main/java/com/xzzn/common/core/modbus/domain/TagConfig.java @@ -0,0 +1,78 @@ +package com.xzzn.common.core.modbus.domain; + + + +public class TagConfig { + private String key; + private String des; + private String address; + private String dataType; + private float a; + private float k; + private float b; + private Integer bit; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getDes() { + return des; + } + + public void setDes(String des) { + this.des = des; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getDataType() { + return dataType; + } + + public void setDataType(String dataType) { + this.dataType = dataType; + } + + public float getA() { + return a; + } + + public void setA(float a) { + this.a = a; + } + + public float getK() { + return k; + } + + public void setK(float k) { + this.k = k; + } + + public float getB() { + return b; + } + + public void setB(float b) { + this.b = b; + } + + public Integer getBit() { + return bit; + } + + public void setBit(Integer bit) { + this.bit = bit; + } +} \ No newline at end of file diff --git a/ems-common/src/main/java/com/xzzn/common/core/modbus/domain/WriteTagConfig.java b/ems-common/src/main/java/com/xzzn/common/core/modbus/domain/WriteTagConfig.java new file mode 100644 index 0000000..8e82405 --- /dev/null +++ b/ems-common/src/main/java/com/xzzn/common/core/modbus/domain/WriteTagConfig.java @@ -0,0 +1,22 @@ +package com.xzzn.common.core.modbus.domain; + +public class WriteTagConfig { + private Object value; + private String address; + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/ems-common/src/main/java/com/xzzn/common/enums/ModBusType.java b/ems-common/src/main/java/com/xzzn/common/enums/ModBusType.java new file mode 100644 index 0000000..3d289ee --- /dev/null +++ b/ems-common/src/main/java/com/xzzn/common/enums/ModBusType.java @@ -0,0 +1,57 @@ +package com.xzzn.common.enums; + +import com.serotonin.modbus4j.code.DataType; + +import java.util.HashMap; +import java.util.Map; + + +public class ModBusType { + + + public static final Map REGISTER_TYPE = new HashMap(); + public static final Map LENGTH = new HashMap(); + + + + static { + REGISTER_TYPE.put(0, RegisterType.COIL); + REGISTER_TYPE.put(1, RegisterType.DISCRETE_INPUT); + REGISTER_TYPE.put(4, RegisterType.HOLDING_REGISTER); + REGISTER_TYPE.put(3, RegisterType.INPUT_REGISTER); + + + LENGTH.put("BINARY",DataType.BINARY); + LENGTH.put("TWO_BYTE_INT_UNSIGNED",DataType.TWO_BYTE_INT_UNSIGNED); + LENGTH.put("TWO_BYTE_INT_SIGNED",DataType.TWO_BYTE_INT_SIGNED); + LENGTH.put("TWO_BYTE_INT_UNSIGNED_SWAPPED",DataType.TWO_BYTE_INT_UNSIGNED_SWAPPED); + LENGTH.put("TWO_BYTE_INT_SIGNED_SWAPPED",DataType.TWO_BYTE_INT_SIGNED_SWAPPED); + LENGTH.put("FOUR_BYTE_INT_UNSIGNED",DataType.FOUR_BYTE_INT_UNSIGNED); + LENGTH.put("FOUR_BYTE_INT_SIGNED",DataType.FOUR_BYTE_INT_SIGNED); + LENGTH.put("FOUR_BYTE_INT_UNSIGNED_SWAPPED",DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED); + LENGTH.put("FOUR_BYTE_INT_SIGNED_SWAPPED",DataType.FOUR_BYTE_INT_SIGNED_SWAPPED); + LENGTH.put("FOUR_BYTE_INT_UNSIGNED_SWAPPED_SWAPPED",DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED_SWAPPED); + LENGTH.put("FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED",DataType.FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED); + LENGTH.put("FOUR_BYTE_FLOAT",DataType.FOUR_BYTE_FLOAT); + LENGTH.put("FOUR_BYTE_FLOAT_SWAPPED",DataType.FOUR_BYTE_FLOAT_SWAPPED); + LENGTH.put("FOUR_BYTE_FLOAT_SWAPPED_INVERTED",DataType.FOUR_BYTE_FLOAT_SWAPPED_INVERTED); + LENGTH.put("FOUR_BYTE_FLOAT_INVERTED",DataType.FOUR_BYTE_FLOAT_INVERTED); + LENGTH.put("FOUR_BYTE_FLOAT_SWAPPED_SWAPPED",DataType.FOUR_BYTE_FLOAT_SWAPPED_SWAPPED); + LENGTH.put("EIGHT_BYTE_INT_UNSIGNED",DataType.EIGHT_BYTE_INT_UNSIGNED); + LENGTH.put("EIGHT_BYTE_INT_SIGNED",DataType.EIGHT_BYTE_INT_SIGNED); + LENGTH.put("EIGHT_BYTE_INT_UNSIGNED_SWAPPED",DataType.EIGHT_BYTE_INT_UNSIGNED_SWAPPED); + LENGTH.put("EIGHT_BYTE_INT_SIGNED_SWAPPED",DataType.EIGHT_BYTE_INT_SIGNED_SWAPPED); + LENGTH.put("EIGHT_BYTE_FLOAT",DataType.EIGHT_BYTE_FLOAT); + LENGTH.put("EIGHT_BYTE_FLOAT_SWAPPED",DataType.EIGHT_BYTE_FLOAT_SWAPPED); + LENGTH.put("TWO_BYTE_BCD",DataType.TWO_BYTE_BCD); + LENGTH.put("FOUR_BYTE_BCD",DataType.FOUR_BYTE_BCD); + LENGTH.put("FOUR_BYTE_BCD_SWAPPED",DataType.FOUR_BYTE_BCD_SWAPPED); + LENGTH.put("CHAR",DataType.CHAR); + LENGTH.put("VARCHAR",DataType.VARCHAR); + + LENGTH.put("FOUR_BYTE_FLOAT_DBCA",28); + + + + } +} diff --git a/ems-common/src/main/java/com/xzzn/common/enums/PcsControlCommand.java b/ems-common/src/main/java/com/xzzn/common/enums/PcsControlCommand.java new file mode 100644 index 0000000..0cd448a --- /dev/null +++ b/ems-common/src/main/java/com/xzzn/common/enums/PcsControlCommand.java @@ -0,0 +1,45 @@ +package com.xzzn.common.enums; + +public enum PcsControlCommand { + TOTAL_START(0x0004, "总运行"), + TOTAL_STOP(0x0010, "总停机"), + TOTAL_RESET(0x0080, "总复位"), +// CLUSTER1_START(0x1004, "电池簇1运行"), +// CLUSTER1_STOP(0x1010, "电池簇1停机"), +// CLUSTER1_RESET(0x1080, "电池簇1复位"), +// CLUSTER2_START(0x2004, "电池簇2运行"), +// CLUSTER2_STOP(0x2010, "电池簇2停机"), +// CLUSTER2_RESET(0x2080, "电池簇2复位"), +// CLUSTER3_START(0x3004, "电池簇3运行"), +// CLUSTER3_STOP(0x3010, "电池簇3停机"), +// CLUSTER3_RESET(0x3080, "电池簇3复位"), +// CLUSTER4_START(0x4004, "电池簇4运行"), +// CLUSTER4_STOP(0x4010, "电池簇4停机"), +// CLUSTER4_RESET(0x4080, "电池簇4复位"), + ; + + private final int code; + private final String description; + + PcsControlCommand(int code, String description) { + this.code = code; + this.description = description; + } + + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public static PcsControlCommand fromDeviceStatus(String deviceStatus) { + if (DeviceRunningStatus.RUNNING.getCode().equals(deviceStatus)) { + return PcsControlCommand.TOTAL_START; + } else if (DeviceRunningStatus.SHUTDOWN.getCode().equals(deviceStatus) ) { + return PcsControlCommand.TOTAL_STOP; + } + return null; + } +} \ No newline at end of file diff --git a/ems-common/src/main/java/com/xzzn/common/enums/RegisterType.java b/ems-common/src/main/java/com/xzzn/common/enums/RegisterType.java new file mode 100644 index 0000000..1c9e3a8 --- /dev/null +++ b/ems-common/src/main/java/com/xzzn/common/enums/RegisterType.java @@ -0,0 +1,8 @@ +package com.xzzn.common.enums; + +public enum RegisterType { + COIL, + DISCRETE_INPUT, + HOLDING_REGISTER, + INPUT_REGISTER +} \ No newline at end of file diff --git a/ems-quartz/src/main/java/com/xzzn/quartz/config/ScheduledTask.java b/ems-quartz/src/main/java/com/xzzn/quartz/config/ScheduledTask.java new file mode 100644 index 0000000..867ac1e --- /dev/null +++ b/ems-quartz/src/main/java/com/xzzn/quartz/config/ScheduledTask.java @@ -0,0 +1,34 @@ +package com.xzzn.quartz.config; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.springframework.stereotype.Component; + +@Component +public class ScheduledTask { + + + private ScheduledExecutorService executor = Executors.newScheduledThreadPool(10); + private final Map> futureMap = new ConcurrentHashMap<>(); + + public void startTask(String deviceId, Runnable task, long period) { + stopTask(deviceId); // 如果已有同ID任务在运行,先停止 + ScheduledFuture future = executor.scheduleAtFixedRate(task, 0, period, TimeUnit.MILLISECONDS); + futureMap.put(deviceId, future); + } + + public void stopTask(String deviceId) { + ScheduledFuture future = futureMap.get(deviceId); + if (future != null && !future.isDone()) { + future.cancel(true); + } + futureMap.remove(deviceId); + } + + +} diff --git a/ems-quartz/src/main/java/com/xzzn/quartz/task/ModbusPoller.java b/ems-quartz/src/main/java/com/xzzn/quartz/task/ModbusPoller.java index 2d1b063..83d9abc 100644 --- a/ems-quartz/src/main/java/com/xzzn/quartz/task/ModbusPoller.java +++ b/ems-quartz/src/main/java/com/xzzn/quartz/task/ModbusPoller.java @@ -1,179 +1,204 @@ package com.xzzn.quartz.task; -import com.xzzn.common.enums.DeviceRunningStatus; -import com.xzzn.ems.domain.EmsDeviceChangeLog; -import com.xzzn.ems.domain.EmsDevicesSetting; -import com.xzzn.ems.mapper.EmsDeviceChangeLogMapper; -import com.xzzn.ems.mapper.EmsDevicesSettingMapper; -import com.xzzn.ems.mapper.EmsMqttMessageMapper; -import com.xzzn.ems.service.impl.EmsDeviceSettingServiceImpl; -import com.xzzn.framework.manager.ModbusConnectionManager; -import com.xzzn.framework.manager.ModbusConnectionWrapper; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.serotonin.modbus4j.ModbusMaster; +import com.xzzn.common.constant.RedisKeyConstants; +import com.xzzn.common.core.modbus.ModbusProcessor; +import com.xzzn.common.core.modbus.domain.DeviceConfig; +import com.xzzn.common.core.redis.RedisCache; +import com.xzzn.common.utils.DateUtils; +import com.xzzn.ems.service.IEmsAlarmRecordsService; +import com.xzzn.ems.service.impl.DeviceDataProcessServiceImpl; import com.xzzn.framework.manager.MqttLifecycleManager; -import com.xzzn.framework.web.service.ModbusService; +import com.xzzn.framework.web.service.MqttPublisher; +import com.xzzn.quartz.config.ScheduledTask; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + import org.eclipse.paho.client.mqttv3.MqttException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import java.util.Date; -import java.util.List; -import java.util.UUID; - /** * 轮询设备-通过modbus协议读取数据 */ @Component("modbusPoller") public class ModbusPoller { - private static final Logger logger = LoggerFactory.getLogger(ModbusPoller.class); + private static final Logger log = LoggerFactory.getLogger(ModbusPoller.class); private final MqttLifecycleManager mqttLifecycleManager; + private final ScheduledTask scheduledTask; + private final ObjectMapper objectMapper = new ObjectMapper(); @Autowired - private ModbusConnectionManager connectionManager; + private ModbusProcessor modbusProcessor; @Autowired - private ModbusService modbusService; + private IEmsAlarmRecordsService iEmsAlarmRecordsService; @Autowired - private EmsDevicesSettingMapper deviceRepo; + private DeviceDataProcessServiceImpl deviceDataProcessServiceImpl; @Autowired - private EmsMqttMessageMapper emsMqttMessageMapper; + private RedisCache redisCache; @Autowired - private EmsDeviceSettingServiceImpl emsDeviceSettingServiceImpl; - @Autowired - private EmsDeviceChangeLogMapper emsDeviceChangeLogMapper; + private MqttPublisher mqttPublisher; + + @Value("${mqtt.topic}") + private String topic; + @Value("${mqtt.siteId}") + private String siteId; @Autowired - public ModbusPoller(MqttLifecycleManager mqttLifecycleManager) { + public ModbusPoller(MqttLifecycleManager mqttLifecycleManager, ScheduledTask scheduledTask) { this.mqttLifecycleManager = mqttLifecycleManager; + this.scheduledTask = scheduledTask; } + public void pollAllDevices() { - logger.info("开始执行Modbus设备轮询..."); - EmsDevicesSetting selectEntity = new EmsDevicesSetting(); - selectEntity.setDeviceStatus(DeviceRunningStatus.RUNNING.getCode()); - List activeDevices = deviceRepo.selectEmsDevicesSettingList(selectEntity); - - EmsDevicesSetting device = activeDevices.get(0); - try { - //pollSingleDevice(device); - } catch (Exception e) { - logger.error("调度设备{}任务失败", device.getId(), e); - } - /*activeDevices.forEach(device -> { - try { - CompletableFuture.runAsync(() -> pollSingleDevice(device)) - .exceptionally(e -> { - logger.error("设备{}轮询异常", device.getId(), e); - return null; - }); - } catch (Exception e) { - logger.error("调度设备{}任务失败", device.getId(), e); - } - });*/ - } - - private void pollSingleDevice(EmsDevicesSetting device) { - logger.debug("开始轮询设备: {}", device.getSiteId(), device.getDeviceName(), device.getId()); - - ModbusConnectionWrapper wrapper = null; - try { - // 获取连接 - wrapper = connectionManager.getConnection(device); - - if(wrapper == null || !wrapper.isActive()){ - logger.error("轮询设备{}连接失败: {}", device.getId()); - return; - } - // 读取保持寄存器 - int[] data = modbusService.readHoldingRegisters( - wrapper.getConnection(), - 1, //从站ID - 10 // 寄存器数量 - ); - - // 处理读取到的数据 - processData(device, data); - } catch (Exception e) { - logger.error("轮询设备{}失败: {}", device.getId(), e.getMessage()); - // 标记连接为无效 - if (wrapper != null) { - wrapper.close(); - connectionManager.removeConnection(Integer.parseInt(device.getDeviceId())); - } - - // 设备轮询不到修改运行状态 - String beforeStatus = device.getDeviceStatus(); - device.setDeviceStatus(DeviceRunningStatus.SHUTDOWN.getCode()); - emsDeviceSettingServiceImpl.updateDevice(device); - - // 轮询设备,设备状态变更日志 - EmsDeviceChangeLog log = createLogEntity(beforeStatus,device); - emsDeviceChangeLogMapper.insertEmsDeviceChangeLog(log); - - throw new RuntimeException("轮询设备失败", e); - } - } - - // 处理获取到的数据 - private void processData(EmsDevicesSetting device, int[] data) throws MqttException { - String beforeStatus = device.getDeviceStatus(); - Boolean error = true; - if (data == null || data.length == 0) { - logger.warn("设备{}返回空数据", device.getId()); - // 设备读取不到-设置设备故障 - device.setDeviceStatus(DeviceRunningStatus.FAULT.getCode()); - error = false; - } else { - // 恢复设备状态 - 运行 - device.setDeviceStatus(DeviceRunningStatus.RUNNING.getCode()); - } - emsDeviceSettingServiceImpl.updateDevice(device); - // 轮询设备,设备状态变更日志 - EmsDeviceChangeLog log = createLogEntity(beforeStatus,device); - emsDeviceChangeLogMapper.insertEmsDeviceChangeLog(log); - // 错误数据-不处理直接返回 - if (!error) { + Path devicesDir = Paths.get(System.getProperty("user.dir"), "devices"); + if (!Files.exists(devicesDir)) { + log.error("Devices目录不存在: {}", devicesDir); return; } - // 数据处理逻辑 - StringBuilder sb = new StringBuilder(); - sb.append("设备[").append(device.getDeviceName()).append("]数据: "); - for (int i = 0; i < data.length; i++) { - sb.append("R").append(i).append("=").append(data[i]).append(" "); + List jsonFiles = null; + try { + jsonFiles = Files.list(devicesDir) + .filter(path -> path.toString().endsWith(".json")) + .collect(Collectors.toList()); + } catch (IOException e) { + log.error("modbusPoller.loadConfigs 获取设备配置文件失败: {}", devicesDir, e); + return; } - String message = sb.toString(); - logger.info(sb.toString()); - /* - String siteId = device.getSiteId(); - if (siteId.startsWith("021_DDS")) { - dDSDataProcessService.handleDdsData(message); - } else if (siteId.startsWith("021_FXX")) { - fXXDataProcessService.handleFxData(message); - }*/ + // 按 host:port 分组 + Map> groupedConfigs = new HashMap<>(); + for (Path filePath : jsonFiles) { + DeviceConfig config = null; + try { + config = objectMapper.readValue(filePath.toFile(), DeviceConfig.class); + } catch (IOException e) { + log.error("modbusPoller.loadConfigs 解析设备配置文件失败: {}", filePath, e); + continue; + } + if (config.isEnabled()) { + String key = config.getHost() + ":" + config.getPort(); + groupedConfigs.computeIfAbsent(key, k -> new ArrayList<>()).add(config); + } + } - // 测试发送mqtt - /* EmsMqttMessage msg = emsMqttMessageMapper.selectEmsMqttMessageById(1L); - String dataJson = msg.getMqttMessage(); - String topic = msg.getMqttTopic(); - logger.info("topic:" + topic); - logger.info("dataJson:" + dataJson); - // 将设备数据下发到mqtt服务器上 - mqttLifecycleManager.publish(topic, dataJson, 0);*/ + // 为每个 host:port 启动一个任务 + for (Map.Entry> entry : groupedConfigs.entrySet()) { + String groupKey = entry.getKey(); + List configs = entry.getValue(); + // 取其中一个配置的时间间隔作为该组任务的执行周期 + long interval = configs.get(0).getTime(); + scheduledTask.startTask(groupKey, () -> { + for (DeviceConfig config : configs) { + try { + scheduledStart(config, null); + } catch (Exception e) { + log.error("采集设备数据异常: {}", config.getDeviceName(), e); + } + } + }, interval); + } } - private EmsDeviceChangeLog createLogEntity(String beforeStatus, EmsDevicesSetting device) { - EmsDeviceChangeLog log = new EmsDeviceChangeLog(); - log.setLogId(UUID.randomUUID().toString()); - log.setLogTime(new Date()); - log.setSiteId(device.getSiteId()); - log.setDeviceId(device.getDeviceId()); - log.setBeforeStatus(beforeStatus); - log.setAfterStatus(device.getDeviceStatus()); - log.setCreateBy("sys"); - log.setCreateTime(new Date()); - return log; + public void scheduledStart(DeviceConfig config, ModbusMaster master) { + try { + if (config.isEnabled()) { + log.info("Reading data from devices: {}", config.getDeviceName()); + Map data = modbusProcessor.readDataFromDevice(config, master); + // 在这里处理采集到的数据 + config.getTags().forEach(tag -> { + Object rawValue = data.get(tag.getKey()); + if (rawValue != null) { + float value = 0; + if (rawValue instanceof Number) { + value = ((Number) rawValue).floatValue(); // 安全地转换为 float + } else { + log.error("tag:{},无法将数据转换为数字: {}", tag.getKey(), rawValue); + } + value = tag.getA() * value * value + tag.getK() * value + tag.getB(); + + int intValue = (int) value; + if (tag.getBit() != null) { + log.info("tag:{},bit:{},value:{}", tag.getKey(), tag.getBit(), value); + String binary = Integer.toBinaryString(intValue); + data.put(tag.getKey(), binary); + } else { + data.put(tag.getKey(), value); + } + } else { + data.put(tag.getKey(), rawValue); + log.warn("tag:{},数据为空: {}", tag.getKey(), rawValue); + } + }); + log.info("Data from {}: {}", config.getDeviceName(), data); + String deviceNumber = config.getDeviceNumber(); + //处理数据并发送MQTT消息、保存Redis数据和数据入库 + processingData(data, deviceNumber); + } + } catch (Exception e) { + log.error("设备数据采集异常: {}", config.getDeviceName(), e); + } } + + private void processingData(Map data, String deviceNumber) { + if (data == null || data.size() == 0) { + // 添加设备告警 + iEmsAlarmRecordsService.addEmptyDataAlarmRecord(siteId, deviceNumber); + return; + } + Long timestamp = System.currentTimeMillis(); + JSONObject json = new JSONObject(); + json.put("Data", data); + json.put("timestamp", timestamp); + json.put("Device", deviceNumber); + sendMqttMsg(json); + saveRedisData(json, deviceNumber); + saveDataToDatabase(data, deviceNumber, timestamp); + } + + public void sendMqttMsg(JSONObject json) { + try { + mqttPublisher.publish(topic, Collections.singletonList(json).toString(), 0); + } catch (MqttException e) { + log.error("MQTT消息发布失败: {}", json.toJSONString(), e); + } + log.info("已发送数据: {}", json.toJSONString()); + } + + + public void saveRedisData(JSONObject obj, String deviceNumber) { + try { + // 存放mqtt原始每个设备最晚一次数据,便于后面点位获取数据 + redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + siteId + "_" + deviceNumber, obj); + // 存放每次同步数据,失效时间(同同步时间)-用于判断是否正常同步数据和保护策略查询 + redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA_ALARM + siteId + "_" + deviceNumber, obj, 1, TimeUnit.MINUTES); + log.info("数据已成功存储在Redis: {}", deviceNumber); + } catch (Exception e) { + log.error("无法在设备的Redis中存储数据: {}", deviceNumber, e); + } + } + + private void saveDataToDatabase(Map data, String deviceNumber, Long timestamp) { + deviceDataProcessServiceImpl.processingDeviceData(siteId, deviceNumber, JSON.toJSONString(data), DateUtils.convertUpdateTime(timestamp)); + } + } \ No newline at end of file diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsBatteryData.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsBatteryData.java index 5c9f2f6..82d3380 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/EmsBatteryData.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsBatteryData.java @@ -1,12 +1,14 @@ package com.xzzn.ems.domain; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.xzzn.common.annotation.Excel; +import com.xzzn.common.core.domain.BaseEntity; + import java.math.BigDecimal; import java.util.Date; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.xzzn.common.core.domain.BaseEntity; + import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; -import com.xzzn.common.annotation.Excel; /** * 单体电池实时数据对象 ems_battery_data @@ -70,6 +72,10 @@ public class EmsBatteryData extends BaseEntity @Excel(name = "单体电池内阻") private BigDecimal interResistance; + /** 单体电池电流 */ + @Excel(name = "单体电池电流") + private BigDecimal current; + public void setId(Long id) { this.id = id; @@ -200,6 +206,14 @@ public class EmsBatteryData extends BaseEntity return interResistance; } + public BigDecimal getCurrent() { + return current; + } + + public void setCurrent(BigDecimal current) { + this.current = current; + } + @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) @@ -221,6 +235,7 @@ public class EmsBatteryData extends BaseEntity .append("deviceId", getDeviceId()) .append("clusterDeviceId", getClusterDeviceId()) .append("interResistance", getInterResistance()) + .append("current", getCurrent()) .toString(); } } diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsPcsData.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPcsData.java index b505401..ae79973 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/EmsPcsData.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPcsData.java @@ -1,12 +1,14 @@ package com.xzzn.ems.domain; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.xzzn.common.annotation.Excel; +import com.xzzn.common.core.domain.BaseEntity; + import java.math.BigDecimal; import java.util.Date; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.xzzn.common.core.domain.BaseEntity; + import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; -import com.xzzn.common.annotation.Excel; /** * PCS数据对象 ems_pcs_data @@ -34,8 +36,8 @@ public class EmsPcsData extends BaseEntity @Excel(name = "并网状态:0-并网 1-未并网") private String gridStatus; - /** 设备状态:0-在线 1-离线 2-维修中 */ - @Excel(name = "设备状态:0-在线 1-离线 2-维修中") + /** 设备运行状态:0-离线、1-待机、2-运行、3-故障、4-停机 */ + @Excel(name = "设备运行状态:0-离线、1-待机、2-运行、3-故障、4-停机") private String deviceStatus; /** 控制模式:0-远程 1-本地 */ @@ -234,6 +236,22 @@ public class EmsPcsData extends BaseEntity @Excel(name = "4#模块IGBT最高温") private BigDecimal module4Temp; + /** 电池簇1PCS有功功率给定 */ + @Excel(name = "电池簇1PCS有功功率给定") + private BigDecimal cluster1ActivePower; + + /** 电池簇2PCS有功功率给定 */ + @Excel(name = "电池簇2PCS有功功率给定") + private BigDecimal cluster2ActivePower; + + /** 电池簇3PCS有功功率给定 */ + @Excel(name = "电池簇3PCS有功功率给定") + private BigDecimal cluster3ActivePower; + + /** 电池簇4PCS有功功率给定 */ + @Excel(name = "电池簇4PCS有功功率给定") + private BigDecimal cluster4ActivePower; + public void setId(Long id) { this.id = id; @@ -774,6 +792,38 @@ public class EmsPcsData extends BaseEntity return module4Temp; } + public BigDecimal getCluster1ActivePower() { + return cluster1ActivePower; + } + + public void setCluster1ActivePower(BigDecimal cluster1ActivePower) { + this.cluster1ActivePower = cluster1ActivePower; + } + + public BigDecimal getCluster2ActivePower() { + return cluster2ActivePower; + } + + public void setCluster2ActivePower(BigDecimal cluster2ActivePower) { + this.cluster2ActivePower = cluster2ActivePower; + } + + public BigDecimal getCluster3ActivePower() { + return cluster3ActivePower; + } + + public void setCluster3ActivePower(BigDecimal cluster3ActivePower) { + this.cluster3ActivePower = cluster3ActivePower; + } + + public BigDecimal getCluster4ActivePower() { + return cluster4ActivePower; + } + + public void setCluster4ActivePower(BigDecimal cluster4ActivePower) { + this.cluster4ActivePower = cluster4ActivePower; + } + @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) @@ -836,6 +886,10 @@ public class EmsPcsData extends BaseEntity .append("module2Temp", getModule2Temp()) .append("module3Temp", getModule3Temp()) .append("module4Temp", getModule4Temp()) + .append("cluster1ActivePower", getCluster1ActivePower()) + .append("cluster2ActivePower", getCluster2ActivePower()) + .append("cluster3ActivePower", getCluster3ActivePower()) + .append("cluster4ActivePower", getCluster4ActivePower()) .toString(); } } diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/DeviceUpdateRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/DeviceUpdateRequest.java index 0666997..9f7fb17 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/vo/DeviceUpdateRequest.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/DeviceUpdateRequest.java @@ -1,5 +1,7 @@ package com.xzzn.ems.domain.vo; +import java.util.List; + import javax.validation.constraints.NotBlank; /** @@ -22,6 +24,10 @@ public class DeviceUpdateRequest { /** 设备类型 */ private String deviceCategory; + /** 设备点位数据匹配表字段 */ + private List matchFields; + + public String getSiteId() { return siteId; } @@ -53,4 +59,12 @@ public class DeviceUpdateRequest { public void setDeviceCategory(String deviceCategory) { this.deviceCategory = deviceCategory; } + + public List getMatchFields() { + return matchFields; + } + + public void setMatchFields(List matchFields) { + this.matchFields = matchFields; + } } diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointMatchMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointMatchMapper.java index 47a6c9d..0a9e0cf 100644 --- a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointMatchMapper.java +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointMatchMapper.java @@ -1,15 +1,15 @@ package com.xzzn.ems.mapper; +import com.xzzn.ems.domain.EmsPointMatch; +import com.xzzn.ems.domain.vo.DevicePointMatchExportVo; +import com.xzzn.ems.domain.vo.DeviceUpdateRequest; +import com.xzzn.ems.domain.vo.GeneralQueryDataVo; +import com.xzzn.ems.domain.vo.PointQueryResponse; + import java.util.Date; import java.util.List; import java.util.Map; -import com.xzzn.ems.domain.EmsPointMatch; -import com.xzzn.ems.domain.vo.DevicePointMatchExportVo; -import com.xzzn.ems.domain.vo.DevicePointMatchVo; -import com.xzzn.ems.domain.vo.DeviceUpdateRequest; -import com.xzzn.ems.domain.vo.GeneralQueryDataVo; -import com.xzzn.ems.domain.vo.PointQueryResponse; import org.apache.ibatis.annotations.Param; /** @@ -171,5 +171,5 @@ public interface EmsPointMatchMapper int getDevicePointAlarmNum(@Param("siteId") String siteId, @Param("deviceId") String deviceId, @Param("deviceCategory") String deviceCategory); - EmsPointMatch selectDeviceStatusPoint(@Param("request") DeviceUpdateRequest request); + List selectDeviceStatusPoint(@Param("request") DeviceUpdateRequest request); } diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/DeviceDataProcessServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/DeviceDataProcessServiceImpl.java index 96ef2dc..d490060 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/DeviceDataProcessServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/DeviceDataProcessServiceImpl.java @@ -161,32 +161,37 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i // 存放每次同步数据,失效时间(同同步时间)-用于判断是否正常同步数据 redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceId, obj, 1, TimeUnit.MINUTES); - if (deviceId.contains(SiteDevice.BMSD.name())) { - batteryStackDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - batteryGroupDataProcess(siteId, deviceId, jsonData); - batteryDataProcessFromBmsd(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.BMSC.name())) { - batteryClusterDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - batteryDataProcessFromBmsc(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.PCS.name())) { - pcsDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - pcsBranchDataProcess(siteId, deviceId, jsonData); - batteryClusterDataProcess(siteId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.LOAD.name())) { - loadDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.METEGF.name()) - || deviceId.contains(SiteDevice.METE.name()) - || deviceId.contains(SiteDevice.METE0.name())) { - meteDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.XF.name())) { - meteXFProcess(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.DH.name()) || deviceId.contains(SiteDevice.donghuan.name())) { - dhDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.ZSLQ.name())) { - coolingDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.EMS.name())) { - emsDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - } + // 处理设备数据 + processingDeviceData(siteId, deviceId, jsonData, dataUpdateTime); + } + } + + public void processingDeviceData(String siteId, String deviceId, String jsonData, Date dataUpdateTime) { + if (deviceId.contains(SiteDevice.BMSD.name())) { + batteryStackDataProcess(siteId, deviceId, jsonData, dataUpdateTime); + batteryGroupDataProcess(siteId, deviceId, jsonData); + batteryDataProcessFromBmsd(siteId, deviceId, jsonData, dataUpdateTime); + } else if (deviceId.contains(SiteDevice.BMSC.name())) { + batteryClusterDataProcess(siteId, deviceId, jsonData, dataUpdateTime); + batteryDataProcessFromBmsc(siteId, deviceId, jsonData, dataUpdateTime); + } else if (deviceId.contains(SiteDevice.PCS.name())) { + pcsDataProcess(siteId, deviceId, jsonData, dataUpdateTime); + pcsBranchDataProcess(siteId, deviceId, jsonData); + batteryClusterDataProcess(siteId, jsonData, dataUpdateTime); + } else if (deviceId.contains(SiteDevice.LOAD.name())) { + loadDataProcess(siteId, deviceId, jsonData, dataUpdateTime); + } else if (deviceId.contains(SiteDevice.METEGF.name()) + || deviceId.contains(SiteDevice.METE.name()) + || deviceId.contains(SiteDevice.METE0.name())) { + meteDataProcess(siteId, deviceId, jsonData, dataUpdateTime); + } else if (deviceId.contains(SiteDevice.XF.name())) { + meteXFProcess(siteId, deviceId, jsonData, dataUpdateTime); + } else if (deviceId.contains(SiteDevice.DH.name()) || deviceId.contains(SiteDevice.donghuan.name())) { + dhDataProcess(siteId, deviceId, jsonData, dataUpdateTime); + } else if (deviceId.contains(SiteDevice.ZSLQ.name())) { + coolingDataProcess(siteId, deviceId, jsonData, dataUpdateTime); + } else if (deviceId.contains(SiteDevice.EMS.name())) { + emsDataProcess(siteId, deviceId, jsonData, dataUpdateTime); } } @@ -249,14 +254,14 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i } - private void meteXFProcess(String siteId, String deviceId, String dataJson, Date dataUpdateTime) { + public void meteXFProcess(String siteId, String deviceId, String dataJson, Date dataUpdateTime) { //消防 Map obj = JSON.parseObject(dataJson, new TypeReference>() { }); // 点位匹配数据 List pointMatchList = devicePointMatchDataProcessor.getDevicePointMatch(siteId, deviceId, DeviceMatchTable.XF.getCode()); if (CollectionUtils.isEmpty(pointMatchList)) { - log.info("未找到匹配的点位数据,无法处理动环数据,siteId: " + siteId + ",deviceId: " + deviceId); + log.info("未找到匹配的点位数据,无法处理消防数据,siteId: " + siteId + ",deviceId: " + deviceId); return; } Map> pointEnumMatchMap = devicePointMatchDataProcessor.getPointEnumMatchMap(siteId, DeviceMatchTable.XF.getCode()); @@ -1415,20 +1420,8 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA_ALARM + siteId + "_" + deviceId, obj); // 存放每次同步数据,失效时间(同同步时间)-用于判断是否正常同步数据 redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA_ALARM + siteId + "_" + deviceId, obj, 1, TimeUnit.MINUTES); - - String deviceCategory = ""; - if (deviceId.contains(SiteDevice.ZSLQ.name())) { - coolingAlarmDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.BMSD.name())) { - deviceCategory = DeviceCategory.STACK.getCode(); - stackAlarmDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.BMSC.name())) { - deviceCategory = DeviceCategory.CLUSTER.getCode(); - clusterAlarmDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.PCS.name())) { - deviceCategory = DeviceCategory.PCS.getCode(); - pcsAlarmDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - } + // 处理设备数据,根据不同设备类型进行不同的数据处理 + String deviceCategory = processingDeviceAlarmData(siteId, deviceId, jsonData, dataUpdateTime); if (StringUtils.isEmpty(deviceCategory)) { // 处理告警信息 alarmDataProcess(siteId, deviceId, jsonData, alarmMatchInfo, deviceCategory); @@ -1436,6 +1429,24 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i } } + public String processingDeviceAlarmData(String siteId, String deviceId, String jsonData, Date dataUpdateTime) { + String deviceCategory = ""; + if (deviceId.contains(SiteDevice.ZSLQ.name())) { + coolingAlarmDataProcess(siteId, deviceId, jsonData, dataUpdateTime); + } else if (deviceId.contains(SiteDevice.BMSD.name())) { + deviceCategory = DeviceCategory.STACK.getCode(); + stackAlarmDataProcess(siteId, deviceId, jsonData, dataUpdateTime); + } else if (deviceId.contains(SiteDevice.BMSC.name())) { + deviceCategory = DeviceCategory.CLUSTER.getCode(); + clusterAlarmDataProcess(siteId, deviceId, jsonData, dataUpdateTime); + } else if (deviceId.contains(SiteDevice.PCS.name())) { + deviceCategory = DeviceCategory.PCS.getCode(); + pcsAlarmDataProcess(siteId, deviceId, jsonData, dataUpdateTime); + } + + return deviceCategory; + } + private void pcsAlarmDataProcess(String siteId, String deviceId, String jsonData, Date dataUpdateTime) { //pcs Map obj = JSON.parseObject(jsonData, new TypeReference>() { diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDeviceSettingServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDeviceSettingServiceImpl.java index 2f279b9..e28157b 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDeviceSettingServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDeviceSettingServiceImpl.java @@ -4,15 +4,19 @@ import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.TypeReference; import com.xzzn.common.constant.RedisKeyConstants; +import com.xzzn.common.core.modbus.ModbusProcessor; +import com.xzzn.common.core.modbus.domain.DeviceConfig; +import com.xzzn.common.core.modbus.domain.WriteTagConfig; import com.xzzn.common.core.redis.RedisCache; import com.xzzn.common.enums.DeviceCategory; import com.xzzn.common.enums.DeviceRunningStatus; +import com.xzzn.common.enums.DeviceType; +import com.xzzn.common.enums.PcsControlCommand; import com.xzzn.common.enums.PointType; import com.xzzn.common.enums.SiteEnum; import com.xzzn.common.exception.ServiceException; import com.xzzn.common.utils.DateUtils; import com.xzzn.common.utils.StringUtils; -import com.xzzn.common.utils.file.ImageUtils; import com.xzzn.ems.domain.EmsDevicesSetting; import com.xzzn.ems.domain.EmsPointMatch; import com.xzzn.ems.domain.vo.DeviceUpdateRequest; @@ -23,15 +27,22 @@ import com.xzzn.ems.mapper.EmsDevicesSettingMapper; import com.xzzn.ems.mapper.EmsPointMatchMapper; import com.xzzn.ems.service.IEmsDeviceSettingService; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; - -import java.math.BigDecimal; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; +import org.springframework.util.CollectionUtils; /** * 站点信息 服务层实现 @@ -43,6 +54,8 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService private static final Logger log = LoggerFactory.getLogger(EmsDeviceSettingServiceImpl.class); private static final String DDS_SITE_ID = "021_DDS_01"; + private static final String DEVICE_STATUS_FIELD = "device_status"; + private static final List CLUSTER_ACTIVE_POWER_FIELDS = Arrays.asList("cluster1_active_power", "cluster2_active_power", "cluster3_active_power", "cluster4_active_power"); @Autowired private EmsDevicesSettingMapper emsDevicesMapper; @@ -54,6 +67,8 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService private EmsBatteryDataMinutesMapper emsBatteryDataMinutesMapper; @Autowired private EmsBatteryClusterServiceImpl emsBatteryClusterServiceImpl; + @Autowired + private ModbusProcessor modbusProcessor; /** * 获取设备详细信息 @@ -486,11 +501,13 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService throw new ServiceException("未找到对应设备配置信息"); } // 查询设备配置的设备状态对应点位 -// request.setDeviceCategory(DeviceCategory.PCS.getCode()); -// EmsPointMatch pointMatch = emsPointMatchMapper.selectDeviceStatusPoint(request); -// if (pointMatch == null) { -// throw new ServiceException("未找到对应设备状态点位"); -// } + CLUSTER_ACTIVE_POWER_FIELDS.add(DEVICE_STATUS_FIELD); + request.setMatchFields(CLUSTER_ACTIVE_POWER_FIELDS); + request.setDeviceCategory(DeviceCategory.PCS.getCode()); + List pointMatchList = emsPointMatchMapper.selectDeviceStatusPoint(request); + if (CollectionUtils.isEmpty(pointMatchList) || pointMatchList.stream().noneMatch(point -> DEVICE_STATUS_FIELD.equals(point.getMatchField()))) { + throw new ServiceException("未找到对应设备状态点位"); + } if (DeviceRunningStatus.getShutdownCodeList().contains(request.getDeviceStatus())) { // 开机逻辑 device.setDeviceStatus(DeviceRunningStatus.RUNNING.getCode()); @@ -500,10 +517,50 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService } else { return false; } - // TODO 调用Modbus向设备发送指令 - + // 根据设备类型和请求状态确定控制命令 + PcsControlCommand command = PcsControlCommand.fromDeviceStatus(device.getDeviceStatus()); + if (command == null) { + throw new ServiceException("不支持的设备状态操作"); + } + // 调用Modbus向设备发送指令 + DeviceConfig deviceConfig = getDeviceConfig(device); + deviceConfig.setWriteTags(getWriteTags(device, pointMatchList, command)); + log.info("设备控制指令发送数据: {}", JSON.toJSONString(deviceConfig)); + boolean result = modbusProcessor.writeDataToDevice(deviceConfig); + if (!result) { + throw new ServiceException("设备控制指令发送失败"); + } device.setUpdatedAt(DateUtils.getNowDate()); emsDevicesMapper.updateEmsDevicesSetting(device); return true; } + + public DeviceConfig getDeviceConfig(EmsDevicesSetting device) { + DeviceConfig deviceConfig = new DeviceConfig(); + deviceConfig.setDeviceNumber(device.getDeviceId()); + deviceConfig.setDeviceName(device.getDeviceName()); + deviceConfig.setSlaveId(device.getSlaveId().intValue()); + if (DeviceType.TCP.name().equals(device.getDeviceType())) { + deviceConfig.setHost(device.getIpAddress()); + deviceConfig.setPort(device.getIpPort().intValue()); + } + + return deviceConfig; + } + + public List getWriteTags(EmsDevicesSetting device, List pointMatchList, PcsControlCommand command) { + List writeTags = new ArrayList<>(); + for (EmsPointMatch pointMatch : pointMatchList) { + WriteTagConfig writeTag = new WriteTagConfig(); + writeTag.setAddress(pointMatch.getIpAddress()); + if (DEVICE_STATUS_FIELD.equals(pointMatch.getMatchField())) { + writeTag.setValue(command.getCode()); + } else { + // 电池簇PCS有功功率给定置0 + writeTag.setValue(0); + } + writeTags.add(writeTag); + } + return writeTags; + } } diff --git a/ems-system/src/main/resources/mapper/ems/EmsBatteryDataDayMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsBatteryDataDayMapper.xml index 96e21ca..6dd7135 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsBatteryDataDayMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsBatteryDataDayMapper.xml @@ -23,11 +23,12 @@ + - select id, battery_pack, battery_cluster, battery_cell_id, voltage, temperature, soc, soh, data_timestamp, create_by, create_time, update_by, update_time, remark, site_id, device_id, cluster_device_id, inter_resistance, day_time from ems_battery_data_day + select id, battery_pack, battery_cluster, battery_cell_id, voltage, temperature, soc, soh, data_timestamp, create_by, create_time, update_by, update_time, remark, site_id, device_id, cluster_device_id, inter_resistance, current, day_time from ems_battery_data_day @@ -74,6 +76,7 @@ device_id, cluster_device_id, inter_resistance, + current, day_time, @@ -118,6 +121,7 @@ device_id = #{deviceId}, cluster_device_id = #{clusterDeviceId}, inter_resistance = #{interResistance}, + current = #{current}, day_time = #{dayTime}, where id = #{id} @@ -164,6 +168,7 @@ device_id, cluster_device_id, inter_resistance, + current, day_time ) VALUES @@ -184,6 +189,7 @@ #{item.deviceId}, #{item.clusterDeviceId}, #{item.interResistance}, + #{item.current}, #{item.dayTime} ) @@ -195,6 +201,7 @@ soc = IF(VALUES(temperature) > temperature, VALUES(soc), soc), soh = IF(VALUES(temperature) > temperature, VALUES(soh), soh), inter_resistance = IF(VALUES(temperature) > temperature, VALUES(inter_resistance), inter_resistance), + current = IF(VALUES(temperature) > temperature, VALUES(current), current), update_by = IF(VALUES(temperature) > temperature, VALUES(update_by), update_by), temperature = IF(VALUES(temperature) > temperature, VALUES(temperature), temperature) diff --git a/ems-system/src/main/resources/mapper/ems/EmsPcsDataMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsPcsDataMapper.xml index 1d064cb..228acc2 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsPcsDataMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsPcsDataMapper.xml @@ -64,10 +64,22 @@ + + + + - select id, data_update_time, work_status, grid_status, device_status, control_mode, total_active_power, daily_ac_charge_energy, a_phase_voltage, a_phase_current, total_reactive_power, daily_ac_discharge_energy, b_phase_voltage, b_phase_current, total_apparent_power, pcs_module_temperature, c_phase_voltage, c_phase_current, total_power_factor, pcs_environment_temperature, ac_frequency, branch_status, discharge_status, dc_power, dc_voltage, dc_current, create_by, create_time, update_by, update_time, remark, site_id, device_id, date_month, date_day, total_ac_charge_energy, total_ac_discharge_energy, ac_charge_active_power, ac_capacitive_reactive_power, ac_discharge_active_power, ac_inductive_reactive_power, max_capacitive_power_capacity, max_inductive_power_capacity, max_charge_power_capacity, max_discharge_power_capacity, ac_switch_status, dc_switch_status, remote_control_status, sys_u_current, sys_v_current, sys_w_current, dw_frequency, u_temperature, v_temperature, w_temperature, module1_temp, module2_temp, module3_temp, module4_temp from ems_pcs_data + select id, data_update_time, work_status, grid_status, device_status, control_mode, total_active_power, daily_ac_charge_energy, + a_phase_voltage, a_phase_current, total_reactive_power, daily_ac_discharge_energy, b_phase_voltage, b_phase_current, + total_apparent_power, pcs_module_temperature, c_phase_voltage, c_phase_current, total_power_factor, pcs_environment_temperature, + ac_frequency, branch_status, discharge_status, dc_power, dc_voltage, dc_current, create_by, create_time, update_by, update_time, + remark, site_id, device_id, date_month, date_day, total_ac_charge_energy, total_ac_discharge_energy, ac_charge_active_power, + ac_capacitive_reactive_power, ac_discharge_active_power, ac_inductive_reactive_power, max_capacitive_power_capacity, + max_inductive_power_capacity, max_charge_power_capacity, max_discharge_power_capacity, ac_switch_status, dc_switch_status, + remote_control_status, sys_u_current, sys_v_current, sys_w_current, dw_frequency, u_temperature, v_temperature, w_temperature, + module1_temp, module2_temp, module3_temp, module4_temp, cluster1_active_power, cluster2_active_power, cluster3_active_power, cluster4_active_power from ems_pcs_data @@ -195,6 +211,10 @@ module2_temp, module3_temp, module4_temp, + cluster1_active_power, + cluster2_active_power, + cluster3_active_power, + cluster4_active_power, #{dataUpdateTime}, @@ -255,6 +275,10 @@ #{module2Temp}, #{module3Temp}, #{module4Temp}, + #{cluster1ActivePower}, + #{cluster2ActivePower}, + #{cluster3ActivePower}, + #{cluster4ActivePower}, @@ -319,6 +343,10 @@ module2_temp = #{module2Temp}, module3_temp = #{module3Temp}, module4_temp = #{module4Temp}, + cluster1_active_power = #{cluster1ActivePower}, + cluster2_active_power = #{cluster2ActivePower}, + cluster3_active_power = #{cluster3ActivePower}, + cluster4_active_power = #{cluster4ActivePower}, where id = #{id} diff --git a/ems-system/src/main/resources/mapper/ems/EmsPointMatchMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsPointMatchMapper.xml index 682d2ad..a620dcc 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsPointMatchMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsPointMatchMapper.xml @@ -541,12 +541,15 @@ and device_id = #{deviceId} and device_category = #{deviceCategory} - where site_id = #{request.siteId} and device_id = #{request.deviceId} and device_category = #{request.deviceCategory} - and match_field = 'device_status' + and match_field in + + #{matchField} + \ No newline at end of file diff --git a/pom.xml b/pom.xml index ad983de..5679ee0 100644 --- a/pom.xml +++ b/pom.xml @@ -171,6 +171,12 @@ j2mod 3.1.0 + + + com.infiniteautomation + modbus4j + 3.0.3 + io.github.resilience4j