告警保护方案轮询-初稿
This commit is contained in:
@ -71,6 +71,10 @@ public class ModbusPoller {
|
||||
// 获取连接
|
||||
wrapper = connectionManager.getConnection(device);
|
||||
|
||||
if(wrapper == null || !wrapper.isActive()){
|
||||
logger.error("轮询设备{}连接失败: {}", device.getId());
|
||||
return;
|
||||
}
|
||||
// 读取保持寄存器
|
||||
int[] data = modbusService.readHoldingRegisters(
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user