diff --git a/ems-common/src/main/java/com/xzzn/common/constant/RedisKeyConstants.java b/ems-common/src/main/java/com/xzzn/common/constant/RedisKeyConstants.java index 37d7394..8d080d8 100644 --- a/ems-common/src/main/java/com/xzzn/common/constant/RedisKeyConstants.java +++ b/ems-common/src/main/java/com/xzzn/common/constant/RedisKeyConstants.java @@ -63,7 +63,7 @@ public class RedisKeyConstants public static final String COOLING = "COOLING_"; /** - * 存放单个设备同步过来的原始数据 + * 存放单个设备同步过来的原始数据-最晚一次数据 */ public static final String ORIGINAL_MQTT_DATA = "MQTT_"; @@ -87,4 +87,7 @@ public class RedisKeyConstants public static final String DDS_TOTAL_REVENUE = "total_revenue_"; /** fx实时总收益和当日实时收益 */ public static final String FXX_REALTIME_REVENUE = "realtime_revenue_"; + + /** 每个设备最新数据-设置失效时间-判断是否正常同步数据 */ + public static final String SYNC_DATA= "SYNC_DATA_"; } diff --git a/ems-framework/src/main/java/com/xzzn/framework/manager/ModbusConnectionManager.java b/ems-framework/src/main/java/com/xzzn/framework/manager/ModbusConnectionManager.java index 6ca81fc..cf968c9 100644 --- a/ems-framework/src/main/java/com/xzzn/framework/manager/ModbusConnectionManager.java +++ b/ems-framework/src/main/java/com/xzzn/framework/manager/ModbusConnectionManager.java @@ -95,7 +95,7 @@ public class ModbusConnectionManager implements ApplicationRunner { */ private TCPMasterConnection createRawConnection(EmsDevicesSetting device) throws Exception { try { - InetAddress addr = InetAddress.getByName("192.168.80.100"); + InetAddress addr = InetAddress.getByName("10.1.0.230"); TCPMasterConnection connection = new TCPMasterConnection(addr); connection.setPort(502); connection.setTimeout(5000); diff --git a/ems-framework/src/main/java/com/xzzn/framework/web/service/ModbusService.java b/ems-framework/src/main/java/com/xzzn/framework/web/service/ModbusService.java index 5783d18..ed7ac59 100644 --- a/ems-framework/src/main/java/com/xzzn/framework/web/service/ModbusService.java +++ b/ems-framework/src/main/java/com/xzzn/framework/web/service/ModbusService.java @@ -2,11 +2,16 @@ package com.xzzn.framework.web.service; import com.ghgande.j2mod.modbus.ModbusException; 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.msg.ReadInputRegistersRequest; 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.TCPMasterConnection; +import com.ghgande.j2mod.modbus.procimg.Register; +import com.ghgande.j2mod.modbus.procimg.SimpleRegister; import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,8 +47,26 @@ public class ModbusService { } } - private int[] readRtuRegisters(SerialConnection connection, int startAddr, int count) { - return null; + private int[] readRtuRegisters(SerialConnection connection, int startAddr, int count) throws ModbusException { + 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 { @@ -98,4 +121,159 @@ public class ModbusService { 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; + } } \ No newline at end of file diff --git a/ems-quartz/src/main/java/com/xzzn/quartz/task/ModbusPoller.java b/ems-quartz/src/main/java/com/xzzn/quartz/task/ModbusPoller.java index ea4a175..4032535 100644 --- a/ems-quartz/src/main/java/com/xzzn/quartz/task/ModbusPoller.java +++ b/ems-quartz/src/main/java/com/xzzn/quartz/task/ModbusPoller.java @@ -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(), diff --git a/ems-quartz/src/main/java/com/xzzn/quartz/task/ProtectionPlanTask.java b/ems-quartz/src/main/java/com/xzzn/quartz/task/ProtectionPlanTask.java new file mode 100644 index 0000000..64cd426 --- /dev/null +++ b/ems-quartz/src/main/java/com/xzzn/quartz/task/ProtectionPlanTask.java @@ -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 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 protSettings = objectMapper.readValue( + protectionSettings, + new TypeReference>() {} + ); + 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 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 protPlanList; + try { + protPlanList = objectMapper.readValue( + protPlanJson, + new TypeReference>() {} + ); + 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 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 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 obj = JSON.parseObject(jsonData, new com.alibaba.fastjson2.TypeReference>() {}); + 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 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 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; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/ProtectionPlanVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/ProtectionPlanVo.java new file mode 100644 index 0000000..c6d12b0 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/ProtectionPlanVo.java @@ -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; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/ProtectionSettingVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/ProtectionSettingVo.java new file mode 100644 index 0000000..229b60b --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/ProtectionSettingVo.java @@ -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; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/DDSDataProcessServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/DDSDataProcessServiceImpl.java index b7cad26..0e67719 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/DDSDataProcessServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/DDSDataProcessServiceImpl.java @@ -108,8 +108,10 @@ public class DDSDataProcessServiceImpl extends AbstractBatteryDataProcessor impl return; } - // 存放mqtt原始每个设备数据,便于后面点位获取数据 + // 存放mqtt原始每个设备最晚一次数据,便于后面点位获取数据 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")) { diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/FXXDataProcessServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/FXXDataProcessServiceImpl.java index 8aad846..32bed2c 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/FXXDataProcessServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/FXXDataProcessServiceImpl.java @@ -110,8 +110,10 @@ public class FXXDataProcessServiceImpl extends AbstractBatteryDataProcessor impl return; } - // 存放mqtt原始每个设备数据,便于后面点位获取数据 + // 存放mqtt原始每个设备最晚一次数据,便于后面点位获取数据 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")) { batteryStackDataProcess(deviceId, jsonData);