集成modbus连接本地设备读取数据代码-配置文件方式读取;
PCS开关机功能通过modbus连接设备发送控制命令;
This commit is contained in:
@ -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:
|
||||
|
||||
@ -194,6 +194,8 @@ mqtt:
|
||||
connection-timeout: 15
|
||||
keep-alive-interval: 30
|
||||
automatic-reconnect: true
|
||||
topic:
|
||||
siteId:
|
||||
|
||||
modbus:
|
||||
pool:
|
||||
|
||||
@ -196,6 +196,8 @@ mqtt:
|
||||
connection-timeout: 15
|
||||
keep-alive-interval: 30
|
||||
automatic-reconnect: true
|
||||
topic:
|
||||
siteId:
|
||||
|
||||
modbus:
|
||||
pool:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<!-- 日志存放路径 -->
|
||||
<property name="log.path" value="../logs" />
|
||||
<property name="log.path" value="/etc/xzzn/logs" />
|
||||
<!-- 日志输出格式 -->
|
||||
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
|
||||
|
||||
|
||||
@ -119,6 +119,12 @@
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- modbus -->
|
||||
<dependency>
|
||||
<groupId>com.infiniteautomation</groupId>
|
||||
<artifactId>modbus4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -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<String, GenericObjectPool<ModbusMaster>> connectionPools = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
public ModbusMaster borrowMaster(DeviceConfig config) throws Exception {
|
||||
String poolKey = getPoolKey(config);
|
||||
GenericObjectPool<ModbusMaster> pool = connectionPools.computeIfAbsent(poolKey, key -> {
|
||||
PooledModbusMasterFactory factory = new PooledModbusMasterFactory(config.getHost(), config.getPort());
|
||||
GenericObjectPoolConfig<ModbusMaster> 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<ModbusMaster> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<WriteTagConfig> tags) {
|
||||
tags.forEach(tag -> {
|
||||
Map<Integer, RegisterType> 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<Boolean> 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<Number> 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<String, Object> readDataFromDevice(DeviceConfig config, ModbusMaster master) {
|
||||
Map<String, Object> deviceData = new HashMap<>();
|
||||
// ModbusMaster master = null; // 将master的声明提前
|
||||
try {
|
||||
master = connectionManager.borrowMaster(config);
|
||||
// 设置了Modbus通信的超时时间为3000毫秒(3秒)。当主设备与从设备通信时,若在3秒内未收到响应,则认为通信超时并抛出异常。这有助于避免长时间等待无响应的设备。
|
||||
master.setTimeout(5000);
|
||||
BatchResults<String> 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<Integer, RegisterType> type = ModBusType.REGISTER_TYPE;
|
||||
Map<String, Integer> 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<Boolean> loc = BaseLocator.coilStatus(slaveId, address);
|
||||
value = master.getValue(loc);
|
||||
logger.info("读取线圈{}成功: {}",tag.getAddress(), value);
|
||||
break;
|
||||
}
|
||||
case DISCRETE_INPUT: {
|
||||
BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, address);
|
||||
value = master.getValue(loc);
|
||||
logger.info("读取输入{}成功: {}",tag.getAddress(), value);
|
||||
break;
|
||||
}
|
||||
case HOLDING_REGISTER: {
|
||||
if (dataLength == 28){
|
||||
BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, address, 4);
|
||||
value = master.getValue(locator);
|
||||
//将 value 转换为二进制数据
|
||||
value = convertValueToFloat(value);
|
||||
}else{
|
||||
BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, address, dataLength);
|
||||
value = master.getValue(locator);
|
||||
}
|
||||
logger.info("读取保持寄存器{}成功: {}",tag.getAddress(), value);
|
||||
break;
|
||||
}
|
||||
case INPUT_REGISTER: {
|
||||
BaseLocator<Number> 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<String> readTagValues(ModbusMaster master, int slaveId, List<TagConfig> tags) throws Exception {
|
||||
try {
|
||||
BatchRead<String> batch = new BatchRead<>();
|
||||
tags.forEach(tag -> {
|
||||
Map<Integer, RegisterType> type = ModBusType.REGISTER_TYPE;
|
||||
Map<String, Integer> 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<Boolean> loc = BaseLocator.coilStatus(slaveId, address);
|
||||
batch.addLocator(tag.getKey(), loc);
|
||||
break;
|
||||
}
|
||||
case DISCRETE_INPUT: {
|
||||
BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, address);
|
||||
batch.addLocator(tag.getKey(), loc);
|
||||
break;
|
||||
}
|
||||
case HOLDING_REGISTER: {
|
||||
logger.info("HOLDING_REGISTER: {}",tag.getAddress());
|
||||
if (dataLength == 28){
|
||||
BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, address, 4);
|
||||
batch.addLocator(tag.getKey(), locator);
|
||||
}else {
|
||||
BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, address, dataLength);
|
||||
batch.addLocator(tag.getKey(), loc);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case INPUT_REGISTER: {
|
||||
logger.info("INPUT_REGISTER: {}",tag.getAddress());
|
||||
BaseLocator<Number> loc = BaseLocator.inputRegister(slaveId, address, dataLength);
|
||||
batch.addLocator(tag.getKey(), loc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BatchResults<String> 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()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<ModbusMaster> {
|
||||
|
||||
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<ModbusMaster> wrap(ModbusMaster master) {
|
||||
return new DefaultPooledObject<>(master);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyObject(PooledObject<ModbusMaster> p) throws Exception {
|
||||
if (p.getObject() != null) {
|
||||
p.getObject().destroy();
|
||||
}
|
||||
super.destroyObject(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateObject(PooledObject<ModbusMaster> p) {
|
||||
// testConnectivity() 方法在 modbus4j 中不存在。
|
||||
// 一个真正的验证需要执行一次读操作,但这需要 slaveId,而工厂是通用的,不知道 slaveId。
|
||||
// 因此,我们使用一个较弱的验证,只检查 master 对象是否已初始化。
|
||||
// 这无法检测到被远程设备断开的连接,错误将在实际读取操作中被捕获。
|
||||
if (p.getObject() == null) {
|
||||
return false;
|
||||
}
|
||||
return p.getObject().isInitialized();
|
||||
}
|
||||
}
|
||||
@ -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<TagConfig> tags;
|
||||
private List<WriteTagConfig> 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<TagConfig> getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
public void setTags(List<TagConfig> tags) {
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
public List<WriteTagConfig> getWriteTags() {
|
||||
return writeTags;
|
||||
}
|
||||
|
||||
public void setWriteTags(List<WriteTagConfig> writeTags) {
|
||||
this.writeTags = writeTags;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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<Integer, RegisterType> REGISTER_TYPE = new HashMap<Integer, RegisterType>();
|
||||
public static final Map<String, Integer> LENGTH = new HashMap<String, Integer>();
|
||||
|
||||
|
||||
|
||||
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);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.xzzn.common.enums;
|
||||
|
||||
public enum RegisterType {
|
||||
COIL,
|
||||
DISCRETE_INPUT,
|
||||
HOLDING_REGISTER,
|
||||
INPUT_REGISTER
|
||||
}
|
||||
@ -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<String, ScheduledFuture<?>> 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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<EmsDevicesSetting> 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());
|
||||
Path devicesDir = Paths.get(System.getProperty("user.dir"), "devices");
|
||||
if (!Files.exists(devicesDir)) {
|
||||
log.error("Devices目录不存在: {}", devicesDir);
|
||||
return;
|
||||
}
|
||||
// 读取保持寄存器
|
||||
int[] data = modbusService.readHoldingRegisters(
|
||||
wrapper.getConnection(),
|
||||
1, //从站ID
|
||||
10 // 寄存器数量
|
||||
);
|
||||
|
||||
// 处理读取到的数据
|
||||
processData(device, data);
|
||||
List<Path> 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;
|
||||
}
|
||||
|
||||
// 按 host:port 分组
|
||||
Map<String, List<DeviceConfig>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个 host:port 启动一个任务
|
||||
for (Map.Entry<String, List<DeviceConfig>> entry : groupedConfigs.entrySet()) {
|
||||
String groupKey = entry.getKey();
|
||||
List<DeviceConfig> configs = entry.getValue();
|
||||
// 取其中一个配置的时间间隔作为该组任务的执行周期
|
||||
long interval = configs.get(0).getTime();
|
||||
scheduledTask.startTask(groupKey, () -> {
|
||||
for (DeviceConfig config : configs) {
|
||||
try {
|
||||
scheduledStart(config, null);
|
||||
} catch (Exception e) {
|
||||
logger.error("轮询设备{}失败: {}", device.getId(), e.getMessage());
|
||||
// 标记连接为无效
|
||||
if (wrapper != null) {
|
||||
wrapper.close();
|
||||
connectionManager.removeConnection(Integer.parseInt(device.getDeviceId()));
|
||||
log.error("采集设备数据异常: {}", config.getDeviceName(), e);
|
||||
}
|
||||
|
||||
// 设备轮询不到修改运行状态
|
||||
String beforeStatus = device.getDeviceStatus();
|
||||
device.setDeviceStatus(DeviceRunningStatus.SHUTDOWN.getCode());
|
||||
emsDeviceSettingServiceImpl.updateDevice(device);
|
||||
|
||||
// 轮询设备,设备状态变更日志
|
||||
EmsDeviceChangeLog log = createLogEntity(beforeStatus,device);
|
||||
emsDeviceChangeLogMapper.insertEmsDeviceChangeLog(log);
|
||||
|
||||
throw new RuntimeException("轮询设备失败", e);
|
||||
}
|
||||
}, interval);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理获取到的数据
|
||||
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;
|
||||
public void scheduledStart(DeviceConfig config, ModbusMaster master) {
|
||||
try {
|
||||
if (config.isEnabled()) {
|
||||
log.info("Reading data from devices: {}", config.getDeviceName());
|
||||
Map<String, Object> 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 {
|
||||
// 恢复设备状态 - 运行
|
||||
device.setDeviceStatus(DeviceRunningStatus.RUNNING.getCode());
|
||||
log.error("tag:{},无法将数据转换为数字: {}", tag.getKey(), rawValue);
|
||||
}
|
||||
emsDeviceSettingServiceImpl.updateDevice(device);
|
||||
// 轮询设备,设备状态变更日志
|
||||
EmsDeviceChangeLog log = createLogEntity(beforeStatus,device);
|
||||
emsDeviceChangeLogMapper.insertEmsDeviceChangeLog(log);
|
||||
// 错误数据-不处理直接返回
|
||||
if (!error) {
|
||||
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<String, Object> data, String deviceNumber) {
|
||||
if (data == null || data.size() == 0) {
|
||||
// 添加设备告警
|
||||
iEmsAlarmRecordsService.addEmptyDataAlarmRecord(siteId, deviceNumber);
|
||||
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(" ");
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}*/
|
||||
|
||||
// 测试发送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);*/
|
||||
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());
|
||||
}
|
||||
|
||||
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 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<String, Object> data, String deviceNumber, Long timestamp) {
|
||||
deviceDataProcessServiceImpl.processingDeviceData(siteId, deviceNumber, JSON.toJSONString(data), DateUtils.convertUpdateTime(timestamp));
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<String> matchFields;
|
||||
|
||||
|
||||
public String getSiteId() {
|
||||
return siteId;
|
||||
}
|
||||
@ -53,4 +59,12 @@ public class DeviceUpdateRequest {
|
||||
public void setDeviceCategory(String deviceCategory) {
|
||||
this.deviceCategory = deviceCategory;
|
||||
}
|
||||
|
||||
public List<String> getMatchFields() {
|
||||
return matchFields;
|
||||
}
|
||||
|
||||
public void setMatchFields(List<String> matchFields) {
|
||||
this.matchFields = matchFields;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<EmsPointMatch> selectDeviceStatusPoint(@Param("request") DeviceUpdateRequest request);
|
||||
}
|
||||
|
||||
@ -161,6 +161,12 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
|
||||
// 存放每次同步数据,失效时间(同同步时间)-用于判断是否正常同步数据
|
||||
redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceId, obj, 1, TimeUnit.MINUTES);
|
||||
|
||||
// 处理设备数据
|
||||
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);
|
||||
@ -188,7 +194,6 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
|
||||
emsDataProcess(siteId, deviceId, jsonData, dataUpdateTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void emsDataProcess(String siteId, String deviceId, String jsonData, Date dataUpdateTime) {
|
||||
//EMS
|
||||
@ -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<String, Object> obj = JSON.parseObject(dataJson, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
// 点位匹配数据
|
||||
List<EmsPointMatch> 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<String, List<EmsPointEnumMatch>> pointEnumMatchMap = devicePointMatchDataProcessor.getPointEnumMatchMap(siteId, DeviceMatchTable.XF.getCode());
|
||||
@ -1415,7 +1420,16 @@ 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 = processingDeviceAlarmData(siteId, deviceId, jsonData, dataUpdateTime);
|
||||
if (StringUtils.isEmpty(deviceCategory)) {
|
||||
// 处理告警信息
|
||||
alarmDataProcess(siteId, deviceId, jsonData, alarmMatchInfo, deviceCategory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String processingDeviceAlarmData(String siteId, String deviceId, String jsonData, Date dataUpdateTime) {
|
||||
String deviceCategory = "";
|
||||
if (deviceId.contains(SiteDevice.ZSLQ.name())) {
|
||||
coolingAlarmDataProcess(siteId, deviceId, jsonData, dataUpdateTime);
|
||||
@ -1429,11 +1443,8 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
|
||||
deviceCategory = DeviceCategory.PCS.getCode();
|
||||
pcsAlarmDataProcess(siteId, deviceId, jsonData, dataUpdateTime);
|
||||
}
|
||||
if (StringUtils.isEmpty(deviceCategory)) {
|
||||
// 处理告警信息
|
||||
alarmDataProcess(siteId, deviceId, jsonData, alarmMatchInfo, deviceCategory);
|
||||
}
|
||||
}
|
||||
|
||||
return deviceCategory;
|
||||
}
|
||||
|
||||
private void pcsAlarmDataProcess(String siteId, String deviceId, String jsonData, Date dataUpdateTime) {
|
||||
|
||||
@ -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<String> 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<EmsPointMatch> 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<WriteTagConfig> getWriteTags(EmsDevicesSetting device, List<EmsPointMatch> pointMatchList, PcsControlCommand command) {
|
||||
List<WriteTagConfig> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,11 +23,12 @@
|
||||
<result property="deviceId" column="device_id" />
|
||||
<result property="clusterDeviceId" column="cluster_device_id" />
|
||||
<result property="interResistance" column="inter_resistance" />
|
||||
<result property="current" column="current" />
|
||||
<result property="dayTime" column="day_time" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectEmsBatteryDataDayVo">
|
||||
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
|
||||
</sql>
|
||||
|
||||
<select id="selectEmsBatteryDataDayList" parameterType="EmsBatteryDataDay" resultMap="EmsBatteryDataDayResult">
|
||||
@ -45,6 +46,7 @@
|
||||
<if test="deviceId != null and deviceId != ''"> and device_id = #{deviceId}</if>
|
||||
<if test="clusterDeviceId != null and clusterDeviceId != ''"> and cluster_device_id = #{clusterDeviceId}</if>
|
||||
<if test="interResistance != null "> and inter_resistance = #{interResistance}</if>
|
||||
<if test="current != null "> and current = #{current}</if>
|
||||
<if test="dayTime != null "> and day_time = #{dayTime}</if>
|
||||
</where>
|
||||
</select>
|
||||
@ -74,6 +76,7 @@
|
||||
<if test="deviceId != null">device_id,</if>
|
||||
<if test="clusterDeviceId != null">cluster_device_id,</if>
|
||||
<if test="interResistance != null">inter_resistance,</if>
|
||||
<if test="current != null">current,</if>
|
||||
<if test="dayTime != null">day_time,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
@ -118,6 +121,7 @@
|
||||
<if test="deviceId != null">device_id = #{deviceId},</if>
|
||||
<if test="clusterDeviceId != null">cluster_device_id = #{clusterDeviceId},</if>
|
||||
<if test="interResistance != null">inter_resistance = #{interResistance},</if>
|
||||
<if test="current != null">current = #{current},</if>
|
||||
<if test="dayTime != null">day_time = #{dayTime},</if>
|
||||
</trim>
|
||||
where id = #{id}
|
||||
@ -164,6 +168,7 @@
|
||||
device_id,
|
||||
cluster_device_id,
|
||||
inter_resistance,
|
||||
current,
|
||||
day_time
|
||||
) VALUES
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
@ -184,6 +189,7 @@
|
||||
#{item.deviceId},
|
||||
#{item.clusterDeviceId},
|
||||
#{item.interResistance},
|
||||
#{item.current},
|
||||
#{item.dayTime}
|
||||
)
|
||||
</foreach>
|
||||
@ -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)
|
||||
</insert>
|
||||
|
||||
@ -64,10 +64,22 @@
|
||||
<result property="module2Temp" column="module2_temp" />
|
||||
<result property="module3Temp" column="module3_temp" />
|
||||
<result property="module4Temp" column="module4_temp" />
|
||||
<result property="cluster1ActivePower" column="cluster1_active_power" />
|
||||
<result property="cluster2ActivePower" column="cluster2_active_power" />
|
||||
<result property="cluster3ActivePower" column="cluster3_active_power" />
|
||||
<result property="cluster4ActivePower" column="cluster4_active_power" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectEmsPcsDataVo">
|
||||
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
|
||||
</sql>
|
||||
|
||||
<select id="selectEmsPcsDataList" parameterType="EmsPcsData" resultMap="EmsPcsDataResult">
|
||||
@ -126,6 +138,10 @@
|
||||
<if test="module2Temp != null "> and module2_temp = #{module2Temp}</if>
|
||||
<if test="module3Temp != null "> and module3_temp = #{module3Temp}</if>
|
||||
<if test="module4Temp != null "> and module4_temp = #{module4Temp}</if>
|
||||
<if test="cluster1ActivePower != null "> and cluster1_active_power = #{cluster1ActivePower}</if>
|
||||
<if test="cluster2ActivePower != null "> and cluster2_active_power = #{cluster2ActivePower}</if>
|
||||
<if test="cluster3ActivePower != null "> and cluster3_active_power = #{cluster3ActivePower}</if>
|
||||
<if test="cluster4ActivePower != null "> and cluster4_active_power = #{cluster4ActivePower}</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
@ -195,6 +211,10 @@
|
||||
<if test="module2Temp != null">module2_temp,</if>
|
||||
<if test="module3Temp != null">module3_temp,</if>
|
||||
<if test="module4Temp != null">module4_temp,</if>
|
||||
<if test="cluster1ActivePower != null">cluster1_active_power,</if>
|
||||
<if test="cluster2ActivePower != null">cluster2_active_power,</if>
|
||||
<if test="cluster3ActivePower != null">cluster3_active_power,</if>
|
||||
<if test="cluster4ActivePower != null">cluster4_active_power,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="dataUpdateTime != null">#{dataUpdateTime},</if>
|
||||
@ -255,6 +275,10 @@
|
||||
<if test="module2Temp != null">#{module2Temp},</if>
|
||||
<if test="module3Temp != null">#{module3Temp},</if>
|
||||
<if test="module4Temp != null">#{module4Temp},</if>
|
||||
<if test="cluster1ActivePower != null">#{cluster1ActivePower},</if>
|
||||
<if test="cluster2ActivePower != null">#{cluster2ActivePower},</if>
|
||||
<if test="cluster3ActivePower != null">#{cluster3ActivePower},</if>
|
||||
<if test="cluster4ActivePower != null">#{cluster4ActivePower},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
@ -319,6 +343,10 @@
|
||||
<if test="module2Temp != null">module2_temp = #{module2Temp},</if>
|
||||
<if test="module3Temp != null">module3_temp = #{module3Temp},</if>
|
||||
<if test="module4Temp != null">module4_temp = #{module4Temp},</if>
|
||||
<if test="cluster1ActivePower != null">cluster1_active_power = #{cluster1ActivePower},</if>
|
||||
<if test="cluster2ActivePower != null">cluster2_active_power = #{cluster2ActivePower},</if>
|
||||
<if test="cluster3ActivePower != null">cluster3_active_power = #{cluster3ActivePower},</if>
|
||||
<if test="cluster4ActivePower != null">cluster4_active_power = #{cluster4ActivePower},</if>
|
||||
</trim>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
@ -541,12 +541,15 @@
|
||||
and device_id = #{deviceId}
|
||||
and device_category = #{deviceCategory}
|
||||
</select>
|
||||
<select id="selectDeviceStatusPoint" resultType="EmsPointMatch" parameterType="com.xzzn.ems.domain.vo.DeviceUpdateRequest">
|
||||
<select id="selectDeviceStatusPoint" parameterType="com.xzzn.ems.domain.vo.DeviceUpdateRequest" resultMap="EmsPointMatchResult" >
|
||||
<include refid="selectEmsPointMatchVo"/>
|
||||
where site_id = #{request.siteId}
|
||||
and device_id = #{request.deviceId}
|
||||
and device_category = #{request.deviceCategory}
|
||||
and match_field = 'device_status'
|
||||
and match_field in
|
||||
<foreach collection="list" item="matchField" open="(" separator="," close=")">
|
||||
#{matchField}
|
||||
</foreach>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
6
pom.xml
6
pom.xml
@ -171,6 +171,12 @@
|
||||
<artifactId>j2mod</artifactId>
|
||||
<version>3.1.0</version> <!-- 提供TCPMasterConnection、SerialConnection等类 -->
|
||||
</dependency>
|
||||
<!-- modbus4j -->
|
||||
<dependency>
|
||||
<groupId>com.infiniteautomation</groupId>
|
||||
<artifactId>modbus4j</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</dependency>
|
||||
<!-- Resilience4j 断路器核心 -->
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
|
||||
Reference in New Issue
Block a user