告警保护方案轮询-初稿
This commit is contained in:
@ -63,7 +63,7 @@ public class RedisKeyConstants
|
|||||||
public static final String COOLING = "COOLING_";
|
public static final String COOLING = "COOLING_";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 存放单个设备同步过来的原始数据
|
* 存放单个设备同步过来的原始数据-最晚一次数据
|
||||||
*/
|
*/
|
||||||
public static final String ORIGINAL_MQTT_DATA = "MQTT_";
|
public static final String ORIGINAL_MQTT_DATA = "MQTT_";
|
||||||
|
|
||||||
@ -87,4 +87,7 @@ public class RedisKeyConstants
|
|||||||
public static final String DDS_TOTAL_REVENUE = "total_revenue_";
|
public static final String DDS_TOTAL_REVENUE = "total_revenue_";
|
||||||
/** fx实时总收益和当日实时收益 */
|
/** fx实时总收益和当日实时收益 */
|
||||||
public static final String FXX_REALTIME_REVENUE = "realtime_revenue_";
|
public static final String FXX_REALTIME_REVENUE = "realtime_revenue_";
|
||||||
|
|
||||||
|
/** 每个设备最新数据-设置失效时间-判断是否正常同步数据 */
|
||||||
|
public static final String SYNC_DATA= "SYNC_DATA_";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -95,7 +95,7 @@ public class ModbusConnectionManager implements ApplicationRunner {
|
|||||||
*/
|
*/
|
||||||
private TCPMasterConnection createRawConnection(EmsDevicesSetting device) throws Exception {
|
private TCPMasterConnection createRawConnection(EmsDevicesSetting device) throws Exception {
|
||||||
try {
|
try {
|
||||||
InetAddress addr = InetAddress.getByName("192.168.80.100");
|
InetAddress addr = InetAddress.getByName("10.1.0.230");
|
||||||
TCPMasterConnection connection = new TCPMasterConnection(addr);
|
TCPMasterConnection connection = new TCPMasterConnection(addr);
|
||||||
connection.setPort(502);
|
connection.setPort(502);
|
||||||
connection.setTimeout(5000);
|
connection.setTimeout(5000);
|
||||||
|
|||||||
@ -2,11 +2,16 @@ package com.xzzn.framework.web.service;
|
|||||||
|
|
||||||
import com.ghgande.j2mod.modbus.ModbusException;
|
import com.ghgande.j2mod.modbus.ModbusException;
|
||||||
import com.ghgande.j2mod.modbus.ModbusIOException;
|
import com.ghgande.j2mod.modbus.ModbusIOException;
|
||||||
|
import com.ghgande.j2mod.modbus.io.ModbusSerialTransaction;
|
||||||
import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction;
|
import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction;
|
||||||
import com.ghgande.j2mod.modbus.msg.ReadInputRegistersRequest;
|
import com.ghgande.j2mod.modbus.msg.ReadInputRegistersRequest;
|
||||||
import com.ghgande.j2mod.modbus.msg.ReadInputRegistersResponse;
|
import com.ghgande.j2mod.modbus.msg.ReadInputRegistersResponse;
|
||||||
|
import com.ghgande.j2mod.modbus.msg.WriteMultipleRegistersRequest;
|
||||||
|
import com.ghgande.j2mod.modbus.msg.WriteSingleRegisterRequest;
|
||||||
import com.ghgande.j2mod.modbus.net.SerialConnection;
|
import com.ghgande.j2mod.modbus.net.SerialConnection;
|
||||||
import com.ghgande.j2mod.modbus.net.TCPMasterConnection;
|
import com.ghgande.j2mod.modbus.net.TCPMasterConnection;
|
||||||
|
import com.ghgande.j2mod.modbus.procimg.Register;
|
||||||
|
import com.ghgande.j2mod.modbus.procimg.SimpleRegister;
|
||||||
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
|
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -42,8 +47,26 @@ public class ModbusService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int[] readRtuRegisters(SerialConnection connection, int startAddr, int count) {
|
private int[] readRtuRegisters(SerialConnection connection, int startAddr, int count) throws ModbusException {
|
||||||
return null;
|
if (!connection.isOpen()) {
|
||||||
|
throw new ModbusIOException("RTU连接未建立");
|
||||||
|
}
|
||||||
|
ReadInputRegistersRequest request = new ReadInputRegistersRequest(startAddr, count);
|
||||||
|
ModbusSerialTransaction transaction = new ModbusSerialTransaction(connection);
|
||||||
|
transaction.setRequest(request);
|
||||||
|
transaction.setRetries(2);
|
||||||
|
|
||||||
|
try {
|
||||||
|
transaction.execute();
|
||||||
|
ReadInputRegistersResponse response = (ReadInputRegistersResponse) transaction.getResponse();
|
||||||
|
if (response == null) {
|
||||||
|
throw new ModbusException("RTU响应为空");
|
||||||
|
}
|
||||||
|
return parseRegisters(response);
|
||||||
|
} catch (ModbusException e) {
|
||||||
|
logger.error("读取RTU寄存器失败: {}", e.getMessage());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int[] readTcpRegisters(TCPMasterConnection conn, int start, int count) throws ModbusException {
|
private int[] readTcpRegisters(TCPMasterConnection conn, int start, int count) throws ModbusException {
|
||||||
@ -98,4 +121,159 @@ public class ModbusService {
|
|||||||
return new int[0];
|
return new int[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入单个寄存器(支持TCP/RTU)
|
||||||
|
* @param connection 连接对象(TCPMasterConnection 或 SerialConnection)
|
||||||
|
* @param registerAddr 寄存器地址
|
||||||
|
* @param value 要写入的值(16位整数)
|
||||||
|
*/
|
||||||
|
@Retryable(
|
||||||
|
value = {ModbusException.class},
|
||||||
|
maxAttempts = 3,
|
||||||
|
backoff = @Backoff(delay = 1000, multiplier = 2)
|
||||||
|
)
|
||||||
|
@CircuitBreaker(name = "modbusOperation", fallbackMethod = "writeRegisterFallback")
|
||||||
|
public boolean writeSingleRegister(Object connection, int registerAddr, int value) throws ModbusException {
|
||||||
|
try {
|
||||||
|
if (connection instanceof TCPMasterConnection) {
|
||||||
|
return writeTcpSingleRegister((TCPMasterConnection) connection, registerAddr, value);
|
||||||
|
} else if (connection instanceof SerialConnection) {
|
||||||
|
return writeRtuSingleRegister((SerialConnection) connection, registerAddr, value);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("不支持的连接类型: " + connection.getClass().getName());
|
||||||
|
} catch (ModbusIOException e) {
|
||||||
|
throw new ModbusException("写入通信故障", e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ModbusException("写入系统错误", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入多个寄存器(支持TCP/RTU)
|
||||||
|
* @param connection 连接对象
|
||||||
|
* @param startAddr 起始寄存器地址
|
||||||
|
* @param values 要写入的值数组(每个值为16位整数)
|
||||||
|
*/
|
||||||
|
@Retryable(
|
||||||
|
value = {ModbusException.class},
|
||||||
|
maxAttempts = 3,
|
||||||
|
backoff = @Backoff(delay = 1000, multiplier = 2)
|
||||||
|
)
|
||||||
|
@CircuitBreaker(name = "modbusOperation", fallbackMethod = "writeRegisterFallback")
|
||||||
|
public boolean writeMultipleRegisters(Object connection, int startAddr, int[] values) throws ModbusException {
|
||||||
|
try {
|
||||||
|
if (connection instanceof TCPMasterConnection) {
|
||||||
|
return writeTcpMultipleRegisters((TCPMasterConnection) connection, startAddr, values);
|
||||||
|
} else if (connection instanceof SerialConnection) {
|
||||||
|
return writeRtuMultipleRegisters((SerialConnection) connection, startAddr, values);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("不支持的连接类型: " + connection.getClass().getName());
|
||||||
|
} catch (ModbusIOException e) {
|
||||||
|
throw new ModbusException("写入通信故障", e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ModbusException("写入系统错误", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== TCP写入实现 ====================
|
||||||
|
private boolean writeTcpSingleRegister(TCPMasterConnection conn, int registerAddr, int value) throws ModbusException {
|
||||||
|
if (!conn.isConnected()) {
|
||||||
|
throw new ModbusIOException("TCP连接未建立,无法写入");
|
||||||
|
}
|
||||||
|
// 创建写入单个寄存器的请求(功能码06)
|
||||||
|
WriteSingleRegisterRequest request = new WriteSingleRegisterRequest(registerAddr, new SimpleRegister(value));
|
||||||
|
ModbusTCPTransaction transaction = new ModbusTCPTransaction(conn);
|
||||||
|
transaction.setRequest(request);
|
||||||
|
transaction.setRetries(2);
|
||||||
|
|
||||||
|
try {
|
||||||
|
transaction.execute();
|
||||||
|
logger.info("TCP写入单个寄存器成功,地址:{},值:{}", registerAddr, value);
|
||||||
|
return true;
|
||||||
|
} catch (ModbusException e) {
|
||||||
|
logger.error("TCP写入单个寄存器失败,地址:{},值:{}", registerAddr, value, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean writeTcpMultipleRegisters(TCPMasterConnection conn, int startAddr, int[] values) throws ModbusException {
|
||||||
|
if (!conn.isConnected()) {
|
||||||
|
throw new ModbusIOException("TCP连接未建立,无法写入");
|
||||||
|
}
|
||||||
|
// 转换值数组为寄存器数组
|
||||||
|
Register[] registers = new Register[values.length];
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
registers[i] = new SimpleRegister(values[i]);
|
||||||
|
}
|
||||||
|
// 创建写入多个寄存器的请求(功能码16)
|
||||||
|
WriteMultipleRegistersRequest request = new WriteMultipleRegistersRequest(startAddr, registers);
|
||||||
|
ModbusTCPTransaction transaction = new ModbusTCPTransaction(conn);
|
||||||
|
transaction.setRequest(request);
|
||||||
|
transaction.setRetries(2);
|
||||||
|
|
||||||
|
try {
|
||||||
|
transaction.execute();
|
||||||
|
logger.info("TCP写入多个寄存器成功,起始地址:{},数量:{}", startAddr, values.length);
|
||||||
|
return true;
|
||||||
|
} catch (ModbusException e) {
|
||||||
|
logger.error("TCP写入多个寄存器失败,起始地址:{}", startAddr, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ==================== RTU写入实现 ====================
|
||||||
|
private boolean writeRtuSingleRegister(SerialConnection connection, int registerAddr, int value) throws ModbusException {
|
||||||
|
if (!connection.isOpen()) {
|
||||||
|
throw new ModbusIOException("RTU串口未打开,请先建立连接");
|
||||||
|
}
|
||||||
|
WriteSingleRegisterRequest request = new WriteSingleRegisterRequest(registerAddr, new SimpleRegister(value));
|
||||||
|
ModbusSerialTransaction transaction = new ModbusSerialTransaction(connection);
|
||||||
|
transaction.setRequest(request);
|
||||||
|
transaction.setRetries(2);
|
||||||
|
|
||||||
|
try {
|
||||||
|
transaction.execute();
|
||||||
|
logger.info("RTU写入单个寄存器成功,地址:{},值:{}", registerAddr, value);
|
||||||
|
return true;
|
||||||
|
} catch (ModbusException e) {
|
||||||
|
logger.error("RTU写入单个寄存器失败,地址:{},值:{}", registerAddr, value, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean writeRtuMultipleRegisters(SerialConnection connection, int startAddr, int[] values) throws ModbusException {
|
||||||
|
if (!connection.isOpen()) {
|
||||||
|
throw new ModbusIOException("RTU串口未打开,请先建立连接");
|
||||||
|
}
|
||||||
|
Register[] registers = new Register[values.length];
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
registers[i] = new SimpleRegister(values[i]);
|
||||||
|
}
|
||||||
|
WriteMultipleRegistersRequest request = new WriteMultipleRegistersRequest(startAddr, registers);
|
||||||
|
ModbusSerialTransaction transaction = new ModbusSerialTransaction(connection);
|
||||||
|
transaction.setRequest(request);
|
||||||
|
transaction.setRetries(2);
|
||||||
|
|
||||||
|
try {
|
||||||
|
transaction.execute();
|
||||||
|
logger.info("RTU写入多个寄存器成功,起始地址:{},数量:{}", startAddr, values.length);
|
||||||
|
return true;
|
||||||
|
} catch (ModbusException e) {
|
||||||
|
logger.error("RTU写入多个寄存器失败,起始地址:{}", startAddr, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ==================== 写入操作的降级方法 ====================
|
||||||
|
public boolean writeRegisterFallback(Object connection, int addr, int value, Exception e) {
|
||||||
|
logger.warn("写入单个寄存器降级(原因: {}),地址:{}", e.getMessage(), addr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean writeRegisterFallback(Object connection, int startAddr, int[] values, Exception e) {
|
||||||
|
logger.warn("写入多个寄存器降级(原因: {}),起始地址:{}", e.getMessage(), startAddr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -71,6 +71,10 @@ public class ModbusPoller {
|
|||||||
// 获取连接
|
// 获取连接
|
||||||
wrapper = connectionManager.getConnection(device);
|
wrapper = connectionManager.getConnection(device);
|
||||||
|
|
||||||
|
if(wrapper == null || !wrapper.isActive()){
|
||||||
|
logger.error("轮询设备{}连接失败: {}", device.getId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 读取保持寄存器
|
// 读取保持寄存器
|
||||||
int[] data = modbusService.readHoldingRegisters(
|
int[] data = modbusService.readHoldingRegisters(
|
||||||
wrapper.getConnection(),
|
wrapper.getConnection(),
|
||||||
|
|||||||
@ -0,0 +1,416 @@
|
|||||||
|
package com.xzzn.quartz.task;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.xzzn.common.constant.RedisKeyConstants;
|
||||||
|
import com.xzzn.common.core.redis.RedisCache;
|
||||||
|
import com.xzzn.common.enums.AlarmLevelStatus;
|
||||||
|
import com.xzzn.common.enums.AlarmStatus;
|
||||||
|
import com.xzzn.common.enums.ProtPlanStatus;
|
||||||
|
import com.xzzn.common.enums.StrategyStatus;
|
||||||
|
import com.xzzn.common.utils.StringUtils;
|
||||||
|
import com.xzzn.ems.domain.EmsAlarmRecords;
|
||||||
|
import com.xzzn.ems.domain.EmsDevicesSetting;
|
||||||
|
import com.xzzn.ems.domain.EmsFaultProtectionPlan;
|
||||||
|
import com.xzzn.ems.domain.vo.ProtectionPlanVo;
|
||||||
|
import com.xzzn.ems.domain.vo.ProtectionSettingVo;
|
||||||
|
import com.xzzn.ems.mapper.EmsAlarmRecordsMapper;
|
||||||
|
import com.xzzn.ems.mapper.EmsDevicesSettingMapper;
|
||||||
|
import com.xzzn.ems.mapper.EmsFaultProtectionPlanMapper;
|
||||||
|
import com.xzzn.ems.mapper.EmsStrategyRunningMapper;
|
||||||
|
import com.xzzn.ems.service.IEmsFaultProtectionPlanService;
|
||||||
|
import com.xzzn.framework.manager.ModbusConnectionManager;
|
||||||
|
import com.xzzn.framework.manager.ModbusConnectionWrapper;
|
||||||
|
import com.xzzn.framework.web.service.ModbusService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 告警保护方案轮询
|
||||||
|
*
|
||||||
|
* @author xzzn
|
||||||
|
*/
|
||||||
|
@Component("protectionPlanTask")
|
||||||
|
public class ProtectionPlanTask {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ProtectionPlanTask.class);
|
||||||
|
@Resource(name = "scheduledExecutorService")
|
||||||
|
private ScheduledExecutorService scheduledExecutorService;
|
||||||
|
@Autowired
|
||||||
|
private IEmsFaultProtectionPlanService iEmsFaultProtectionPlanService;
|
||||||
|
@Autowired
|
||||||
|
private EmsAlarmRecordsMapper emsAlarmRecordsMapper;
|
||||||
|
@Autowired
|
||||||
|
private EmsStrategyRunningMapper emsStrategyRunningMapper;
|
||||||
|
@Autowired
|
||||||
|
private EmsFaultProtectionPlanMapper emsFaultProtectionPlanMapper;
|
||||||
|
@Autowired
|
||||||
|
private RedisCache redisCache;
|
||||||
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
@Autowired
|
||||||
|
private EmsDevicesSettingMapper emsDevicesSettingMapper;
|
||||||
|
@Autowired
|
||||||
|
private ModbusConnectionManager connectionManager;
|
||||||
|
@Autowired
|
||||||
|
private ModbusService modbusService;
|
||||||
|
|
||||||
|
public ProtectionPlanTask(IEmsFaultProtectionPlanService iEmsFaultProtectionPlanService) {
|
||||||
|
this.iEmsFaultProtectionPlanService = iEmsFaultProtectionPlanService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pollPlanList() {
|
||||||
|
Long planId = 0L;
|
||||||
|
try {
|
||||||
|
// 获取所有方案,轮询
|
||||||
|
List<EmsFaultProtectionPlan> planList = iEmsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(null);
|
||||||
|
|
||||||
|
for (EmsFaultProtectionPlan plan : planList) {
|
||||||
|
planId = plan.getId();
|
||||||
|
String siteId = plan.getSiteId();
|
||||||
|
if (StringUtils.isEmpty(siteId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 保护前提
|
||||||
|
String protectionSettings = plan.getProtectionSettings();
|
||||||
|
final List<ProtectionSettingVo> protSettings = objectMapper.readValue(
|
||||||
|
protectionSettings,
|
||||||
|
new TypeReference<List<ProtectionSettingVo>>() {}
|
||||||
|
);
|
||||||
|
if (protSettings == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理告警保护方案
|
||||||
|
boolean isHighLevel = dealWithProtectionPlan(plan, protSettings);
|
||||||
|
if (isHighLevel) {
|
||||||
|
// 触发最高故障等级-结束循环
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("轮询失败,方案id为:", planId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理告警保护方案-返回触发下发方案时是否最高等级
|
||||||
|
private boolean dealWithProtectionPlan(EmsFaultProtectionPlan plan, List<ProtectionSettingVo> protSettings) {
|
||||||
|
boolean isHighLevel = false;
|
||||||
|
|
||||||
|
String siteId = plan.getSiteId();
|
||||||
|
final Integer isAlertAlarm = plan.getIsAlert();
|
||||||
|
final Long status = plan.getStatus();
|
||||||
|
// 看方案是否启用,走不同判断
|
||||||
|
if (status == ProtPlanStatus.STOP.getCode()) {
|
||||||
|
// 未启用,获取方案的故障值与最新数据判断是否需要下发方案
|
||||||
|
if(checkIsNeedIssuedPlan(protSettings, siteId)){
|
||||||
|
if("3".equals(plan.getFaultLevel())){
|
||||||
|
isHighLevel = true;//最高故障等级
|
||||||
|
}
|
||||||
|
// 延时
|
||||||
|
final int faultDelay = plan.getFaultDelaySeconds().intValue();
|
||||||
|
ScheduledFuture<?> delayTask = scheduledExecutorService.schedule(() -> {
|
||||||
|
// 延时后再次确认是否仍满足触发条件(防止期间状态变化)
|
||||||
|
if (checkIsNeedIssuedPlan(protSettings, siteId)) {
|
||||||
|
// 判断是否需要生成告警
|
||||||
|
if (isAlertAlarm == 1) {
|
||||||
|
logger.info("<生成告警> 方案ID:{},站点:{}", plan.getId(), siteId);
|
||||||
|
EmsAlarmRecords alarmRecords = addAlarmRecord(siteId,"PCS", plan.getDescription(),
|
||||||
|
getAlarmLevel(plan.getFaultLevel()));
|
||||||
|
emsAlarmRecordsMapper.insertEmsAlarmRecords(alarmRecords);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否有保护方案,有则通过modbus连接设备下发方案
|
||||||
|
String protPlanJson = plan.getProtectionPlan();
|
||||||
|
if (protPlanJson != null && !protPlanJson.isEmpty()) {
|
||||||
|
logger.info("<下发保护方案> 方案内容:{}", protPlanJson);
|
||||||
|
executeProtectionActions(protPlanJson,siteId,plan.getId()); // 执行Modbus指令
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新方案状态为“已启用”
|
||||||
|
logger.info("<方案已启用> 方案ID:{}", plan.getId());
|
||||||
|
plan.setStatus(ProtPlanStatus.RUNNING.getCode());
|
||||||
|
emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan);
|
||||||
|
}
|
||||||
|
}, faultDelay, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String deviceId = protSettings.get(0).getDeviceId();
|
||||||
|
// 已启用,则获取方案的释放值与最新数据判断是否需要取消方案
|
||||||
|
if(checkIsNeedCancelPlan(protSettings, siteId)){
|
||||||
|
// 延时,
|
||||||
|
int releaseDelay = plan.getReleaseDelaySeconds().intValue();
|
||||||
|
ScheduledFuture<?> delayTask = scheduledExecutorService.schedule(() -> {
|
||||||
|
// 判断是否已存在未处理告警,有着取消
|
||||||
|
if(isAlertAlarm == 1){
|
||||||
|
logger.info("<取消告警>");
|
||||||
|
EmsAlarmRecords emsAlarmRecords = emsAlarmRecordsMapper.getFailedRecord(siteId,deviceId,
|
||||||
|
plan.getDescription(),getAlarmLevel(plan.getFaultLevel()));
|
||||||
|
if(emsAlarmRecords != null){
|
||||||
|
emsAlarmRecords.setStatus(AlarmStatus.DONE.getCode());
|
||||||
|
emsAlarmRecordsMapper.updateEmsAlarmRecords(emsAlarmRecords);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 更新该站点策略为启用
|
||||||
|
updateStrategyRunning(siteId);
|
||||||
|
}, releaseDelay, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isHighLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下发保护方案
|
||||||
|
private void executeProtectionActions(String protPlanJson, String siteId, Long planId){
|
||||||
|
final List<ProtectionPlanVo> protPlanList;
|
||||||
|
try {
|
||||||
|
protPlanList = objectMapper.readValue(
|
||||||
|
protPlanJson,
|
||||||
|
new TypeReference<List<ProtectionPlanVo>>() {}
|
||||||
|
);
|
||||||
|
if (protPlanList == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历保护方案
|
||||||
|
for (ProtectionPlanVo plan : protPlanList) {
|
||||||
|
if (StringUtils.isEmpty(plan.getDeviceId()) || StringUtils.isEmpty(plan.getPoint())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过modbus连接设备,发送数据
|
||||||
|
executeSinglePlan(plan,siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("下发保护方案失败,方案id为:", planId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeSinglePlan(ProtectionPlanVo plan, String siteId) throws Exception {
|
||||||
|
String deviceId = plan.getDeviceId();
|
||||||
|
// 获取设备地址信息
|
||||||
|
EmsDevicesSetting device = emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(deviceId, siteId);
|
||||||
|
if (device == null || StringUtils.isEmpty(device.getIpAddress()) || device.getIpPort()==null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 获取设备连接
|
||||||
|
ModbusConnectionWrapper wrapper = connectionManager.getConnection(device);
|
||||||
|
if (wrapper == null || !wrapper.isActive()) {
|
||||||
|
logger.info("<设备连接无效>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入寄存器
|
||||||
|
boolean success = modbusService.writeSingleRegister(
|
||||||
|
wrapper.getConnection(),
|
||||||
|
1,
|
||||||
|
plan.getValue().intValue());
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
logger.error("写入失败,设备地址:{}", device.getIpAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验释放值是否取消方案
|
||||||
|
private boolean checkIsNeedCancelPlan(List<ProtectionSettingVo> protSettings, String siteId) {
|
||||||
|
BigDecimal releaseValue = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
StringBuilder conditionSb = new StringBuilder();
|
||||||
|
for (int i = 0; i < protSettings.size(); i++) {
|
||||||
|
ProtectionSettingVo vo = protSettings.get(i);
|
||||||
|
String deviceId = vo.getDeviceId();
|
||||||
|
String point = vo.getPoint();
|
||||||
|
releaseValue = vo.getFaultValue();
|
||||||
|
if(StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(point) || releaseValue == null
|
||||||
|
|| StringUtils.isEmpty(vo.getReleaseOperator())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 获取点位最新值
|
||||||
|
BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId);
|
||||||
|
if(lastPointValue == null){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接校验语句-最新值+比较方式+故障值+与下一点位关系(最后一个条件后不加关系)
|
||||||
|
conditionSb.append(lastPointValue).append(vo.getReleaseOperator()).append(releaseValue);
|
||||||
|
if (i < protSettings.size() - 1) {
|
||||||
|
String relation = vo.getRelationNext();
|
||||||
|
conditionSb.append(" ").append(relation).append(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行比较语句
|
||||||
|
return executeWithParser(conditionSb.toString());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验故障值是否需要下发方案
|
||||||
|
private boolean checkIsNeedIssuedPlan(List<ProtectionSettingVo> protSettings, String siteId) {
|
||||||
|
BigDecimal faultValue = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
StringBuilder conditionSb = new StringBuilder();
|
||||||
|
for (int i = 0; i < protSettings.size(); i++) {
|
||||||
|
ProtectionSettingVo vo = protSettings.get(i);
|
||||||
|
String deviceId = vo.getDeviceId();
|
||||||
|
String point = vo.getPoint();
|
||||||
|
faultValue = vo.getFaultValue();
|
||||||
|
if(StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(point) || faultValue == null
|
||||||
|
|| StringUtils.isEmpty(vo.getFaultOperator())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 获取点位最新值
|
||||||
|
BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId);
|
||||||
|
if(lastPointValue == null){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接校验语句-最新值+比较方式+故障值+与下一点位关系(最后一个条件后不加关系)
|
||||||
|
conditionSb.append(lastPointValue).append(vo.getFaultOperator()).append(faultValue);
|
||||||
|
if (i < protSettings.size() - 1) {
|
||||||
|
String relation = vo.getRelationNext();
|
||||||
|
conditionSb.append(" ").append(relation).append(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行比较语句
|
||||||
|
return executeWithParser(conditionSb.toString());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal getPointLastValue(String deviceId, String point, String siteId) {
|
||||||
|
JSONObject mqttJson = redisCache.getCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceId);
|
||||||
|
if(mqttJson == null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String jsonData = mqttJson.get("Data").toString();
|
||||||
|
if(StringUtils.isEmpty(jsonData)){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Map<String, Object> obj = JSON.parseObject(jsonData, new com.alibaba.fastjson2.TypeReference<Map<String, Object>>() {});
|
||||||
|
return StringUtils.getBigDecimal(obj.get(point));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新站点策略为启用
|
||||||
|
private void updateStrategyRunning(String siteId) {
|
||||||
|
if (!StringUtils.isEmpty(siteId)) {
|
||||||
|
emsStrategyRunningMapper.updateStatusRunning(siteId, StrategyStatus.RUNNING.getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private EmsAlarmRecords addAlarmRecord(String siteId, String deviceId,String content,String level) {
|
||||||
|
EmsAlarmRecords emsAlarmRecords = new EmsAlarmRecords();
|
||||||
|
emsAlarmRecords.setSiteId(siteId);
|
||||||
|
emsAlarmRecords.setDeviceId(deviceId);
|
||||||
|
emsAlarmRecords.setAlarmContent(content);
|
||||||
|
emsAlarmRecords.setAlarmLevel(level);
|
||||||
|
emsAlarmRecords.setAlarmStartTime(new Date());
|
||||||
|
emsAlarmRecords.setStatus(AlarmStatus.WAITING.getCode());
|
||||||
|
emsAlarmRecords.setDeviceType("TCP");
|
||||||
|
emsAlarmRecords.setCreateBy("system");
|
||||||
|
emsAlarmRecords.setCreateTime(new Date());
|
||||||
|
return emsAlarmRecords;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 故障等级-告警等级匹配
|
||||||
|
private String getAlarmLevel(Integer faultLevel) {
|
||||||
|
if (ObjectUtils.isEmpty(faultLevel) || faultLevel < 1 || faultLevel > 3) {
|
||||||
|
logger.warn("非法故障等级:{},默认返回普通告警", faultLevel);
|
||||||
|
return AlarmLevelStatus.EMERGENCY.getCode();
|
||||||
|
}
|
||||||
|
switch (faultLevel) {
|
||||||
|
case 1: return AlarmLevelStatus.GENERAL.getCode();
|
||||||
|
case 2: return AlarmLevelStatus.SERIOUS.getCode();
|
||||||
|
case 3: return AlarmLevelStatus.EMERGENCY.getCode();
|
||||||
|
default:
|
||||||
|
logger.error("未匹配的故障等级:{}", faultLevel);
|
||||||
|
return AlarmLevelStatus.EMERGENCY.getCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义表达式解析器(仅支持简单运算符和逻辑关系)
|
||||||
|
public boolean executeWithParser(String conditionStr) {
|
||||||
|
if (conditionStr == null || conditionStr.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 拆分逻辑关系(提取 && 或 ||)
|
||||||
|
List<String> logicRelations = new ArrayList<>();
|
||||||
|
Pattern logicPattern = Pattern.compile("(&&|\\|\\|)");
|
||||||
|
Matcher logicMatcher = logicPattern.matcher(conditionStr);
|
||||||
|
while (logicMatcher.find()) {
|
||||||
|
logicRelations.add(logicMatcher.group());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 拆分原子条件(如 "3.55>3.52")
|
||||||
|
String[] atomicConditions = logicPattern.split(conditionStr);
|
||||||
|
|
||||||
|
// 3. 解析每个原子条件并计算结果
|
||||||
|
List<Boolean> atomicResults = new ArrayList<>();
|
||||||
|
Pattern conditionPattern = Pattern.compile("(\\d+\\.?\\d*)\\s*([><]=?|==)\\s*(\\d+\\.?\\d*)");
|
||||||
|
for (String atomic : atomicConditions) {
|
||||||
|
Matcher matcher = conditionPattern.matcher(atomic.trim());
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
logger.error("无效的原子条件:{}", atomic);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
double left = Double.parseDouble(matcher.group(1)); // 左值(最新值)
|
||||||
|
String operator = matcher.group(2); // 运算符
|
||||||
|
double right = Double.parseDouble(matcher.group(3)); // 右值(故障值)
|
||||||
|
|
||||||
|
// 执行比较
|
||||||
|
boolean result;
|
||||||
|
switch (operator) {
|
||||||
|
case ">":
|
||||||
|
result = left > right;
|
||||||
|
break;
|
||||||
|
case ">=":
|
||||||
|
result = left >= right;
|
||||||
|
break;
|
||||||
|
case "<":
|
||||||
|
result = left < right;
|
||||||
|
break;
|
||||||
|
case "<=":
|
||||||
|
result = left <= right;
|
||||||
|
break;
|
||||||
|
case "==":
|
||||||
|
result = left == right;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
atomicResults.add(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 组合原子结果(根据逻辑关系)
|
||||||
|
boolean finalResult = atomicResults.get(0);
|
||||||
|
for (int i = 0; i < logicRelations.size(); i++) {
|
||||||
|
String relation = logicRelations.get(i);
|
||||||
|
boolean nextResult = atomicResults.get(i + 1);
|
||||||
|
if ("&&".equals(relation)) {
|
||||||
|
finalResult = finalResult && nextResult;
|
||||||
|
} else if ("||".equals(relation)) {
|
||||||
|
finalResult = finalResult || nextResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return finalResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
package com.xzzn.ems.domain.vo;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 告警保护方案-保护方案设置
|
||||||
|
*/
|
||||||
|
public class ProtectionPlanVo {
|
||||||
|
/** 设备 */
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
/** 点位 */
|
||||||
|
private String point;
|
||||||
|
|
||||||
|
/** 点位名称 */
|
||||||
|
private String pointName;
|
||||||
|
|
||||||
|
/** 设置值 */
|
||||||
|
private BigDecimal value;
|
||||||
|
|
||||||
|
/** 设备类型 */
|
||||||
|
private String deviceCategory;
|
||||||
|
|
||||||
|
/** 设备类型名称 */
|
||||||
|
private String categoryName;
|
||||||
|
|
||||||
|
public String getDeviceId() {
|
||||||
|
return deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceId(String deviceId) {
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPoint() {
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPoint(String point) {
|
||||||
|
this.point = point;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPointName() {
|
||||||
|
return pointName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPointName(String pointName) {
|
||||||
|
this.pointName = pointName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(BigDecimal value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeviceCategory() {
|
||||||
|
return deviceCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceCategory(String deviceCategory) {
|
||||||
|
this.deviceCategory = deviceCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategoryName() {
|
||||||
|
return categoryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategoryName(String categoryName) {
|
||||||
|
this.categoryName = categoryName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,118 @@
|
|||||||
|
package com.xzzn.ems.domain.vo;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 告警保护方案-保护前提设置
|
||||||
|
*/
|
||||||
|
public class ProtectionSettingVo {
|
||||||
|
/** 设备 */
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
/** 点位 */
|
||||||
|
private String point;
|
||||||
|
|
||||||
|
/** 点位名称 */
|
||||||
|
private String pointName;
|
||||||
|
|
||||||
|
/** 故障值 */
|
||||||
|
private BigDecimal faultValue;
|
||||||
|
|
||||||
|
/** 故障值比较方式 如"<="、"<"、"=="、">="、">" */
|
||||||
|
private String faultOperator;
|
||||||
|
|
||||||
|
/** 释放值 */
|
||||||
|
private BigDecimal releaseValue;
|
||||||
|
|
||||||
|
/** 释放值比较方式 如"<="、"<"、"=="、">="、">"*/
|
||||||
|
private String releaseOperator;
|
||||||
|
|
||||||
|
/** 与下一点位关系: && 、|| */
|
||||||
|
private String relationNext;
|
||||||
|
|
||||||
|
/** 设备类型 */
|
||||||
|
private String deviceCategory;
|
||||||
|
|
||||||
|
/** 设备类型名称 */
|
||||||
|
private String categoryName;
|
||||||
|
|
||||||
|
public String getDeviceId() {
|
||||||
|
return deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceId(String deviceId) {
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPoint() {
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPoint(String point) {
|
||||||
|
this.point = point;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPointName() {
|
||||||
|
return pointName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPointName(String pointName) {
|
||||||
|
this.pointName = pointName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getFaultValue() {
|
||||||
|
return faultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFaultValue(BigDecimal faultValue) {
|
||||||
|
this.faultValue = faultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFaultOperator() {
|
||||||
|
return faultOperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFaultOperator(String faultOperator) {
|
||||||
|
this.faultOperator = faultOperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getReleaseValue() {
|
||||||
|
return releaseValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReleaseValue(BigDecimal releaseValue) {
|
||||||
|
this.releaseValue = releaseValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReleaseOperator() {
|
||||||
|
return releaseOperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReleaseOperator(String releaseOperator) {
|
||||||
|
this.releaseOperator = releaseOperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRelationNext() {
|
||||||
|
return relationNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRelationNext(String relationNext) {
|
||||||
|
this.relationNext = relationNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeviceCategory() {
|
||||||
|
return deviceCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceCategory(String deviceCategory) {
|
||||||
|
this.deviceCategory = deviceCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategoryName() {
|
||||||
|
return categoryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategoryName(String categoryName) {
|
||||||
|
this.categoryName = categoryName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -108,8 +108,10 @@ public class DDSDataProcessServiceImpl extends AbstractBatteryDataProcessor impl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 存放mqtt原始每个设备数据,便于后面点位获取数据
|
// 存放mqtt原始每个设备最晚一次数据,便于后面点位获取数据
|
||||||
redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + SITE_ID + "_" + deviceId, obj);
|
redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + SITE_ID + "_" + deviceId, obj);
|
||||||
|
// 存放每次同步数据,失效时间(同同步时间)-用于判断是否正常同步数据
|
||||||
|
redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA + SITE_ID + "_" + deviceId, obj, 2, TimeUnit.MINUTES);
|
||||||
|
|
||||||
// 处理相关数据
|
// 处理相关数据
|
||||||
if (deviceId.contains("BMSD")) {
|
if (deviceId.contains("BMSD")) {
|
||||||
|
|||||||
@ -110,8 +110,10 @@ public class FXXDataProcessServiceImpl extends AbstractBatteryDataProcessor impl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 存放mqtt原始每个设备数据,便于后面点位获取数据
|
// 存放mqtt原始每个设备最晚一次数据,便于后面点位获取数据
|
||||||
redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + SITE_ID + "_" + deviceId, obj);
|
redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + SITE_ID + "_" + deviceId, obj);
|
||||||
|
// 存放每次同步数据,失效时间(同同步时间)-用于判断是否正常同步数据
|
||||||
|
redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA + SITE_ID + "_" + deviceId, obj, 1, TimeUnit.MINUTES);
|
||||||
|
|
||||||
if (deviceId.contains("BMSD")) {
|
if (deviceId.contains("BMSD")) {
|
||||||
batteryStackDataProcess(deviceId, jsonData);
|
batteryStackDataProcess(deviceId, jsonData);
|
||||||
|
|||||||
Reference in New Issue
Block a user