363 lines
19 KiB
Java
363 lines
19 KiB
Java
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;
|
||
}
|
||
|
||
} |