重构
This commit is contained in:
@ -10,40 +10,14 @@ 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.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.ProtectionConstraintVo;
|
||||
import com.xzzn.ems.domain.vo.ProtectionSettingVo;
|
||||
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.common.core.modbus.ModbusProcessor;
|
||||
import com.xzzn.common.core.modbus.domain.DeviceConfig;
|
||||
import com.xzzn.common.core.modbus.domain.WriteTagConfig;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
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;
|
||||
@ -51,14 +25,29 @@ 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.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
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);
|
||||
private static final BigDecimal DEFAULT_L1_POWER_RATIO = new BigDecimal("0.5");
|
||||
private static final int CONSTRAINT_TTL_SECONDS = 120;
|
||||
|
||||
@Resource(name = "scheduledExecutorService")
|
||||
private ScheduledExecutorService scheduledExecutorService;
|
||||
@Autowired
|
||||
@ -66,18 +55,11 @@ public class ProtectionPlanTask {
|
||||
@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 ModbusProcessor modbusProcessor;
|
||||
@Autowired
|
||||
private EmsFaultIssueLogMapper emsFaultIssueLogMapper;
|
||||
|
||||
public ProtectionPlanTask(IEmsFaultProtectionPlanService iEmsFaultProtectionPlanService) {
|
||||
this.iEmsFaultProtectionPlanService = iEmsFaultProtectionPlanService;
|
||||
@ -86,303 +68,320 @@ public class ProtectionPlanTask {
|
||||
public void pollPlanList() {
|
||||
Long planId = 0L;
|
||||
try {
|
||||
// 获取所有方案,轮询
|
||||
List<EmsFaultProtectionPlan> planList = iEmsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(null);
|
||||
|
||||
for (EmsFaultProtectionPlan plan : planList) {
|
||||
planId = plan.getId();
|
||||
String siteId = plan.getSiteId();
|
||||
if (StringUtils.isEmpty(siteId)) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
// 保护前提
|
||||
String protectionSettings = plan.getProtectionSettings();
|
||||
final List<ProtectionSettingVo> protSettings = objectMapper.readValue(
|
||||
protectionSettings,
|
||||
new TypeReference<List<ProtectionSettingVo>>() {}
|
||||
);
|
||||
if (protSettings == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理告警保护方案
|
||||
boolean isHighLevel = dealWithProtectionPlan(plan, protSettings);
|
||||
if (isHighLevel) {
|
||||
// 触发最高故障等级-结束循环
|
||||
return;
|
||||
List<ProtectionSettingVo> protSettings = parseProtectionSettings(plan.getProtectionSettings());
|
||||
if (CollectionUtils.isEmpty(protSettings)) {
|
||||
continue;
|
||||
}
|
||||
dealWithProtectionPlan(plan, protSettings);
|
||||
}
|
||||
refreshProtectionConstraintCache(planList);
|
||||
} catch (Exception e) {
|
||||
logger.error("轮询失败,方案id为:{}", planId, e);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理告警保护方案-返回触发下发方案时是否最高等级
|
||||
// 需要同步云端
|
||||
@SyncAfterInsert
|
||||
private boolean dealWithProtectionPlan(EmsFaultProtectionPlan plan, List<ProtectionSettingVo> protSettings) {
|
||||
private void dealWithProtectionPlan(EmsFaultProtectionPlan plan, List<ProtectionSettingVo> protSettings) {
|
||||
logger.info("<轮询保护方案> 站点:{},方案ID:{}", plan.getSiteId(), plan.getId());
|
||||
boolean isHighLevel = false;
|
||||
|
||||
String siteId = plan.getSiteId();
|
||||
final Integer isAlertAlarm = plan.getIsAlert();
|
||||
final Long status = plan.getStatus();
|
||||
// 看方案是否启用,走不同判断
|
||||
Integer isAlertAlarm = plan.getIsAlert();
|
||||
Long status = plan.getStatus();
|
||||
if (status == null) {
|
||||
status = ProtPlanStatus.STOP.getCode();
|
||||
}
|
||||
|
||||
if (Objects.equals(status, ProtPlanStatus.STOP.getCode())) {
|
||||
logger.info("<方案未启用> 站点:{},方案ID:{}", siteId, plan.getId());
|
||||
// 未启用,获取方案的故障值与最新数据判断是否需要下发方案
|
||||
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,plan.getFaultName(),
|
||||
getAlarmLevel(plan.getFaultLevel()));
|
||||
emsAlarmRecordsMapper.insertEmsAlarmRecords(alarmRecords);
|
||||
}
|
||||
|
||||
// 是否有保护方案,有则通过modbus连接设备下发方案
|
||||
String protPlanJson = plan.getProtectionPlan();
|
||||
if (protPlanJson != null && !protPlanJson.isEmpty() && !"[]".equals(protPlanJson)) {
|
||||
logger.info("<下发保护方案> 方案内容:{}", protPlanJson);
|
||||
executeProtectionActions(protPlanJson,siteId,plan.getId(),plan.getFaultLevel()); // 执行Modbus指令
|
||||
}
|
||||
|
||||
// 更新方案状态为“已启用”
|
||||
logger.info("<方案已启用> 方案ID:{}", plan.getId());
|
||||
plan.setStatus(ProtPlanStatus.RUNNING.getCode());
|
||||
emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan);
|
||||
|
||||
// 更新该站点策略为暂停状态
|
||||
updateStrategyRunningStatus(siteId, StrategyStatus.SUSPENDED.getCode());
|
||||
if (checkIsNeedIssuedPlan(protSettings, siteId)) {
|
||||
int faultDelay = safeDelaySeconds(plan.getFaultDelaySeconds(), 0);
|
||||
scheduledExecutorService.schedule(() -> {
|
||||
if (!checkIsNeedIssuedPlan(protSettings, siteId)) {
|
||||
return;
|
||||
}
|
||||
}, faultDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
} else {
|
||||
logger.info("<方案已启用> 站点:{},方案ID:{}", siteId, plan.getId());
|
||||
// 已启用,则获取方案的释放值与最新数据判断是否需要取消方案
|
||||
if(checkIsNeedCancelPlan(protSettings, siteId)){
|
||||
// 延时,
|
||||
int releaseDelay = plan.getReleaseDelaySeconds().intValue();
|
||||
ScheduledFuture<?> delayTask = scheduledExecutorService.schedule(() -> {
|
||||
// 判断是否已存在未处理告警,有着取消
|
||||
if(isAlertAlarm == 1){
|
||||
logger.info("<取消告警>");
|
||||
EmsAlarmRecords emsAlarmRecords = emsAlarmRecordsMapper.getFailedRecord(siteId,
|
||||
plan.getFaultName(),getAlarmLevel(plan.getFaultLevel()));
|
||||
if(emsAlarmRecords != null){
|
||||
emsAlarmRecords.setStatus(AlarmStatus.DONE.getCode());
|
||||
emsAlarmRecordsMapper.updateEmsAlarmRecords(emsAlarmRecords);
|
||||
}
|
||||
if (Integer.valueOf(1).equals(isAlertAlarm)) {
|
||||
EmsAlarmRecords alarmRecords = addAlarmRecord(siteId, plan.getFaultName(), getAlarmLevel(plan.getFaultLevel()));
|
||||
emsAlarmRecordsMapper.insertEmsAlarmRecords(alarmRecords);
|
||||
}
|
||||
// 更新方案状态为“未启用”
|
||||
logger.info("<方案变更为未启用> 方案ID:{}", plan.getId());
|
||||
plan.setStatus(ProtPlanStatus.STOP.getCode());
|
||||
plan.setStatus(ProtPlanStatus.RUNNING.getCode());
|
||||
plan.setUpdateBy("system");
|
||||
emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan);
|
||||
// 更新该站点策略为启用状态
|
||||
updateStrategyRunningStatus(siteId, StrategyStatus.RUNNING.getCode());
|
||||
}, releaseDelay, TimeUnit.SECONDS);
|
||||
refreshSiteProtectionConstraint(siteId);
|
||||
}, faultDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
return isHighLevel;
|
||||
}
|
||||
|
||||
// 下发保护方案
|
||||
private void executeProtectionActions(String protPlanJson, String siteId, Long planId, Integer faultLevel){
|
||||
final List<ProtectionPlanVo> protPlanList;
|
||||
try {
|
||||
protPlanList = objectMapper.readValue(
|
||||
protPlanJson,
|
||||
new TypeReference<List<ProtectionPlanVo>>() {}
|
||||
);
|
||||
if (protPlanList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历保护方案
|
||||
for (ProtectionPlanVo plan : protPlanList) {
|
||||
if (StringUtils.isEmpty(plan.getDeviceId()) || StringUtils.isEmpty(plan.getPoint())) {
|
||||
return;
|
||||
}
|
||||
// 给设备发送指令记录日志,并同步云端
|
||||
EmsFaultIssueLog faultIssueLog = createLogEntity(plan,siteId);
|
||||
faultIssueLog.setLogLevel(faultLevel);
|
||||
emsFaultIssueLogMapper.insertEmsFaultIssueLog(faultIssueLog);
|
||||
|
||||
// 通过modbus连接设备,发送数据
|
||||
executeSinglePlan(plan,siteId);
|
||||
}
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("下发保护方案失败,方案id为:", planId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private EmsFaultIssueLog createLogEntity(ProtectionPlanVo plan,String siteId) {
|
||||
EmsFaultIssueLog faultIssueLog = new EmsFaultIssueLog();
|
||||
faultIssueLog.setLogId(UUID.randomUUID().toString());
|
||||
faultIssueLog.setLogTime(new Date());
|
||||
faultIssueLog.setSiteId(siteId);
|
||||
faultIssueLog.setDeviceId(plan.getDeviceId());
|
||||
faultIssueLog.setPoint(plan.getPoint());
|
||||
faultIssueLog.setValue(plan.getValue());
|
||||
faultIssueLog.setCreateBy("sys");
|
||||
faultIssueLog.setCreateTime(new Date());
|
||||
return faultIssueLog;
|
||||
}
|
||||
|
||||
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) {
|
||||
logger.warn("设备信息不完整,deviceId:{}", deviceId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建设备配置
|
||||
DeviceConfig config = new DeviceConfig();
|
||||
config.setHost(device.getIpAddress());
|
||||
config.setPort(device.getIpPort().intValue());
|
||||
config.setSlaveId(device.getSlaveId().intValue());
|
||||
config.setDeviceName(device.getDeviceName());
|
||||
config.setDeviceNumber(device.getDeviceId());
|
||||
|
||||
// 构建写入标签配置
|
||||
WriteTagConfig writeTag = new WriteTagConfig();
|
||||
writeTag.setAddress(plan.getPoint());
|
||||
writeTag.setValue(plan.getValue());
|
||||
|
||||
List<WriteTagConfig> writeTags = new ArrayList<>();
|
||||
writeTags.add(writeTag);
|
||||
config.setWriteTags(writeTags);
|
||||
|
||||
// 写入数据到设备
|
||||
boolean success = modbusProcessor.writeDataToDeviceWithRetry(config);
|
||||
|
||||
if (!success) {
|
||||
logger.error("写入失败,设备地址:{}", device.getIpAddress());
|
||||
if (checkIsNeedCancelPlan(protSettings, siteId)) {
|
||||
int releaseDelay = safeDelaySeconds(plan.getReleaseDelaySeconds(), 0);
|
||||
scheduledExecutorService.schedule(() -> {
|
||||
if (Integer.valueOf(1).equals(isAlertAlarm)) {
|
||||
EmsAlarmRecords emsAlarmRecords = emsAlarmRecordsMapper.getFailedRecord(
|
||||
siteId,
|
||||
plan.getFaultName(),
|
||||
getAlarmLevel(plan.getFaultLevel())
|
||||
);
|
||||
if (emsAlarmRecords != null) {
|
||||
emsAlarmRecords.setStatus(AlarmStatus.DONE.getCode());
|
||||
emsAlarmRecordsMapper.updateEmsAlarmRecords(emsAlarmRecords);
|
||||
}
|
||||
}
|
||||
plan.setStatus(ProtPlanStatus.STOP.getCode());
|
||||
plan.setUpdateBy("system");
|
||||
emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan);
|
||||
refreshSiteProtectionConstraint(siteId);
|
||||
}, releaseDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
// 校验释放值是否取消方案
|
||||
private boolean checkIsNeedCancelPlan(List<ProtectionSettingVo> protSettings, String siteId) {
|
||||
BigDecimal releaseValue = BigDecimal.ZERO;
|
||||
private int safeDelaySeconds(Long delay, int defaultSeconds) {
|
||||
if (delay == null || delay < 0) {
|
||||
return defaultSeconds;
|
||||
}
|
||||
return delay.intValue();
|
||||
}
|
||||
|
||||
private List<ProtectionSettingVo> parseProtectionSettings(String settingsJson) {
|
||||
if (StringUtils.isEmpty(settingsJson)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(settingsJson, new TypeReference<List<ProtectionSettingVo>>() {});
|
||||
} catch (Exception e) {
|
||||
logger.error("解析保护前提失败,json:{}", settingsJson, e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshProtectionConstraintCache(List<EmsFaultProtectionPlan> allPlans) {
|
||||
Map<String, List<EmsFaultProtectionPlan>> planBySite = new HashMap<>();
|
||||
for (EmsFaultProtectionPlan plan : allPlans) {
|
||||
if (StringUtils.isEmpty(plan.getSiteId())) {
|
||||
continue;
|
||||
}
|
||||
planBySite.computeIfAbsent(plan.getSiteId(), k -> new ArrayList<>()).add(plan);
|
||||
}
|
||||
for (Map.Entry<String, List<EmsFaultProtectionPlan>> entry : planBySite.entrySet()) {
|
||||
writeSiteProtectionConstraint(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshSiteProtectionConstraint(String siteId) {
|
||||
if (StringUtils.isEmpty(siteId)) {
|
||||
return;
|
||||
}
|
||||
EmsFaultProtectionPlan query = new EmsFaultProtectionPlan();
|
||||
query.setSiteId(siteId);
|
||||
List<EmsFaultProtectionPlan> sitePlans = iEmsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(query);
|
||||
writeSiteProtectionConstraint(siteId, sitePlans);
|
||||
}
|
||||
|
||||
private void writeSiteProtectionConstraint(String siteId, List<EmsFaultProtectionPlan> sitePlans) {
|
||||
List<EmsFaultProtectionPlan> runningPlans = new ArrayList<>();
|
||||
for (EmsFaultProtectionPlan plan : sitePlans) {
|
||||
if (Objects.equals(plan.getStatus(), ProtPlanStatus.RUNNING.getCode())) {
|
||||
runningPlans.add(plan);
|
||||
}
|
||||
}
|
||||
|
||||
String key = RedisKeyConstants.PROTECTION_CONSTRAINT + siteId;
|
||||
if (runningPlans.isEmpty()) {
|
||||
redisCache.deleteObject(key);
|
||||
return;
|
||||
}
|
||||
|
||||
ProtectionConstraintVo merged = ProtectionConstraintVo.empty();
|
||||
for (EmsFaultProtectionPlan runningPlan : runningPlans) {
|
||||
ProtectionConstraintVo single = buildConstraintFromPlan(runningPlan);
|
||||
mergeConstraint(merged, single);
|
||||
}
|
||||
merged.setUpdateAt(System.currentTimeMillis());
|
||||
redisCache.setCacheObject(key, merged, CONSTRAINT_TTL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private ProtectionConstraintVo buildConstraintFromPlan(EmsFaultProtectionPlan plan) {
|
||||
ProtectionConstraintVo vo = ProtectionConstraintVo.empty();
|
||||
int level = plan.getFaultLevel() == null ? 0 : plan.getFaultLevel();
|
||||
vo.setLevel(level);
|
||||
vo.setSourcePlanIds(new ArrayList<>());
|
||||
vo.getSourcePlanIds().add(plan.getId());
|
||||
|
||||
if (level == 1) {
|
||||
vo.setPowerLimitRatio(DEFAULT_L1_POWER_RATIO);
|
||||
} else if (level >= 3) {
|
||||
vo.setForceStop(true);
|
||||
vo.setForceStandby(true);
|
||||
vo.setAllowCharge(false);
|
||||
vo.setAllowDischarge(false);
|
||||
vo.setPowerLimitRatio(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
String description = StringUtils.isEmpty(plan.getDescription()) ? "" : plan.getDescription();
|
||||
BigDecimal ratioByDesc = parseDerateRatio(description);
|
||||
if (ratioByDesc != null) {
|
||||
vo.setPowerLimitRatio(minRatio(vo.getPowerLimitRatio(), ratioByDesc));
|
||||
}
|
||||
|
||||
if (description.contains("禁止充放电")) {
|
||||
vo.setAllowCharge(false);
|
||||
vo.setAllowDischarge(false);
|
||||
} else {
|
||||
if (description.contains("禁止充电")) {
|
||||
vo.setAllowCharge(false);
|
||||
}
|
||||
if (description.contains("禁止放电")) {
|
||||
vo.setAllowDischarge(false);
|
||||
}
|
||||
if (description.contains("允许充电")) {
|
||||
vo.setAllowCharge(true);
|
||||
}
|
||||
if (description.contains("允许放电")) {
|
||||
vo.setAllowDischarge(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (description.contains("待机")) {
|
||||
vo.setForceStandby(true);
|
||||
}
|
||||
if (description.contains("停机") || description.contains("切断") || level >= 3) {
|
||||
vo.setForceStop(true);
|
||||
vo.setForceStandby(true);
|
||||
vo.setAllowCharge(false);
|
||||
vo.setAllowDischarge(false);
|
||||
vo.setPowerLimitRatio(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
private BigDecimal parseDerateRatio(String text) {
|
||||
if (StringUtils.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
Matcher m = Pattern.compile("降功率\\s*(\\d+(?:\\.\\d+)?)%")
|
||||
.matcher(text);
|
||||
if (!m.find()) {
|
||||
return null;
|
||||
}
|
||||
BigDecimal percent = new BigDecimal(m.group(1));
|
||||
if (percent.compareTo(BigDecimal.ZERO) < 0) {
|
||||
return null;
|
||||
}
|
||||
return percent.divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
private void mergeConstraint(ProtectionConstraintVo merged, ProtectionConstraintVo incoming) {
|
||||
if (incoming == null) {
|
||||
return;
|
||||
}
|
||||
merged.setLevel(Math.max(nullSafeInt(merged.getLevel()), nullSafeInt(incoming.getLevel())));
|
||||
merged.setAllowCharge(boolAnd(merged.getAllowCharge(), incoming.getAllowCharge()));
|
||||
merged.setAllowDischarge(boolAnd(merged.getAllowDischarge(), incoming.getAllowDischarge()));
|
||||
merged.setForceStandby(boolOr(merged.getForceStandby(), incoming.getForceStandby()));
|
||||
merged.setForceStop(boolOr(merged.getForceStop(), incoming.getForceStop()));
|
||||
merged.setPowerLimitRatio(minRatio(merged.getPowerLimitRatio(), incoming.getPowerLimitRatio()));
|
||||
|
||||
if (incoming.getSourcePlanIds() != null && !incoming.getSourcePlanIds().isEmpty()) {
|
||||
if (merged.getSourcePlanIds() == null) {
|
||||
merged.setSourcePlanIds(new ArrayList<>());
|
||||
}
|
||||
merged.getSourcePlanIds().addAll(incoming.getSourcePlanIds());
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal minRatio(BigDecimal a, BigDecimal b) {
|
||||
BigDecimal left = a == null ? BigDecimal.ONE : a;
|
||||
BigDecimal right = b == null ? BigDecimal.ONE : b;
|
||||
return left.min(right);
|
||||
}
|
||||
|
||||
private int nullSafeInt(Integer value) {
|
||||
return value == null ? 0 : value;
|
||||
}
|
||||
|
||||
private Boolean boolAnd(Boolean a, Boolean b) {
|
||||
boolean left = a == null || a;
|
||||
boolean right = b == null || b;
|
||||
return left && right;
|
||||
}
|
||||
|
||||
private Boolean boolOr(Boolean a, Boolean b) {
|
||||
boolean left = a != null && a;
|
||||
boolean right = b != null && b;
|
||||
return left || right;
|
||||
}
|
||||
|
||||
private boolean checkIsNeedCancelPlan(List<ProtectionSettingVo> protSettings, String siteId) {
|
||||
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())){
|
||||
BigDecimal releaseValue = vo.getReleaseValue();
|
||||
if (StringUtils.isEmpty(deviceId)
|
||||
|| StringUtils.isEmpty(point)
|
||||
|| releaseValue == null
|
||||
|| StringUtils.isEmpty(vo.getReleaseOperator())) {
|
||||
return false;
|
||||
}
|
||||
// 获取点位最新值
|
||||
BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId);
|
||||
logger.info("checkIsNeedCancelPlan 点位:{},最新值:{},比较方式:{},释放值:{}", point, lastPointValue, vo.getReleaseOperator(), releaseValue);
|
||||
if(lastPointValue == null){
|
||||
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(" ");
|
||||
conditionSb.append(" ").append(vo.getRelationNext()).append(" ");
|
||||
}
|
||||
|
||||
}
|
||||
// 执行比较语句
|
||||
return executeWithParser(conditionSb.toString());
|
||||
}
|
||||
|
||||
// 校验故障值是否需要下发方案
|
||||
private boolean checkIsNeedIssuedPlan(List<ProtectionSettingVo> protSettings, String siteId) {
|
||||
BigDecimal faultValue = BigDecimal.ZERO;
|
||||
|
||||
StringBuilder conditionSb = new StringBuilder();
|
||||
for (int i = 0; i < protSettings.size(); i++) {
|
||||
ProtectionSettingVo vo = protSettings.get(i);
|
||||
String deviceId = vo.getDeviceId();
|
||||
String point = vo.getPoint();
|
||||
faultValue = vo.getFaultValue();
|
||||
if(StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(point) || faultValue == null
|
||||
|| StringUtils.isEmpty(vo.getFaultOperator())){
|
||||
BigDecimal faultValue = vo.getFaultValue();
|
||||
if (StringUtils.isEmpty(deviceId)
|
||||
|| StringUtils.isEmpty(point)
|
||||
|| faultValue == null
|
||||
|| StringUtils.isEmpty(vo.getFaultOperator())) {
|
||||
return false;
|
||||
}
|
||||
// 获取点位最新值
|
||||
BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId);
|
||||
logger.info("checkIsNeedIssuedPlan 点位:{},最新值:{},比较方式:{},故障值:{}", point, lastPointValue, vo.getFaultOperator(), faultValue);
|
||||
if(lastPointValue == null){
|
||||
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(" ");
|
||||
conditionSb.append(" ").append(vo.getRelationNext()).append(" ");
|
||||
}
|
||||
|
||||
}
|
||||
// 执行比较语句
|
||||
return executeWithParser(conditionSb.toString());
|
||||
}
|
||||
|
||||
private BigDecimal getPointLastValue(String deviceId, String point, String siteId) {
|
||||
JSONObject mqttJson = redisCache.getCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceId);
|
||||
if(mqttJson == null){
|
||||
if (mqttJson == null) {
|
||||
return null;
|
||||
}
|
||||
String jsonData = mqttJson.get("Data").toString();
|
||||
if(StringUtils.isEmpty(jsonData)){
|
||||
if (StringUtils.isEmpty(jsonData)) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> obj = JSON.parseObject(jsonData, new com.alibaba.fastjson2.TypeReference<Map<String, Object>>() {});
|
||||
return StringUtils.getBigDecimal(obj.get(point));
|
||||
}
|
||||
|
||||
// 更新站点策略为启用
|
||||
private void 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<EmsStrategyRunning> strategyRunningList = emsStrategyRunningMapper.selectEmsStrategyRunningList(query);
|
||||
if (CollectionUtils.isNotEmpty(strategyRunningList)) {
|
||||
// 获取已存在并且状态为:未启用和已暂停的最晚一条策略,更新为已启用
|
||||
strategyRunningList.forEach(emsStrategyRunning -> {
|
||||
emsStrategyRunning.setStatus(status);
|
||||
emsStrategyRunningMapper.updateEmsStrategyRunning(emsStrategyRunning);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新站点策略为启用
|
||||
private void updateStrategyRunning(String siteId) {
|
||||
// 获取是否有正在运行的策略,如果有则不更改
|
||||
EmsStrategyRunning emsStrategyRunning = emsStrategyRunningMapper.getRunningStrategy(siteId);
|
||||
if (emsStrategyRunning == null) {
|
||||
// 获取已存在并且状态为:未启用和已暂停的最晚一条策略,更新为已启用
|
||||
emsStrategyRunning = emsStrategyRunningMapper.getPendingStrategy(siteId);
|
||||
emsStrategyRunning.setStatus(StrategyStatus.RUNNING.getCode());
|
||||
emsStrategyRunningMapper.updateEmsStrategyRunning(emsStrategyRunning);
|
||||
}
|
||||
}
|
||||
|
||||
private EmsAlarmRecords addAlarmRecord(String siteId, String content,String level) {
|
||||
private EmsAlarmRecords addAlarmRecord(String siteId, String content, String level) {
|
||||
EmsAlarmRecords emsAlarmRecords = new EmsAlarmRecords();
|
||||
emsAlarmRecords.setSiteId(siteId);
|
||||
emsAlarmRecords.setAlarmContent(content);
|
||||
@ -395,29 +394,31 @@ public class ProtectionPlanTask {
|
||||
return emsAlarmRecords;
|
||||
}
|
||||
|
||||
// 故障等级-告警等级匹配
|
||||
private String getAlarmLevel(Integer faultLevel) {
|
||||
if (ObjectUtils.isEmpty(faultLevel) || faultLevel < 1 || faultLevel > 3) {
|
||||
logger.warn("非法故障等级:{},默认返回普通告警", faultLevel);
|
||||
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();
|
||||
case 1:
|
||||
return AlarmLevelStatus.GENERAL.getCode();
|
||||
case 2:
|
||||
return AlarmLevelStatus.SERIOUS.getCode();
|
||||
case 3:
|
||||
return AlarmLevelStatus.EMERGENCY.getCode();
|
||||
default:
|
||||
logger.error("未匹配的故障等级:{}", faultLevel);
|
||||
return AlarmLevelStatus.EMERGENCY.getCode();
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义表达式解析器(仅支持简单运算符和逻辑关系)
|
||||
/**
|
||||
* 自定义表达式解析器(仅支持简单运算符和逻辑关系)
|
||||
*/
|
||||
public boolean executeWithParser(String conditionStr) {
|
||||
if (conditionStr == null || conditionStr.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. 拆分逻辑关系(提取 && 或 ||)
|
||||
List<String> logicRelations = new ArrayList<>();
|
||||
Pattern logicPattern = Pattern.compile("(&&|\\|\\|)");
|
||||
Matcher logicMatcher = logicPattern.matcher(conditionStr);
|
||||
@ -425,10 +426,7 @@ public class ProtectionPlanTask {
|
||||
logicRelations.add(logicMatcher.group());
|
||||
}
|
||||
|
||||
// 2. 拆分原子条件(如 "3.55>3.52")
|
||||
String[] atomicConditions = logicPattern.split(conditionStr);
|
||||
|
||||
// 3. 解析每个原子条件并计算结果
|
||||
List<Boolean> atomicResults = new ArrayList<>();
|
||||
Pattern conditionPattern = Pattern.compile("(\\d+\\.?\\d*)\\s*([><]=?|==)\\s*(\\d+\\.?\\d*)");
|
||||
for (String atomic : atomicConditions) {
|
||||
@ -437,11 +435,10 @@ public class ProtectionPlanTask {
|
||||
logger.error("无效的原子条件:{}", atomic);
|
||||
return false;
|
||||
}
|
||||
double left = Double.parseDouble(matcher.group(1)); // 左值(最新值)
|
||||
String operator = matcher.group(2); // 运算符
|
||||
double right = Double.parseDouble(matcher.group(3)); // 右值(故障值)
|
||||
double left = Double.parseDouble(matcher.group(1));
|
||||
String operator = matcher.group(2);
|
||||
double right = Double.parseDouble(matcher.group(3));
|
||||
|
||||
// 执行比较
|
||||
boolean result;
|
||||
switch (operator) {
|
||||
case ">":
|
||||
@ -466,11 +463,10 @@ public class ProtectionPlanTask {
|
||||
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);
|
||||
boolean nextResult = atomicResults.get(i + 1);
|
||||
if ("&&".equals(relation)) {
|
||||
finalResult = finalResult && nextResult;
|
||||
} else if ("||".equals(relation)) {
|
||||
|
||||
@ -2,9 +2,11 @@ package com.xzzn.quartz.task;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.xzzn.common.constant.RedisKeyConstants;
|
||||
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.core.redis.RedisCache;
|
||||
import com.xzzn.common.enums.ChargeStatus;
|
||||
import com.xzzn.common.enums.DeviceCategory;
|
||||
import com.xzzn.common.enums.SiteDevice;
|
||||
@ -20,6 +22,7 @@ import com.xzzn.ems.domain.EmsStrategyRuntimeConfig;
|
||||
import com.xzzn.ems.domain.EmsStrategyLog;
|
||||
import com.xzzn.ems.domain.EmsStrategyTemp;
|
||||
import com.xzzn.ems.domain.EmsStrategyTimeConfig;
|
||||
import com.xzzn.ems.domain.vo.ProtectionConstraintVo;
|
||||
import com.xzzn.ems.domain.vo.StrategyRunningVo;
|
||||
import com.xzzn.ems.mapper.EmsAmmeterDataMapper;
|
||||
import com.xzzn.ems.mapper.EmsBatteryStackMapper;
|
||||
@ -73,6 +76,20 @@ public class StrategyPoller {
|
||||
private static final BigDecimal DEFAULT_ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal(10);
|
||||
// 电网有功功率低于20kW时,强制待机
|
||||
private static final BigDecimal DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD = new BigDecimal(20);
|
||||
// 设定功率倍率,默认10
|
||||
private static final BigDecimal DEFAULT_POWER_SET_MULTIPLIER = new BigDecimal(10);
|
||||
// 保护介入默认开启
|
||||
private static final Integer DEFAULT_PROTECT_INTERVENE_ENABLE = 1;
|
||||
// 一级保护默认降额50%
|
||||
private static final BigDecimal DEFAULT_PROTECT_L1_DERATE_PERCENT = new BigDecimal("50");
|
||||
// 保护约束失效保护时长(秒)
|
||||
private static final Integer DEFAULT_PROTECT_RECOVERY_STABLE_SECONDS = 5;
|
||||
// 三级保护默认锁存开启
|
||||
private static final Integer DEFAULT_PROTECT_L3_LATCH_ENABLE = 1;
|
||||
// 保护冲突策略默认值
|
||||
private static final String DEFAULT_PROTECT_CONFLICT_POLICY = "MAX_LEVEL_WIN";
|
||||
// 保护约束默认功率比例
|
||||
private static final BigDecimal DEFAULT_PROTECTION_RATIO = BigDecimal.ONE;
|
||||
// 除法精度,避免BigDecimal除不尽异常
|
||||
private static final int POWER_SCALE = 4;
|
||||
|
||||
@ -95,6 +112,8 @@ public class StrategyPoller {
|
||||
@Autowired
|
||||
private EmsStrategyRuntimeConfigMapper runtimeConfigMapper;
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
@Autowired
|
||||
private ModbusProcessor modbusProcessor;
|
||||
|
||||
@Resource(name = "modbusExecutor")
|
||||
@ -186,6 +205,7 @@ public class StrategyPoller {
|
||||
}
|
||||
// 判断SOC上下限
|
||||
if (isSocInRange(emsStrategyTemp, runtimeConfig)) {
|
||||
ProtectionConstraintVo protectionConstraint = getProtectionConstraint(siteId);
|
||||
Map<Long, EmsPcsSetting> pcsSettingCache = new HashMap<>();
|
||||
BigDecimal avgChargeDischargePower = emsStrategyTemp.getChargeDischargePower()
|
||||
.divide(new BigDecimal(pcsDeviceList.size()), POWER_SCALE, RoundingMode.HALF_UP);
|
||||
@ -206,13 +226,27 @@ public class StrategyPoller {
|
||||
logger.info("当前站点: {}, PCS设备: {} 未获取电池簇数量", siteId, pcsDevice.getDeviceId());
|
||||
continue;
|
||||
}
|
||||
// 功率默认放大10倍,平均功率值,根据电池簇数量进行平均分配
|
||||
BigDecimal strategyPower = avgChargeDischargePower.multiply(new BigDecimal(10))
|
||||
// 平均功率值根据倍率放大后,再按电池簇数量平均分配
|
||||
BigDecimal strategyPower = avgChargeDischargePower.multiply(runtimeConfig.getPowerSetMultiplier())
|
||||
.divide(new BigDecimal(pcsSetting.getClusterNum()), POWER_SCALE, RoundingMode.HALF_UP);
|
||||
// 根据充电状态,处理数据
|
||||
if (ChargeStatus.CHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
|
||||
StrategyCommandDecision decision = applyProtectionConstraint(
|
||||
strategyPower,
|
||||
ChargeStatus.CHARGING,
|
||||
runtimeConfig,
|
||||
protectionConstraint
|
||||
);
|
||||
// 发送Modbus命令控制设备-充电
|
||||
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.CHARGING, strategyPower, emsStrategyTemp, false, null);
|
||||
sendModbusCommand(
|
||||
Collections.singletonList(pcsDevice),
|
||||
pcsSetting,
|
||||
decision.getChargeStatus(),
|
||||
decision.getPower(),
|
||||
emsStrategyTemp,
|
||||
false,
|
||||
null
|
||||
);
|
||||
} else if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
|
||||
boolean needAntiReverseFlow = false;
|
||||
Integer powerDownType = null;
|
||||
@ -277,13 +311,21 @@ public class StrategyPoller {
|
||||
if (chargeDischargePower.compareTo(BigDecimal.ZERO) < 0) {
|
||||
chargeDischargePower = BigDecimal.ZERO;
|
||||
}
|
||||
if (BigDecimal.ZERO.compareTo(chargeDischargePower) == 0) {
|
||||
StrategyCommandDecision decision = applyProtectionConstraint(
|
||||
chargeDischargePower,
|
||||
ChargeStatus.DISCHARGING,
|
||||
runtimeConfig,
|
||||
protectionConstraint
|
||||
);
|
||||
ChargeStatus finalStatus = decision.getChargeStatus();
|
||||
BigDecimal finalPower = decision.getPower();
|
||||
if (ChargeStatus.STANDBY.equals(finalStatus) || BigDecimal.ZERO.compareTo(finalPower) == 0) {
|
||||
// 如果已经降功率到0,则设备直接待机
|
||||
// 发送Modbus命令控制设备-待机
|
||||
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, needAntiReverseFlow, powerDownType);
|
||||
} else {
|
||||
// 发送Modbus命令控制设备-放电
|
||||
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.DISCHARGING, chargeDischargePower, emsStrategyTemp, needAntiReverseFlow, powerDownType);
|
||||
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, finalStatus, finalPower, emsStrategyTemp, needAntiReverseFlow, powerDownType);
|
||||
}
|
||||
} else {
|
||||
// 发送Modbus命令控制设备-待机
|
||||
@ -503,6 +545,103 @@ public class StrategyPoller {
|
||||
return result;
|
||||
}
|
||||
|
||||
private ProtectionConstraintVo getProtectionConstraint(String siteId) {
|
||||
ProtectionConstraintVo constraint = redisCache.getCacheObject(RedisKeyConstants.PROTECTION_CONSTRAINT + siteId);
|
||||
if (constraint == null) {
|
||||
return ProtectionConstraintVo.empty();
|
||||
}
|
||||
if (constraint.getPowerLimitRatio() == null) {
|
||||
constraint.setPowerLimitRatio(DEFAULT_PROTECTION_RATIO);
|
||||
}
|
||||
if (constraint.getAllowCharge() == null) {
|
||||
constraint.setAllowCharge(true);
|
||||
}
|
||||
if (constraint.getAllowDischarge() == null) {
|
||||
constraint.setAllowDischarge(true);
|
||||
}
|
||||
if (constraint.getForceStandby() == null) {
|
||||
constraint.setForceStandby(false);
|
||||
}
|
||||
if (constraint.getForceStop() == null) {
|
||||
constraint.setForceStop(false);
|
||||
}
|
||||
if (constraint.getLevel() == null) {
|
||||
constraint.setLevel(0);
|
||||
}
|
||||
return constraint;
|
||||
}
|
||||
|
||||
private StrategyCommandDecision applyProtectionConstraint(BigDecimal targetPower,
|
||||
ChargeStatus targetStatus,
|
||||
EmsStrategyRuntimeConfig runtimeConfig,
|
||||
ProtectionConstraintVo constraint) {
|
||||
if (!Integer.valueOf(1).equals(runtimeConfig.getProtectInterveneEnable())) {
|
||||
return new StrategyCommandDecision(targetStatus, safePower(targetPower));
|
||||
}
|
||||
if (constraint == null || nullSafeInt(constraint.getLevel()) <= 0) {
|
||||
return new StrategyCommandDecision(targetStatus, safePower(targetPower));
|
||||
}
|
||||
if (Boolean.TRUE.equals(constraint.getForceStop()) || Boolean.TRUE.equals(constraint.getForceStandby())) {
|
||||
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
|
||||
}
|
||||
if (ChargeStatus.CHARGING.equals(targetStatus) && Boolean.FALSE.equals(constraint.getAllowCharge())) {
|
||||
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
|
||||
}
|
||||
if (ChargeStatus.DISCHARGING.equals(targetStatus) && Boolean.FALSE.equals(constraint.getAllowDischarge())) {
|
||||
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
|
||||
}
|
||||
BigDecimal ratio = getPowerLimitRatio(constraint, runtimeConfig);
|
||||
BigDecimal finalPower = safePower(targetPower).multiply(ratio).setScale(POWER_SCALE, RoundingMode.HALF_UP);
|
||||
if (finalPower.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
|
||||
}
|
||||
return new StrategyCommandDecision(targetStatus, finalPower);
|
||||
}
|
||||
|
||||
private BigDecimal getPowerLimitRatio(ProtectionConstraintVo constraint, EmsStrategyRuntimeConfig runtimeConfig) {
|
||||
BigDecimal ratio = constraint.getPowerLimitRatio();
|
||||
if (ratio == null || ratio.compareTo(BigDecimal.ZERO) < 0 || ratio.compareTo(BigDecimal.ONE) > 0) {
|
||||
ratio = DEFAULT_PROTECTION_RATIO;
|
||||
}
|
||||
if (nullSafeInt(constraint.getLevel()) == 1 && DEFAULT_PROTECTION_RATIO.compareTo(ratio) == 0) {
|
||||
BigDecimal deratePercent = runtimeConfig.getProtectL1DeratePercent();
|
||||
if (deratePercent == null || deratePercent.compareTo(BigDecimal.ZERO) < 0 || deratePercent.compareTo(new BigDecimal("100")) > 0) {
|
||||
deratePercent = DEFAULT_PROTECT_L1_DERATE_PERCENT;
|
||||
}
|
||||
ratio = deratePercent.divide(new BigDecimal("100"), POWER_SCALE, RoundingMode.HALF_UP);
|
||||
}
|
||||
return ratio;
|
||||
}
|
||||
|
||||
private BigDecimal safePower(BigDecimal power) {
|
||||
if (power == null || power.compareTo(BigDecimal.ZERO) < 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return power;
|
||||
}
|
||||
|
||||
private int nullSafeInt(Integer value) {
|
||||
return value == null ? 0 : value;
|
||||
}
|
||||
|
||||
private static class StrategyCommandDecision {
|
||||
private final ChargeStatus chargeStatus;
|
||||
private final BigDecimal power;
|
||||
|
||||
private StrategyCommandDecision(ChargeStatus chargeStatus, BigDecimal power) {
|
||||
this.chargeStatus = chargeStatus;
|
||||
this.power = power;
|
||||
}
|
||||
|
||||
public ChargeStatus getChargeStatus() {
|
||||
return chargeStatus;
|
||||
}
|
||||
|
||||
public BigDecimal getPower() {
|
||||
return power;
|
||||
}
|
||||
}
|
||||
|
||||
// 判断当前时间是否在时间范围内
|
||||
private static boolean isTimeInRange(LocalTime now, Date startTime, Date endTime) {
|
||||
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
|
||||
@ -573,6 +712,26 @@ public class StrategyPoller {
|
||||
if (config.getAntiReverseHardStopThreshold() == null) {
|
||||
config.setAntiReverseHardStopThreshold(DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD);
|
||||
}
|
||||
if (config.getPowerSetMultiplier() == null || config.getPowerSetMultiplier().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
config.setPowerSetMultiplier(DEFAULT_POWER_SET_MULTIPLIER);
|
||||
}
|
||||
if (config.getProtectInterveneEnable() == null) {
|
||||
config.setProtectInterveneEnable(DEFAULT_PROTECT_INTERVENE_ENABLE);
|
||||
}
|
||||
if (config.getProtectL1DeratePercent() == null
|
||||
|| config.getProtectL1DeratePercent().compareTo(BigDecimal.ZERO) < 0
|
||||
|| config.getProtectL1DeratePercent().compareTo(new BigDecimal("100")) > 0) {
|
||||
config.setProtectL1DeratePercent(DEFAULT_PROTECT_L1_DERATE_PERCENT);
|
||||
}
|
||||
if (config.getProtectRecoveryStableSeconds() == null || config.getProtectRecoveryStableSeconds() < 0) {
|
||||
config.setProtectRecoveryStableSeconds(DEFAULT_PROTECT_RECOVERY_STABLE_SECONDS);
|
||||
}
|
||||
if (config.getProtectL3LatchEnable() == null) {
|
||||
config.setProtectL3LatchEnable(DEFAULT_PROTECT_L3_LATCH_ENABLE);
|
||||
}
|
||||
if (StringUtils.isEmpty(config.getProtectConflictPolicy())) {
|
||||
config.setProtectConflictPolicy(DEFAULT_PROTECT_CONFLICT_POLICY);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user