运行策略防逆流逻辑修改

This commit is contained in:
zq
2026-01-12 13:25:34 +08:00
parent 2fe5d7b8a1
commit ebc06e2a2d
4 changed files with 181 additions and 49 deletions

View File

@ -9,6 +9,7 @@ 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.enums.WorkStatus;
import com.xzzn.common.utils.DateUtils;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsAmmeterData;
@ -57,6 +58,8 @@ public class StrategyPoller {
private static final BigDecimal ANTI_REVERSE_THRESHOLD = new BigDecimal(30);
// 逆变器下限值范围默认为20%
private static final BigDecimal ANTI_REVERSE_RANGE_PERCENT = new BigDecimal(20);
// 逆变器功率上限值默认为50kW
private static final BigDecimal ANTI_REVERSE_UP = new BigDecimal(50);
// PCS功率降幅默认为10%
private static final BigDecimal ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal(10);
@ -170,47 +173,74 @@ public class StrategyPoller {
// 根据充电状态,处理数据
if (ChargeStatus.CHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
// 发送Modbus命令控制设备-充电
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.CHARGING, avgChargeDischargePower, emsStrategyTemp, false);
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.CHARGING, avgChargeDischargePower, emsStrategyTemp, false, null);
} 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(pcsDevice.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));
boolean needAntiReverseFlow = false;
Integer powerDownType = null;
BigDecimal chargeDischargePower = avgChargeDischargePower;
// 查询策略运行日志
EmsStrategyLog lastStrategyLog = getLastStrategyLog(pcsDevice.getDeviceId(), emsStrategyTemp);
if (lastStrategyLog != null) {
// 如果当前时间段已经进入待机状态,则不处理
if (ChargeStatus.STANDBY.getCode().equals(lastStrategyLog.getChargeStatus())) {
continue;
}
avgChargeDischargePower = avgChargeDischargePower.subtract(avgChargeDischargePower.multiply(powerDown).divide(new BigDecimal(100)));
chargeDischargePower = lastStrategyLog.getChargeDischargePower();
powerDownType = lastStrategyLog.getPowerDownType();
}
if (BigDecimal.ZERO.compareTo(avgChargeDischargePower) == 0) {
// 查询电网电表的正向有功功率
EmsAmmeterData emsAmmeterData = emsAmmeterDataMapper.getLastData(emsStrategyTemp.getSiteId(), SiteDevice.LOAD.name());
if (emsAmmeterData == null || emsAmmeterData.getTotalActivePower() == null) {
logger.info("当前站点: {}, 未获取到最新电表数据", emsStrategyTemp.getSiteId());
} else {
// 判断是否需要防逆流
needAntiReverseFlow = isNeedAntiReverseFlow(emsAmmeterData.getTotalActivePower());
BigDecimal power = avgChargeDischargePower.multiply(ANTI_REVERSE_POWER_DOWN_PERCENT).divide(new BigDecimal(100));
if (needAntiReverseFlow) {
// 降功率
chargeDischargePower = chargeDischargePower.subtract(power);
powerDownType = 0;
} else {
// 判断是否需要增加功率
if (powerDownType != null && emsAmmeterData.getTotalActivePower().compareTo(ANTI_REVERSE_UP) < 0) {
if (chargeDischargePower.compareTo(avgChargeDischargePower) == 0) {
// 功率增加到平均值则停止
continue;
}
// 增加功率
chargeDischargePower = chargeDischargePower.add(power);
powerDownType = 1;
needAntiReverseFlow = true;
}
}
}
if (BigDecimal.ZERO.compareTo(chargeDischargePower) == 0) {
// 如果已经降功率到0则设备直接待机
// 发送Modbus命令控制设备-待机
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, needAntiReverseFlow);
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, needAntiReverseFlow, powerDownType);
} else {
// 发送Modbus命令控制设备-放电
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.DISCHARGING, avgChargeDischargePower, emsStrategyTemp, needAntiReverseFlow);
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.DISCHARGING, chargeDischargePower, emsStrategyTemp, needAntiReverseFlow, powerDownType);
}
} else {
// 发送Modbus命令控制设备-待机
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, false);
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, false, null);
}
}
} else {
// 发送Modbus命令控制设备-待机
sendModbusCommand(pcsDeviceList, null, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, false);
sendModbusCommand(pcsDeviceList, null, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, false, null);
}
}
}
}
private void saveStrategyLog(String deviceId, BigDecimal chargeDischargePower, String chargeStatus,
EmsStrategyTemp strategyTemp, boolean needAntiReverseFlow) {
EmsStrategyTemp strategyTemp, boolean needAntiReverseFlow, Integer powerDownType) {
EmsStrategyLog log = new EmsStrategyLog();
log.setStrategyId(strategyTemp.getStrategyId());
log.setTemplateId(strategyTemp.getTemplateId());
@ -222,6 +252,7 @@ public class StrategyPoller {
log.setChargeStatus(chargeStatus);
log.setExecutionDate(DateUtils.toDate(LocalDateTime.now()));
log.setAntiReverse(needAntiReverseFlow ? 1 : 0);
log.setPowerDownType(powerDownType);
emsStrategyLogMapper.insertEmsStrategyLog(log);
}
@ -240,49 +271,72 @@ public class StrategyPoller {
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;
}
private EmsStrategyLog getLastStrategyLog(String deviceId, EmsStrategyTemp strategyTemp) {
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.setExecutionDate(DateUtils.toDate(LocalDateTime.now()));
return emsStrategyLogMapper.getLastStrategyLog(query);
}
private boolean isNeedAntiReverseFlow(BigDecimal totalActivePower) {
// 获取当前设定的防逆流阈值(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;
return totalActivePower.compareTo(upperLimit) < 0;
}
public List<WriteTagConfig> getWriteTags(EmsPcsSetting pcsSetting,
ChargeStatus chargeStatus, BigDecimal chargeDischargePower) {
public List<WriteTagConfig> getSwitchDeviceWriteTags(EmsPcsSetting pcsSetting, String workStatus) {
List<WriteTagConfig> writeTags = new ArrayList<>();
BigDecimal power;
WriteTagConfig writeTag = new WriteTagConfig();
writeTag.setAddress(pcsSetting.getPointAddress());
if (WorkStatus.NORMAL.getCode().equals(workStatus)) {
// 开机
writeTag.setValue(pcsSetting.getStartCommand());
power = pcsSetting.getStartPower();
} else {
// 关机
writeTag.setValue(pcsSetting.getStopCommand());
power = pcsSetting.getStopPower();
}
writeTags.add(writeTag);
JSONArray array = JSON.parseArray(pcsSetting.getClusterPointAddress());
for (int i = 0; i < pcsSetting.getClusterNum(); i++) {
Object clusterPointAddress = array.get(i);
WriteTagConfig clusterWriteTag = new WriteTagConfig();
clusterWriteTag.setAddress(String.valueOf(clusterPointAddress));
// 电池簇PCS有功功率给定置0
if (power == null) {
power = BigDecimal.ZERO;
}
clusterWriteTag.setValue(power);
writeTags.add(clusterWriteTag);
}
return writeTags;
}
public List<WriteTagConfig> getWriteTags(EmsPcsSetting pcsSetting, BigDecimal chargeDischargePower) {
List<WriteTagConfig> writeTags = new ArrayList<>();
JSONArray array = JSON.parseArray(pcsSetting.getClusterPointAddress());
for (int i = 0; i < pcsSetting.getClusterNum(); i++) {
Object clusterPointAddress = array.get(i);
WriteTagConfig clusterWriteTag = new WriteTagConfig();
clusterWriteTag.setAddress(String.valueOf(clusterPointAddress));
// if (ChargeStatus.CHARGING.equals(chargeStatus)) {
// writeTag.setValue(chargeDischargePower);
// } else if (ChargeStatus.DISCHARGING.equals(chargeStatus)) {
// writeTag.setValue(chargeDischargePower);
// } else {
// // 待机状态-电池簇PCS有功功率给定置0
// writeTag.setValue(chargeDischargePower);
// }
clusterWriteTag.setValue(chargeDischargePower);
writeTags.add(clusterWriteTag);
}
return writeTags;
}
public DeviceConfig getDeviceConfig(String siteId, String deviceId, EmsPcsSetting pcsSetting, ChargeStatus chargeStatus, BigDecimal chargeDischargePower) {
EmsDevicesSetting device = emsDevicesMapper.getDeviceBySiteAndDeviceId(deviceId, siteId);
if (device == null) {
logger.info("当前站点: {}, PCS设备: {} 未找到对应设备配置信息", siteId, deviceId);
return null;
}
public DeviceConfig getDeviceConfig(String siteId, String deviceId, EmsDevicesSetting device, EmsPcsSetting pcsSetting, BigDecimal chargeDischargePower, int writeType) {
if (Objects.isNull(pcsSetting)) {
pcsSetting = emsPcsSettingMapper.selectEmsPcsSettingByDeviceId(device.getId());
if (pcsSetting == null) {
@ -290,27 +344,47 @@ public class StrategyPoller {
return null;
}
}
if (device.getIpPort() == null || device.getSlaveId() == null) {
logger.info("当前站点: {}, PCS设备: {} 未配置IP端口或从站号", siteId, deviceId);
return null;
}
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(pcsSetting, chargeStatus, chargeDischargePower));
deviceConfig.setWriteTags(writeType == 0 ? getWriteTags(pcsSetting, chargeDischargePower) : getSwitchDeviceWriteTags(pcsSetting, device.getWorkStatus()));
return deviceConfig;
}
private void sendModbusCommand(List<EmsDevicesSetting> pcsDeviceList, EmsPcsSetting pcsSetting, ChargeStatus chargeStatus, BigDecimal chargeDischargePower,
EmsStrategyTemp emsStrategyTemp, boolean needAntiReverseFlow) {
EmsStrategyTemp emsStrategyTemp, boolean needAntiReverseFlow, Integer powerDownType) {
for (EmsDevicesSetting pcsDevice : pcsDeviceList) {
String siteId = pcsDevice.getSiteId();
String deviceId = pcsDevice.getDeviceId();
List<EmsStrategyLog> strategyLogList = getStrategyLog(deviceId, chargeStatus.getCode(), emsStrategyTemp, needAntiReverseFlow);
if (CollectionUtils.isNotEmpty(strategyLogList) && !ChargeStatus.DISCHARGING.equals(chargeStatus) && !needAntiReverseFlow) {
if (CollectionUtils.isNotEmpty(strategyLogList) && (!ChargeStatus.DISCHARGING.equals(chargeStatus) || !needAntiReverseFlow)) {
logger.info("当前站点: {}, PCS设备: {} 当前时间段已存在策略执行记录,不再重复执行", siteId, deviceId);
continue;
}
DeviceConfig deviceConfig = getDeviceConfig(siteId, deviceId, pcsSetting, chargeStatus, chargeDischargePower);
// 每次操作先判断设备工作状态
if (WorkStatus.ABNORMAL.getCode().equals(pcsDevice.getWorkStatus())) {
// 设备故障,不发送指令
continue;
} else if (WorkStatus.STOP.getCode().equals(pcsDevice.getWorkStatus())) {
// 设备停机
if (ChargeStatus.STANDBY.equals(chargeStatus)) {
// 待机,则不写入功率值
continue;
} else {
// 充、放电,则先开机设备
switchDevice(pcsDevice, pcsSetting, WorkStatus.NORMAL);
}
}
DeviceConfig deviceConfig = getDeviceConfig(siteId, deviceId, pcsDevice, pcsSetting, chargeDischargePower, 0);
if (deviceConfig == null) {
continue;
}
@ -318,8 +392,27 @@ public class StrategyPoller {
if (!result) {
logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceId, chargeStatus.getInfo());
}
if (ChargeStatus.STANDBY.equals(chargeStatus)) {
// 待机,先写功率值,再关机
switchDevice(pcsDevice, pcsSetting, WorkStatus.STOP);
}
// 记录策略执行日志
saveStrategyLog(deviceId, chargeDischargePower, chargeStatus.getCode(), emsStrategyTemp, needAntiReverseFlow);
saveStrategyLog(deviceId, chargeDischargePower, chargeStatus.getCode(), emsStrategyTemp, needAntiReverseFlow, powerDownType);
}
}
//设备开关机
private void switchDevice(EmsDevicesSetting pcsDevice, EmsPcsSetting pcsSetting, WorkStatus workStatus) {
String siteId = pcsDevice.getSiteId();
String deviceId = pcsDevice.getDeviceId();
pcsDevice.setWorkStatus(workStatus.getCode());
DeviceConfig deviceConfig = getDeviceConfig(siteId, deviceId, pcsDevice, pcsSetting , null, 0);
if (deviceConfig == null) {
return;
}
boolean result = modbusProcessor.writeDataToDevice(deviceConfig);
if (!result) {
logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceConfig, workStatus.getInfo());
}
}

View File

@ -66,6 +66,10 @@ public class EmsStrategyLog extends BaseEntity
@Excel(name = "防逆流, 1-是、0-否")
private Integer antiReverse;
/** PCS降功率类型 */
@Excel(name = "PCS降功率类型0-降低功率、1-增加功率")
private Integer powerDownType;
public void setId(Long id)
{
this.id = id;
@ -172,6 +176,14 @@ public class EmsStrategyLog extends BaseEntity
this.antiReverse = antiReverse;
}
public Integer getPowerDownType() {
return powerDownType;
}
public void setPowerDownType(Integer powerDownType) {
this.powerDownType = powerDownType;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@ -187,6 +199,7 @@ public class EmsStrategyLog extends BaseEntity
.append("chargeStatus", getChargeStatus())
.append("executionDate", getExecutionDate())
.append("antiReverse", getAntiReverse())
.append("powerDownType", getPowerDownType())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())

View File

@ -59,4 +59,6 @@ public interface EmsStrategyLogMapper
* @return 结果
*/
public int deleteEmsStrategyLogByIds(Long[] ids);
EmsStrategyLog getLastStrategyLog(EmsStrategyLog query);
}

View File

@ -16,6 +16,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="chargeStatus" column="charge_status" />
<result property="executionDate" column="execution_date" />
<result property="antiReverse" column="anti_reverse" />
<result property="powerDownType" column="power_down_type" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
@ -24,7 +25,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectEmsStrategyLogVo">
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
select id, strategy_id, template_id, site_id, device_id, start_time, end_time, charge_discharge_power, charge_status, execution_date, anti_reverse, power_down_type, create_by, create_time, update_by, update_time, remark from ems_strategy_log
</sql>
<select id="selectEmsStrategyLogList" parameterType="EmsStrategyLog" resultMap="EmsStrategyLogResult">
@ -40,6 +41,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="chargeStatus != null and chargeStatus != ''"> and charge_status = #{chargeStatus}</if>
<if test="executionDate != null "> and execution_date = #{executionDate}</if>
<if test="antiReverse != null "> and anti_reverse = #{antiReverse}</if>
<if test="powerDownType != null "> and power_down_type = #{powerDownType}</if>
</where>
order by create_time desc
</select>
@ -62,6 +64,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="chargeStatus != null">charge_status,</if>
<if test="executionDate != null">execution_date,</if>
<if test="antiReverse != null">anti_reverse,</if>
<if test="powerDownType != null">power_down_type,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
@ -79,6 +82,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="chargeStatus != null">#{chargeStatus},</if>
<if test="executionDate != null">#{executionDate},</if>
<if test="antiReverse != null">#{antiReverse},</if>
<if test="powerDownType != null">#{powerDownType},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
@ -100,6 +104,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="chargeStatus != null">charge_status = #{chargeStatus},</if>
<if test="executionDate != null">execution_date = #{executionDate},</if>
<if test="antiReverse != null">anti_reverse = #{antiReverse},</if>
<if test="powerDownType != null">power_down_type = #{powerDownType},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
@ -119,4 +124,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{id}
</foreach>
</delete>
<select id="getLastStrategyLog" parameterType="EmsStrategyLog" resultMap="EmsStrategyLogResult">
<include refid="selectEmsStrategyLogVo"/>
<where>
<if test="strategyId != null "> and strategy_id = #{strategyId}</if>
<if test="templateId != null and templateId != ''"> and template_id = #{templateId}</if>
<if test="siteId != null and siteId != ''"> and site_id = #{siteId}</if>
<if test="deviceId != null and deviceId != ''"> and device_id = #{deviceId}</if>
<if test="startTime != null "> and start_time = #{startTime}</if>
<if test="endTime != null "> and end_time = #{endTime}</if>
<if test="chargeDischargePower != null "> and charge_discharge_power = #{chargeDischargePower}</if>
<if test="chargeStatus != null and chargeStatus != ''"> and charge_status = #{chargeStatus}</if>
<if test="executionDate != null "> and execution_date = #{executionDate}</if>
<if test="antiReverse != null "> and anti_reverse = #{antiReverse}</if>
<if test="powerDownType != null "> and power_down_type = #{powerDownType}</if>
</where>
order by create_time desc
limit 1
</select>
</mapper>