From 5456d4742e396adf472d545bb20a1015ad9eb95c Mon Sep 17 00:00:00 2001 From: zq Date: Mon, 29 Dec 2025 15:47:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=BF=90=E8=A1=8C=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E8=BD=AE=E8=AF=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/xzzn/common/enums/SocLimit.java | 32 ++ .../java/com/xzzn/common/enums/YesOrNo.java | 33 ++ .../java/com/xzzn/common/utils/DateUtils.java | 10 +- .../xzzn/quartz/task/ProtectionPlanTask.java | 59 ++- .../com/xzzn/quartz/task/StrategyPoller.java | 385 +++++++++++++----- .../com/xzzn/ems/domain/EmsStrategyLog.java | 197 +++++++++ .../com/xzzn/ems/mapper/EmsPcsDataMapper.java | 14 +- .../xzzn/ems/mapper/EmsStrategyLogMapper.java | 62 +++ .../mapper/EmsStrategyTimeConfigMapper.java | 7 +- .../mapper/ems/EmsAmmeterDataMapper.xml | 1 + .../resources/mapper/ems/EmsPcsDataMapper.xml | 16 + .../mapper/ems/EmsStrategyLogMapper.xml | 122 ++++++ .../ems/EmsStrategyTimeConfigMapper.xml | 6 + 13 files changed, 835 insertions(+), 109 deletions(-) create mode 100644 ems-common/src/main/java/com/xzzn/common/enums/SocLimit.java create mode 100644 ems-common/src/main/java/com/xzzn/common/enums/YesOrNo.java create mode 100644 ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyLog.java create mode 100644 ems-system/src/main/java/com/xzzn/ems/mapper/EmsStrategyLogMapper.java create mode 100644 ems-system/src/main/resources/mapper/ems/EmsStrategyLogMapper.xml diff --git a/ems-common/src/main/java/com/xzzn/common/enums/SocLimit.java b/ems-common/src/main/java/com/xzzn/common/enums/SocLimit.java new file mode 100644 index 0000000..a1846e0 --- /dev/null +++ b/ems-common/src/main/java/com/xzzn/common/enums/SocLimit.java @@ -0,0 +1,32 @@ +package com.xzzn.common.enums; + +/** + * SOC限制 (%) 1 = 开,0 = 关 + * + * @author xzzn + */ +public enum SocLimit +{ + OFF(0, "关"), + ON(1, "开"), + ; + + private final Integer code; + private final String info; + + SocLimit(Integer code, String info) + { + this.code = code; + this.info = info; + } + + public Integer getCode() + { + return code; + } + + public String getInfo() + { + return info; + } +} diff --git a/ems-common/src/main/java/com/xzzn/common/enums/YesOrNo.java b/ems-common/src/main/java/com/xzzn/common/enums/YesOrNo.java new file mode 100644 index 0000000..adda8ac --- /dev/null +++ b/ems-common/src/main/java/com/xzzn/common/enums/YesOrNo.java @@ -0,0 +1,33 @@ +package com.xzzn.common.enums; + +public enum YesOrNo +{ + YES(1, "是", "Y"), + NO(0, "否", "N"); + + private final Integer code; + private final String info; + private final String value; + + YesOrNo(Integer code, String info, String value) + { + this.code = code; + this.info = info; + this.value = value; + } + + public Integer getCode() + { + return code; + } + + public String getInfo() + { + return info; + } + + public String getValue() + { + return value; + } +} diff --git a/ems-common/src/main/java/com/xzzn/common/utils/DateUtils.java b/ems-common/src/main/java/com/xzzn/common/utils/DateUtils.java index 29ff17c..29725be 100644 --- a/ems-common/src/main/java/com/xzzn/common/utils/DateUtils.java +++ b/ems-common/src/main/java/com/xzzn/common/utils/DateUtils.java @@ -1,7 +1,5 @@ package com.xzzn.common.utils; -import com.xzzn.common.annotation.Log; - import java.lang.management.ManagementFactory; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -161,6 +159,14 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); } + /** + * 计算相差分钟 + */ + public static int differentMinutesByMillisecond(Date date1, Date date2) + { + return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 60))); + } + /** * 计算时间差 * 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 index a809cae..0d9690c 100644 --- a/ems-quartz/src/main/java/com/xzzn/quartz/task/ProtectionPlanTask.java +++ b/ems-quartz/src/main/java/com/xzzn/quartz/task/ProtectionPlanTask.java @@ -12,29 +12,45 @@ 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.*; +import com.xzzn.ems.domain.EmsAlarmRecords; +import com.xzzn.ems.domain.EmsDevicesSetting; +import com.xzzn.ems.domain.EmsFaultIssueLog; +import com.xzzn.ems.domain.EmsFaultProtectionPlan; +import com.xzzn.ems.domain.EmsStrategyRunning; import com.xzzn.ems.domain.vo.ProtectionPlanVo; import com.xzzn.ems.domain.vo.ProtectionSettingVo; -import com.xzzn.ems.mapper.*; +import com.xzzn.ems.mapper.EmsAlarmRecordsMapper; +import com.xzzn.ems.mapper.EmsDevicesSettingMapper; +import com.xzzn.ems.mapper.EmsFaultIssueLogMapper; +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.*; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; 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; +import javax.annotation.Resource; + +import org.apache.commons.collections4.CollectionUtils; +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; + /** * 告警保护方案轮询 * @@ -113,7 +129,7 @@ public class ProtectionPlanTask { final Integer isAlertAlarm = plan.getIsAlert(); final Long status = plan.getStatus(); // 看方案是否启用,走不同判断 - if (status == ProtPlanStatus.STOP.getCode()) { + if (Objects.equals(status, ProtPlanStatus.STOP.getCode())) { // 未启用,获取方案的故障值与最新数据判断是否需要下发方案 if(checkIsNeedIssuedPlan(protSettings, siteId)){ if("3".equals(plan.getFaultLevel())){ @@ -143,6 +159,9 @@ public class ProtectionPlanTask { logger.info("<方案已启用> 方案ID:{}", plan.getId()); plan.setStatus(ProtPlanStatus.RUNNING.getCode()); emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan); + + // 更新该站点策略为暂停状态 + updateStrategyRunningStatus(siteId, StrategyStatus.SUSPENDED.getCode()); } }, faultDelay, TimeUnit.SECONDS); } @@ -167,8 +186,8 @@ public class ProtectionPlanTask { plan.setStatus(ProtPlanStatus.STOP.getCode()); plan.setUpdateBy("system"); emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan); - // 更新该站点策略为启用 - updateStrategyRunning(siteId); + // 更新该站点策略为启用状态 + updateStrategyRunningStatus(siteId, StrategyStatus.RUNNING.getCode()); }, releaseDelay, TimeUnit.SECONDS); } } @@ -326,6 +345,22 @@ public class ProtectionPlanTask { return StringUtils.getBigDecimal(obj.get(point)); } + // 更新站点策略为启用 + private void updateStrategyRunningStatus(String siteId, String status) { + // 获取是否有正在运行的策略,如果有则不更改 + EmsStrategyRunning query = new EmsStrategyRunning(); + query.setSiteId(siteId); + query.setStatus(StrategyStatus.RUNNING.getCode().equals(status) ? StrategyStatus.SUSPENDED.getCode() : StrategyStatus.RUNNING.getCode()); + List strategyRunningList = emsStrategyRunningMapper.selectEmsStrategyRunningList(query); + if (CollectionUtils.isNotEmpty(strategyRunningList)) { + // 获取已存在并且状态为:未启用和已暂停的最晚一条策略,更新为已启用 + strategyRunningList.forEach(emsStrategyRunning -> { + emsStrategyRunning.setStatus(status); + emsStrategyRunningMapper.updateEmsStrategyRunning(emsStrategyRunning); + }); + } + } + // 更新站点策略为启用 private void updateStrategyRunning(String siteId) { // 获取是否有正在运行的策略,如果有则不更改 diff --git a/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java b/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java index b605fda..976c408 100644 --- a/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java +++ b/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java @@ -1,44 +1,67 @@ package com.xzzn.quartz.task; -import com.alibaba.fastjson2.JSON; +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.enums.ChargeStatus; +import com.xzzn.common.enums.DeviceCategory; +import com.xzzn.common.enums.SiteDevice; +import com.xzzn.common.enums.SocLimit; import com.xzzn.common.utils.DateUtils; import com.xzzn.common.utils.StringUtils; -import com.xzzn.ems.domain.EmsStrategyCurve; +import com.xzzn.ems.domain.EmsAmmeterData; +import com.xzzn.ems.domain.EmsBatteryStack; +import com.xzzn.ems.domain.EmsDevicesSetting; +import com.xzzn.ems.domain.EmsPcsData; +import com.xzzn.ems.domain.EmsPointMatch; +import com.xzzn.ems.domain.EmsStrategyLog; import com.xzzn.ems.domain.EmsStrategyTemp; import com.xzzn.ems.domain.EmsStrategyTimeConfig; -import com.xzzn.ems.domain.vo.StrategyPowerDataVo; +import com.xzzn.ems.domain.vo.DeviceUpdateRequest; import com.xzzn.ems.domain.vo.StrategyRunningVo; -import com.xzzn.ems.mapper.*; -import com.xzzn.framework.manager.ModbusConnectionManager; -import com.xzzn.framework.manager.MqttLifecycleManager; -import com.xzzn.framework.web.service.ModbusService; +import com.xzzn.ems.mapper.EmsAmmeterDataMapper; +import com.xzzn.ems.mapper.EmsBatteryStackMapper; +import com.xzzn.ems.mapper.EmsDevicesSettingMapper; +import com.xzzn.ems.mapper.EmsPcsDataMapper; +import com.xzzn.ems.mapper.EmsPointMatchMapper; +import com.xzzn.ems.mapper.EmsStrategyLogMapper; +import com.xzzn.ems.mapper.EmsStrategyRunningMapper; +import com.xzzn.ems.mapper.EmsStrategyTempMapper; +import com.xzzn.ems.mapper.EmsStrategyTimeConfigMapper; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.collections4.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.time.LocalDate; -import java.time.YearMonth; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - @Component("strategyPoller") public class StrategyPoller { private static final Logger logger = LoggerFactory.getLogger(StrategyPoller.class); - private final MqttLifecycleManager mqttLifecycleManager; + // SOC 上下限值,默认为0%-100% + private static final BigDecimal SOC_DOWN = new BigDecimal(0); + private static final BigDecimal SOC_UP = new BigDecimal(100); + // 逆变器功率下限值,默认为30kW + private static final BigDecimal ANTI_REVERSE_THRESHOLD = new BigDecimal(30); + // 逆变器下限值范围,默认为20% + private static final BigDecimal ANTI_REVERSE_RANGE_PERCENT = new BigDecimal(20); + // PCS功率降幅,默认为10% + private static final BigDecimal ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal(10); + private final Map sitePcsPowerDown = new ConcurrentHashMap<>(); - @Autowired - private ModbusConnectionManager connectionManager; - @Autowired - private ModbusService modbusService; - @Autowired - private EmsDevicesSettingMapper deviceRepo; - @Autowired - private EmsMqttMessageMapper emsMqttMessageMapper; @Autowired private EmsStrategyRunningMapper emsStrategyRunningMapper; @Autowired @@ -46,15 +69,22 @@ public class StrategyPoller { @Autowired private EmsStrategyTimeConfigMapper emsStrategyTimeConfigMapper; @Autowired - private EmsStrategyCurveMapper emsStrategyCurveMapper; - + private EmsBatteryStackMapper emsBatteryStackMapper; @Autowired - public StrategyPoller(MqttLifecycleManager mqttLifecycleManager) { - this.mqttLifecycleManager = mqttLifecycleManager; - } + private EmsPcsDataMapper emsPcsDataMapper; + @Autowired + private EmsDevicesSettingMapper emsDevicesMapper; + @Autowired + private EmsPointMatchMapper emsPointMatchMapper; + @Autowired + private EmsAmmeterDataMapper emsAmmeterDataMapper; + @Autowired + private EmsStrategyLogMapper emsStrategyLogMapper; + @Autowired + private ModbusProcessor modbusProcessor; public void pollAllDevices() { - logger.info("开始执行策略数据轮询..."); + logger.info("开始执行运行策略数据轮询..."); List strategyRunningVoList = emsStrategyRunningMapper.getPendingPollerStrategy(null); strategyRunningVoList.forEach(strategyVo -> { try { @@ -62,17 +92,17 @@ public class StrategyPoller { processData(strategyVo); }) .exceptionally(e -> { - logger.error("策略{}轮询异常", strategyVo.getId(), e); + logger.error("运行策略{}轮询异常", strategyVo.getId(), e); return null; }); } catch (Exception e) { - logger.error("策略下方{}任务失败", strategyVo.getId(), e); + logger.error("运行策略{}任务失败", strategyVo.getId(), e); } }); } - // 处理获取到的数据,发到mqtt服务上 + // 处理获取到的运行策略数据,modbus发送指定的命令控制设备 private void processData(StrategyRunningVo strategyVo) { - logger.info("策略下发数据处理开始"); + logger.info("运行策略数据处理开始"); // 根据运行策略获取主副策略的模板数据 Long mainStrategyId = strategyVo.getMainStrategyId(); Long auxStrategyId = strategyVo.getAuxStrategyId(); @@ -86,78 +116,249 @@ public class StrategyPoller { dealStrategyCurveData(auxStrategyId, siteId); } - // 策略数据下发-下方格式暂无 - logger.info("策略下发结束"); + logger.info("运行策略轮询处理结束"); } - private void dealStrategyCurveData(Long mainStrategyId, String siteId) { - // 获取当前策略的所有模板 - List> temps = emsStrategyTempMapper.getTempNameList(mainStrategyId,siteId); - if (temps != null && temps.size() > 0) { - for (Map temp : temps) { - String tempId = temp.get("templateId"); - List timeConfigs = emsStrategyTimeConfigMapper.getAllTimeConfigByTempId(tempId); - if (timeConfigs != null && timeConfigs.size() > 0) { - for (EmsStrategyTimeConfig timeConfig : timeConfigs) { - EmsStrategyCurve curve = new EmsStrategyCurve(); - curve.setStrategyId(mainStrategyId); - curve.setSiteId(siteId); - curve.setTemplateId(tempId); - curve.setCreateBy("system"); - curve.setCreateTime(DateUtils.getNowDate()); - curve.setUpdateBy("system"); - curve.setUpdateTime(DateUtils.getNowDate()); - // 时间设置 - int month = Integer.parseInt(timeConfig.getMonth().toString()); - String[] dateList= dealWithMonth(month); - curve.setMonth(Long.valueOf(month)); - curve.setStartDate(DateUtils.dateTime(DateUtils.YYYY_MM_DD,dateList[0])); - curve.setEndDate(DateUtils.dateTime(DateUtils.YYYY_MM_DD,dateList[1])); - // powerData-存json格式 - List powerConfig = emsStrategyTempMapper.selectStrategyTempByTempId(tempId); - List powerDataVoList = new ArrayList<>(); - for (int i = 0; i < powerConfig.size(); i++) { - EmsStrategyTemp powerTemp = powerConfig.get(i); - StrategyPowerDataVo powerDataVo = new StrategyPowerDataVo(); - powerDataVo.setPowerData(powerTemp.getChargeDischargePower()); - powerDataVo.setEndTime(DateUtils.parseDateToStr("HH:mm:ss",powerTemp.getEndTime())); - powerDataVo.setStartTime(DateUtils.parseDateToStr("HH:mm:ss",powerTemp.getStartTime())); - powerDataVoList.add(powerDataVo); + private void dealStrategyCurveData(Long strategyId, String siteId) { + // 1.获取当前策略的所有模板 + List> temps = emsStrategyTempMapper.getTempNameList(strategyId, siteId); + if (CollectionUtils.isEmpty(temps)) { + logger.info("当前站点: {}, 策略: {} 没有模板数据", siteId, strategyId); + return; + } + for (Map temp : temps) { + // 2.查询当月配置的运行策略 + String tempId = temp.get("templateId"); + int month = LocalDateTime.now().getMonthValue(); + List timeConfigs = emsStrategyTimeConfigMapper.getTimeConfigByTempIdAndMonth(tempId, month); + if (CollectionUtils.isEmpty(timeConfigs)) { + continue; + } + logger.info("当前站点: {}, 策略: {}, {}月配置模版:{}", siteId, strategyId, month, tempId); + // 3.查询当月配置的运行策略时间阶段数据 + List powerConfig = emsStrategyTempMapper.selectStrategyTempByTempId(tempId); + if (CollectionUtils.isEmpty(powerConfig)) { + logger.info("当前站点: {}, 策略: {}, 模版:{} 未配置数据", siteId, strategyId, tempId); + continue; + } + // 4.遍历时间段数据,判断当前时间是否在时间段内,在时间段内的进行处理 + for (EmsStrategyTemp emsStrategyTemp : powerConfig) { + if (emsStrategyTemp.getStartTime() == null || emsStrategyTemp.getEndTime() == null) { + logger.info("当前站点: {}, 策略: {}, 模版:{} 未配置时间阶段数据", siteId, strategyId, tempId); + continue; + } + // 判断当前时间是否在时间段内 + if (!isTimeInRange(LocalTime.now(), emsStrategyTemp.getStartTime(), emsStrategyTemp.getEndTime())) { + continue; + } + // 查询PCS设备信息 + List pcsDataList = emsPcsDataMapper.getSitePcsDataInfo(siteId); + if (CollectionUtils.isEmpty(pcsDataList)) { + logger.info("当前站点: {} 未配置PCS设备", siteId); + continue; + } + // 判断SOC上限和下限是否在范围内 + if (isSocInRange(emsStrategyTemp)) { + BigDecimal avgChargeDischargePower = emsStrategyTemp.getChargeDischargePower().divide(new BigDecimal(pcsDataList.size())); + for (EmsPcsData pcsData : pcsDataList) { + if (pcsData.getClusterNum() == null || pcsData.getClusterNum() < 1) { + logger.info("当前站点: {}, PCS设备: {} 未获取电池簇数量", siteId, pcsData.getDeviceId()); + continue; + } + // 平均功率值,根据电池簇数量进行平均分配 + avgChargeDischargePower = avgChargeDischargePower.divide(new BigDecimal(pcsData.getClusterNum())); + // 根据充电状态,处理数据 + if (ChargeStatus.CHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) { + // 发送Modbus命令控制设备-充电 + sendModbusCommand(Collections.singletonList(pcsData), ChargeStatus.CHARGING, avgChargeDischargePower, emsStrategyTemp, false); + } else if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) { + // 判断是否需要防逆流,PCS降功率运行 + boolean needAntiReverseFlow = isNeedAntiReverseFlow(emsStrategyTemp); + if (needAntiReverseFlow) { + BigDecimal powerDown = ANTI_REVERSE_POWER_DOWN_PERCENT; + // 查询站点PCS功率降幅记录,如果有则累加增幅 + List strategyLogList = getStrategyLog(pcsData.getDeviceId(), emsStrategyTemp.getChargeStatus(), emsStrategyTemp, needAntiReverseFlow); + if (CollectionUtils.isNotEmpty(strategyLogList)) { + // 判断上次防逆流时间是否已经过了15分钟 + if (DateUtils.differentMinutesByMillisecond(strategyLogList.get(0).getExecutionDate(), new Date()) < 15) { + continue; + } + powerDown = powerDown.multiply(new BigDecimal(strategyLogList.size() + 1)); + } + avgChargeDischargePower = avgChargeDischargePower.subtract(avgChargeDischargePower.multiply(powerDown).divide(new BigDecimal(100))); + } + if (BigDecimal.ZERO.compareTo(avgChargeDischargePower) == 0) { + // 如果已经降功率到0,则设备直接待机 + // 发送Modbus命令控制设备-待机 + sendModbusCommand(Collections.singletonList(pcsData), ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, needAntiReverseFlow); + } else { + // 发送Modbus命令控制设备-放电 + sendModbusCommand(Collections.singletonList(pcsData), ChargeStatus.DISCHARGING, avgChargeDischargePower, emsStrategyTemp, needAntiReverseFlow); + } + } else { + // 发送Modbus命令控制设备-待机 + sendModbusCommand(Collections.singletonList(pcsData), ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, false); } - curve.setPowerData(powerDataVoList !=null ? JSON.toJSON(powerDataVoList).toString() : ""); - - // 记录推送记录 - emsStrategyCurveMapper.insertEmsStrategyCurve(curve); - - // 设置已下发 - timeConfig.setIsPost(0); - emsStrategyTimeConfigMapper.updateEmsStrategyTimeConfig(timeConfig); } + } else { + // 发送Modbus命令控制设备-待机 + sendModbusCommand(pcsDataList, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, false); } } + } } - private String[] dealWithMonth(int month) { - // 获取当前年份 - int currentYear = LocalDate.now().getYear(); + private void saveStrategyLog(String deviceId, BigDecimal chargeDischargePower, String chargeStatus, + EmsStrategyTemp strategyTemp, boolean needAntiReverseFlow) { + EmsStrategyLog log = new EmsStrategyLog(); + log.setStrategyId(strategyTemp.getStrategyId()); + log.setTemplateId(strategyTemp.getTemplateId()); + log.setSiteId(strategyTemp.getSiteId()); + log.setDeviceId(deviceId); + log.setStartTime(strategyTemp.getStartTime()); + log.setEndTime(strategyTemp.getEndTime()); + log.setChargeDischargePower(chargeDischargePower); + log.setChargeStatus(chargeStatus); + log.setExecutionDate(DateUtils.toDate(LocalDateTime.now())); + log.setAntiReverse(needAntiReverseFlow ? 1 : 0); + emsStrategyLogMapper.insertEmsStrategyLog(log); + } + + private List getStrategyLog(String deviceId, String chargeStatus, + EmsStrategyTemp strategyTemp, boolean needAntiReverseFlow) { + EmsStrategyLog query = new EmsStrategyLog(); + query.setStrategyId(strategyTemp.getStrategyId()); + query.setTemplateId(strategyTemp.getTemplateId()); + query.setSiteId(strategyTemp.getSiteId()); + query.setDeviceId(deviceId); + query.setStartTime(strategyTemp.getStartTime()); + query.setEndTime(strategyTemp.getEndTime()); + query.setChargeStatus(chargeStatus); + query.setExecutionDate(DateUtils.toDate(LocalDateTime.now())); + query.setAntiReverse(needAntiReverseFlow ? 1 : 0); + return emsStrategyLogMapper.selectEmsStrategyLogList(query); + } - // 创建YearMonth对象表示当年指定的月份 - YearMonth yearMonth = YearMonth.of(currentYear, month); + private boolean isNeedAntiReverseFlow(EmsStrategyTemp emsStrategyTemp) { + EmsAmmeterData emsAmmeterData = emsAmmeterDataMapper.getLastData(emsStrategyTemp.getSiteId(), SiteDevice.LOAD.name()); + if (emsAmmeterData == null || emsAmmeterData.getTotalActivePower() == null) { + logger.info("当前站点: {}, 未获取到最新电表数据", emsStrategyTemp.getSiteId()); + return false; + } + // 获取当前设定的防逆流阈值(30kW) + BigDecimal threshold = ANTI_REVERSE_THRESHOLD; + // 计算20%范围的上限(36kW) + BigDecimal upperLimit = threshold.multiply(ANTI_REVERSE_RANGE_PERCENT).divide(new BigDecimal(100)).add(threshold); - // 获取当月的第一天和最后一天 - LocalDate firstDay = yearMonth.atDay(1); - LocalDate lastDay = yearMonth.atEndOfMonth(); + // 判断电网电表正向有功功率是否小于36kW(接近30kW的20%范围) + return emsAmmeterData.getTotalActivePower().compareTo(upperLimit) < 0; + } - // 定义日期格式(年月日) - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + public List getWriteTags(List pointMatchList, + ChargeStatus chargeStatus, BigDecimal chargeDischargePower) { + List writeTags = new ArrayList<>(); + for (EmsPointMatch pointMatch : pointMatchList) { + WriteTagConfig writeTag = new WriteTagConfig(); + writeTag.setAddress(pointMatch.getIpAddress()); + if (ChargeStatus.CHARGING.equals(chargeStatus)) { + writeTag.setValue(chargeDischargePower); + } else if (ChargeStatus.DISCHARGING.equals(chargeStatus)) { + writeTag.setValue(chargeDischargePower.negate()); + } else { + // 待机状态-电池簇PCS有功功率给定置0 + writeTag.setValue(chargeDischargePower); + } + writeTags.add(writeTag); + } + return writeTags; + } - // 格式化日期 - return new String[]{ - firstDay.format(formatter), - lastDay.format(formatter) - }; + private List getMatchFields(Integer clusterNum) { + List matchFields = new ArrayList<>(); + for (int i = 1; i <= clusterNum; i++) { + //电池簇PCS有功功率给定 + matchFields.add("cluster"+ i +"_active_power"); + } + return matchFields; + } + public DeviceConfig getDeviceConfig(EmsPcsData pcsData, ChargeStatus chargeStatus, BigDecimal chargeDischargePower) { + EmsDevicesSetting device = emsDevicesMapper.getDeviceBySiteAndDeviceId(pcsData.getDeviceId(), pcsData.getSiteId()); + if (device == null) { + logger.info("当前站点: {}, PCS设备: {} 未找到对应设备配置信息", pcsData.getSiteId(), pcsData.getDeviceId()); + return null; + } + DeviceUpdateRequest query = new DeviceUpdateRequest(); + query.setSiteId(device.getSiteId()); + query.setDeviceId(device.getDeviceId()); + query.setMatchFields(getMatchFields(pcsData.getClusterNum())); + query.setDeviceCategory(DeviceCategory.PCS.getCode()); + List pointMatchList = emsPointMatchMapper.selectDeviceStatusPoint(query); + if (CollectionUtils.isEmpty(pointMatchList)) { + logger.info("当前站点: {}, PCS设备: {} 未找到对应设备点位字段配置", pcsData.getSiteId(), pcsData.getDeviceId()); + return null; + } + if (pointMatchList.size() != pcsData.getClusterNum()) { + logger.info("当前站点: {}, PCS设备: {} 设备点位字段配置数量与电池簇数不一致", pcsData.getSiteId(), pcsData.getDeviceId()); + } + DeviceConfig deviceConfig = new DeviceConfig(); + deviceConfig.setDeviceNumber(device.getDeviceId()); + deviceConfig.setDeviceName(device.getDeviceName()); + deviceConfig.setSlaveId(device.getSlaveId().intValue()); + deviceConfig.setHost(device.getIpAddress()); + deviceConfig.setPort(device.getIpPort().intValue()); + deviceConfig.setWriteTags(getWriteTags(pointMatchList, chargeStatus, chargeDischargePower)); + return deviceConfig; + } + + private void sendModbusCommand(List pcsDataList, ChargeStatus chargeStatus, BigDecimal chargeDischargePower, + EmsStrategyTemp emsStrategyTemp, boolean needAntiReverseFlow) { + for (EmsPcsData pcsData : pcsDataList) { + List strategyLogList = getStrategyLog(pcsData.getDeviceId(), chargeStatus.getCode(), emsStrategyTemp, needAntiReverseFlow); + if (CollectionUtils.isNotEmpty(strategyLogList) && !ChargeStatus.DISCHARGING.equals(chargeStatus) && !needAntiReverseFlow) { + logger.info("当前站点: {}, PCS设备: {} 当前时间段已存在策略执行记录,不再重复执行", pcsData.getSiteId(), pcsData.getDeviceId()); + continue; + } + DeviceConfig deviceConfig = getDeviceConfig(pcsData, chargeStatus, chargeDischargePower); + if (deviceConfig == null) { + continue; + } + boolean result = modbusProcessor.writeDataToDevice(deviceConfig); + if (!result) { + logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", pcsData.getSiteId(), pcsData.getDeviceId(), chargeStatus.getInfo()); + } + // 记录策略执行日志 + saveStrategyLog(pcsData.getDeviceId(), chargeDischargePower, chargeStatus.getCode(), emsStrategyTemp, needAntiReverseFlow); + } + } + + // 判断当前时间是否在时间范围内 + private static boolean isTimeInRange(LocalTime now, Date startTime, Date endTime) { + ZoneId zoneId = ZoneId.of("Asia/Shanghai"); + LocalTime startLocalTime = startTime.toInstant() + .atZone(zoneId) + .toLocalTime(); + LocalTime endLocalTime = endTime.toInstant() + .atZone(zoneId) + .toLocalTime(); + return now.equals(startLocalTime) || (now.isAfter(startLocalTime) && now.isBefore(endLocalTime)); + } + + // 判断SOC上限和下限是否在范围内 + private boolean isSocInRange(EmsStrategyTemp emsStrategyTemp) { + BigDecimal socDown = SOC_DOWN; + BigDecimal socUp = SOC_UP; + if (SocLimit.ON.getCode().equals(emsStrategyTemp.getSdcLimit())) { + socDown = emsStrategyTemp.getSdcDown(); + socUp = emsStrategyTemp.getSdcUp(); + } + // 查询电池堆(BMSD) SOC + EmsBatteryStack emsBatteryStack = emsBatteryStackMapper.getSiteSumStackInfo(emsStrategyTemp.getSiteId()); + if (emsBatteryStack != null && emsBatteryStack.getStackSoc() != null) { + return emsBatteryStack.getStackSoc().compareTo(socDown) > 0 && emsBatteryStack.getStackSoc().compareTo(socUp) < 0; + } + return true; } } \ No newline at end of file diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyLog.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyLog.java new file mode 100644 index 0000000..74954e8 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyLog.java @@ -0,0 +1,197 @@ +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 org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 策略运行日志对象 ems_strategy_log + * + * @author xzzn + * @date 2025-12-26 + */ +public class EmsStrategyLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** $column.columnComment */ + private Long id; + + /** 策略ID */ + @Excel(name = "策略ID") + private Long strategyId; + + /** 模版id */ + @Excel(name = "模版id") + private String templateId; + + /** 站点id */ + @Excel(name = "站点id") + private String siteId; + + /** 设备唯一标识符 */ + @Excel(name = "设备唯一标识符") + private String deviceId; + + /** 开始时间 */ + @JsonFormat(pattern = "HH:mm") + @Excel(name = "开始时间", width = 30, dateFormat = "HH:mm") + private Date startTime; + + /** 结束时间 */ + @JsonFormat(pattern = "HH:mm") + @Excel(name = "结束时间", width = 30, dateFormat = "HH:mm") + private Date endTime; + + /** 功率 (kW) */ + @Excel(name = "功率 (kW)") + private BigDecimal chargeDischargePower; + + /** 充电状态,如“1-充电”、“2-待机”、“3-放电” */ + @Excel(name = "充电状态,如“1-充电”、“2-待机”、“3-放电”") + private String chargeStatus; + + /** 执行时间 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "执行时间", width = 30, dateFormat = "yyyy-MM-dd") + private Date executionDate; + + /** 防逆流, 1-是、0-否 */ + @Excel(name = "防逆流, 1-是、0-否") + private Integer antiReverse; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + + public void setStrategyId(Long strategyId) + { + this.strategyId = strategyId; + } + + public Long getStrategyId() + { + return strategyId; + } + + public void setTemplateId(String templateId) + { + this.templateId = templateId; + } + + public String getTemplateId() + { + return templateId; + } + + public void setSiteId(String siteId) + { + this.siteId = siteId; + } + + public String getSiteId() + { + return siteId; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public void setStartTime(Date startTime) + { + this.startTime = startTime; + } + + public Date getStartTime() + { + return startTime; + } + + public void setEndTime(Date endTime) + { + this.endTime = endTime; + } + + public Date getEndTime() + { + return endTime; + } + + public void setChargeDischargePower(BigDecimal chargeDischargePower) + { + this.chargeDischargePower = chargeDischargePower; + } + + public BigDecimal getChargeDischargePower() + { + return chargeDischargePower; + } + + public void setChargeStatus(String chargeStatus) + { + this.chargeStatus = chargeStatus; + } + + public String getChargeStatus() + { + return chargeStatus; + } + + public void setExecutionDate(Date executionDate) + { + this.executionDate = executionDate; + } + + public Date getExecutionDate() + { + return executionDate; + } + + public Integer getAntiReverse() { + return antiReverse; + } + + public void setAntiReverse(Integer antiReverse) { + this.antiReverse = antiReverse; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("strategyId", getStrategyId()) + .append("templateId", getTemplateId()) + .append("siteId", getSiteId()) + .append("deviceId", getDeviceId()) + .append("antiReverse", getAntiReverse()) + .append("startTime", getStartTime()) + .append("endTime", getEndTime()) + .append("chargeDischargePower", getChargeDischargePower()) + .append("chargeStatus", getChargeStatus()) + .append("executionDate", getExecutionDate()) + .append("antiReverse", getAntiReverse()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPcsDataMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPcsDataMapper.java index 8a76ae6..2292584 100644 --- a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPcsDataMapper.java +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPcsDataMapper.java @@ -1,10 +1,18 @@ package com.xzzn.ems.mapper; +import com.xzzn.ems.domain.EmsPcsData; +import com.xzzn.ems.domain.vo.DateSearchRequest; +import com.xzzn.ems.domain.vo.ElectricIndexList; +import com.xzzn.ems.domain.vo.EnergyStoragePowVo; +import com.xzzn.ems.domain.vo.PcsDetailInfoVo; +import com.xzzn.ems.domain.vo.PcsMaxTempVo; +import com.xzzn.ems.domain.vo.PcsStatisListVo; +import com.xzzn.ems.domain.vo.SiteMonitorDataVo; +import com.xzzn.ems.domain.vo.SiteMonitorRunningHeadInfoVo; + import java.util.Date; import java.util.List; -import com.xzzn.ems.domain.EmsPcsData; -import com.xzzn.ems.domain.vo.*; import org.apache.ibatis.annotations.Param; /** @@ -132,4 +140,6 @@ public interface EmsPcsDataMapper public List getFXMaxTemp(@Param("siteId")String siteId, @Param("startDate")Date startDate, @Param("endDate")Date endDate); // 实时运行-dds-pcs最高温度 public List getDDSMaxTemp(@Param("siteId")String siteId, @Param("startDate")Date startDate, @Param("endDate")Date endDate); + + List getSitePcsDataInfo(@Param("siteId") String siteId); } diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsStrategyLogMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsStrategyLogMapper.java new file mode 100644 index 0000000..768b1e6 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsStrategyLogMapper.java @@ -0,0 +1,62 @@ +package com.xzzn.ems.mapper; + +import com.xzzn.ems.domain.EmsStrategyLog; + +import java.util.List; + +/** + * 策略运行日志Mapper接口 + * + * @author xzzn + * @date 2025-12-26 + */ +public interface EmsStrategyLogMapper +{ + /** + * 查询策略运行日志 + * + * @param id 策略运行日志主键 + * @return 策略运行日志 + */ + public EmsStrategyLog selectEmsStrategyLogById(Long id); + + /** + * 查询策略运行日志列表 + * + * @param emsStrategyLog 策略运行日志 + * @return 策略运行日志集合 + */ + public List selectEmsStrategyLogList(EmsStrategyLog emsStrategyLog); + + /** + * 新增策略运行日志 + * + * @param emsStrategyLog 策略运行日志 + * @return 结果 + */ + public int insertEmsStrategyLog(EmsStrategyLog emsStrategyLog); + + /** + * 修改策略运行日志 + * + * @param emsStrategyLog 策略运行日志 + * @return 结果 + */ + public int updateEmsStrategyLog(EmsStrategyLog emsStrategyLog); + + /** + * 删除策略运行日志 + * + * @param id 策略运行日志主键 + * @return 结果 + */ + public int deleteEmsStrategyLogById(Long id); + + /** + * 批量删除策略运行日志 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteEmsStrategyLogByIds(Long[] ids); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsStrategyTimeConfigMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsStrategyTimeConfigMapper.java index 94cbaf7..131d09b 100644 --- a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsStrategyTimeConfigMapper.java +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsStrategyTimeConfigMapper.java @@ -1,8 +1,10 @@ package com.xzzn.ems.mapper; -import java.util.List; import com.xzzn.ems.domain.EmsStrategyTimeConfig; import com.xzzn.ems.domain.vo.StrategyTimeConfigVo; + +import java.util.List; + import org.apache.ibatis.annotations.Param; /** @@ -70,6 +72,9 @@ public interface EmsStrategyTimeConfigMapper // 获取该策略下的时间配置 List getAllTimeConfigByTempId(String templateId); + // 获取指定月份和策略下的时间配置 + List getTimeConfigByTempIdAndMonth(@Param("templateId") String templateId, @Param("month") int month); + // 设置该模版的时间配置为待下发 public void updateTimeConfigWaitingPost(String templateId); diff --git a/ems-system/src/main/resources/mapper/ems/EmsAmmeterDataMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsAmmeterDataMapper.xml index 8b1c6e8..966cb00 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsAmmeterDataMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsAmmeterDataMapper.xml @@ -1054,6 +1054,7 @@ t.site_id, t.device_id, t.data_update_time, + t.total_active_power, t.current_forward_active_total, t.current_reverse_active_total FROM ems_ammeter_data t diff --git a/ems-system/src/main/resources/mapper/ems/EmsPcsDataMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsPcsDataMapper.xml index 0510453..82754b8 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsPcsDataMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsPcsDataMapper.xml @@ -655,4 +655,20 @@ group by deviceId,dateDay,createDate,temp order by dateDay,createDate + \ No newline at end of file diff --git a/ems-system/src/main/resources/mapper/ems/EmsStrategyLogMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsStrategyLogMapper.xml new file mode 100644 index 0000000..485d5d2 --- /dev/null +++ b/ems-system/src/main/resources/mapper/ems/EmsStrategyLogMapper.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + select id, strategy_id, template_id, site_id, device_id, start_time, end_time, charge_discharge_power, charge_status, execution_date, anti_reverse, create_by, create_time, update_by, update_time, remark from ems_strategy_log + + + + + + + + insert into ems_strategy_log + + strategy_id, + template_id, + site_id, + device_id, + start_time, + end_time, + charge_discharge_power, + charge_status, + execution_date, + anti_reverse, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{strategyId}, + #{templateId}, + #{siteId}, + #{deviceId}, + #{startTime}, + #{endTime}, + #{chargeDischargePower}, + #{chargeStatus}, + #{executionDate}, + #{antiReverse}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update ems_strategy_log + + strategy_id = #{strategyId}, + template_id = #{templateId}, + site_id = #{siteId}, + device_id = #{deviceId}, + start_time = #{startTime}, + end_time = #{endTime}, + charge_discharge_power = #{chargeDischargePower}, + charge_status = #{chargeStatus}, + execution_date = #{executionDate}, + anti_reverse = #{antiReverse}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + delete from ems_strategy_log where id = #{id} + + + + delete from ems_strategy_log where id in + + #{id} + + + \ No newline at end of file diff --git a/ems-system/src/main/resources/mapper/ems/EmsStrategyTimeConfigMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsStrategyTimeConfigMapper.xml index 157f534..3138f41 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsStrategyTimeConfigMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsStrategyTimeConfigMapper.xml @@ -134,4 +134,10 @@ and strategy_id = #{strategyId} and month = #{month} + + \ No newline at end of file