Files
emsback/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java
2025-12-30 14:36:11 +08:00

363 lines
19 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.xzzn.quartz.task;
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.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.DeviceUpdateRequest;
import com.xzzn.ems.domain.vo.StrategyRunningVo;
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 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;
@Component("strategyPoller")
public class StrategyPoller {
private static final Logger logger = LoggerFactory.getLogger(StrategyPoller.class);
// 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);
@Autowired
private EmsStrategyRunningMapper emsStrategyRunningMapper;
@Autowired
private EmsStrategyTempMapper emsStrategyTempMapper;
@Autowired
private EmsStrategyTimeConfigMapper emsStrategyTimeConfigMapper;
@Autowired
private EmsBatteryStackMapper emsBatteryStackMapper;
@Autowired
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("开始执行运行策略数据轮询...");
List<StrategyRunningVo> strategyRunningVoList = emsStrategyRunningMapper.getPendingPollerStrategy(null);
strategyRunningVoList.forEach(strategyVo -> {
try {
CompletableFuture.runAsync(() -> {
processData(strategyVo);
})
.exceptionally(e -> {
logger.error("运行策略{}轮询异常", strategyVo.getId(), e);
return null;
});
} catch (Exception e) {
logger.error("运行策略{}任务失败", strategyVo.getId(), e);
}
});
}
// 处理获取到的运行策略数据modbus发送指定的命令控制设备
private void processData(StrategyRunningVo strategyVo) {
logger.info("运行策略数据处理开始");
// 根据运行策略获取主副策略的模板数据
Long mainStrategyId = strategyVo.getMainStrategyId();
Long auxStrategyId = strategyVo.getAuxStrategyId();
String siteId = strategyVo.getSiteId();
// 处理主策略数据
if (mainStrategyId != null && StringUtils.isNotBlank(siteId)) {
dealStrategyCurveData(mainStrategyId, siteId);
}
// 处理副策略数据
if (auxStrategyId != null && StringUtils.isNotBlank(siteId)) {
dealStrategyCurveData(auxStrategyId, siteId);
}
logger.info("运行策略轮询处理结束");
}
private void dealStrategyCurveData(Long strategyId, String siteId) {
// 1.获取当前策略的所有模板
List<Map<String, String>> temps = emsStrategyTempMapper.getTempNameList(strategyId, siteId);
if (CollectionUtils.isEmpty(temps)) {
logger.info("当前站点: {}, 策略: {} 没有模板数据", siteId, strategyId);
return;
}
for (Map<String, String> temp : temps) {
// 2.查询当月配置的运行策略
String tempId = temp.get("templateId");
int month = LocalDateTime.now().getMonthValue();
List<EmsStrategyTimeConfig> timeConfigs = emsStrategyTimeConfigMapper.getTimeConfigByTempIdAndMonth(tempId, month);
if (CollectionUtils.isEmpty(timeConfigs)) {
continue;
}
logger.info("当前站点: {}, 策略: {}, {}月配置模版:{}", siteId, strategyId, month, tempId);
// 3.查询当月配置的运行策略时间阶段数据
List<EmsStrategyTemp> 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<EmsPcsData> 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<EmsStrategyLog> 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);
}
}
} else {
// 发送Modbus命令控制设备-待机
sendModbusCommand(pcsDataList, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, false);
}
}
}
}
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<EmsStrategyLog> 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);
}
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);
// 判断电网电表正向有功功率是否小于36kW(接近30kW的20%范围)
return emsAmmeterData.getTotalActivePower().compareTo(upperLimit) < 0;
}
public List<WriteTagConfig> getWriteTags(List<EmsPointMatch> pointMatchList,
ChargeStatus chargeStatus, BigDecimal chargeDischargePower) {
List<WriteTagConfig> 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);
// } else {
// // 待机状态-电池簇PCS有功功率给定置0
// writeTag.setValue(chargeDischargePower);
// }
writeTag.setValue(chargeDischargePower);
writeTags.add(writeTag);
}
return writeTags;
}
private List<String> getMatchFields(Integer clusterNum) {
List<String> 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<EmsPointMatch> 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<EmsPcsData> pcsDataList, ChargeStatus chargeStatus, BigDecimal chargeDischargePower,
EmsStrategyTemp emsStrategyTemp, boolean needAntiReverseFlow) {
for (EmsPcsData pcsData : pcsDataList) {
List<EmsStrategyLog> 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;
}
}