From 141e65a10379234154623794cfd9d9388932a4e0 Mon Sep 17 00:00:00 2001 From: Igor Rutka Date: Tue, 29 Sep 2026 11:07:11 +0200 Subject: [PATCH 1/2] Updated EmsOptimisation with EmsOptimisationBeta --- .../optimisationMethods/EmsOptimisation.java | 2442 +++++++++-------- 1 file changed, 1360 insertions(+), 1082 deletions(-) diff --git a/ems/src/main/java/org/openremote/extension/ems/manager/optimisationMethods/EmsOptimisation.java b/ems/src/main/java/org/openremote/extension/ems/manager/optimisationMethods/EmsOptimisation.java index 5e857f6..828b829 100644 --- a/ems/src/main/java/org/openremote/extension/ems/manager/optimisationMethods/EmsOptimisation.java +++ b/ems/src/main/java/org/openremote/extension/ems/manager/optimisationMethods/EmsOptimisation.java @@ -34,30 +34,40 @@ import org.openremote.extension.ems.agent.EmsGOPACSAsset; import org.openremote.extension.ems.manager.Services; import org.openremote.model.asset.Asset; +import org.openremote.model.attribute.Attribute; import org.openremote.model.attribute.AttributeEvent; import org.openremote.model.datapoint.ValueDatapoint; import org.openremote.model.datapoint.query.AssetDatapointAllQuery; import org.openremote.model.query.AssetQuery; import org.openremote.model.syslog.SyslogCategory; +import org.openremote.model.value.ValueType; public class EmsOptimisation implements OptimisationMethod { protected static final Logger LOG = - SyslogCategory.getLogger(DATA, EmsOptimisation.class.getName()); + SyslogCategory.getLogger(DATA, EmsOptimisation.class.getName()); private final String optimisationMethodName = EmsOptimisation.class.getSimpleName(); // Maximum interval between data-points send by the device to be considered connected private final int ACTIVE_PERIOD_MINUTES = 5; - // EMS power limits settings - private final int POWER_LIMIT_MAXIMUM_SAFETY_MARGIN_PERCENTAGE = 5; - private final int POWER_LIMIT_MINIMUM_SAFETY_MARGIN_PERCENTAGE = 5; - private final int POWER_LIMIT_MAXIMUM_BATTERIES_MARGIN_PERCENTAGE = 10; - private final int POWER_LIMIT_MINIMUM_BATTERIES_MARGIN_PERCENTAGE = 10; + // Default EMS power limit settings + private final int POWER_LIMIT_FLUCTUATION_MARGIN_PERCENTAGE_DEFAULT = 10; - // Battery settings + // Default battery settings private final int BATTERY_ENERGY_LEVEL_PERCENTAGE_DEFAULT = 50; - private final int BATTERY_ENERGY_LEVEL_PERCENTAGE_DEBOUNCE_INTERVAL = 3; - private final int BATTERY_EFFICIENCY_BUFFER_PERCENTAGE = 3; + private final double BATTERY_POWER_SETPOINT_RESPONSIVENESS_DEFAULT = 0.5; + private final int BATTERY_TARIFF_OPTIMISATION_WINDOW_DEFAULT = 8; + + // Advanced settings attribute names + private final String POWER_LIMIT_MAXIMUM_FLUCTUATION_MARGIN_ATTRIBUTE_NAME = + "powerLimitMaximumFluctuationMargin"; + private final String POWER_LIMIT_MINIMUM_FLUCTUATION_MARGIN_ATTRIBUTE_NAME = + "powerLimitMinimumFluctuationMargin"; + + private final String[][] advancedSettingsAttributesInfo = { + {POWER_LIMIT_MAXIMUM_FLUCTUATION_MARGIN_ATTRIBUTE_NAME, ValueType.POSITIVE_NUMBER.getName()}, + {POWER_LIMIT_MINIMUM_FLUCTUATION_MARGIN_ATTRIBUTE_NAME, ValueType.POSITIVE_NUMBER.getName()} + }; @Override public void execute(String energyOptimisationAssetId, Services services) { @@ -67,60 +77,82 @@ public void execute(String energyOptimisationAssetId, Services services) { private void runOptimisationMethodEms(String energyOptimisationAssetId, Services services) { // Get energy optimisation asset EmsEnergyOptimisationAsset energyOptimisationAsset = - (EmsEnergyOptimisationAsset) - services.getAssetStorageService().find(energyOptimisationAssetId); + (EmsEnergyOptimisationAsset) + services.getAssetStorageService().find(energyOptimisationAssetId); if (energyOptimisationAsset == null) { return; } + // Update Advanced settings attributes info + String advancedSettingsAttributes = + energyOptimisationAsset.getAdvancedSettingsAttributes().orElse(""); + + if (advancedSettingsAttributes.isBlank()) { + StringBuilder advancedSettingsAttributesBody = new StringBuilder(); + + String firstRow = String.format("Optimisation method '%s':\n", optimisationMethodName); + advancedSettingsAttributesBody.append(firstRow); + + for (String[] row : advancedSettingsAttributesInfo) { + advancedSettingsAttributesBody.append(String.join(",", row)).append("\n"); + } + + services + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + energyOptimisationAssetId, + EmsEnergyOptimisationAsset.ADVANCED_SETTINGS_ATTRIBUTES, + advancedSettingsAttributesBody.toString()), + getClass().getSimpleName()); + } + String logPrefixEnergyOptimisation = - String.format( - "assetType='%s', assetId='%s', assetName='%s'", - energyOptimisationAsset.getAssetType(), - energyOptimisationAssetId, - energyOptimisationAsset.getAssetName()); + String.format( + "assetType='%s', assetId='%s', assetName='%s'", + energyOptimisationAsset.getAssetType(), + energyOptimisationAssetId, + energyOptimisationAsset.getAssetName()); // Get all battery assets List electricityBatteryAssets = - services - .getAssetStorageService() - .findAll( - new AssetQuery() - .parents(energyOptimisationAssetId) - .types(EmsElectricityBatteryAsset.class)) - .stream() - .map(asset -> (EmsElectricityBatteryAsset) asset) - .toList(); - - // TODO: Add order to battery assets from longest to smallest discharge duration + services + .getAssetStorageService() + .findAll( + new AssetQuery() + .parents(energyOptimisationAssetId) + .types(EmsElectricityBatteryAsset.class)) + .stream() + .map(asset -> (EmsElectricityBatteryAsset) asset) + .toList(); if (energyOptimisationAsset.getEnableDetailedLogging().orElse(false)) { int allowChargingSize = - electricityBatteryAssets.stream() - .filter( - electricityBatteryAsset -> - electricityBatteryAsset.getAllowCharging().orElse(false)) - .toList() - .size(); + electricityBatteryAssets.stream() + .filter( + electricityBatteryAsset -> + electricityBatteryAsset.getAllowCharging().orElse(false)) + .toList() + .size(); int allowDischargingSize = - electricityBatteryAssets.stream() - .filter( - electricityBatteryAsset -> - electricityBatteryAsset.getAllowDischarging().orElse(false)) - .toList() - .size(); + electricityBatteryAssets.stream() + .filter( + electricityBatteryAsset -> + electricityBatteryAsset.getAllowDischarging().orElse(false)) + .toList() + .size(); LOG.info( - String.format( - "%s; Energy optimisation asset has %s battery asset(s). Number of batteries with '%s'= %s and '%s'= %s", - logPrefixEnergyOptimisation, - electricityBatteryAssets.size(), - EmsElectricityBatteryAsset.ALLOW_CHARGING.getName(), - allowChargingSize, - EmsElectricityBatteryAsset.ALLOW_DISCHARGING.getName(), - allowDischargingSize)); + String.format( + "%s; Energy optimisation asset has %s battery asset(s). Number of batteries with '%s'= %s and '%s'= %s", + logPrefixEnergyOptimisation, + electricityBatteryAssets.size(), + EmsElectricityBatteryAsset.ALLOW_CHARGING.getName(), + allowChargingSize, + EmsElectricityBatteryAsset.ALLOW_DISCHARGING.getName(), + allowDischargingSize)); } // Check if power net is connected @@ -128,44 +160,44 @@ private void runOptimisationMethodEms(String energyOptimisationAssetId, Services if (powerNet == null) { LOG.warning( - String.format( - "%s; Failed to perform '%s' energy optimisation method. '%s' attribute is not connected", - logPrefixEnergyOptimisation, - optimisationMethodName, - EmsEnergyOptimisationAsset.POWER_NET.getName())); + String.format( + "%s; Failed to perform '%s' energy optimisation method. '%s' attribute is not connected", + logPrefixEnergyOptimisation, + optimisationMethodName, + EmsEnergyOptimisationAsset.POWER_NET.getName())); } // Validate power limits Double powerLimitMaximumProfileTotal = - energyOptimisationAsset.getPowerLimitMaximumProfileTotal().orElse(null); + energyOptimisationAsset.getPowerLimitMaximumProfileTotal().orElse(null); Double powerLimitMinimumProfileTotal = - energyOptimisationAsset.getPowerLimitMinimumProfileTotal().orElse(null); + energyOptimisationAsset.getPowerLimitMinimumProfileTotal().orElse(null); Long powerLimitMaximumProfileTimestampMillis = - energyOptimisationAsset.getPowerLimitMaximumProfileTotalTimestamp().orElse(null); + energyOptimisationAsset.getPowerLimitMaximumProfileTotalTimestamp().orElse(null); DateTimeFormatter formatter = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").withZone(ZoneId.systemDefault()); + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").withZone(ZoneId.systemDefault()); String powerLimitMaximumProfileDateTime = ""; if (powerLimitMaximumProfileTimestampMillis != null) { powerLimitMaximumProfileDateTime = - formatter.format(Instant.ofEpochMilli(powerLimitMaximumProfileTimestampMillis)); + formatter.format(Instant.ofEpochMilli(powerLimitMaximumProfileTimestampMillis)); } if (powerLimitMaximumProfileTotal != null - && powerLimitMinimumProfileTotal != null - && powerLimitMaximumProfileTotal <= powerLimitMinimumProfileTotal) { + && powerLimitMinimumProfileTotal != null + && powerLimitMaximumProfileTotal <= powerLimitMinimumProfileTotal) { LOG.warning( - String.format( - "%s; Failed to perform '%s' energy optimisation method. The '%s'= %s kW is lower than or equal to '%s'= %s kW for timestamp='%s'", - logPrefixEnergyOptimisation, - optimisationMethodName, - EmsEnergyOptimisationAsset.POWER_LIMIT_MAXIMUM_PROFILE_TOTAL.getName(), - powerLimitMaximumProfileTotal, - EmsEnergyOptimisationAsset.POWER_LIMIT_MINIMUM_PROFILE_TOTAL.getName(), - powerLimitMinimumProfileTotal, - powerLimitMaximumProfileDateTime)); + String.format( + "%s; Failed to perform '%s' energy optimisation method. The '%s'= %s kW is lower than or equal to '%s'= %s kW for timestamp='%s'", + logPrefixEnergyOptimisation, + optimisationMethodName, + EmsEnergyOptimisationAsset.POWER_LIMIT_MAXIMUM_PROFILE_TOTAL.getName(), + powerLimitMaximumProfileTotal, + EmsEnergyOptimisationAsset.POWER_LIMIT_MINIMUM_PROFILE_TOTAL.getName(), + powerLimitMinimumProfileTotal, + powerLimitMaximumProfileDateTime)); } // Get day ahead asset @@ -181,37 +213,81 @@ private void runOptimisationMethodEms(String energyOptimisationAssetId, Services return; } + // Order battery assets + electricityBatteryAssets = batteryOrder(electricityBatteryAssets); + // Check connection status of battery assets batteryCheckConnection(electricityBatteryAssets, services); // Check if all required attributes are connected/set and create log messages batteryCheckSetup(electricityBatteryAssets); - // Calculate new battery power set-points + // Find latest power set-point update across all batteries + long powerSetpointTimestampLatestMillis = + batteriesLatestPowerSetpointUpdate(electricityBatteryAssets); + + // Calculate battery energy level percentage targets + int intervalPeriodMinutes = 15; + int currentMinute = LocalDateTime.now().getMinute(); + long currentTimeMillis = services.getTimerService().getCurrentTimeMillis(); + long forecastUpdateTimeMillis = + currentTimeMillis - currentTimeMillis % (intervalPeriodMinutes * 60 * 1000); + + Map batteryEnergyLevelPercentageTargets; + + if ((currentMinute % intervalPeriodMinutes) == 0 + || powerSetpointTimestampLatestMillis < forecastUpdateTimeMillis) { + batteryEnergyLevelPercentageTargets = + batteryCalculateForecasts(electricityBatteryAssets, energyOptimisationAsset, services); + } else { + batteryEnergyLevelPercentageTargets = + batteryGetEnergyLevelPercentageTargetsCurrent(electricityBatteryAssets, services); + } + + // Calculate battery power set-points Map powerSetpointsNew = - batteryCalculatePowerSetpoints( - energyOptimisationAsset, - electricityBatteryAssets, - services, - logPrefixEnergyOptimisation); + batteryCalculatePowerSetpoints( + energyOptimisationAsset, + electricityBatteryAssets, + powerSetpointTimestampLatestMillis, + batteryEnergyLevelPercentageTargets, + services, + logPrefixEnergyOptimisation); // Update battery power set-points batteryUpdatePowerSetpoints(electricityBatteryAssets, powerSetpointsNew, services); } + private long batteriesLatestPowerSetpointUpdate( + List electricityBatteryAssets) { + // Find latest power set-point update across all batteries + long powerSetpointTimestampLatestMillis = 0L; + + for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { + long powerSetpointTimestampMillis = + electricityBatteryAsset.getPowerSetpointTimestamp().orElse(0L); + + if (powerSetpointTimestampMillis > powerSetpointTimestampLatestMillis) { + powerSetpointTimestampLatestMillis = powerSetpointTimestampMillis; + } + } + + return powerSetpointTimestampLatestMillis; + } + private int batteryCalculateEnergyLevelPercentageDefault( - Integer energyLevelPercentageMaximumBattery, - Integer energyLevelPercentageMinimumBattery, - EmsEnergyOptimisationAsset energyOptimisationAsset) { + Integer energyLevelPercentageMaximumBattery, + Integer energyLevelPercentageMinimumBattery, + EmsEnergyOptimisationAsset energyOptimisationAsset) { if (energyLevelPercentageMaximumBattery == null - || energyLevelPercentageMinimumBattery == null) { + || energyLevelPercentageMinimumBattery == null) { return BATTERY_ENERGY_LEVEL_PERCENTAGE_DEFAULT; } Double powerLimitMaximumProfileTotal = - energyOptimisationAsset.getPowerLimitMaximumInput().orElse(null); + energyOptimisationAsset.getPowerLimitMaximumInput().orElse(null); Double powerLimitMinimumProfileTotal = - energyOptimisationAsset.getPowerLimitMinimumInput().orElse(null); + energyOptimisationAsset.getPowerLimitMinimumInput().orElse(null); int energyLevelPercentageDefault; @@ -221,40 +297,45 @@ private int batteryCalculateEnergyLevelPercentageDefault( energyLevelPercentageDefault = energyLevelPercentageMaximumBattery; } else { energyLevelPercentageDefault = - (int) - Math.round( - (double) - (energyLevelPercentageMaximumBattery - - energyLevelPercentageMinimumBattery) - / 2 - + energyLevelPercentageMinimumBattery); + (int) + Math.round( + (double) + (energyLevelPercentageMaximumBattery + + energyLevelPercentageMinimumBattery) + / 2); } return energyLevelPercentageDefault; } private Map batteryCalculateForecasts( - List electricityBatteryAssets, - EmsEnergyOptimisationAsset energyOptimisationAsset, - Services services) { + List electricityBatteryAssets, + EmsEnergyOptimisationAsset energyOptimisationAsset, + Services services) { Map batteryEnergyLevelPercentageTargets = new HashMap<>(); + StringBuilder infoStr = new StringBuilder(); + infoStr.append("EMS forecast:\n"); + // Get power consumption forecast for 1 week long intervalMillis = 15 * 60000; long currentTimeMillis = services.getTimerService().getCurrentTimeMillis(); - long startTimeMillis = currentTimeMillis - currentTimeMillis % intervalMillis; - long endTimeMillis = startTimeMillis + 7 * 24 * 60 * 60000; + long startTimeForecastPeriodMillis = currentTimeMillis - currentTimeMillis % intervalMillis; + long endTimeForecastPeriodMillis = + startTimeForecastPeriodMillis + - startTimeForecastPeriodMillis % (24 * 60 * 60000) + + (8 * 24 * 60 * 60000); String energyOptimisationAssetId = energyOptimisationAsset.getId(); AssetDatapointAllQuery assetDatapointQueryConsumptionPredicted = - new AssetDatapointAllQuery(startTimeMillis, endTimeMillis); + new AssetDatapointAllQuery(startTimeForecastPeriodMillis, endTimeForecastPeriodMillis); List> energyOptimisationPowerConsumptionPredicted = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - energyOptimisationAssetId, - EmsEnergyOptimisationAsset.POWER_CONSUMPTION.getName(), - assetDatapointQueryConsumptionPredicted); + services + .getAssetPredictedDatapointService() + .queryDatapoints( + energyOptimisationAssetId, + EmsEnergyOptimisationAsset.POWER_CONSUMPTION.getName(), + assetDatapointQueryConsumptionPredicted); // System.out.println("energyOptimisationPowerConsumptionPredicted = " + // energyOptimisationPowerConsumptionPredicted); @@ -265,22 +346,22 @@ private Map batteryCalculateForecasts( // Calculate power average for each 15-minute interval List> totalPowerConsumptionAveraged = - intervalAverage(energyOptimisationPowerConsumptionPredicted, intervalMillis); + intervalAverage(energyOptimisationPowerConsumptionPredicted, intervalMillis); totalPowerConsumptionAveraged.sort(Comparator.comparingLong(ValueDatapoint::getTimestamp)); // System.out.println("totalPowerConsumptionAveraged = " + // totalPowerConsumptionAveraged); - long startTimestampMillis = totalPowerConsumptionAveraged.getFirst().getTimestamp(); - long endTimestampMillis = totalPowerConsumptionAveraged.getLast().getTimestamp(); + long startTimeForecastDataMillis = totalPowerConsumptionAveraged.getFirst().getTimestamp(); + long endTimeForecastDataMillis = totalPowerConsumptionAveraged.getLast().getTimestamp(); // Interpolate power average values for each 15-minute interval List> totalPowerConsumptionInterpolated = - intervalInterpolate( - totalPowerConsumptionAveraged, - startTimestampMillis, - endTimestampMillis, - intervalMillis); + intervalInterpolate( + totalPowerConsumptionAveraged, + startTimeForecastDataMillis, + endTimeForecastDataMillis, + intervalMillis); List timestampsMillisList = new ArrayList<>(); List totalPowerConsumptionList = new ArrayList<>(); @@ -293,22 +374,25 @@ private Map batteryCalculateForecasts( totalPowerConsumptionList.add(value); } - // System.out.println("timestampsMillisList = " + timestampsMillisList); - // System.out.println("totalPowerConsumptionList = " + totalPowerConsumptionList); + int numberOfTimestamps = timestampsMillisList.size(); + + infoStr.append(String.format("numberOfTimestamps = %s \n", numberOfTimestamps)); + infoStr.append(String.format("timestampsMillisList = %s \n", timestampsMillisList)); + infoStr.append(String.format("totalPowerConsumptionList = %s \n", totalPowerConsumptionList)); if (totalPowerConsumptionList.isEmpty()) { return batteryEnergyLevelPercentageTargets; } AssetDatapointAllQuery assetDatapointQueryPeriodPredicted = - new AssetDatapointAllQuery(startTimestampMillis, endTimestampMillis); + new AssetDatapointAllQuery(startTimeForecastDataMillis, endTimeForecastDataMillis); List> energyOptimisationPowerProductionPredicted = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - energyOptimisationAssetId, - EmsEnergyOptimisationAsset.POWER_PRODUCTION.getName(), - assetDatapointQueryPeriodPredicted); + services + .getAssetPredictedDatapointService() + .queryDatapoints( + energyOptimisationAssetId, + EmsEnergyOptimisationAsset.POWER_PRODUCTION.getName(), + assetDatapointQueryPeriodPredicted); // System.out.println("energyOptimisationPowerProductionPredicted = " + // energyOptimisationPowerProductionPredicted); @@ -318,58 +402,59 @@ private Map batteryCalculateForecasts( if (!energyOptimisationPowerProductionPredicted.isEmpty()) { // Calculate power average for each 15-minute interval List> totalPowerProductionAveraged = - intervalAverage(energyOptimisationPowerProductionPredicted, intervalMillis); + intervalAverage(energyOptimisationPowerProductionPredicted, intervalMillis); totalPowerProductionAveraged.sort(Comparator.comparingLong(ValueDatapoint::getTimestamp)); // Interpolate power average values for each 15-minute interval List> totalPowerProductionInterpolated = - intervalInterpolate( - totalPowerProductionAveraged, - totalPowerProductionAveraged.getFirst().getTimestamp(), - totalPowerProductionAveraged.getLast().getTimestamp(), - intervalMillis); + intervalInterpolate( + totalPowerProductionAveraged, + totalPowerProductionAveraged.getFirst().getTimestamp(), + totalPowerProductionAveraged.getLast().getTimestamp(), + intervalMillis); totalPowerProductionMap = - totalPowerProductionInterpolated.stream() - .collect( - Collectors.toMap(ValueDatapoint::getTimestamp, dp -> ((Double) dp.getValue()))); + totalPowerProductionInterpolated.stream() + .collect( + Collectors.toMap(ValueDatapoint::getTimestamp, dp -> ((Double) dp.getValue()))); } - // List to store the sum of total power consumption, production and flexible power - List totalPowerConsumptionProductionFlexList = new ArrayList<>(); + // List to store the sum of total power consumption, production and flexible + List totalPowerConsumptionProductionFlexibleList = new ArrayList<>(); // Sum total power consumption and production - for (int i = 0; i < timestampsMillisList.size(); i++) { + for (int i = 0; i < numberOfTimestamps; i++) { Double powerConsumption = totalPowerConsumptionList.get(i); Double powerProduction = totalPowerProductionMap.get(timestampsMillisList.get(i)); if (powerProduction != null) { double sum = powerConsumption + powerProduction; - totalPowerConsumptionProductionFlexList.add(sum); + totalPowerConsumptionProductionFlexibleList.add(sum); } else { - totalPowerConsumptionProductionFlexList.add(powerConsumption); + totalPowerConsumptionProductionFlexibleList.add(powerConsumption); } } - // System.out.println("totalPowerConsumptionProductionFlexList = " + - // totalPowerConsumptionProductionFlexList); - // System.out.println(); + infoStr.append( + String.format( + "totalPowerConsumptionProductionFlexibleList = %s \n", + totalPowerConsumptionProductionFlexibleList)); - // Get power limits forecasts + // Get power limit forecasts List> energyOptimisationPowerLimitMaximumPredicted = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - energyOptimisationAssetId, - EmsEnergyOptimisationAsset.POWER_LIMIT_MAXIMUM_PROFILE_TOTAL.getName(), - assetDatapointQueryPeriodPredicted); + services + .getAssetPredictedDatapointService() + .queryDatapoints( + energyOptimisationAssetId, + EmsEnergyOptimisationAsset.POWER_LIMIT_MAXIMUM_PROFILE_TOTAL.getName(), + assetDatapointQueryPeriodPredicted); List> energyOptimisationPowerLimitMinimumPredicted = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - energyOptimisationAssetId, - EmsEnergyOptimisationAsset.POWER_LIMIT_MINIMUM_PROFILE_TOTAL.getName(), - assetDatapointQueryPeriodPredicted); + services + .getAssetPredictedDatapointService() + .queryDatapoints( + energyOptimisationAssetId, + EmsEnergyOptimisationAsset.POWER_LIMIT_MINIMUM_PROFILE_TOTAL.getName(), + assetDatapointQueryPeriodPredicted); Map powerLimitMaximumMap = new HashMap<>(); Map powerLimitMinimumMap = new HashMap<>(); @@ -385,67 +470,79 @@ private Map batteryCalculateForecasts( List chargePowerAvailableTotalList = new ArrayList<>(); List dischargePowerAvailableTotalList = new ArrayList<>(); - // Add current available charge and discharge power + // Add current available charge and discharge power which is 0 chargePowerAvailableTotalList.add(0.0); dischargePowerAvailableTotalList.add(0.0); - double intervalHour = (double) intervalMillis / (60 * 60000); + double intervalHour = round((double) intervalMillis / (60 * 60000), 5); + int batteryPercentageRoundingPrecision = 5; // Calculate forecast for each battery for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { String batteryAssetId = electricityBatteryAsset.getId(); Integer chargeEfficiencyBattery = electricityBatteryAsset.getChargeEfficiency().orElse(null); Double chargePowerMaximumBattery = - electricityBatteryAsset.getChargePowerMaximum().orElse(null); + electricityBatteryAsset.getChargePowerMaximum().orElse(null); Integer dischargeEfficiencyBattery = - electricityBatteryAsset.getDischargeEfficiency().orElse(null); + electricityBatteryAsset.getDischargeEfficiency().orElse(null); Double dischargePowerMaximumBattery = - electricityBatteryAsset.getDischargePowerMaximum().orElse(null); + electricityBatteryAsset.getDischargePowerMaximum().orElse(null); Double energyCapacityBattery = electricityBatteryAsset.getEnergyCapacity().orElse(null); Double energyLevelPercentageBattery = - electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); Integer energyLevelPercentageMaximumBattery = - electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); Integer energyLevelPercentageMinimumBattery = - electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); if (chargeEfficiencyBattery == null - || chargePowerMaximumBattery == null - || dischargeEfficiencyBattery == null - || dischargePowerMaximumBattery == null - || energyCapacityBattery == null - || energyLevelPercentageBattery == null - || energyLevelPercentageMaximumBattery == null - || energyLevelPercentageMinimumBattery == null) { + || chargePowerMaximumBattery == null + || dischargeEfficiencyBattery == null + || dischargePowerMaximumBattery == null + || energyCapacityBattery == null + || energyLevelPercentageBattery == null + || energyLevelPercentageMaximumBattery == null + || energyLevelPercentageMinimumBattery == null) { continue; } // Check if the energy level percentage maximum and minimum are valid - if ((energyLevelPercentageMaximumBattery - energyLevelPercentageMinimumBattery) - < BATTERY_ENERGY_LEVEL_PERCENTAGE_DEBOUNCE_INTERVAL) { + if (energyLevelPercentageMaximumBattery <= energyLevelPercentageMinimumBattery) { + continue; + } + + // Check if the charge and discharge efficiency are valid + if (chargeEfficiencyBattery <= 0 || dischargeEfficiencyBattery <= 0) { continue; } + List powerLimitMaximumVirtualList = new ArrayList<>(); + List powerLimitMinimumVirtualList = new ArrayList<>(); + // Calculate total available charge and discharge power - for (int i = 0; i < timestampsMillisList.size(); i++) { + for (int i = 0; i < numberOfTimestamps; i++) { long timestampMillis = timestampsMillisList.get(i); - double totalPower = totalPowerConsumptionProductionFlexList.get(i); + double totalPower = totalPowerConsumptionProductionFlexibleList.get(i); Double powerLimitMaximum = powerLimitMaximumMap.getOrDefault(timestampMillis, null); Double powerLimitMinimum = powerLimitMinimumMap.getOrDefault(timestampMillis, null); - if (powerLimitMaximum != null) { - double powerLimitMaximumVirtual = - powerLimitMaximum * (1 - POWER_LIMIT_MAXIMUM_SAFETY_MARGIN_PERCENTAGE * 0.01); + Double powerLimitMaximumVirtual = + calculatePowerLimitVirtual(energyOptimisationAsset, powerLimitMaximum, "max"); + Double powerLimitMinimumVirtual = + calculatePowerLimitVirtual(energyOptimisationAsset, powerLimitMinimum, "min"); + + powerLimitMaximumVirtualList.add(powerLimitMaximumVirtual); + powerLimitMinimumVirtualList.add(powerLimitMinimumVirtual); + + if (powerLimitMaximumVirtual != null) { double chargePowerTotalAvailable = powerLimitMaximumVirtual - totalPower; chargePowerAvailableTotalList.add(chargePowerTotalAvailable); } else { chargePowerAvailableTotalList.add(Double.POSITIVE_INFINITY); } - if (powerLimitMinimum != null) { - double powerLimitMinimumVirtual = - powerLimitMinimum * (1 - POWER_LIMIT_MINIMUM_SAFETY_MARGIN_PERCENTAGE * 0.01); + if (powerLimitMinimumVirtual != null) { double dischargePowerTotalAvailable = powerLimitMinimumVirtual - totalPower; dischargePowerAvailableTotalList.add(dischargePowerTotalAvailable); } else { @@ -455,28 +552,33 @@ private Map batteryCalculateForecasts( int numberOfDataPoints = chargePowerAvailableTotalList.size(); - // System.out.println("numberOfDataPoints = " + numberOfDataPoints); + infoStr.append( + String.format("powerLimitMaximumVirtualList = %s \n", powerLimitMaximumVirtualList)); + infoStr.append( + String.format("powerLimitMinimumVirtualList = %s \n", powerLimitMinimumVirtualList)); + infoStr.append("\n"); + // System.out.println("chargePowerAvailableTotalList = " + // chargePowerAvailableTotalList); // System.out.println("dischargePowerAvailableTotalList = " + // dischargePowerAvailableTotalList); List chargeNeededTotalList = - dischargePowerAvailableTotalList.stream().map(x -> x > 0 ? x : 0).toList(); + dischargePowerAvailableTotalList.stream().map(x -> x > 0 ? x : 0).toList(); List dischargeNeededTotalList = - chargePowerAvailableTotalList.stream().map(x -> x < 0 ? x : 0).toList(); + chargePowerAvailableTotalList.stream().map(x -> x < 0 ? x : 0).toList(); // System.out.println("chargeNeededTotalList = " + chargeNeededTotalList); // System.out.println("dischargeNeededTotalList = " + dischargeNeededTotalList); List chargePowerAvailableBatteryList = - chargePowerAvailableTotalList.stream() - .map(x -> x > 0 ? Math.min(x, chargePowerMaximumBattery) : 0) - .toList(); + chargePowerAvailableTotalList.stream() + .map(x -> x > 0 ? Math.min(x, chargePowerMaximumBattery) : 0) + .toList(); List dischargePowerAvailableBatteryList = - dischargePowerAvailableTotalList.stream() - .map(x -> x < 0 ? Math.max(x, dischargePowerMaximumBattery) : 0) - .toList(); + dischargePowerAvailableTotalList.stream() + .map(x -> x < 0 ? Math.max(x, dischargePowerMaximumBattery) : 0) + .toList(); // System.out.println("chargeAvailableBatteryList = " + // chargePowerAvailableBatteryList); @@ -488,23 +590,19 @@ private Map batteryCalculateForecasts( List chargePercentageNeededTotalList = new ArrayList<>(); List dischargePercentageNeededTotalList = new ArrayList<>(); - chargeEfficiencyBattery = chargeEfficiencyBattery - BATTERY_EFFICIENCY_BUFFER_PERCENTAGE; - dischargeEfficiencyBattery = - dischargeEfficiencyBattery - BATTERY_EFFICIENCY_BUFFER_PERCENTAGE; - for (int i = 0; i < numberOfDataPoints; i++) { double c = - intervalHour - * chargeNeededTotalList.get(i) - * chargeEfficiencyBattery - / energyCapacityBattery; + intervalHour + * chargeNeededTotalList.get(i) + * chargeEfficiencyBattery + / energyCapacityBattery; double d = - 10000 - * intervalHour - * dischargeNeededTotalList.get(i) - / (energyCapacityBattery * dischargeEfficiencyBattery); - chargePercentageNeededTotalList.add(c); - dischargePercentageNeededTotalList.add(d); + 10000 + * intervalHour + * dischargeNeededTotalList.get(i) + / (energyCapacityBattery * dischargeEfficiencyBattery); + chargePercentageNeededTotalList.add(round(c, batteryPercentageRoundingPrecision)); + dischargePercentageNeededTotalList.add(round(d, batteryPercentageRoundingPrecision)); } // System.out.println("chargePercentageNeededTotalList = " + @@ -517,17 +615,17 @@ private Map batteryCalculateForecasts( for (int i = 0; i < numberOfDataPoints; i++) { double c = - intervalHour - * chargePowerAvailableBatteryList.get(i) - * chargeEfficiencyBattery - / energyCapacityBattery; + intervalHour + * chargePowerAvailableBatteryList.get(i) + * chargeEfficiencyBattery + / energyCapacityBattery; double d = - 10000 - * intervalHour - * dischargePowerAvailableBatteryList.get(i) - / (energyCapacityBattery * dischargeEfficiencyBattery); - chargePercentageAvailableBatteryList.add(c); - dischargePercentageAvailableBatteryList.add(d); + 10000 + * intervalHour + * dischargePowerAvailableBatteryList.get(i) + / (energyCapacityBattery * dischargeEfficiencyBattery); + chargePercentageAvailableBatteryList.add(round(c, batteryPercentageRoundingPrecision)); + dischargePercentageAvailableBatteryList.add(round(d, batteryPercentageRoundingPrecision)); } // System.out.println("chargePercentageAvailableBatteryList = " + @@ -546,22 +644,30 @@ private Map batteryCalculateForecasts( if (getToLimitPercentage > energyLevelPercentageMaximumBattery) { double dischargePercentageNeeded = - energyLevelPercentageMaximumBattery - getToLimitPercentage; + energyLevelPercentageMaximumBattery - getToLimitPercentage; double dischargePercentageAvailable = - Math.max(dischargePercentageAvailableBatteryList.get(i), dischargePercentageNeeded); + Math.max(dischargePercentageAvailableBatteryList.get(i), dischargePercentageNeeded); getToLimitPercentage = getToLimitPercentage + dischargePercentageAvailable; dischargePercentageAvailableBatteryList.set( - i, dischargePercentageAvailableBatteryList.get(i) - dischargePercentageAvailable); - getToLimitPercentageList.add(getToLimitPercentage); + i, + round( + dischargePercentageAvailableBatteryList.get(i) - dischargePercentageAvailable, + batteryPercentageRoundingPrecision)); + getToLimitPercentageList.add( + round(getToLimitPercentage, batteryPercentageRoundingPrecision)); } else if (getToLimitPercentage < energyLevelPercentageMinimumBattery) { double chargePercentageNeeded = - energyLevelPercentageMinimumBattery - getToLimitPercentage; + energyLevelPercentageMinimumBattery - getToLimitPercentage; double chargePercentageAvailable = - Math.min(chargePercentageAvailableBatteryList.get(i), chargePercentageNeeded); + Math.min(chargePercentageAvailableBatteryList.get(i), chargePercentageNeeded); getToLimitPercentage = getToLimitPercentage + chargePercentageAvailable; chargePercentageAvailableBatteryList.set( - i, chargePercentageAvailableBatteryList.get(i) - chargePercentageAvailable); - getToLimitPercentageList.add(getToLimitPercentage); + i, + round( + chargePercentageAvailableBatteryList.get(i) - chargePercentageAvailable, + batteryPercentageRoundingPrecision)); + getToLimitPercentageList.add( + round(getToLimitPercentage, batteryPercentageRoundingPrecision)); } else { break; } @@ -570,16 +676,16 @@ private Map batteryCalculateForecasts( // System.out.println("getToLimitIndex = " + getToLimitIndex); // System.out.println("getToLimitPercentage = " + getToLimitPercentage); // System.out.println("getToLimitPercentageList = " + getToLimitPercentageList); - // System.out.println("chargePercentageAvailableBatteryList = " + + // System.out.println("chargePercentageAvailableBatteryList2 = " + // chargePercentageAvailableBatteryList); - // System.out.println("dischargePercentageAvailableBatteryList = " + + // System.out.println("dischargePercentageAvailableBatteryList2 = " + // dischargePercentageAvailableBatteryList); List energyLevelPredictionList = new ArrayList<>(); if (getToLimitPercentage > energyLevelPercentageMaximumBattery - || getToLimitPercentage < energyLevelPercentageMinimumBattery) { - // Energy level percentage forecast when whole forecast is outside of battery percentage + || getToLimitPercentage < energyLevelPercentageMinimumBattery) { + // Energy level percentage forecast when entire forecast is outside of battery percentage // limits energyLevelPredictionList = getToLimitPercentageList; } else { @@ -590,13 +696,13 @@ private Map batteryCalculateForecasts( for (int i = 0; i < numberOfDataPoints; i++) { chargePercentageWantBatteryList.add( - Math.min( - chargePercentageNeededTotalList.get(i), - chargePercentageAvailableBatteryList.get(i))); + Math.min( + chargePercentageNeededTotalList.get(i), + chargePercentageAvailableBatteryList.get(i))); dischargePercentageWantBatteryList.add( - Math.max( - dischargePercentageNeededTotalList.get(i), - dischargePercentageAvailableBatteryList.get(i))); + Math.max( + dischargePercentageNeededTotalList.get(i), + dischargePercentageAvailableBatteryList.get(i))); } // System.out.println("chargePercentageWantBatteryList = " + @@ -608,7 +714,10 @@ private Map batteryCalculateForecasts( for (int i = 0; i < numberOfDataPoints; i++) { chargeAndDischargePercentageWantBatteryList.add( - chargePercentageWantBatteryList.get(i) + dischargePercentageWantBatteryList.get(i)); + round( + chargePercentageWantBatteryList.get(i) + + dischargePercentageWantBatteryList.get(i), + batteryPercentageRoundingPrecision)); } // System.out.println("chargeAndDischargePercentageWantBatteryList = " + @@ -622,7 +731,7 @@ private Map batteryCalculateForecasts( for (int i = startRunningSumIndex; i < numberOfDataPoints; i++) { sum = sum + chargeAndDischargePercentageWantBatteryList.get(i); - runningSumList.add(sum); + runningSumList.add(round(sum, batteryPercentageRoundingPrecision)); } // System.out.println("getToLimitPercentageList = " + @@ -633,23 +742,23 @@ private Map batteryCalculateForecasts( // Combine outside limits and running sum energy level percentages if (getToLimitPercentageList.size() > 1) { energyLevelPredictionList.addAll( - getToLimitPercentageList.subList(0, getToLimitPercentageList.size() - 1)); + getToLimitPercentageList.subList(0, getToLimitPercentageList.size() - 1)); } energyLevelPredictionList.addAll(runningSumList); + int energyLevelPredictionListSize = energyLevelPredictionList.size(); - // System.out.println("energyLevelPredictionList = " + - // energyLevelPredictionList); - // System.out.println(); + infoStr.append( + String.format("energyLevelPredictionListOriginal = %s \n", energyLevelPredictionList)); // Calculate forecast starting within battery percentage limits double energyLevelPredictionMaximum = - Collections.max( - energyLevelPredictionList.subList( - startRunningSumIndex, energyLevelPredictionList.size())); + Collections.max( + energyLevelPredictionList.subList( + startRunningSumIndex, energyLevelPredictionListSize)); double energyLevelPredictionMinimum = - Collections.min( - energyLevelPredictionList.subList( - startRunningSumIndex, energyLevelPredictionList.size())); + Collections.min( + energyLevelPredictionList.subList( + startRunningSumIndex, energyLevelPredictionListSize)); long dtStart = services.getTimerService().getCurrentTimeMillis(); long dt = 0; @@ -659,11 +768,11 @@ private Map batteryCalculateForecasts( int predictionListIndex = startRunningSumIndex; while (energyLevelPredictionMaximum > energyLevelPercentageMaximumBattery - || energyLevelPredictionMinimum < energyLevelPercentageMinimumBattery) { + || energyLevelPredictionMinimum < energyLevelPercentageMinimumBattery) { String chargeOrDischarge = ""; int intervalStartIndex = startRunningSumIndex; - for (int i = intervalEndIndex; i < energyLevelPredictionList.size(); i++) { + for (int i = intervalEndIndex; i < energyLevelPredictionListSize; i++) { predictionListIndex = i; if (energyLevelPredictionList.get(i) < energyLevelPercentageMinimumBattery) { @@ -713,9 +822,12 @@ private Map batteryCalculateForecasts( if (chargeOrDischarge.equals("charge")) { double chargeAvailable = 0; double chargeAvailableInterval = - energyLevelPercentageMaximumBattery - - Collections.max( - energyLevelPredictionList.subList(intervalStartIndex, intervalEndIndex)); + round( + energyLevelPercentageMaximumBattery + - Collections.max( + energyLevelPredictionList.subList( + intervalStartIndex, intervalEndIndex)), + batteryPercentageRoundingPrecision); if (chargeAvailableInterval > 0) { for (int i = intervalEndIndex - 1; i >= intervalStartIndex; i--) { @@ -727,8 +839,11 @@ private Map batteryCalculateForecasts( if (dischargePercentageWantBatteryList.get(i) >= 0) { double chargeInUse = - energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); - chargeAvailableLeft = chargePercentageAvailableBatteryList.get(i) - chargeInUse; + energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); + chargeAvailableLeft = + round( + chargePercentageAvailableBatteryList.get(i) - chargeInUse, + batteryPercentageRoundingPrecision); } if (chargeAvailableLeft > 0) { @@ -740,8 +855,8 @@ private Map batteryCalculateForecasts( } double chargeNeeded = - energyLevelPercentageMinimumBattery - - energyLevelPredictionList.get(intervalEndIndex); + energyLevelPercentageMinimumBattery + - energyLevelPredictionList.get(intervalEndIndex); double changeValue = Math.min(chargeAvailable, chargeNeeded); if (changeIndex == -1) { @@ -749,15 +864,22 @@ private Map batteryCalculateForecasts( changeValue = chargeNeeded; } - for (int i = changeIndex; i < energyLevelPredictionList.size(); i++) { - energyLevelPredictionList.set(i, energyLevelPredictionList.get(i) + changeValue); + for (int i = changeIndex; i < energyLevelPredictionListSize; i++) { + energyLevelPredictionList.set( + i, + round( + energyLevelPredictionList.get(i) + changeValue, + batteryPercentageRoundingPrecision)); } } else if (chargeOrDischarge.equals("discharge")) { double dischargeAvailable = 0; double dischargeAvailableInterval = - energyLevelPercentageMinimumBattery - - Collections.min( - energyLevelPredictionList.subList(intervalStartIndex, intervalEndIndex)); + round( + energyLevelPercentageMinimumBattery + - Collections.min( + energyLevelPredictionList.subList( + intervalStartIndex, intervalEndIndex)), + batteryPercentageRoundingPrecision); if (dischargeAvailableInterval > 0) { for (int i = intervalEndIndex - 1; i >= intervalStartIndex; i--) { @@ -769,9 +891,11 @@ private Map batteryCalculateForecasts( if (chargePercentageWantBatteryList.get(i) <= 0) { double dischargeInUse = - energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); + energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); dischargeAvailableLeft = - dischargePercentageAvailableBatteryList.get(i) - dischargeInUse; + round( + dischargePercentageAvailableBatteryList.get(i) - dischargeInUse, + batteryPercentageRoundingPrecision); } if (dischargeAvailableLeft < 0) { changeIndex = i; @@ -782,8 +906,8 @@ private Map batteryCalculateForecasts( } double dischargeNeeded = - energyLevelPercentageMaximumBattery - - energyLevelPredictionList.get(intervalEndIndex); + energyLevelPercentageMaximumBattery + - energyLevelPredictionList.get(intervalEndIndex); double changeValue = Math.max(dischargeAvailable, dischargeNeeded); if (changeIndex == -1) { @@ -791,41 +915,51 @@ private Map batteryCalculateForecasts( changeValue = dischargeNeeded; } - for (int i = changeIndex; i < energyLevelPredictionList.size(); i++) { - energyLevelPredictionList.set(i, energyLevelPredictionList.get(i) + changeValue); + for (int i = changeIndex; i < energyLevelPredictionListSize; i++) { + energyLevelPredictionList.set( + i, + round( + energyLevelPredictionList.get(i) + changeValue, + batteryPercentageRoundingPrecision)); } } if (energyLevelPredictionValueBefore == energyLevelPredictionList.get(intervalEndIndex)) { - LOG.warning( - String.format( - "Battery energy level percentage calculation error at index = %s", - predictionListIndex)); - // System.out.println("ERROR: calculation error at index = " + - // predictionListIndex); + infoStr.insert( + 0, + String.format( + "Battery energy level percentage calculation error at timestamp = %s", + timestampsMillisList.get(predictionListIndex))); + infoStr.append( + String.format( + "energyLevelPredictionListOptimised = %s \n", energyLevelPredictionList)); + LOG.warning(infoStr.toString()); break; } else if (dt > timeoutMillis) { - LOG.warning( - String.format( - "Battery energy level percentage calculation timed out at index = %s", - predictionListIndex)); - // System.out.println("ERROR: calculation timeout at index = " + - // predictionListIndex); + infoStr.insert( + 0, + String.format( + "Battery energy level percentage calculation timed out during power limit optimisation at timestamp = %s", + timestampsMillisList.get(predictionListIndex))); + infoStr.append( + String.format( + "energyLevelPredictionListOptimised = %s \n", energyLevelPredictionList)); + LOG.warning(infoStr.toString()); break; } energyLevelPredictionMaximum = - Collections.max( - energyLevelPredictionList.subList( - startRunningSumIndex, energyLevelPredictionList.size())); + Collections.max( + energyLevelPredictionList.subList( + startRunningSumIndex, energyLevelPredictionListSize)); energyLevelPredictionMinimum = - Collections.min( - energyLevelPredictionList.subList( - startRunningSumIndex, energyLevelPredictionList.size())); + Collections.min( + energyLevelPredictionList.subList( + startRunningSumIndex, energyLevelPredictionListSize)); dt = services.getTimerService().getCurrentTimeMillis() - dtStart; } - // System.out.println("After limits: energyLevelPredictionList = " + + // System.out.println("energyLevelPredictionListOptimisedLimits = " + // energyLevelPredictionList + "\n"); // Get day ahead asset @@ -836,26 +970,27 @@ private Map batteryCalculateForecasts( useDayAheadTariffs = dayAheadAsset.getUseTariffDayAheadForecasts().orElse(false); } - // Get the tariff forecasts from energy optimisation asset for 1 week + // Get the tariff forecasts from energy optimisation asset for 1 week, timestamps are + // ordered from newest to oldest (descending order) List> tariffExportDatapoints = - getTariffDatapoints( - energyOptimisationAsset, - EmsEnergyOptimisationAsset.TARIFF_EXPORT.getName(), - services); + getTariffDatapoints( + energyOptimisationAsset, + EmsEnergyOptimisationAsset.TARIFF_EXPORT.getName(), + services); List> tariffImportDatapoints = - getTariffDatapoints( - energyOptimisationAsset, - EmsEnergyOptimisationAsset.TARIFF_IMPORT.getName(), - services); + getTariffDatapoints( + energyOptimisationAsset, + EmsEnergyOptimisationAsset.TARIFF_IMPORT.getName(), + services); // Overwrite the current tariff forecasts with the day ahead tariff forecasts if (useDayAheadTariffs) { List> tariffExportDayAheadAssetDatapoints = - getTariffDayAheadDatapoints( - dayAheadAsset, EmsDayAheadAsset.TARIFF_EXPORT_DAY_AHEAD.getName(), services); + getTariffDayAheadDatapoints( + dayAheadAsset, EmsDayAheadAsset.TARIFF_EXPORT_DAY_AHEAD.getName(), services); List> tariffImportDayAheadAssetDatapoints = - getTariffDayAheadDatapoints( - dayAheadAsset, EmsDayAheadAsset.TARIFF_IMPORT_DAY_AHEAD.getName(), services); + getTariffDayAheadDatapoints( + dayAheadAsset, EmsDayAheadAsset.TARIFF_IMPORT_DAY_AHEAD.getName(), services); // Combine tariff export forecasts if (!tariffExportDayAheadAssetDatapoints.isEmpty()) { @@ -890,22 +1025,16 @@ private Map batteryCalculateForecasts( } } - // Calculate battery energy level percentage default - double energyLevelPercentageDefault = - batteryCalculateEnergyLevelPercentageDefault( - energyLevelPercentageMaximumBattery, - energyLevelPercentageMinimumBattery, - energyOptimisationAsset); - - // TODO: make window dynamic based on time needed to reach energyLevelPercentageDefault // Calculate optimal charge and discharge zone for each day based on tariffs Map chargeAndDischargeZonesMap = - calculateTariffChargeAndDischargeZones( - tariffImportDatapoints, tariffExportDatapoints, 4); + calculateTariffChargeAndDischargeZones( + tariffImportDatapoints, + tariffExportDatapoints, + BATTERY_TARIFF_OPTIMISATION_WINDOW_DEFAULT); // Charge zone = 1, discharge zone = -1 List chargeAndDischargeZonesList = - new ArrayList<>(Collections.nCopies(energyLevelPredictionList.size(), 0)); + new ArrayList<>(Collections.nCopies(energyLevelPredictionListSize, 0)); for (Map.Entry entry : chargeAndDischargeZonesMap.entrySet()) { long timestampMillis = entry.getKey(); @@ -922,192 +1051,282 @@ private Map batteryCalculateForecasts( // System.out.println("chargeAndDischargeZonesList = " + // chargeAndDischargeZonesList); - // Default energy level percentage for each interval + // Calculate battery energy level percentage default list + double energyLevelPercentageDefault = + batteryCalculateEnergyLevelPercentageDefault( + energyLevelPercentageMaximumBattery, + energyLevelPercentageMinimumBattery, + energyOptimisationAsset); List energyLevelPercentageDefaultList = - new ArrayList<>( - Collections.nCopies( - chargeAndDischargeZonesList.size(), energyLevelPercentageDefault)); + new ArrayList<>( + Collections.nCopies(energyLevelPredictionListSize, energyLevelPercentageDefault)); - for (int i = 0; i < chargeAndDischargeZonesList.size(); i++) { + for (int i = 0; i < energyLevelPredictionListSize; i++) { if (chargeAndDischargeZonesList.get(i) == 1) { energyLevelPercentageDefaultList.set( - i, Double.valueOf(energyLevelPercentageMaximumBattery)); + i, Double.valueOf(energyLevelPercentageMaximumBattery)); } else if (chargeAndDischargeZonesList.get(i) == -1) { energyLevelPercentageDefaultList.set( - i, Double.valueOf(energyLevelPercentageMinimumBattery)); + i, Double.valueOf(energyLevelPercentageMinimumBattery)); } } // System.out.println("energyLevelPercentageDefaultList = " + // energyLevelPercentageDefaultList); - // TODO: handle debounce interval in forecast - // Optimise forecast based on optimal tariffs + // Optimise forecast based on tariffs for (int i = 1; - i < chargeAndDischargeZonesList.size() && i < energyLevelPredictionList.size(); - i++) { - // Only adjust forecast if predicted energy level percentage is outside battery debounce - // interval - if (Math.abs(energyLevelPredictionList.get(i) - energyLevelPercentageDefaultList.get(i)) - <= BATTERY_ENERGY_LEVEL_PERCENTAGE_DEBOUNCE_INTERVAL) { - continue; - } - + i < chargeAndDischargeZonesList.size() && i < energyLevelPredictionListSize; + i++) { if (chargeAndDischargeZonesList.get(i) == 1 - && energyLevelPredictionList.get(i) < energyLevelPercentageDefaultList.get(i)) { + && energyLevelPredictionList.get(i) < energyLevelPercentageDefaultList.get(i)) { + // Interval from charge zone index till end of forecast Double energyLevelIntervalMaximum = - Collections.max( - energyLevelPredictionList.subList(i, energyLevelPredictionList.size())); - - double chargeSpaceLeft = - energyLevelPercentageMaximumBattery - energyLevelIntervalMaximum; - - if (chargeSpaceLeft > 0) { + Collections.max( + energyLevelPredictionList.subList(i, energyLevelPredictionListSize)); + double chargeSpaceOverall = + round( + energyLevelPercentageMaximumBattery - energyLevelIntervalMaximum, + batteryPercentageRoundingPrecision); + + if (chargeSpaceOverall > 0) { double chargeNeeded = - energyLevelPercentageDefaultList.get(i) - energyLevelPredictionList.get(i); + round( + energyLevelPercentageDefaultList.get(i) - energyLevelPredictionList.get(i), + batteryPercentageRoundingPrecision); double chargeAvailableLeft = 0; if (dischargePercentageWantBatteryList.get(i) >= 0) { double chargeInUse = - energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); - chargeAvailableLeft = chargePercentageAvailableBatteryList.get(i) - chargeInUse; + energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); + chargeAvailableLeft = + round( + chargePercentageAvailableBatteryList.get(i) - chargeInUse, + batteryPercentageRoundingPrecision); } if (chargeAvailableLeft > 0) { chargeAvailableLeft = - Math.min(Math.min(chargeSpaceLeft, chargeAvailableLeft), chargeNeeded); + Math.min(Math.min(chargeSpaceOverall, chargeAvailableLeft), chargeNeeded); + chargeSpaceOverall = + round( + chargeSpaceOverall - chargeAvailableLeft, + batteryPercentageRoundingPrecision); - for (int j = i; j < energyLevelPredictionList.size(); j++) { + for (int j = i; j < energyLevelPredictionListSize; j++) { energyLevelPredictionList.set( - j, energyLevelPredictionList.get(j) + chargeAvailableLeft); + j, + round( + energyLevelPredictionList.get(j) + chargeAvailableLeft, + batteryPercentageRoundingPrecision)); } } - } else { - double chargeZoneEnergyLevel = energyLevelPredictionList.get(i); - - int energyLevelIntervalMaximumIndex = energyLevelPredictionList.size(); + } - if (energyLevelIntervalMaximum >= energyLevelPercentageMaximumBattery) { - energyLevelIntervalMaximumIndex = + // Get the index of the first maximum + energyLevelIntervalMaximum = + Collections.max( + energyLevelPredictionList.subList(i, energyLevelPredictionListSize)); + int energyLevelIntervalMaximumIndex = energyLevelPredictionList.indexOf(energyLevelIntervalMaximum); - } - for (int k = i; k <= energyLevelIntervalMaximumIndex; k++) { - double chargeNeeded = energyLevelPredictionList.get(k) - chargeZoneEnergyLevel; - - if (chargeNeeded > 0 && chargePercentageWantBatteryList.get(k) == 0) { - double chargeAvailableLeft = 0; - - if (dischargePercentageWantBatteryList.get(i) >= 0) { - double chargeInUse = - energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); - chargeAvailableLeft = chargePercentageAvailableBatteryList.get(i) - chargeInUse; - } - - if (chargeAvailableLeft > 0) { + if (chargeSpaceOverall <= 0 && i < energyLevelIntervalMaximumIndex) { + double chargeInUse = + energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); + double chargeAvailableLeft = + round( + chargePercentageAvailableBatteryList.get(i) - chargeInUse, + batteryPercentageRoundingPrecision); + + int energyLevelIntervalIndex = 0; + dt = 0; + dtStart = services.getTimerService().getCurrentTimeMillis(); + + while (chargeAvailableLeft > 0 + && energyLevelIntervalIndex < energyLevelIntervalMaximumIndex) { + + for (int k = i + 1; k <= energyLevelIntervalMaximumIndex; k++) { + energyLevelIntervalIndex = k; + double chargeNeeded = + round( + energyLevelPredictionList.get(k) - energyLevelPredictionList.get(i), + batteryPercentageRoundingPrecision); + + // Only allow moving charging moment into charge zone if no charge is wanted at + // index k + if (chargeNeeded > 0 && chargePercentageWantBatteryList.get(k) == 0) { double energyLevelIntervalMaximum2 = - Collections.max(energyLevelPredictionList.subList(i, k - 1)); + Collections.max(energyLevelPredictionList.subList(i, k)); double chargeSpaceInterval = - energyLevelPercentageMaximumBattery - energyLevelIntervalMaximum2; + round( + energyLevelPercentageMaximumBattery - energyLevelIntervalMaximum2, + batteryPercentageRoundingPrecision); if (chargeSpaceInterval > 0) { + double chargeChange = + Math.min( + Math.min(chargeAvailableLeft, chargeNeeded), chargeSpaceInterval); chargeAvailableLeft = - Math.min( - Math.min(chargeAvailableLeft, chargeNeeded), chargeSpaceInterval); + round( + chargeAvailableLeft - chargeChange, + batteryPercentageRoundingPrecision); for (int j = i; j < k; j++) { energyLevelPredictionList.set( - j, energyLevelPredictionList.get(j) + chargeAvailableLeft); + j, + round( + energyLevelPredictionList.get(j) + chargeChange, + batteryPercentageRoundingPrecision)); } + break; } } + } + if (dt > timeoutMillis) { + infoStr.insert( + 0, + String.format( + "Battery energy level percentage calculation timed out during tariff charge optimisation at timestamp = %s", + timestampsMillisList.get(i))); + infoStr.append( + String.format( + "energyLevelPredictionListOptimised = %s \n", energyLevelPredictionList)); + LOG.warning(infoStr.toString()); break; } + + dt = services.getTimerService().getCurrentTimeMillis() - dtStart; } } } else if (chargeAndDischargeZonesList.get(i) == -1 - && energyLevelPredictionList.get(i) > energyLevelPercentageDefaultList.get(i)) { + && energyLevelPredictionList.get(i) > energyLevelPercentageDefaultList.get(i)) { + // Interval from discharge zone index till end of forecast Double energyLevelIntervalMinimum = - Collections.min( - energyLevelPredictionList.subList(i, energyLevelPredictionList.size())); - - double dischargeSpaceLeft = - energyLevelPercentageMinimumBattery - energyLevelIntervalMinimum; - - if (dischargeSpaceLeft < 0) { + Collections.min( + energyLevelPredictionList.subList(i, energyLevelPredictionListSize)); + double dischargeSpaceOverall = + round( + energyLevelPercentageMinimumBattery - energyLevelIntervalMinimum, + batteryPercentageRoundingPrecision); + + if (dischargeSpaceOverall < 0) { double dischargeNeeded = - energyLevelPercentageDefaultList.get(i) - energyLevelPredictionList.get(i); + round( + energyLevelPercentageDefaultList.get(i) - energyLevelPredictionList.get(i), + batteryPercentageRoundingPrecision); double dischargeAvailableLeft = 0; if (chargePercentageWantBatteryList.get(i) <= 0) { double dischargeInUse = - energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); + energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); dischargeAvailableLeft = - dischargePercentageAvailableBatteryList.get(i) - dischargeInUse; + round( + dischargePercentageAvailableBatteryList.get(i) - dischargeInUse, + batteryPercentageRoundingPrecision); } if (dischargeAvailableLeft < 0) { dischargeAvailableLeft = - Math.max(Math.max(dischargeSpaceLeft, dischargeAvailableLeft), dischargeNeeded); - - for (int j = i; j < energyLevelPredictionList.size(); j++) { + Math.max( + Math.max(dischargeSpaceOverall, dischargeAvailableLeft), dischargeNeeded); + dischargeSpaceOverall = + round( + dischargeSpaceOverall - dischargeAvailableLeft, + batteryPercentageRoundingPrecision); + + for (int j = i; j < energyLevelPredictionListSize; j++) { energyLevelPredictionList.set( - j, energyLevelPredictionList.get(j) + dischargeAvailableLeft); + j, + round( + energyLevelPredictionList.get(j) + dischargeAvailableLeft, + batteryPercentageRoundingPrecision)); } } - } else { - double dischargeZoneEnergyLevel = energyLevelPredictionList.get(i); - - int energyLevelIntervalMinimumIndex = energyLevelPredictionList.size(); + } - if (energyLevelIntervalMinimum <= energyLevelPercentageMinimumBattery) { - energyLevelIntervalMinimumIndex = + // Get the index of the first minimum + energyLevelIntervalMinimum = + Collections.min( + energyLevelPredictionList.subList(i, energyLevelPredictionListSize)); + int energyLevelIntervalMinimumIndex = energyLevelPredictionList.indexOf(energyLevelIntervalMinimum); - } - - for (int k = i; k <= energyLevelIntervalMinimumIndex; k++) { - double dischargeNeeded = - energyLevelPredictionList.get(k) - dischargeZoneEnergyLevel; - - if (dischargeNeeded < 0 && dischargePercentageWantBatteryList.get(k) == 0) { - double dischargeAvailableLeft = 0; - - if (chargePercentageWantBatteryList.get(i) <= 0) { - double dischargeInUse = - energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); - dischargeAvailableLeft = - dischargePercentageAvailableBatteryList.get(i) - dischargeInUse; - } - if (dischargeAvailableLeft < 0) { + if (dischargeSpaceOverall >= 0 && i < energyLevelIntervalMinimumIndex) { + double dischargeInUse = + energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); + double dischargeAvailableLeft = + round( + dischargePercentageAvailableBatteryList.get(i) - dischargeInUse, + batteryPercentageRoundingPrecision); + + int energyLevelIntervalIndex = 0; + dt = 0; + dtStart = services.getTimerService().getCurrentTimeMillis(); + + while (dischargeAvailableLeft < 0 + && energyLevelIntervalIndex < energyLevelIntervalMinimumIndex) { + + for (int k = i + 1; k <= energyLevelIntervalMinimumIndex; k++) { + energyLevelIntervalIndex = k; + double dischargeNeeded = + round( + energyLevelPredictionList.get(k) - energyLevelPredictionList.get(i), + batteryPercentageRoundingPrecision); + + // Only allow moving discharging moment into discharge zone if no discharge is + // wanted at index k + if (dischargeNeeded < 0 && dischargePercentageWantBatteryList.get(k) == 0) { double energyLevelIntervalMinimum2 = - Collections.min(energyLevelPredictionList.subList(i, k - 1)); + Collections.min(energyLevelPredictionList.subList(i, k)); double dischargeSpaceInterval = - energyLevelPercentageMinimumBattery - energyLevelIntervalMinimum2; + round( + energyLevelPercentageMinimumBattery - energyLevelIntervalMinimum2, + batteryPercentageRoundingPrecision); if (dischargeSpaceInterval < 0) { + double dischargeChange = + Math.max( + Math.max(dischargeAvailableLeft, dischargeNeeded), + dischargeSpaceInterval); dischargeAvailableLeft = - Math.max( - Math.max(dischargeAvailableLeft, dischargeNeeded), - dischargeSpaceInterval); + round( + dischargeAvailableLeft - dischargeChange, + batteryPercentageRoundingPrecision); for (int j = i; j < k; j++) { energyLevelPredictionList.set( - j, energyLevelPredictionList.get(j) + dischargeAvailableLeft); + j, + round( + energyLevelPredictionList.get(j) + dischargeChange, + batteryPercentageRoundingPrecision)); } + break; } } + } + if (dt > timeoutMillis) { + infoStr.insert( + 0, + String.format( + "Battery energy level percentage calculation timed out during tariff discharge optimisation at timestamp = %s", + timestampsMillisList.get(i))); + infoStr.append( + String.format( + "energyLevelPredictionListOptimised = %s \n", energyLevelPredictionList)); + LOG.warning(infoStr.toString()); break; } + + dt = services.getTimerService().getCurrentTimeMillis() - dtStart; } } } } } - // System.out.println("After Tariffs: energyLevelPredictionList = " + + // System.out.println("energyLevelPredictionListOptimisedLimitsAndTariffs = " + // energyLevelPredictionList + "\n"); // Get the current energy level percentage target @@ -1119,7 +1338,7 @@ private Map batteryCalculateForecasts( for (int i = 0; i < energyLevelPredictionList.size() - 1; i++) { double percentageChange = - energyLevelPredictionList.get(i + 1) - energyLevelPredictionList.get(i); + energyLevelPredictionList.get(i + 1) - energyLevelPredictionList.get(i); percentageChangeBatteryList.add(percentageChange); } @@ -1134,13 +1353,13 @@ private Map batteryCalculateForecasts( if (percentageChange > 0) { power = - percentageChange * energyCapacityBattery / (intervalHour * chargeEfficiencyBattery); + percentageChange * energyCapacityBattery / (intervalHour * chargeEfficiencyBattery); } else if (percentageChange < 0) { power = - percentageChange - * energyCapacityBattery - * dischargeEfficiencyBattery - / (10000 * intervalHour); + percentageChange + * energyCapacityBattery + * dischargeEfficiencyBattery + / (10000 * intervalHour); } powerChangeTotalList.add(round(power, 3)); @@ -1148,62 +1367,61 @@ private Map batteryCalculateForecasts( // System.out.println("powerChangeTotalList = " + powerChangeTotalList); + // Update list with power flexible changes for (int i = 0; i < powerChangeTotalList.size() - 1; i++) { - totalPowerConsumptionProductionFlexList.set( - i, totalPowerConsumptionProductionFlexList.get(i) - powerChangeTotalList.get(i)); + totalPowerConsumptionProductionFlexibleList.set( + i, totalPowerConsumptionProductionFlexibleList.get(i) - powerChangeTotalList.get(i)); } - // System.out.println("totalPowerConsumptionProductionFlexList = " + - // totalPowerConsumptionProductionFlexList); + // System.out.println("totalPowerConsumptionProductionFlexibleList = " + + // totalPowerConsumptionProductionFlexibleList); List> energyLevelPercentageForecast = new ArrayList<>(); List> powerSetpointForecast = new ArrayList<>(); // Update energy level percentage forecast starting after current time - for (int i = 1; i < timestampsMillisList.size(); i++) { + for (int i = 1; i < numberOfTimestamps; i++) { energyLevelPercentageForecast.add( - new ValueDatapoint<>( - timestampsMillisList.get(i), (int) Math.round(energyLevelPredictionList.get(i)))); + new ValueDatapoint<>( + timestampsMillisList.get(i), (int) Math.round(energyLevelPredictionList.get(i)))); } // Update power set-point starting from current power limit - for (int i = 0; i < timestampsMillisList.size(); i++) { + for (int i = 0; i < numberOfTimestamps; i++) { powerSetpointForecast.add( - new ValueDatapoint<>(timestampsMillisList.get(i), powerChangeTotalList.get(i))); + new ValueDatapoint<>(timestampsMillisList.get(i), powerChangeTotalList.get(i))); } services - .getAssetPredictedDatapointService() - .updateValues( - batteryAssetId, - EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE.getName(), - energyLevelPercentageForecast); + .getAssetPredictedDatapointService() + .updateValues( + batteryAssetId, + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE.getName(), + energyLevelPercentageForecast); services - .getAssetPredictedDatapointService() - .updateValues( - batteryAssetId, - EmsElectricityBatteryAsset.POWER_SETPOINT.getName(), - powerSetpointForecast); - - // System.out.println("UPDATED forecasts"); + .getAssetPredictedDatapointService() + .updateValues( + batteryAssetId, + EmsElectricityBatteryAsset.POWER_SETPOINT.getName(), + powerSetpointForecast); } return batteryEnergyLevelPercentageTargets; } - private Map batteryCalculatePowerFlexibleAvailable( - List electricityBatteryAssets) { - HashMap powerFlexibleAvailable = new HashMap<>(); + private Map batteryCalculatePowerFlexibleAvailable( + List electricityBatteryAssets) { + Map powerFlexibleAvailable = new HashMap<>(); for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { boolean allowCharging = electricityBatteryAsset.getAllowCharging().orElse(false); boolean allowDischarging = electricityBatteryAsset.getAllowDischarging().orElse(false); Double energyLevelPercentage = - electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); Integer energyLevelPercentageMaximum = - electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); Integer energyLevelPercentageMinimum = - electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); double chargePowerMaximum = electricityBatteryAsset.getChargePowerMaximum().orElse(0.0); double dischargePowerMaximum = electricityBatteryAsset.getDischargePowerMaximum().orElse(0.0); @@ -1215,43 +1433,33 @@ private Map batteryCalculatePowerFlexibleAvailable( } if (!allowDischarging - || energyLevelPercentageMinimum == null - || energyLevelPercentage == null) { + || energyLevelPercentageMinimum == null + || energyLevelPercentage == null) { dischargePowerMaximum = 0.0; } else if (energyLevelPercentage <= energyLevelPercentageMinimum) { dischargePowerMaximum = 0.0; } powerFlexibleAvailable.put( - electricityBatteryAsset.getId(), - new double[] {chargePowerMaximum, dischargePowerMaximum}); + electricityBatteryAsset.getId(), + new ChargeDischarge(chargePowerMaximum, dischargePowerMaximum)); } return powerFlexibleAvailable; } private Map batteryCalculatePowerSetpoints( - EmsEnergyOptimisationAsset energyOptimisationAsset, - List electricityBatteryAssets, - Services services, - String logPrefix) { - Map powerFlexibleAvailable = - batteryCalculatePowerFlexibleAvailable(electricityBatteryAssets); + EmsEnergyOptimisationAsset energyOptimisationAsset, + List electricityBatteryAssets, + long powerSetpointTimestampMillisLatest, + Map batteryEnergyLevelPercentageTargets, + Services services, + String logPrefixEnergyOptimisation) { + Map powerFlexibleAvailable = + batteryCalculatePowerFlexibleAvailable(electricityBatteryAssets); Map powerSetpointsNew; - // Get latest power set-point across all batteries - long powerSetpointTimestampMillisLatest = 0L; - - for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { - long powerSetpointTimestampMillis = - electricityBatteryAsset.getPowerSetpointTimestamp().orElse(0L); - - if (powerSetpointTimestampMillis > powerSetpointTimestampMillisLatest) { - powerSetpointTimestampMillisLatest = powerSetpointTimestampMillis; - } - } - - // Check if power net is updated since last battery power set-points update + // Check if power net updated since last batteries power set-point update Double powerNet = energyOptimisationAsset.getPowerNet().orElse(null); long powerNetTimestampMillis = energyOptimisationAsset.getPowerNetTimestamp().orElse(0L); @@ -1259,174 +1467,280 @@ private Map batteryCalculatePowerSetpoints( // disconnect if (powerSetpointTimestampMillisLatest > powerNetTimestampMillis || powerNet == null) { powerSetpointsNew = - batteryCheckPowerSetpointsCurrent(electricityBatteryAssets, powerFlexibleAvailable); + batteryCheckPowerSetpointsCurrent(electricityBatteryAssets, powerFlexibleAvailable); return powerSetpointsNew; } - // Check if power limits are present and calculate additional power limits + // Check if power limits are present Double powerLimitMaximumProfileTotal = - energyOptimisationAsset.getPowerLimitMaximumProfileTotal().orElse(null); + energyOptimisationAsset.getPowerLimitMaximumProfileTotal().orElse(null); Double powerLimitMinimumProfileTotal = - energyOptimisationAsset.getPowerLimitMinimumProfileTotal().orElse(null); - - Double powerLimitMaximumBatteries = null; - - if (powerLimitMaximumProfileTotal != null) { - powerLimitMaximumBatteries = - round( - powerLimitMaximumProfileTotal - * (1 - POWER_LIMIT_MAXIMUM_BATTERIES_MARGIN_PERCENTAGE * 0.01), - 3); - } - - Double powerLimitMinimumBatteries = null; - - if (powerLimitMinimumProfileTotal != null) { - powerLimitMinimumBatteries = - round( - powerLimitMinimumProfileTotal - * (1 - POWER_LIMIT_MINIMUM_BATTERIES_MARGIN_PERCENTAGE * 0.01), - 3); - } + energyOptimisationAsset.getPowerLimitMinimumProfileTotal().orElse(null); - // TODO: add battery limits cross-over warning - // TODO: add advanced settings + // Calculate debounce power limits + Double powerLimitMaximumVirtual = + calculatePowerLimitVirtual(energyOptimisationAsset, powerLimitMaximumProfileTotal, "max"); + Double powerLimitMinimumVirtual = + calculatePowerLimitVirtual(energyOptimisationAsset, powerLimitMinimumProfileTotal, "min"); - int currentMinute = LocalDateTime.now().getMinute(); - Map batteryEnergyLevelPercentageTargets = - batteryGetEnergyLevelPercentageTargetsCurrent(electricityBatteryAssets, services); + // Send warning LOG message when too large fluctuation margins are set + if (powerLimitMaximumProfileTotal != null + && powerLimitMinimumProfileTotal != null + && powerLimitMaximumVirtual != null + && powerLimitMinimumVirtual != null + && powerLimitMaximumVirtual < powerLimitMinimumVirtual) { + double diffPowerLimit = powerLimitMaximumProfileTotal - powerLimitMinimumProfileTotal; + double fluctuationMarginSum = + Math.abs(powerLimitMaximumProfileTotal - powerLimitMaximumVirtual) + + Math.abs(powerLimitMinimumProfileTotal - powerLimitMinimumVirtual); + + Long powerLimitMaximumProfileTimestampMillis = + energyOptimisationAsset.getPowerLimitMaximumProfileTotalTimestamp().orElse(null); + DateTimeFormatter formatter = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").withZone(ZoneId.systemDefault()); + String powerLimitMaximumProfileDateTime = ""; + + if (powerLimitMaximumProfileTimestampMillis != null) { + powerLimitMaximumProfileDateTime = + formatter.format(Instant.ofEpochMilli(powerLimitMaximumProfileTimestampMillis)); + } - // Calculate battery energy level forecast - // TODO: add 15 minute skip protection with forecast update timestamp for robust forecast - // updating - if ((currentMinute % 15) == 0) { - batteryEnergyLevelPercentageTargets = - batteryCalculateForecasts(electricityBatteryAssets, energyOptimisationAsset, services); + LOG.warning( + String.format( + "%s; Failed to perform '%s' energy optimisation method. The difference between '%s' - %s = %s kW is smaller than the fluctuation margin sum = %s kW for timestamp='%s'", + logPrefixEnergyOptimisation, + optimisationMethodName, + EmsEnergyOptimisationAsset.POWER_LIMIT_MAXIMUM_PROFILE_TOTAL.getName(), + EmsEnergyOptimisationAsset.POWER_LIMIT_MINIMUM_PROFILE_TOTAL.getName(), + diffPowerLimit, + fluctuationMarginSum, + powerLimitMaximumProfileDateTime)); } // Calculate virtual power consumption Map powerSetpointsCurrent = new HashMap<>(); for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { - String electricityBatteryAssetId = electricityBatteryAsset.getId(); double powerSetpoint = electricityBatteryAsset.getPowerSetpoint().orElse(0.0); - powerSetpointsCurrent.put(electricityBatteryAssetId, powerSetpoint); + powerSetpointsCurrent.put(electricityBatteryAsset.getId(), powerSetpoint); } double powerSetpointsCurrentSum = - powerSetpointsCurrent.values().stream().mapToDouble(Double::doubleValue).sum(); + powerSetpointsCurrent.values().stream().mapToDouble(Double::doubleValue).sum(); double powerConsumptionVirtual = powerNet - powerSetpointsCurrentSum; - // Calculate new power set-points - if (powerLimitMaximumProfileTotal != null - && powerConsumptionVirtual > powerLimitMaximumProfileTotal) { - Double powerLimitMaximumVirtual = - round( - powerLimitMaximumProfileTotal - * (1 - POWER_LIMIT_MAXIMUM_SAFETY_MARGIN_PERCENTAGE * 0.01), - 3); + // Calculate new power set-points without limits + Map powerSetpointsNewWithoutLimits = + batteryCalculatePowerSetpointsWithoutLimits( + electricityBatteryAssets, + batteryEnergyLevelPercentageTargets, + energyOptimisationAsset, + powerFlexibleAvailable, + services); + + // Apply power limits and adjust new power set-points + double powerSetpointsNewWithoutLimitsSum = + powerSetpointsNewWithoutLimits.values().stream().mapToDouble(Double::doubleValue).sum(); + double powerNetVirtualNew = powerConsumptionVirtual + powerSetpointsNewWithoutLimitsSum; + + double powerLimitMaximumFluctuation = + calculatePowerFluctuationMargin( + energyOptimisationAsset, powerLimitMaximumProfileTotal, "max"); + double powerLimitMinimumFluctuation = + calculatePowerFluctuationMargin( + energyOptimisationAsset, powerLimitMinimumProfileTotal, "min"); + + if (powerLimitMaximumVirtual != null + && (powerNetVirtualNew + powerLimitMaximumFluctuation) > powerLimitMaximumVirtual) { powerSetpointsNew = - batteryCalculatePowerSetpointsOnLimitBreach( - powerFlexibleAvailable, powerConsumptionVirtual, powerLimitMaximumVirtual, "max"); - } else if (powerLimitMinimumProfileTotal != null - && powerConsumptionVirtual < powerLimitMinimumProfileTotal) { - Double powerLimitMinimumVirtual = - round( - powerLimitMinimumProfileTotal - * (1 - POWER_LIMIT_MINIMUM_SAFETY_MARGIN_PERCENTAGE * 0.01), - 3); + batteryCalculatePowerSetpointsOnLimitBreach( + powerNetVirtualNew, + powerLimitMaximumVirtual, + electricityBatteryAssets, + powerFlexibleAvailable, + powerSetpointsNewWithoutLimits); + } else if (powerLimitMinimumVirtual != null + && (powerNetVirtualNew - powerLimitMinimumFluctuation) < powerLimitMinimumVirtual) { powerSetpointsNew = - batteryCalculatePowerSetpointsOnLimitBreach( - powerFlexibleAvailable, powerConsumptionVirtual, powerLimitMinimumVirtual, "min"); + batteryCalculatePowerSetpointsOnLimitBreach( + powerNetVirtualNew, + powerLimitMinimumVirtual, + electricityBatteryAssets, + powerFlexibleAvailable, + powerSetpointsNewWithoutLimits); } else { - powerSetpointsNew = - batteryRestoreEnergyLevels( - energyOptimisationAsset, - electricityBatteryAssets, - powerFlexibleAvailable, - powerNet, - powerSetpointsCurrentSum, - powerLimitMinimumBatteries, - powerLimitMaximumBatteries, - batteryEnergyLevelPercentageTargets); + powerSetpointsNew = powerSetpointsNewWithoutLimits; } - // Create log messages on power limit breach + // Send warning LOG message on power limit breach double powerSetpointsNewSum = - powerSetpointsNew.values().stream().mapToDouble(Double::doubleValue).sum(); + powerSetpointsNew.values().stream().mapToDouble(Double::doubleValue).sum(); double powerNetNewVirtual = powerConsumptionVirtual + powerSetpointsNewSum; - if (powerLimitMaximumProfileTotal != null && powerNet > powerLimitMaximumProfileTotal) { - double powerExceededAmount = round((powerNet - powerLimitMaximumProfileTotal), 3); + if (powerLimitMaximumProfileTotal != null + && powerNetNewVirtual > powerLimitMaximumProfileTotal) { + double powerReductionShortage = + round((powerNetNewVirtual - powerLimitMaximumProfileTotal), 3); LOG.warning( - String.format( - "%s; Power net is %s kW above power limit maximum", logPrefix, powerExceededAmount)); - - if (powerNetNewVirtual > powerLimitMaximumProfileTotal) { - double powerReductionShortage = - round((powerNetNewVirtual - powerLimitMaximumProfileTotal), 3); - LOG.warning( - String.format( - "%s; Not enough flexible power to get below power limit maximum; Shortage of %s kW", - logPrefix, powerReductionShortage)); - } - } else if (powerLimitMinimumProfileTotal != null && powerNet < powerLimitMinimumProfileTotal) { - double powerExceededAmount = round((powerNet - powerLimitMinimumProfileTotal), 3); + String.format( + "%s; Not enough flexible power to get below power limit maximum; Shortage of %s kW", + logPrefixEnergyOptimisation, powerReductionShortage)); + } else if (powerLimitMinimumProfileTotal != null + && powerNetNewVirtual < powerLimitMinimumProfileTotal) { + double powerReductionShortage = + round((powerNetNewVirtual - powerLimitMinimumProfileTotal), 3); LOG.warning( - String.format( - "%s; Power net is %s kW below power limit minimum", logPrefix, powerExceededAmount)); - - if (powerNetNewVirtual < powerLimitMinimumProfileTotal) { - double powerReductionShortage = - round((powerNetNewVirtual - powerLimitMinimumProfileTotal), 3); - LOG.warning( - String.format( - "%s; Not enough flexible power to get above power limit minimum; Shortage of %s kW", - logPrefix, powerReductionShortage)); - } + String.format( + "%s; Not enough flexible power to get above power limit minimum; Shortage of %s kW", + logPrefixEnergyOptimisation, powerReductionShortage)); } return powerSetpointsNew; } private Map batteryCalculatePowerSetpointsOnLimitBreach( - Map powerFlexibleAvailable, - Double powerConsumptionVirtual, - Double powerLimitVirtual, - String maxOrMin) { + double power, + double powerLimitVirtual, + List electricityBatteryAssets, + Map powerFlexibleAvailable, + Map powerSetpointsNewWithoutLimits) { Map powerSetpointsNew = new HashMap<>(); - boolean isMin = "min".equals(maxOrMin); + double powerChangeNeeded = round(power - powerLimitVirtual, 3); - // Calculate the total power adjustment needed - double powerReduction = powerConsumptionVirtual - powerLimitVirtual; + for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { + String electricityBatteryAssetId = electricityBatteryAsset.getId(); - if (isMin) { - powerReduction = -powerReduction; + double chargePowerAvailable = powerFlexibleAvailable.get(electricityBatteryAssetId).charge; + double dischargePowerAvailable = + powerFlexibleAvailable.get(electricityBatteryAssetId).discharge; + double powerSetpointNewWithoutLimits = + powerSetpointsNewWithoutLimits.getOrDefault(electricityBatteryAssetId, 0.0); + double chargePowerAvailableTotal = chargePowerAvailable - powerSetpointNewWithoutLimits; + double dischargePowerAvailableTotal = dischargePowerAvailable - powerSetpointNewWithoutLimits; + + double powerSetpointChange = 0.0; + + if (powerChangeNeeded > 0) { + powerSetpointChange = Math.max(-powerChangeNeeded, dischargePowerAvailableTotal); + } else if (powerChangeNeeded < 0) { + powerSetpointChange = Math.min(-powerChangeNeeded, chargePowerAvailableTotal); + } + + powerChangeNeeded = round(powerChangeNeeded + powerSetpointChange, 3); + + double powerSetpointNew = round(powerSetpointNewWithoutLimits + powerSetpointChange, 3); + powerSetpointsNew.put(electricityBatteryAssetId, powerSetpointNew); } - // Iterate over each battery asset - for (Map.Entry entry : powerFlexibleAvailable.entrySet()) { - String electricityBatteryAssetId = entry.getKey(); - double[] powerBatteryAvailableChargeDischarge = entry.getValue(); + return powerSetpointsNew; + } - // Choose available power based on breach type - double powerBatteryAvailable = - isMin - ? -powerBatteryAvailableChargeDischarge[0] - : powerBatteryAvailableChargeDischarge[1]; + private Map batteryCalculatePowerSetpointsWithoutLimits( + List electricityBatteryAssets, + Map batteryEnergyLevelPercentageTargets, + EmsEnergyOptimisationAsset energyOptimisationAsset, + Map powerFlexibleAvailable, + Services services) { + // Find battery power set-points for a system without power limits + Map powerSetpointsNew = new HashMap<>(); - if (powerReduction > 0) { - // Limit the power set-point to available battery power or remaining reduction needed - double setpoint = round(Math.max(-powerReduction, powerBatteryAvailable), 3); + long intervalMillis = 15 * 60000; + long endTimeMillis = services.getTimerService().getCurrentTimeMillis(); + long startTimeMillis = endTimeMillis - endTimeMillis % intervalMillis; - if (isMin) { - setpoint = -setpoint; + for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { + String electricityBatteryAssetId = electricityBatteryAsset.getId(); + Double energyLevelPercentage = + electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); + + // Set initial power set-point of 0 + powerSetpointsNew.put(electricityBatteryAssetId, 0.0); + + if (energyLevelPercentage == null) { + continue; + } + + double energyCapacity = electricityBatteryAsset.getEnergyCapacity().orElse(0.0); + double powerSetpointCurrent = electricityBatteryAsset.getPowerSetpoint().orElse(0.0); + + // Get battery energy level percentage target + Integer energyLevelPercentageTarget = + batteryEnergyLevelPercentageTargets.get(electricityBatteryAssetId); + + if (energyLevelPercentageTarget == null) { + Integer energyLevelPercentageMaximum = + electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); + Integer energyLevelPercentageMinimum = + electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); + energyLevelPercentageTarget = + batteryCalculateEnergyLevelPercentageDefault( + energyLevelPercentageMaximum, + energyLevelPercentageMinimum, + energyOptimisationAsset); + } + + // Get battery power set-point target + AssetDatapointAllQuery assetDatapointQueryPredicted = + new AssetDatapointAllQuery(startTimeMillis, endTimeMillis); + List> powerSetpointForecastList = + services + .getAssetPredictedDatapointService() + .queryDatapoints( + electricityBatteryAssetId, + EmsElectricityBatteryAsset.POWER_SETPOINT.getName(), + assetDatapointQueryPredicted); + + // Calculate battery power set-point target + double energyLevelPercentageRounded = round(energyLevelPercentage, 1); + double powerNeeded = + round( + (energyLevelPercentageTarget - energyLevelPercentageRounded) * 0.01 * energyCapacity, + 3); + double powerSetpointNeeded = + round(powerNeeded * 60 * BATTERY_POWER_SETPOINT_RESPONSIVENESS_DEFAULT, 3); + + if (energyLevelPercentageRounded < energyLevelPercentageTarget + || (powerSetpointCurrent > 0.0 + && energyLevelPercentageRounded < energyLevelPercentageTarget)) { + // Start charging or continue charging + int chargeEfficiencyPercentage = electricityBatteryAsset.getChargeEfficiency().orElse(100); + + double chargePowerAvailable = powerFlexibleAvailable.get(electricityBatteryAssetId).charge; + double powerSetpointEfficiency = + round(powerSetpointNeeded / (chargeEfficiencyPercentage * 0.01), 3); + double powerSetpointNew = Math.min(powerSetpointEfficiency, chargePowerAvailable); + + if (!powerSetpointForecastList.isEmpty()) { + Double powerSetpointForecast = (Double) powerSetpointForecastList.getLast().getValue(); + + if (powerSetpointForecast != null && powerSetpointForecast > 0) { + powerSetpointNew = Math.min(powerSetpointNew, powerSetpointForecast); + } } - powerSetpointsNew.put(electricityBatteryAssetId, setpoint); - powerReduction += setpoint; - } else { - powerSetpointsNew.put(electricityBatteryAssetId, 0.0); + powerSetpointsNew.put(electricityBatteryAssetId, powerSetpointNew); + } else if (energyLevelPercentageRounded > energyLevelPercentageTarget + || (powerSetpointCurrent < 0.0 + && energyLevelPercentageRounded > energyLevelPercentageTarget)) { + // Start discharging or continue discharging + int dischargeEfficiencyPercentage = + electricityBatteryAsset.getDischargeEfficiency().orElse(100); + + double dischargePowerAvailable = + powerFlexibleAvailable.get(electricityBatteryAssetId).discharge; + double powerSetpointEfficiency = + round(powerSetpointNeeded / (dischargeEfficiencyPercentage * 0.01), 3); + double powerSetpointNew = Math.max(powerSetpointEfficiency, dischargePowerAvailable); + + if (!powerSetpointForecastList.isEmpty()) { + Double powerSetpointForecast = (Double) powerSetpointForecastList.getLast().getValue(); + + if (powerSetpointForecast != null && powerSetpointForecast < 0) { + powerSetpointNew = Math.max(powerSetpointNew, powerSetpointForecast); + } + } + + powerSetpointsNew.put(electricityBatteryAssetId, powerSetpointNew); } } @@ -1434,15 +1748,15 @@ private Map batteryCalculatePowerSetpointsOnLimitBreach( } private void batteryCheckConnection( - List electricityBatteryAssets, Services services) { - // This method checks if a battery is connected based on if the power and energyLevelPercentage - // attributes update within the active time interval + List electricityBatteryAssets, Services services) { + // This method checks if a battery is connected based on if the 'power' and + // 'energyLevelPercentage' attributes update within the active time interval String connected = - EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType.connected - .toString(); + EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType.connected + .toString(); String disconnected = - EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType.disconnected - .toString(); + EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType.disconnected + .toString(); long currentTimestampMillis = services.getTimerService().getCurrentTimeMillis(); long activePeriodMillis = ACTIVE_PERIOD_MINUTES * 60000L; @@ -1451,7 +1765,7 @@ private void batteryCheckConnection( String electricityBatteryAssetId = electricityBatteryAsset.getId(); Optional - connectionStatusValue = electricityBatteryAsset.getConnectionStatus(); + connectionStatusValue = electricityBatteryAsset.getConnectionStatus(); String connectionStatusPrevious = ""; if (connectionStatusValue.isPresent()) { @@ -1462,81 +1776,117 @@ private void batteryCheckConnection( long powerTimestampMillisBattery = electricityBatteryAsset.getPowerTimestamp().orElse(0L); Double energyLevelPercentageBattery = - electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); long energyLevelPercentageTimestampMillisBattery = - electricityBatteryAsset.getEnergyLevelPercentageTimestamp().orElse(0L); + electricityBatteryAsset.getEnergyLevelPercentageTimestamp().orElse(0L); String connectionStatusCurrent = disconnected; if ((currentTimestampMillis - powerTimestampMillisBattery) < activePeriodMillis - && powerBattery != null - && (currentTimestampMillis - energyLevelPercentageTimestampMillisBattery) + && powerBattery != null + && (currentTimestampMillis - energyLevelPercentageTimestampMillisBattery) < activePeriodMillis - && energyLevelPercentageBattery != null) { + && energyLevelPercentageBattery != null) { connectionStatusCurrent = connected; } if (connectionStatusCurrent.equals(connected) - && connectionStatusPrevious.equals(disconnected)) { + && connectionStatusPrevious.equals(disconnected)) { services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - electricityBatteryAssetId, - EmsElectricityBatteryAsset.CONNECTION_STATUS, - EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType - .connected), - getClass().getSimpleName()); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + electricityBatteryAssetId, + EmsElectricityBatteryAsset.CONNECTION_STATUS, + EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType + .connected), + getClass().getSimpleName()); } else if (connectionStatusCurrent.equals(disconnected) - && connectionStatusPrevious.equals(connected)) { + && connectionStatusPrevious.equals(connected)) { services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - electricityBatteryAssetId, - EmsElectricityBatteryAsset.CONNECTION_STATUS, - EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType - .disconnected), - getClass().getSimpleName()); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + electricityBatteryAssetId, + EmsElectricityBatteryAsset.CONNECTION_STATUS, + EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType + .disconnected), + getClass().getSimpleName()); services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - electricityBatteryAssetId, - EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE, - null), - getClass().getSimpleName()); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + electricityBatteryAssetId, + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE, + null), + getClass().getSimpleName()); services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - electricityBatteryAssetId, EmsElectricityBatteryAsset.POWER, null), - getClass().getSimpleName()); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + electricityBatteryAssetId, EmsElectricityBatteryAsset.POWER, null), + getClass().getSimpleName()); } else if (connectionStatusCurrent.equals(connected) && connectionStatusPrevious.isEmpty()) { services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - electricityBatteryAssetId, - EmsElectricityBatteryAsset.CONNECTION_STATUS, - EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType - .connected), - getClass().getSimpleName()); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + electricityBatteryAssetId, + EmsElectricityBatteryAsset.CONNECTION_STATUS, + EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType + .connected), + getClass().getSimpleName()); } else if (connectionStatusCurrent.equals(disconnected) - && connectionStatusPrevious.isEmpty()) { + && connectionStatusPrevious.isEmpty()) { services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - electricityBatteryAssetId, - EmsElectricityBatteryAsset.CONNECTION_STATUS, - EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType - .disconnected), - getClass().getSimpleName()); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + electricityBatteryAssetId, + EmsElectricityBatteryAsset.CONNECTION_STATUS, + EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType + .disconnected), + getClass().getSimpleName()); } } } + private Map batteryCheckPowerSetpointsCurrent( + List electricityBatteryAssets, + Map powerFlexibleAvailable) { + Map powerSetpointsNew = new HashMap<>(); + + for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { + String electricityBatteryAssetId = electricityBatteryAsset.getId(); + Double energyLevelPercentage = + electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); + + if (energyLevelPercentage == null) { + powerSetpointsNew.put(electricityBatteryAssetId, 0.0); + continue; + } + + double powerSetpointCurrent = electricityBatteryAsset.getPowerSetpoint().orElse(0.0); + double chargePowerAvailable = powerFlexibleAvailable.get(electricityBatteryAssetId).charge; + double dischargePowerAvailable = + powerFlexibleAvailable.get(electricityBatteryAssetId).discharge; + + // Set initial power set-point new + double powerSetpointNew = 0.0; + + // Check if power set-point current is within available power limits + if (powerSetpointCurrent > 0.0) { + powerSetpointNew = Math.min(powerSetpointCurrent, chargePowerAvailable); + } else if (powerSetpointCurrent < 0.0) { + powerSetpointNew = Math.max(powerSetpointCurrent, dischargePowerAvailable); + } + + powerSetpointsNew.put(electricityBatteryAssetId, powerSetpointNew); + } + + return powerSetpointsNew; + } + private void batteryCheckSetup(List electricityBatteryAssets) { for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { // Check if the battery is enabled @@ -1548,152 +1898,131 @@ private void batteryCheckSetup(List electricityBatte } String logPrefixBattery = - String.format( - "assetType='%s', assetId='%s', assetName='%s'", - electricityBatteryAsset.getAssetType(), - electricityBatteryAsset.getId(), - electricityBatteryAsset.getAssetName()); + String.format( + "assetType='%s', assetId='%s', assetName='%s'", + electricityBatteryAsset.getAssetType(), + electricityBatteryAsset.getId(), + electricityBatteryAsset.getAssetName()); // Check if the following attributes are connected - Double energyLevelPercentage = - electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); - Double power = electricityBatteryAsset.getPower().orElse(null); - StringBuilder logMessageBatteryConnect = new StringBuilder(); + Map requiredFieldsConnection = new HashMap<>(); - if (energyLevelPercentage == null) { - logMessageBatteryConnect.append( - String.format(" '%s',", EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE.getName())); - } - - if (power == null) { - logMessageBatteryConnect.append( - String.format(" '%s',", EmsElectricityBatteryAsset.POWER.getName())); - } - - if (!logMessageBatteryConnect.isEmpty()) { - logMessageBatteryConnect.setLength(logMessageBatteryConnect.length() - 1); - logMessageBatteryConnect.insert( - 0, - String.format( - "%s; Can't use battery for flexible power. The following attributes are not connected:", - logPrefixBattery)); - LOG.warning(logMessageBatteryConnect.toString()); + requiredFieldsConnection.put( + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE.getName(), + electricityBatteryAsset.getEnergyLevelPercentage().orElse(null)); + requiredFieldsConnection.put( + EmsElectricityBatteryAsset.POWER.getName(), + electricityBatteryAsset.getPower().orElse(null)); + + List missingFieldsConnection = + requiredFieldsConnection.entrySet().stream() + .filter(entry -> entry.getValue() == null) + .map(Map.Entry::getKey) + .toList(); + + if (!missingFieldsConnection.isEmpty()) { + LOG.warning( + String.format( + "%s; Can't use battery for flexible power. The following attributes are not connected: %s", + logPrefixBattery, + String.join( + ", ", + missingFieldsConnection.stream().map(attr -> "'" + attr + "'").toList()))); } // Check if the following attributes are set - Double chargePowerMaximum = electricityBatteryAsset.getChargePowerMaximum().orElse(null); - Double dischargePowerMaximum = - electricityBatteryAsset.getDischargePowerMaximum().orElse(null); + Map requiredFieldsSetup = new HashMap<>(); + Integer energyLevelPercentageMaximum = - electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); Integer energyLevelPercentageMinimum = - electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); - StringBuilder logMessageBatterySet = new StringBuilder(); - - if (chargePowerMaximum == null) { - logMessageBatterySet.append( - String.format(" '%s',", EmsElectricityBatteryAsset.CHARGE_POWER_MAXIMUM.getName())); - } - - if (dischargePowerMaximum == null) { - logMessageBatterySet.append( - String.format(" '%s',", EmsElectricityBatteryAsset.DISCHARGE_POWER_MAXIMUM.getName())); - } - - if (energyLevelPercentageMaximum == null) { - logMessageBatterySet.append( - String.format( - " '%s',", EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MAXIMUM.getName())); - } - - if (energyLevelPercentageMinimum == null) { - logMessageBatterySet.append( - String.format( - " '%s',", EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MINIMUM.getName())); - } - - if (!logMessageBatterySet.isEmpty()) { - logMessageBatterySet.setLength(logMessageBatterySet.length() - 1); - logMessageBatterySet.insert( - 0, - String.format( - "%s; Can't use battery for flexible power. The following attributes are not set:", - logPrefixBattery)); - LOG.warning(logMessageBatterySet.toString()); + electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); + Integer chargeEfficiency = electricityBatteryAsset.getChargeEfficiency().orElse(null); + Integer dischargeEfficiency = electricityBatteryAsset.getDischargeEfficiency().orElse(null); + + requiredFieldsSetup.put( + EmsElectricityBatteryAsset.CHARGE_EFFICIENCY.getName(), chargeEfficiency); + requiredFieldsSetup.put( + EmsElectricityBatteryAsset.CHARGE_POWER_MAXIMUM.getName(), + electricityBatteryAsset.getChargePowerMaximum().orElse(null)); + requiredFieldsSetup.put( + EmsElectricityBatteryAsset.DISCHARGE_EFFICIENCY.getName(), dischargeEfficiency); + requiredFieldsSetup.put( + EmsElectricityBatteryAsset.DISCHARGE_POWER_MAXIMUM.getName(), + electricityBatteryAsset.getDischargePowerMaximum().orElse(null)); + requiredFieldsSetup.put( + EmsElectricityBatteryAsset.ENERGY_CAPACITY.getName(), + electricityBatteryAsset.getEnergyCapacity().orElse(null)); + requiredFieldsSetup.put( + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MAXIMUM.getName(), + energyLevelPercentageMaximum); + requiredFieldsSetup.put( + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MINIMUM.getName(), + energyLevelPercentageMinimum); + + List missingFieldsSetup = + requiredFieldsSetup.entrySet().stream() + .filter(entry -> entry.getValue() == null) + .map(Map.Entry::getKey) + .toList(); + + if (!missingFieldsSetup.isEmpty()) { + LOG.warning( + String.format( + "%s; Can't use battery for flexible power. The following attributes are not set: %s", + logPrefixBattery, + String.join( + ", ", missingFieldsSetup.stream().map(attr -> "'" + attr + "'").toList()))); } + // Check if the following attributes are set correctly if (energyLevelPercentageMaximum != null - && energyLevelPercentageMinimum != null - && (energyLevelPercentageMaximum - energyLevelPercentageMinimum) - < BATTERY_ENERGY_LEVEL_PERCENTAGE_DEBOUNCE_INTERVAL) { - int diff = energyLevelPercentageMaximum - energyLevelPercentageMinimum; + && energyLevelPercentageMinimum != null + && energyLevelPercentageMaximum <= energyLevelPercentageMinimum) { LOG.warning( - String.format( - "%s; Can't use battery for flexible power. %s - %s = %s - %s = %s < %s%%)", - logPrefixBattery, - EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MAXIMUM.getName(), - EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MINIMUM.getName(), - energyLevelPercentageMaximum, - energyLevelPercentageMinimum, - diff, - BATTERY_ENERGY_LEVEL_PERCENTAGE_DEBOUNCE_INTERVAL)); + String.format( + "%s; Can't use battery for flexible power. '%s' = %s%% is smaller than or equal to '%s'= %s%%)", + logPrefixBattery, + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MAXIMUM.getName(), + energyLevelPercentageMaximum, + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MINIMUM.getName(), + energyLevelPercentageMinimum)); } - } - } - - private Map batteryCheckPowerSetpointsCurrent( - List electricityBatteryAssets, - Map powerFlexibleAvailable) { - Map powerSetpointsNew = new HashMap<>(); - - for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { - String electricityBatteryAssetId = electricityBatteryAsset.getId(); - Double energyLevelPercentage = - electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); - if (energyLevelPercentage == null) { - powerSetpointsNew.put(electricityBatteryAssetId, 0.0); - continue; + if (chargeEfficiency != null && chargeEfficiency <= 0) { + LOG.warning( + String.format( + "The charge efficiency = %s%% is smaller than or equal to 0", chargeEfficiency)); } - double powerSetpointCurrent = electricityBatteryAsset.getPowerSetpoint().orElse(0.0); - double chargePowerAvailable = powerFlexibleAvailable.get(electricityBatteryAssetId)[0]; - double dischargePowerAvailable = powerFlexibleAvailable.get(electricityBatteryAssetId)[1]; - - // Set initial power set-point new - double powerSetpointNew = 0.0; - - // Check if power set-point current is within available power limits - if (powerSetpointCurrent > 0.0) { - powerSetpointNew = Math.min(powerSetpointCurrent, chargePowerAvailable); - } else if (powerSetpointCurrent < 0.0) { - powerSetpointNew = Math.max(powerSetpointCurrent, dischargePowerAvailable); + if (dischargeEfficiency != null && dischargeEfficiency <= 0) { + LOG.warning( + String.format( + "The discharge efficiency = %s%% is smaller than or equal to 0", + dischargeEfficiency)); } - - powerSetpointsNew.put(electricityBatteryAssetId, powerSetpointNew); } - - return powerSetpointsNew; } private Map batteryGetEnergyLevelPercentageTargetsCurrent( - List electricityBatteryAssets, Services services) { + List electricityBatteryAssets, Services services) { Map batteryEnergyLevelPercentageTargetsCurrent = new HashMap<>(); + long intervalMillis = 15 * 60000; long currentTimeMillis = services.getTimerService().getCurrentTimeMillis(); - long endTimeMillis = currentTimeMillis - currentTimeMillis % (15 * 60000) + (15 * 60000); + long endTimeMillis = currentTimeMillis - currentTimeMillis % intervalMillis + intervalMillis; for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { String batteryAssetId = electricityBatteryAsset.getId(); AssetDatapointAllQuery assetDatapointQueryPredicted = - new AssetDatapointAllQuery(currentTimeMillis, endTimeMillis); + new AssetDatapointAllQuery(currentTimeMillis, endTimeMillis); List> energyLevelPercentagePredictedCurrent = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - batteryAssetId, - EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE.getName(), - assetDatapointQueryPredicted); + services + .getAssetPredictedDatapointService() + .queryDatapoints( + batteryAssetId, + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE.getName(), + assetDatapointQueryPredicted); Integer energyLevelPercentageTarget = null; @@ -1708,157 +2037,106 @@ private Map batteryGetEnergyLevelPercentageTargetsCurrent( return batteryEnergyLevelPercentageTargetsCurrent; } - private Map batteryRestoreEnergyLevels( - EmsEnergyOptimisationAsset energyOptimisationAsset, - List electricityBatteryAssets, - Map powerFlexibleAvailable, - Double powerNet, - Double powerSetpointsCurrentSum, - Double powerLimitMinimumBatteries, - Double powerLimitMaximumBatteries, - Map batteryEnergyLevelPercentageTargets) { - Map powerSetpointsNew = new HashMap<>(); + private List batteryOrder( + List electricityBatteryAssets) { + List sorted = new ArrayList<>(electricityBatteryAssets); + + sorted.sort( + Comparator.comparing( + (EmsElectricityBatteryAsset b) -> + b.getEnergyCapacity().isPresent() + && b.getDischargePowerMaximum().orElse(0.0) != 0.0) + .reversed() + .thenComparing( + b -> + b.getDischargePowerMaximum().orElse(0.0) == 0 + ? null + : round( + b.getEnergyCapacity().orElse(0.0) + / b.getDischargePowerMaximum().orElse(0.0), + 2), + Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(EmsElectricityBatteryAsset::getId)); + + return sorted; + } - // Find battery power set-points for a system without power limits + private void batteryUpdatePowerSetpoints( + List electricityBatteryAssets, + Map powerSetpointsNew, + Services services) { for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { String electricityBatteryAssetId = electricityBatteryAsset.getId(); - Double energyLevelPercentage = - electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); - - // Set initial power set-point of 0 - powerSetpointsNew.put(electricityBatteryAssetId, 0.0); - - if (energyLevelPercentage == null) { - continue; - } - - double powerSetpointCurrent = electricityBatteryAsset.getPowerSetpoint().orElse(0.0); - - // Get battery energy level target - Integer energyLevelPercentageTarget = - batteryEnergyLevelPercentageTargets.get(electricityBatteryAssetId); - - if (energyLevelPercentageTarget == null) { - Integer energyLevelPercentageMaximum = - electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); - Integer energyLevelPercentageMinimum = - electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); - energyLevelPercentageTarget = - batteryCalculateEnergyLevelPercentageDefault( - energyLevelPercentageMaximum, - energyLevelPercentageMinimum, - energyOptimisationAsset); - } + Double powerSetpointNew = powerSetpointsNew.get(electricityBatteryAssetId); - if (energyLevelPercentage - < (energyLevelPercentageTarget - BATTERY_ENERGY_LEVEL_PERCENTAGE_DEBOUNCE_INTERVAL)) { - // Start charging - double powerSetpointNew = powerFlexibleAvailable.get(electricityBatteryAssetId)[0]; - powerSetpointsNew.put(electricityBatteryAssetId, powerSetpointNew); - } else if (energyLevelPercentage - > (energyLevelPercentageTarget + BATTERY_ENERGY_LEVEL_PERCENTAGE_DEBOUNCE_INTERVAL)) { - // Start discharging - double powerSetpointNew = powerFlexibleAvailable.get(electricityBatteryAssetId)[1]; - powerSetpointsNew.put(electricityBatteryAssetId, powerSetpointNew); - } else if (powerSetpointCurrent > 0.0 - && energyLevelPercentage < energyLevelPercentageTarget) { - // Continue charging - double powerSetpointNew = powerFlexibleAvailable.get(electricityBatteryAssetId)[0]; - powerSetpointsNew.put(electricityBatteryAssetId, powerSetpointNew); - } else if (powerSetpointCurrent < 0.0 - && energyLevelPercentage > energyLevelPercentageTarget) { - // Continue discharging - double powerSetpointNew = powerFlexibleAvailable.get(electricityBatteryAssetId)[1]; - powerSetpointsNew.put(electricityBatteryAssetId, powerSetpointNew); - } + services + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + electricityBatteryAssetId, + EmsElectricityBatteryAsset.POWER_SETPOINT, + powerSetpointNew), + getClass().getSimpleName()); } + } - // Adjust battery power set-points in a system with power limits - double powerSetpointsNewSum = - powerSetpointsNew.values().stream().mapToDouble(Double::doubleValue).sum(); - double powerConsumptionVirtual = powerNet - powerSetpointsCurrentSum; - - Double chargingSpace = null; - Double dischargingSpace = null; - - if (powerLimitMaximumBatteries != null) { - chargingSpace = powerLimitMaximumBatteries - powerConsumptionVirtual; + private double calculatePowerFluctuationMargin( + EmsEnergyOptimisationAsset energyOptimisationAsset, + Double powerLimitProfileTotal, + String maxOrMin) { + if (powerLimitProfileTotal == null) { + return 0.0; } - if (powerLimitMinimumBatteries != null) { - dischargingSpace = powerLimitMinimumBatteries - powerConsumptionVirtual; + boolean isMin = "min".equals(maxOrMin); + String fluctuationMarginAttributeName = POWER_LIMIT_MAXIMUM_FLUCTUATION_MARGIN_ATTRIBUTE_NAME; + + if (isMin) { + fluctuationMarginAttributeName = POWER_LIMIT_MINIMUM_FLUCTUATION_MARGIN_ATTRIBUTE_NAME; } - if (chargingSpace != null && powerSetpointsNewSum > chargingSpace) { - // Adjust battery power set-points in case of charging space shortage - double powerReduction = powerSetpointsNewSum - chargingSpace; + Double powerLimitFluctuationMargin = + (Double) + energyOptimisationAsset + .getAttribute(fluctuationMarginAttributeName) + .flatMap(Attribute::getValue) + .orElse(null); - for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { - String electricityBatteryAssetId = electricityBatteryAsset.getId(); - double powerSetpointNew = powerSetpointsNew.getOrDefault(electricityBatteryAssetId, 0.0); + if (powerLimitFluctuationMargin == null) { + powerLimitFluctuationMargin = + powerLimitProfileTotal * POWER_LIMIT_FLUCTUATION_MARGIN_PERCENTAGE_DEFAULT * 0.01; + } - // Only adjust battery power set-point for charging batteries - if (powerSetpointNew > 0.0) { - powerReduction = powerReduction - powerSetpointNew; + return round(Math.abs(powerLimitFluctuationMargin), 3); + } - if (powerReduction >= 0.0) { - powerSetpointsNew.put(electricityBatteryAssetId, 0.0); - } else { - double powerSetpointAdjusted = round(Math.min(-powerReduction, powerSetpointNew), 3); - powerSetpointsNew.put(electricityBatteryAssetId, powerSetpointAdjusted); - break; - } - } - } - } else if (dischargingSpace != null && powerSetpointsNewSum < dischargingSpace) { - // Adjust battery power set-points in case of discharging space shortage - double powerReduction = powerSetpointsNewSum - dischargingSpace; - - for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { - String electricityBatteryAssetId = electricityBatteryAsset.getId(); - double powerSetpointNew = powerSetpointsNew.getOrDefault(electricityBatteryAssetId, 0.0); - - // Only adjust battery power set-point for discharging batteries - if (powerSetpointNew < 0.0) { - powerReduction = powerReduction - powerSetpointNew; - - if (powerReduction <= 0.0) { - powerSetpointsNew.put(electricityBatteryAssetId, 0.0); - } else { - double powerSetpointAdjusted = Math.max(-powerReduction, powerSetpointNew); - powerSetpointsNew.put(electricityBatteryAssetId, powerSetpointAdjusted); - break; - } - } - } + private Double calculatePowerLimitVirtual( + EmsEnergyOptimisationAsset energyOptimisationAsset, + Double powerLimitProfileTotal, + String maxOrMin) { + if (powerLimitProfileTotal == null) { + return null; } - return powerSetpointsNew; - } + double powerLimitFluctuationMargin = + calculatePowerFluctuationMargin(energyOptimisationAsset, powerLimitProfileTotal, maxOrMin); + boolean isMin = "min".equals(maxOrMin); - private void batteryUpdatePowerSetpoints( - List electricityBatteryAssets, - Map powerSetpointsNew, - Services services) { - for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { - String electricityBatteryAssetId = electricityBatteryAsset.getId(); - Double powerSetpointNew = powerSetpointsNew.get(electricityBatteryAssetId); + double powerLimitVirtual; - services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - electricityBatteryAssetId, - EmsElectricityBatteryAsset.POWER_SETPOINT, - powerSetpointNew), - getClass().getSimpleName()); + if (isMin) { + powerLimitVirtual = powerLimitProfileTotal + powerLimitFluctuationMargin; + } else { + powerLimitVirtual = powerLimitProfileTotal - powerLimitFluctuationMargin; } + + return round(powerLimitVirtual, 3); } private Map calculateTariffChargeAndDischargeZones( - List> tariffImportDatapoints, - List> tariffExportDatapoints, - int window) { + List> tariffImportDatapoints, + List> tariffExportDatapoints, + int window) { Map chargeAndDischargeZonesMap = new HashMap<>(); // Return empty map when there is no tariff forecast present @@ -1866,39 +2144,39 @@ private Map calculateTariffChargeAndDischargeZones( return chargeAndDischargeZonesMap; } - // Find the best import price for each day - ZoneId zoneId = ZoneId.systemDefault(); - + // Find the best import/export tariff window for each day List> tariffImportMovingAverage = - movingAverage(tariffImportDatapoints, window); + movingAverage(tariffImportDatapoints, window); List> tariffExportMovingAverage = - movingAverage(tariffExportDatapoints, window); + movingAverage(tariffExportDatapoints, window); + + ZoneId zoneId = ZoneId.systemDefault(); Map tariffImportDailyMinimumMap = - IntStream.range(0, tariffImportMovingAverage.size()) - .mapToObj( - i -> { - ValueDatapoint dp = tariffImportMovingAverage.get(i); - return new IndexedDatapoint(dp.getTimestamp(), (Double) dp.getValue(), i); - }) - .collect( - Collectors.toMap( - dp -> Instant.ofEpochMilli(dp.timestamp()).atZone(zoneId).toLocalDate(), - Function.identity(), - BinaryOperator.minBy(Comparator.comparing(IndexedDatapoint::value)))); + IntStream.range(0, tariffImportMovingAverage.size()) + .mapToObj( + i -> { + ValueDatapoint dp = tariffImportMovingAverage.get(i); + return new IndexedDatapoint(dp.getTimestamp(), (Double) dp.getValue(), i); + }) + .collect( + Collectors.toMap( + dp -> Instant.ofEpochMilli(dp.timestamp()).atZone(zoneId).toLocalDate(), + Function.identity(), + BinaryOperator.minBy(Comparator.comparing(IndexedDatapoint::value)))); Map tariffExportDailyMinimumMap = - IntStream.range(0, tariffExportMovingAverage.size()) - .mapToObj( - i -> { - ValueDatapoint dp = tariffExportMovingAverage.get(i); - return new IndexedDatapoint(dp.getTimestamp(), (Double) dp.getValue(), i); - }) - .collect( - Collectors.toMap( - dp -> Instant.ofEpochMilli(dp.timestamp()).atZone(zoneId).toLocalDate(), - Function.identity(), - BinaryOperator.minBy(Comparator.comparing(IndexedDatapoint::value)))); + IntStream.range(0, tariffExportMovingAverage.size()) + .mapToObj( + i -> { + ValueDatapoint dp = tariffExportMovingAverage.get(i); + return new IndexedDatapoint(dp.getTimestamp(), (Double) dp.getValue(), i); + }) + .collect( + Collectors.toMap( + dp -> Instant.ofEpochMilli(dp.timestamp()).atZone(zoneId).toLocalDate(), + Function.identity(), + BinaryOperator.minBy(Comparator.comparing(IndexedDatapoint::value)))); Map chargeZonesMap = new HashMap<>(); Map dischargeZonesMap = new HashMap<>(); @@ -1939,18 +2217,18 @@ private Map calculateTariffChargeAndDischargeZones( } private EmsDayAheadAsset getDayAheadAsset( - EmsEnergyOptimisationAsset energyOptimisationAsset, Services services) { + EmsEnergyOptimisationAsset energyOptimisationAsset, Services services) { // Find assets in database List assets = - services - .getAssetStorageService() - .findAll( - new AssetQuery() - .parents(energyOptimisationAsset.getId()) - .types(EmsDayAheadAsset.class)) - .stream() - .map(asset -> (EmsDayAheadAsset) asset) - .toList(); + services + .getAssetStorageService() + .findAll( + new AssetQuery() + .parents(energyOptimisationAsset.getId()) + .types(EmsDayAheadAsset.class)) + .stream() + .map(asset -> (EmsDayAheadAsset) asset) + .toList(); EmsDayAheadAsset asset = null; @@ -1958,80 +2236,81 @@ private EmsDayAheadAsset getDayAheadAsset( asset = assets.getFirst(); } else if (assets.size() > 1) { String logPrefixEnergyOptimisation = - String.format( - "assetType='%s', assetId='%s', assetName='%s'", - energyOptimisationAsset.getAssetType(), - energyOptimisationAsset.getId(), - energyOptimisationAsset.getAssetName()); + String.format( + "assetType='%s', assetId='%s', assetName='%s'", + energyOptimisationAsset.getAssetType(), + energyOptimisationAsset.getId(), + energyOptimisationAsset.getAssetName()); LOG.warning( - String.format( - "%s; Found %s '%s' assets; Only 1 '%s' asset is allowed; Remove additional '%s' assets", - logPrefixEnergyOptimisation, - assets.size(), - EmsGOPACSAsset.class.getSimpleName(), - EmsGOPACSAsset.class.getSimpleName(), - EmsGOPACSAsset.class.getSimpleName())); + String.format( + "%s; Found %s '%s' assets; Only 1 '%s' asset is allowed; Remove additional '%s' assets", + logPrefixEnergyOptimisation, + assets.size(), + EmsGOPACSAsset.class.getSimpleName(), + EmsGOPACSAsset.class.getSimpleName(), + EmsGOPACSAsset.class.getSimpleName())); } return asset; } - private List> getTariffDayAheadDatapoints( - Asset asset, String attributeName, Services services) { + private List> getTariffDatapoints( + EmsEnergyOptimisationAsset energyOptimisationAsset, String attributeName, Services services) { // Get the start of the day (00:00) in milliseconds long currentTimeMillis = services.getTimerService().getCurrentTimeMillis(); ZoneId zoneId = ZoneId.systemDefault(); LocalDate date = Instant.ofEpochMilli(currentTimeMillis).atZone(zoneId).toLocalDate(); long startOfCurrentDayMillis = date.atStartOfDay(zoneId).toInstant().toEpochMilli(); - // Get tariff data-points of yesterday, today and tomorrow + // Get tariff data-points of yesterday and 1 week into the future long startTimeMillis = startOfCurrentDayMillis - 24 * 60 * 60000; - long endTimeMillis = startOfCurrentDayMillis + 2 * 24 * 60 * 60000; + long endTimeMillis = startOfCurrentDayMillis + 8 * 24 * 60 * 60000; AssetDatapointAllQuery assetDatapointQueryHistoric = - new AssetDatapointAllQuery(startTimeMillis, endTimeMillis); + new AssetDatapointAllQuery(startTimeMillis, currentTimeMillis); + AssetDatapointAllQuery assetDatapointQueryPredicted = + new AssetDatapointAllQuery(currentTimeMillis, endTimeMillis); List> tariffHistoric = - services - .getAssetDatapointService() - .queryDatapoints(asset.getId(), attributeName, assetDatapointQueryHistoric); + services + .getAssetDatapointService() + .queryDatapoints( + energyOptimisationAsset.getId(), attributeName, assetDatapointQueryHistoric); + List> tariffPredicted = + services + .getAssetPredictedDatapointService() + .queryDatapoints( + energyOptimisationAsset.getId(), attributeName, assetDatapointQueryPredicted); - return tariffHistoric; + // Combine historic and predicted data-points, timestamps are ordered from newest to oldest + // (descending order) + List> tariffCombined = new ArrayList<>(tariffPredicted); + tariffCombined.addAll(tariffHistoric); + + return tariffCombined; } - private List> getTariffDatapoints( - EmsEnergyOptimisationAsset energyOptimisationAsset, String attributeName, Services services) { + private List> getTariffDayAheadDatapoints( + Asset asset, String attributeName, Services services) { // Get the start of the day (00:00) in milliseconds long currentTimeMillis = services.getTimerService().getCurrentTimeMillis(); ZoneId zoneId = ZoneId.systemDefault(); LocalDate date = Instant.ofEpochMilli(currentTimeMillis).atZone(zoneId).toLocalDate(); long startOfCurrentDayMillis = date.atStartOfDay(zoneId).toInstant().toEpochMilli(); - // Get tariff data-points of yesterday and 1 week into the future + // Get tariff data-points of yesterday, today and tomorrow long startTimeMillis = startOfCurrentDayMillis - 24 * 60 * 60000; - long endTimeMillis = startOfCurrentDayMillis + 8 * 24 * 60 * 60000; + long endTimeMillis = startOfCurrentDayMillis + 2 * 24 * 60 * 60000; AssetDatapointAllQuery assetDatapointQueryHistoric = - new AssetDatapointAllQuery(startTimeMillis, currentTimeMillis); - AssetDatapointAllQuery assetDatapointQueryPredicted = - new AssetDatapointAllQuery(currentTimeMillis, endTimeMillis); + new AssetDatapointAllQuery(startTimeMillis, endTimeMillis); List> tariffHistoric = - services - .getAssetDatapointService() - .queryDatapoints( - energyOptimisationAsset.getId(), attributeName, assetDatapointQueryHistoric); - List> tariffPredicted = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - energyOptimisationAsset.getId(), attributeName, assetDatapointQueryPredicted); - - // Combine historic and predicted data-points into one list - List> tariffCombined = new ArrayList<>(tariffHistoric); - tariffCombined.addAll(tariffPredicted); + services + .getAssetDatapointService() + .queryDatapoints(asset.getId(), attributeName, assetDatapointQueryHistoric); - return tariffCombined; + return tariffHistoric; } public static List> intervalAverage( - List> dataPoints, long intervalMillis) { + List> dataPoints, long intervalMillis) { // Map Map> valuesPerIntervalMap = new HashMap<>(); @@ -2045,8 +2324,8 @@ public static List> intervalAverage( // Add value to corresponding interval valuesPerIntervalMap - .computeIfAbsent(intervalStartMillis, key -> new ArrayList<>()) - .add(value); + .computeIfAbsent(intervalStartMillis, key -> new ArrayList<>()) + .add(value); } } @@ -2071,10 +2350,10 @@ public static List> intervalAverage( } private List> intervalInterpolate( - List> dataPoints, - long startTimeMillis, - long endTimeMillis, - long intervalMillis) { + List> dataPoints, + long startTimeMillis, + long endTimeMillis, + long intervalMillis) { List> interpolatedList = new ArrayList<>(); if (dataPoints == null || dataPoints.size() < 2) { @@ -2084,11 +2363,11 @@ private List> intervalInterpolate( int idx1 = 0; for (long intervalTimeMillis = startTimeMillis; - intervalTimeMillis <= endTimeMillis; - intervalTimeMillis += intervalMillis) { + intervalTimeMillis <= endTimeMillis; + intervalTimeMillis += intervalMillis) { // Find data-point before and after interval while (idx1 < (dataPoints.size() - 1) - && dataPoints.get(idx1 + 1).getTimestamp() < intervalTimeMillis) { + && dataPoints.get(idx1 + 1).getTimestamp() < intervalTimeMillis) { idx1++; } @@ -2101,7 +2380,7 @@ private List> intervalInterpolate( double valueAfter = (double) dataPoints.get(idx1 + 1).getValue(); double factor = - (double) (intervalTimeMillis - timeBeforeMillis) / (timeAfterMillis - timeBeforeMillis); + (double) (intervalTimeMillis - timeBeforeMillis) / (timeAfterMillis - timeBeforeMillis); double interpolatedValue = valueBefore + factor * (valueAfter - valueBefore); interpolatedList.add(new ValueDatapoint<>(intervalTimeMillis, interpolatedValue)); @@ -2111,8 +2390,7 @@ private List> intervalInterpolate( return interpolatedList; } - public static List> movingAverage( - List> dataPoints, int window) { + public List> movingAverage(List> dataPoints, int window) { List> result = new ArrayList<>(); if (window <= 0 || dataPoints.size() < window) { return result; @@ -2121,24 +2399,21 @@ public static List> movingAverage( double sum = 0.0; for (int i = 0; i < dataPoints.size(); i++) { - // Extract numeric value - Number value = (Number) dataPoints.get(i).getValue(); - sum += value.doubleValue(); + Double value = (Double) dataPoints.get(i).getValue(); + sum += value; - // Remove value exiting the sliding window if (i >= window) { - Number oldValue = (Number) dataPoints.get(i - window).getValue(); - sum -= oldValue.doubleValue(); + Double valueOld = (Double) dataPoints.get(i - window).getValue(); + sum -= valueOld; } - // When the window is "full", generate output datapoint if (i >= window - 1) { double avg = sum / window; - // Use the timestamp of the window end (index i) ValueDatapoint original = dataPoints.get(i); - ValueDatapoint averaged = new ValueDatapoint<>(original.getTimestamp(), avg); + ValueDatapoint averaged = + new ValueDatapoint<>(original.getTimestamp(), round(avg, 7)); result.add(averaged); } } @@ -2152,22 +2427,22 @@ private double round(double value, int precision) { } private void updateDayAheadAsset( - EmsEnergyOptimisationAsset energyOptimisationAsset, - EmsDayAheadAsset dayAheadAsset, - Services services) { + EmsEnergyOptimisationAsset energyOptimisationAsset, + EmsDayAheadAsset dayAheadAsset, + Services services) { String dayAheadAssetId = dayAheadAsset.getId(); String logPrefixDayAhead = - String.format( - "assetType='%s', assetId='%s', assetName='%s'", - dayAheadAsset.getAssetType(), dayAheadAssetId, dayAheadAsset.getAssetName()); + String.format( + "assetType='%s', assetId='%s', assetName='%s'", + dayAheadAsset.getAssetType(), dayAheadAssetId, dayAheadAsset.getAssetName()); String collectTimeForecasts = dayAheadAsset.getCollectTimeForecasts().orElse(""); if (collectTimeForecasts.isBlank()) { LOG.warning( - String.format( - "%s, attributeName='%s'; Set time to collect day ahead forecasts", - logPrefixDayAhead, EmsDayAheadAsset.COLLECT_TIME_FORECASTS.getName())); + String.format( + "%s, attributeName='%s'; Set time to collect day ahead forecasts", + logPrefixDayAhead, EmsDayAheadAsset.COLLECT_TIME_FORECASTS.getName())); } // Parse the time string @@ -2178,108 +2453,111 @@ private void updateDayAheadAsset( collectTime = LocalTime.parse(collectTimeForecasts, DateTimeFormatter.ofPattern("HH:mm")); } catch (Exception e) { LOG.warning( - String.format( - "%s, attributeName='%s'; Error while parsing collect time; Exception: %s", - logPrefixDayAhead, EmsDayAheadAsset.COLLECT_TIME_FORECASTS.getName(), e)); + String.format( + "%s, attributeName='%s'; Error while parsing collect time; Exception: %s", + logPrefixDayAhead, EmsDayAheadAsset.COLLECT_TIME_FORECASTS.getName(), e)); } } - // Collect the day ahead tariff forecasts at the desired collect time - if (collectTime != null) { - // Calculate the 15-minute interval for collecting the day ahead tariff forecasts - LocalDate currentDate = LocalDate.now(); - LocalDateTime collectDateTime = LocalDateTime.of(currentDate, collectTime); - long collectTimeMillisStart = - collectDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); - long collectTimeMillisEnd = collectTimeMillisStart + 15 * 60000; - long currentTimeMillis = services.getTimerService().getCurrentTimeMillis(); - - if (currentTimeMillis >= collectTimeMillisStart && currentTimeMillis < collectTimeMillisEnd) { - // Create asset datapoint query - LocalDate tomorrowDate = currentDate.plusDays(1); - LocalDateTime startOfNextDay = tomorrowDate.atStartOfDay(); - long startTimeMillis = - startOfNextDay.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); - long endTimeMillis = startTimeMillis + 24 * 60 * 60000 - 60000; - AssetDatapointAllQuery assetDatapointQuery = - new AssetDatapointAllQuery(startTimeMillis, endTimeMillis); + Long lastUpdateForecastsTimestamp = + dayAheadAsset.getLastUpdateForecastsTimestamp().orElse(null); - // Get the tariffs number of data-points from day ahead asset - int tariffExportDayAheadSize = - services - .getAssetDatapointService() - .queryDatapoints( - dayAheadAssetId, - EmsDayAheadAsset.TARIFF_EXPORT_DAY_AHEAD.getName(), - assetDatapointQuery) - .size(); - int tariffImportDayAheadSize = - services - .getAssetDatapointService() - .queryDatapoints( - dayAheadAssetId, - EmsDayAheadAsset.TARIFF_IMPORT_DAY_AHEAD.getName(), - assetDatapointQuery) - .size(); - - // Only update the historic datapoint table if there are no day ahead tariffs present - if (tariffExportDayAheadSize == 0) { - List> tariffExport = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - energyOptimisationAsset.getId(), - EmsEnergyOptimisationAsset.TARIFF_EXPORT.getName(), - assetDatapointQuery); - services - .getAssetDatapointService() - .upsertValues( - dayAheadAssetId, - EmsDayAheadAsset.TARIFF_EXPORT_DAY_AHEAD.getName(), - tariffExport); - } + if (collectTime == null || lastUpdateForecastsTimestamp == null) { + return; + } - if (tariffImportDayAheadSize == 0) { - List> tariffImport = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - energyOptimisationAsset.getId(), - EmsEnergyOptimisationAsset.TARIFF_IMPORT.getName(), - assetDatapointQuery); - services - .getAssetDatapointService() - .upsertValues( - dayAheadAssetId, - EmsDayAheadAsset.TARIFF_IMPORT_DAY_AHEAD.getName(), - tariffImport); - } + // Calculate the 15-minute interval for collecting the day ahead tariff forecasts + LocalDate currentDate = LocalDate.now(); + LocalDateTime currentCollectDateTime = LocalDateTime.of(currentDate, collectTime); + long collectTimeStartMillis = + currentCollectDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + long collectTimeEndMillis = collectTimeStartMillis + 15 * 60000; + long currentTimeMillis = services.getTimerService().getCurrentTimeMillis(); - // Update the last update forecasts datetime field - String lastUpdateForecasts = dayAheadAsset.getLastUpdateForecasts().orElse(""); - String lastUpdateForecastsNew = collectDateTime.toString(); + String lastUpdateForecasts = dayAheadAsset.getLastUpdateForecasts().orElse(""); + LocalDateTime lastUpdateDateTime = currentCollectDateTime.plusDays(-1); - LocalDateTime lastUpdateDateTime = collectDateTime.plusDays(-1); + if (!lastUpdateForecasts.isEmpty()) { + lastUpdateDateTime = LocalDateTime.parse(lastUpdateForecasts); + } - if (!lastUpdateForecasts.isEmpty()) { - lastUpdateDateTime = LocalDateTime.parse(lastUpdateForecasts); - } + LocalDate nextUpdateDate = lastUpdateDateTime.plusDays(1).toLocalDate(); - LocalDate nextUpdateDate = lastUpdateDateTime.plusDays(1).toLocalDate(); + // Collect the day ahead tariff forecasts at the desired collect time + if (currentTimeMillis >= collectTimeStartMillis + && currentTimeMillis < collectTimeEndMillis + && collectTimeStartMillis > lastUpdateForecastsTimestamp + && currentDate.isEqual(nextUpdateDate)) { + // Create asset datapoint query + LocalDateTime startOfNextDay = currentDate.plusDays(1).atStartOfDay(); + long startTimeMillis = + startOfNextDay.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + long endTimeMillis = startTimeMillis + 24 * 60 * 60000 - 60000; + AssetDatapointAllQuery assetDatapointQuery = + new AssetDatapointAllQuery(startTimeMillis, endTimeMillis); + + // Get the tariffs number of data-points from day ahead asset + int tariffExportDayAheadSize = + services + .getAssetDatapointService() + .queryDatapoints( + dayAheadAssetId, + EmsDayAheadAsset.TARIFF_EXPORT_DAY_AHEAD.getName(), + assetDatapointQuery) + .size(); + int tariffImportDayAheadSize = + services + .getAssetDatapointService() + .queryDatapoints( + dayAheadAssetId, + EmsDayAheadAsset.TARIFF_IMPORT_DAY_AHEAD.getName(), + assetDatapointQuery) + .size(); + + // Only update the historic data-point table if there are no day ahead tariffs present in the + // historic data-point table for current interval + if (tariffExportDayAheadSize == 0) { + List> tariffExport = + services + .getAssetPredictedDatapointService() + .queryDatapoints( + energyOptimisationAsset.getId(), + EmsEnergyOptimisationAsset.TARIFF_EXPORT.getName(), + assetDatapointQuery); + services + .getAssetDatapointService() + .upsertValues( + dayAheadAssetId, EmsDayAheadAsset.TARIFF_EXPORT_DAY_AHEAD.getName(), tariffExport); + } + + if (tariffImportDayAheadSize == 0) { + List> tariffImport = + services + .getAssetPredictedDatapointService() + .queryDatapoints( + energyOptimisationAsset.getId(), + EmsEnergyOptimisationAsset.TARIFF_IMPORT.getName(), + assetDatapointQuery); + services + .getAssetDatapointService() + .upsertValues( + dayAheadAssetId, EmsDayAheadAsset.TARIFF_IMPORT_DAY_AHEAD.getName(), tariffImport); + } - if (nextUpdateDate.isEqual(currentDate)) { - services + // Update the 'last update forecasts' datetime field with current update datetime + String lastUpdateForecastsNew = currentCollectDateTime.toString(); + services .getAssetProcessingService() .sendAttributeEvent( - new AttributeEvent( - dayAheadAssetId, - EmsDayAheadAsset.LAST_UPDATE_FORECASTS.getName(), - lastUpdateForecastsNew, - collectTimeMillisStart)); - } - } + new AttributeEvent( + dayAheadAssetId, + EmsDayAheadAsset.LAST_UPDATE_FORECASTS.getName(), + lastUpdateForecastsNew, + collectTimeStartMillis)); } } - record IndexedDatapoint(long timestamp, double value, int index) {} + private record ChargeDischarge(double charge, double discharge) {} + + private record IndexedDatapoint(long timestamp, double value, int index) {} } From 6f324bacf1ff62b41dd94653103fdff90446e2b3 Mon Sep 17 00:00:00 2001 From: Igor Rutka Date: Tue, 6 Oct 2026 10:54:54 +0200 Subject: [PATCH 2/2] Applied spotless --- .../optimisationMethods/EmsOptimisation.java | 1668 ++++++++--------- 1 file changed, 834 insertions(+), 834 deletions(-) diff --git a/ems/src/main/java/org/openremote/extension/ems/manager/optimisationMethods/EmsOptimisation.java b/ems/src/main/java/org/openremote/extension/ems/manager/optimisationMethods/EmsOptimisation.java index 828b829..c3310c5 100644 --- a/ems/src/main/java/org/openremote/extension/ems/manager/optimisationMethods/EmsOptimisation.java +++ b/ems/src/main/java/org/openremote/extension/ems/manager/optimisationMethods/EmsOptimisation.java @@ -44,7 +44,7 @@ public class EmsOptimisation implements OptimisationMethod { protected static final Logger LOG = - SyslogCategory.getLogger(DATA, EmsOptimisation.class.getName()); + SyslogCategory.getLogger(DATA, EmsOptimisation.class.getName()); private final String optimisationMethodName = EmsOptimisation.class.getSimpleName(); // Maximum interval between data-points send by the device to be considered connected @@ -60,13 +60,13 @@ public class EmsOptimisation implements OptimisationMethod { // Advanced settings attribute names private final String POWER_LIMIT_MAXIMUM_FLUCTUATION_MARGIN_ATTRIBUTE_NAME = - "powerLimitMaximumFluctuationMargin"; + "powerLimitMaximumFluctuationMargin"; private final String POWER_LIMIT_MINIMUM_FLUCTUATION_MARGIN_ATTRIBUTE_NAME = - "powerLimitMinimumFluctuationMargin"; + "powerLimitMinimumFluctuationMargin"; private final String[][] advancedSettingsAttributesInfo = { - {POWER_LIMIT_MAXIMUM_FLUCTUATION_MARGIN_ATTRIBUTE_NAME, ValueType.POSITIVE_NUMBER.getName()}, - {POWER_LIMIT_MINIMUM_FLUCTUATION_MARGIN_ATTRIBUTE_NAME, ValueType.POSITIVE_NUMBER.getName()} + {POWER_LIMIT_MAXIMUM_FLUCTUATION_MARGIN_ATTRIBUTE_NAME, ValueType.POSITIVE_NUMBER.getName()}, + {POWER_LIMIT_MINIMUM_FLUCTUATION_MARGIN_ATTRIBUTE_NAME, ValueType.POSITIVE_NUMBER.getName()} }; @Override @@ -77,8 +77,8 @@ public void execute(String energyOptimisationAssetId, Services services) { private void runOptimisationMethodEms(String energyOptimisationAssetId, Services services) { // Get energy optimisation asset EmsEnergyOptimisationAsset energyOptimisationAsset = - (EmsEnergyOptimisationAsset) - services.getAssetStorageService().find(energyOptimisationAssetId); + (EmsEnergyOptimisationAsset) + services.getAssetStorageService().find(energyOptimisationAssetId); if (energyOptimisationAsset == null) { return; @@ -86,7 +86,7 @@ private void runOptimisationMethodEms(String energyOptimisationAssetId, Services // Update Advanced settings attributes info String advancedSettingsAttributes = - energyOptimisationAsset.getAdvancedSettingsAttributes().orElse(""); + energyOptimisationAsset.getAdvancedSettingsAttributes().orElse(""); if (advancedSettingsAttributes.isBlank()) { StringBuilder advancedSettingsAttributesBody = new StringBuilder(); @@ -99,60 +99,60 @@ private void runOptimisationMethodEms(String energyOptimisationAssetId, Services } services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - energyOptimisationAssetId, - EmsEnergyOptimisationAsset.ADVANCED_SETTINGS_ATTRIBUTES, - advancedSettingsAttributesBody.toString()), - getClass().getSimpleName()); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + energyOptimisationAssetId, + EmsEnergyOptimisationAsset.ADVANCED_SETTINGS_ATTRIBUTES, + advancedSettingsAttributesBody.toString()), + getClass().getSimpleName()); } String logPrefixEnergyOptimisation = - String.format( - "assetType='%s', assetId='%s', assetName='%s'", - energyOptimisationAsset.getAssetType(), - energyOptimisationAssetId, - energyOptimisationAsset.getAssetName()); + String.format( + "assetType='%s', assetId='%s', assetName='%s'", + energyOptimisationAsset.getAssetType(), + energyOptimisationAssetId, + energyOptimisationAsset.getAssetName()); // Get all battery assets List electricityBatteryAssets = - services - .getAssetStorageService() - .findAll( - new AssetQuery() - .parents(energyOptimisationAssetId) - .types(EmsElectricityBatteryAsset.class)) - .stream() - .map(asset -> (EmsElectricityBatteryAsset) asset) - .toList(); + services + .getAssetStorageService() + .findAll( + new AssetQuery() + .parents(energyOptimisationAssetId) + .types(EmsElectricityBatteryAsset.class)) + .stream() + .map(asset -> (EmsElectricityBatteryAsset) asset) + .toList(); if (energyOptimisationAsset.getEnableDetailedLogging().orElse(false)) { int allowChargingSize = - electricityBatteryAssets.stream() - .filter( - electricityBatteryAsset -> - electricityBatteryAsset.getAllowCharging().orElse(false)) - .toList() - .size(); + electricityBatteryAssets.stream() + .filter( + electricityBatteryAsset -> + electricityBatteryAsset.getAllowCharging().orElse(false)) + .toList() + .size(); int allowDischargingSize = - electricityBatteryAssets.stream() - .filter( - electricityBatteryAsset -> - electricityBatteryAsset.getAllowDischarging().orElse(false)) - .toList() - .size(); + electricityBatteryAssets.stream() + .filter( + electricityBatteryAsset -> + electricityBatteryAsset.getAllowDischarging().orElse(false)) + .toList() + .size(); LOG.info( - String.format( - "%s; Energy optimisation asset has %s battery asset(s). Number of batteries with '%s'= %s and '%s'= %s", - logPrefixEnergyOptimisation, - electricityBatteryAssets.size(), - EmsElectricityBatteryAsset.ALLOW_CHARGING.getName(), - allowChargingSize, - EmsElectricityBatteryAsset.ALLOW_DISCHARGING.getName(), - allowDischargingSize)); + String.format( + "%s; Energy optimisation asset has %s battery asset(s). Number of batteries with '%s'= %s and '%s'= %s", + logPrefixEnergyOptimisation, + electricityBatteryAssets.size(), + EmsElectricityBatteryAsset.ALLOW_CHARGING.getName(), + allowChargingSize, + EmsElectricityBatteryAsset.ALLOW_DISCHARGING.getName(), + allowDischargingSize)); } // Check if power net is connected @@ -160,44 +160,44 @@ private void runOptimisationMethodEms(String energyOptimisationAssetId, Services if (powerNet == null) { LOG.warning( - String.format( - "%s; Failed to perform '%s' energy optimisation method. '%s' attribute is not connected", - logPrefixEnergyOptimisation, - optimisationMethodName, - EmsEnergyOptimisationAsset.POWER_NET.getName())); + String.format( + "%s; Failed to perform '%s' energy optimisation method. '%s' attribute is not connected", + logPrefixEnergyOptimisation, + optimisationMethodName, + EmsEnergyOptimisationAsset.POWER_NET.getName())); } // Validate power limits Double powerLimitMaximumProfileTotal = - energyOptimisationAsset.getPowerLimitMaximumProfileTotal().orElse(null); + energyOptimisationAsset.getPowerLimitMaximumProfileTotal().orElse(null); Double powerLimitMinimumProfileTotal = - energyOptimisationAsset.getPowerLimitMinimumProfileTotal().orElse(null); + energyOptimisationAsset.getPowerLimitMinimumProfileTotal().orElse(null); Long powerLimitMaximumProfileTimestampMillis = - energyOptimisationAsset.getPowerLimitMaximumProfileTotalTimestamp().orElse(null); + energyOptimisationAsset.getPowerLimitMaximumProfileTotalTimestamp().orElse(null); DateTimeFormatter formatter = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").withZone(ZoneId.systemDefault()); + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").withZone(ZoneId.systemDefault()); String powerLimitMaximumProfileDateTime = ""; if (powerLimitMaximumProfileTimestampMillis != null) { powerLimitMaximumProfileDateTime = - formatter.format(Instant.ofEpochMilli(powerLimitMaximumProfileTimestampMillis)); + formatter.format(Instant.ofEpochMilli(powerLimitMaximumProfileTimestampMillis)); } if (powerLimitMaximumProfileTotal != null - && powerLimitMinimumProfileTotal != null - && powerLimitMaximumProfileTotal <= powerLimitMinimumProfileTotal) { + && powerLimitMinimumProfileTotal != null + && powerLimitMaximumProfileTotal <= powerLimitMinimumProfileTotal) { LOG.warning( - String.format( - "%s; Failed to perform '%s' energy optimisation method. The '%s'= %s kW is lower than or equal to '%s'= %s kW for timestamp='%s'", - logPrefixEnergyOptimisation, - optimisationMethodName, - EmsEnergyOptimisationAsset.POWER_LIMIT_MAXIMUM_PROFILE_TOTAL.getName(), - powerLimitMaximumProfileTotal, - EmsEnergyOptimisationAsset.POWER_LIMIT_MINIMUM_PROFILE_TOTAL.getName(), - powerLimitMinimumProfileTotal, - powerLimitMaximumProfileDateTime)); + String.format( + "%s; Failed to perform '%s' energy optimisation method. The '%s'= %s kW is lower than or equal to '%s'= %s kW for timestamp='%s'", + logPrefixEnergyOptimisation, + optimisationMethodName, + EmsEnergyOptimisationAsset.POWER_LIMIT_MAXIMUM_PROFILE_TOTAL.getName(), + powerLimitMaximumProfileTotal, + EmsEnergyOptimisationAsset.POWER_LIMIT_MINIMUM_PROFILE_TOTAL.getName(), + powerLimitMinimumProfileTotal, + powerLimitMaximumProfileDateTime)); } // Get day ahead asset @@ -224,48 +224,48 @@ private void runOptimisationMethodEms(String energyOptimisationAssetId, Services // Find latest power set-point update across all batteries long powerSetpointTimestampLatestMillis = - batteriesLatestPowerSetpointUpdate(electricityBatteryAssets); + batteriesLatestPowerSetpointUpdate(electricityBatteryAssets); // Calculate battery energy level percentage targets int intervalPeriodMinutes = 15; int currentMinute = LocalDateTime.now().getMinute(); long currentTimeMillis = services.getTimerService().getCurrentTimeMillis(); long forecastUpdateTimeMillis = - currentTimeMillis - currentTimeMillis % (intervalPeriodMinutes * 60 * 1000); + currentTimeMillis - currentTimeMillis % (intervalPeriodMinutes * 60 * 1000); Map batteryEnergyLevelPercentageTargets; if ((currentMinute % intervalPeriodMinutes) == 0 - || powerSetpointTimestampLatestMillis < forecastUpdateTimeMillis) { + || powerSetpointTimestampLatestMillis < forecastUpdateTimeMillis) { batteryEnergyLevelPercentageTargets = - batteryCalculateForecasts(electricityBatteryAssets, energyOptimisationAsset, services); + batteryCalculateForecasts(electricityBatteryAssets, energyOptimisationAsset, services); } else { batteryEnergyLevelPercentageTargets = - batteryGetEnergyLevelPercentageTargetsCurrent(electricityBatteryAssets, services); + batteryGetEnergyLevelPercentageTargetsCurrent(electricityBatteryAssets, services); } // Calculate battery power set-points Map powerSetpointsNew = - batteryCalculatePowerSetpoints( - energyOptimisationAsset, - electricityBatteryAssets, - powerSetpointTimestampLatestMillis, - batteryEnergyLevelPercentageTargets, - services, - logPrefixEnergyOptimisation); + batteryCalculatePowerSetpoints( + energyOptimisationAsset, + electricityBatteryAssets, + powerSetpointTimestampLatestMillis, + batteryEnergyLevelPercentageTargets, + services, + logPrefixEnergyOptimisation); // Update battery power set-points batteryUpdatePowerSetpoints(electricityBatteryAssets, powerSetpointsNew, services); } private long batteriesLatestPowerSetpointUpdate( - List electricityBatteryAssets) { + List electricityBatteryAssets) { // Find latest power set-point update across all batteries long powerSetpointTimestampLatestMillis = 0L; for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { long powerSetpointTimestampMillis = - electricityBatteryAsset.getPowerSetpointTimestamp().orElse(0L); + electricityBatteryAsset.getPowerSetpointTimestamp().orElse(0L); if (powerSetpointTimestampMillis > powerSetpointTimestampLatestMillis) { powerSetpointTimestampLatestMillis = powerSetpointTimestampMillis; @@ -276,18 +276,18 @@ private long batteriesLatestPowerSetpointUpdate( } private int batteryCalculateEnergyLevelPercentageDefault( - Integer energyLevelPercentageMaximumBattery, - Integer energyLevelPercentageMinimumBattery, - EmsEnergyOptimisationAsset energyOptimisationAsset) { + Integer energyLevelPercentageMaximumBattery, + Integer energyLevelPercentageMinimumBattery, + EmsEnergyOptimisationAsset energyOptimisationAsset) { if (energyLevelPercentageMaximumBattery == null - || energyLevelPercentageMinimumBattery == null) { + || energyLevelPercentageMinimumBattery == null) { return BATTERY_ENERGY_LEVEL_PERCENTAGE_DEFAULT; } Double powerLimitMaximumProfileTotal = - energyOptimisationAsset.getPowerLimitMaximumInput().orElse(null); + energyOptimisationAsset.getPowerLimitMaximumInput().orElse(null); Double powerLimitMinimumProfileTotal = - energyOptimisationAsset.getPowerLimitMinimumInput().orElse(null); + energyOptimisationAsset.getPowerLimitMinimumInput().orElse(null); int energyLevelPercentageDefault; @@ -297,21 +297,21 @@ private int batteryCalculateEnergyLevelPercentageDefault( energyLevelPercentageDefault = energyLevelPercentageMaximumBattery; } else { energyLevelPercentageDefault = - (int) - Math.round( - (double) - (energyLevelPercentageMaximumBattery - + energyLevelPercentageMinimumBattery) - / 2); + (int) + Math.round( + (double) + (energyLevelPercentageMaximumBattery + + energyLevelPercentageMinimumBattery) + / 2); } return energyLevelPercentageDefault; } private Map batteryCalculateForecasts( - List electricityBatteryAssets, - EmsEnergyOptimisationAsset energyOptimisationAsset, - Services services) { + List electricityBatteryAssets, + EmsEnergyOptimisationAsset energyOptimisationAsset, + Services services) { Map batteryEnergyLevelPercentageTargets = new HashMap<>(); StringBuilder infoStr = new StringBuilder(); @@ -322,20 +322,20 @@ private Map batteryCalculateForecasts( long currentTimeMillis = services.getTimerService().getCurrentTimeMillis(); long startTimeForecastPeriodMillis = currentTimeMillis - currentTimeMillis % intervalMillis; long endTimeForecastPeriodMillis = - startTimeForecastPeriodMillis - - startTimeForecastPeriodMillis % (24 * 60 * 60000) - + (8 * 24 * 60 * 60000); + startTimeForecastPeriodMillis + - startTimeForecastPeriodMillis % (24 * 60 * 60000) + + (8 * 24 * 60 * 60000); String energyOptimisationAssetId = energyOptimisationAsset.getId(); AssetDatapointAllQuery assetDatapointQueryConsumptionPredicted = - new AssetDatapointAllQuery(startTimeForecastPeriodMillis, endTimeForecastPeriodMillis); + new AssetDatapointAllQuery(startTimeForecastPeriodMillis, endTimeForecastPeriodMillis); List> energyOptimisationPowerConsumptionPredicted = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - energyOptimisationAssetId, - EmsEnergyOptimisationAsset.POWER_CONSUMPTION.getName(), - assetDatapointQueryConsumptionPredicted); + services + .getAssetPredictedDatapointService() + .queryDatapoints( + energyOptimisationAssetId, + EmsEnergyOptimisationAsset.POWER_CONSUMPTION.getName(), + assetDatapointQueryConsumptionPredicted); // System.out.println("energyOptimisationPowerConsumptionPredicted = " + // energyOptimisationPowerConsumptionPredicted); @@ -346,7 +346,7 @@ private Map batteryCalculateForecasts( // Calculate power average for each 15-minute interval List> totalPowerConsumptionAveraged = - intervalAverage(energyOptimisationPowerConsumptionPredicted, intervalMillis); + intervalAverage(energyOptimisationPowerConsumptionPredicted, intervalMillis); totalPowerConsumptionAveraged.sort(Comparator.comparingLong(ValueDatapoint::getTimestamp)); // System.out.println("totalPowerConsumptionAveraged = " + @@ -357,11 +357,11 @@ private Map batteryCalculateForecasts( // Interpolate power average values for each 15-minute interval List> totalPowerConsumptionInterpolated = - intervalInterpolate( - totalPowerConsumptionAveraged, - startTimeForecastDataMillis, - endTimeForecastDataMillis, - intervalMillis); + intervalInterpolate( + totalPowerConsumptionAveraged, + startTimeForecastDataMillis, + endTimeForecastDataMillis, + intervalMillis); List timestampsMillisList = new ArrayList<>(); List totalPowerConsumptionList = new ArrayList<>(); @@ -385,14 +385,14 @@ private Map batteryCalculateForecasts( } AssetDatapointAllQuery assetDatapointQueryPeriodPredicted = - new AssetDatapointAllQuery(startTimeForecastDataMillis, endTimeForecastDataMillis); + new AssetDatapointAllQuery(startTimeForecastDataMillis, endTimeForecastDataMillis); List> energyOptimisationPowerProductionPredicted = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - energyOptimisationAssetId, - EmsEnergyOptimisationAsset.POWER_PRODUCTION.getName(), - assetDatapointQueryPeriodPredicted); + services + .getAssetPredictedDatapointService() + .queryDatapoints( + energyOptimisationAssetId, + EmsEnergyOptimisationAsset.POWER_PRODUCTION.getName(), + assetDatapointQueryPeriodPredicted); // System.out.println("energyOptimisationPowerProductionPredicted = " + // energyOptimisationPowerProductionPredicted); @@ -402,21 +402,21 @@ private Map batteryCalculateForecasts( if (!energyOptimisationPowerProductionPredicted.isEmpty()) { // Calculate power average for each 15-minute interval List> totalPowerProductionAveraged = - intervalAverage(energyOptimisationPowerProductionPredicted, intervalMillis); + intervalAverage(energyOptimisationPowerProductionPredicted, intervalMillis); totalPowerProductionAveraged.sort(Comparator.comparingLong(ValueDatapoint::getTimestamp)); // Interpolate power average values for each 15-minute interval List> totalPowerProductionInterpolated = - intervalInterpolate( - totalPowerProductionAveraged, - totalPowerProductionAveraged.getFirst().getTimestamp(), - totalPowerProductionAveraged.getLast().getTimestamp(), - intervalMillis); + intervalInterpolate( + totalPowerProductionAveraged, + totalPowerProductionAveraged.getFirst().getTimestamp(), + totalPowerProductionAveraged.getLast().getTimestamp(), + intervalMillis); totalPowerProductionMap = - totalPowerProductionInterpolated.stream() - .collect( - Collectors.toMap(ValueDatapoint::getTimestamp, dp -> ((Double) dp.getValue()))); + totalPowerProductionInterpolated.stream() + .collect( + Collectors.toMap(ValueDatapoint::getTimestamp, dp -> ((Double) dp.getValue()))); } // List to store the sum of total power consumption, production and flexible @@ -436,25 +436,25 @@ private Map batteryCalculateForecasts( } infoStr.append( - String.format( - "totalPowerConsumptionProductionFlexibleList = %s \n", - totalPowerConsumptionProductionFlexibleList)); + String.format( + "totalPowerConsumptionProductionFlexibleList = %s \n", + totalPowerConsumptionProductionFlexibleList)); // Get power limit forecasts List> energyOptimisationPowerLimitMaximumPredicted = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - energyOptimisationAssetId, - EmsEnergyOptimisationAsset.POWER_LIMIT_MAXIMUM_PROFILE_TOTAL.getName(), - assetDatapointQueryPeriodPredicted); + services + .getAssetPredictedDatapointService() + .queryDatapoints( + energyOptimisationAssetId, + EmsEnergyOptimisationAsset.POWER_LIMIT_MAXIMUM_PROFILE_TOTAL.getName(), + assetDatapointQueryPeriodPredicted); List> energyOptimisationPowerLimitMinimumPredicted = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - energyOptimisationAssetId, - EmsEnergyOptimisationAsset.POWER_LIMIT_MINIMUM_PROFILE_TOTAL.getName(), - assetDatapointQueryPeriodPredicted); + services + .getAssetPredictedDatapointService() + .queryDatapoints( + energyOptimisationAssetId, + EmsEnergyOptimisationAsset.POWER_LIMIT_MINIMUM_PROFILE_TOTAL.getName(), + assetDatapointQueryPeriodPredicted); Map powerLimitMaximumMap = new HashMap<>(); Map powerLimitMinimumMap = new HashMap<>(); @@ -482,27 +482,27 @@ private Map batteryCalculateForecasts( String batteryAssetId = electricityBatteryAsset.getId(); Integer chargeEfficiencyBattery = electricityBatteryAsset.getChargeEfficiency().orElse(null); Double chargePowerMaximumBattery = - electricityBatteryAsset.getChargePowerMaximum().orElse(null); + electricityBatteryAsset.getChargePowerMaximum().orElse(null); Integer dischargeEfficiencyBattery = - electricityBatteryAsset.getDischargeEfficiency().orElse(null); + electricityBatteryAsset.getDischargeEfficiency().orElse(null); Double dischargePowerMaximumBattery = - electricityBatteryAsset.getDischargePowerMaximum().orElse(null); + electricityBatteryAsset.getDischargePowerMaximum().orElse(null); Double energyCapacityBattery = electricityBatteryAsset.getEnergyCapacity().orElse(null); Double energyLevelPercentageBattery = - electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); Integer energyLevelPercentageMaximumBattery = - electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); Integer energyLevelPercentageMinimumBattery = - electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); if (chargeEfficiencyBattery == null - || chargePowerMaximumBattery == null - || dischargeEfficiencyBattery == null - || dischargePowerMaximumBattery == null - || energyCapacityBattery == null - || energyLevelPercentageBattery == null - || energyLevelPercentageMaximumBattery == null - || energyLevelPercentageMinimumBattery == null) { + || chargePowerMaximumBattery == null + || dischargeEfficiencyBattery == null + || dischargePowerMaximumBattery == null + || energyCapacityBattery == null + || energyLevelPercentageBattery == null + || energyLevelPercentageMaximumBattery == null + || energyLevelPercentageMinimumBattery == null) { continue; } @@ -528,9 +528,9 @@ private Map batteryCalculateForecasts( Double powerLimitMinimum = powerLimitMinimumMap.getOrDefault(timestampMillis, null); Double powerLimitMaximumVirtual = - calculatePowerLimitVirtual(energyOptimisationAsset, powerLimitMaximum, "max"); + calculatePowerLimitVirtual(energyOptimisationAsset, powerLimitMaximum, "max"); Double powerLimitMinimumVirtual = - calculatePowerLimitVirtual(energyOptimisationAsset, powerLimitMinimum, "min"); + calculatePowerLimitVirtual(energyOptimisationAsset, powerLimitMinimum, "min"); powerLimitMaximumVirtualList.add(powerLimitMaximumVirtual); powerLimitMinimumVirtualList.add(powerLimitMinimumVirtual); @@ -553,9 +553,9 @@ private Map batteryCalculateForecasts( int numberOfDataPoints = chargePowerAvailableTotalList.size(); infoStr.append( - String.format("powerLimitMaximumVirtualList = %s \n", powerLimitMaximumVirtualList)); + String.format("powerLimitMaximumVirtualList = %s \n", powerLimitMaximumVirtualList)); infoStr.append( - String.format("powerLimitMinimumVirtualList = %s \n", powerLimitMinimumVirtualList)); + String.format("powerLimitMinimumVirtualList = %s \n", powerLimitMinimumVirtualList)); infoStr.append("\n"); // System.out.println("chargePowerAvailableTotalList = " + @@ -564,21 +564,21 @@ private Map batteryCalculateForecasts( // dischargePowerAvailableTotalList); List chargeNeededTotalList = - dischargePowerAvailableTotalList.stream().map(x -> x > 0 ? x : 0).toList(); + dischargePowerAvailableTotalList.stream().map(x -> x > 0 ? x : 0).toList(); List dischargeNeededTotalList = - chargePowerAvailableTotalList.stream().map(x -> x < 0 ? x : 0).toList(); + chargePowerAvailableTotalList.stream().map(x -> x < 0 ? x : 0).toList(); // System.out.println("chargeNeededTotalList = " + chargeNeededTotalList); // System.out.println("dischargeNeededTotalList = " + dischargeNeededTotalList); List chargePowerAvailableBatteryList = - chargePowerAvailableTotalList.stream() - .map(x -> x > 0 ? Math.min(x, chargePowerMaximumBattery) : 0) - .toList(); + chargePowerAvailableTotalList.stream() + .map(x -> x > 0 ? Math.min(x, chargePowerMaximumBattery) : 0) + .toList(); List dischargePowerAvailableBatteryList = - dischargePowerAvailableTotalList.stream() - .map(x -> x < 0 ? Math.max(x, dischargePowerMaximumBattery) : 0) - .toList(); + dischargePowerAvailableTotalList.stream() + .map(x -> x < 0 ? Math.max(x, dischargePowerMaximumBattery) : 0) + .toList(); // System.out.println("chargeAvailableBatteryList = " + // chargePowerAvailableBatteryList); @@ -592,15 +592,15 @@ private Map batteryCalculateForecasts( for (int i = 0; i < numberOfDataPoints; i++) { double c = - intervalHour - * chargeNeededTotalList.get(i) - * chargeEfficiencyBattery - / energyCapacityBattery; + intervalHour + * chargeNeededTotalList.get(i) + * chargeEfficiencyBattery + / energyCapacityBattery; double d = - 10000 - * intervalHour - * dischargeNeededTotalList.get(i) - / (energyCapacityBattery * dischargeEfficiencyBattery); + 10000 + * intervalHour + * dischargeNeededTotalList.get(i) + / (energyCapacityBattery * dischargeEfficiencyBattery); chargePercentageNeededTotalList.add(round(c, batteryPercentageRoundingPrecision)); dischargePercentageNeededTotalList.add(round(d, batteryPercentageRoundingPrecision)); } @@ -615,15 +615,15 @@ private Map batteryCalculateForecasts( for (int i = 0; i < numberOfDataPoints; i++) { double c = - intervalHour - * chargePowerAvailableBatteryList.get(i) - * chargeEfficiencyBattery - / energyCapacityBattery; + intervalHour + * chargePowerAvailableBatteryList.get(i) + * chargeEfficiencyBattery + / energyCapacityBattery; double d = - 10000 - * intervalHour - * dischargePowerAvailableBatteryList.get(i) - / (energyCapacityBattery * dischargeEfficiencyBattery); + 10000 + * intervalHour + * dischargePowerAvailableBatteryList.get(i) + / (energyCapacityBattery * dischargeEfficiencyBattery); chargePercentageAvailableBatteryList.add(round(c, batteryPercentageRoundingPrecision)); dischargePercentageAvailableBatteryList.add(round(d, batteryPercentageRoundingPrecision)); } @@ -644,30 +644,30 @@ private Map batteryCalculateForecasts( if (getToLimitPercentage > energyLevelPercentageMaximumBattery) { double dischargePercentageNeeded = - energyLevelPercentageMaximumBattery - getToLimitPercentage; + energyLevelPercentageMaximumBattery - getToLimitPercentage; double dischargePercentageAvailable = - Math.max(dischargePercentageAvailableBatteryList.get(i), dischargePercentageNeeded); + Math.max(dischargePercentageAvailableBatteryList.get(i), dischargePercentageNeeded); getToLimitPercentage = getToLimitPercentage + dischargePercentageAvailable; dischargePercentageAvailableBatteryList.set( - i, - round( - dischargePercentageAvailableBatteryList.get(i) - dischargePercentageAvailable, - batteryPercentageRoundingPrecision)); + i, + round( + dischargePercentageAvailableBatteryList.get(i) - dischargePercentageAvailable, + batteryPercentageRoundingPrecision)); getToLimitPercentageList.add( - round(getToLimitPercentage, batteryPercentageRoundingPrecision)); + round(getToLimitPercentage, batteryPercentageRoundingPrecision)); } else if (getToLimitPercentage < energyLevelPercentageMinimumBattery) { double chargePercentageNeeded = - energyLevelPercentageMinimumBattery - getToLimitPercentage; + energyLevelPercentageMinimumBattery - getToLimitPercentage; double chargePercentageAvailable = - Math.min(chargePercentageAvailableBatteryList.get(i), chargePercentageNeeded); + Math.min(chargePercentageAvailableBatteryList.get(i), chargePercentageNeeded); getToLimitPercentage = getToLimitPercentage + chargePercentageAvailable; chargePercentageAvailableBatteryList.set( - i, - round( - chargePercentageAvailableBatteryList.get(i) - chargePercentageAvailable, - batteryPercentageRoundingPrecision)); + i, + round( + chargePercentageAvailableBatteryList.get(i) - chargePercentageAvailable, + batteryPercentageRoundingPrecision)); getToLimitPercentageList.add( - round(getToLimitPercentage, batteryPercentageRoundingPrecision)); + round(getToLimitPercentage, batteryPercentageRoundingPrecision)); } else { break; } @@ -684,7 +684,7 @@ private Map batteryCalculateForecasts( List energyLevelPredictionList = new ArrayList<>(); if (getToLimitPercentage > energyLevelPercentageMaximumBattery - || getToLimitPercentage < energyLevelPercentageMinimumBattery) { + || getToLimitPercentage < energyLevelPercentageMinimumBattery) { // Energy level percentage forecast when entire forecast is outside of battery percentage // limits energyLevelPredictionList = getToLimitPercentageList; @@ -696,13 +696,13 @@ private Map batteryCalculateForecasts( for (int i = 0; i < numberOfDataPoints; i++) { chargePercentageWantBatteryList.add( - Math.min( - chargePercentageNeededTotalList.get(i), - chargePercentageAvailableBatteryList.get(i))); + Math.min( + chargePercentageNeededTotalList.get(i), + chargePercentageAvailableBatteryList.get(i))); dischargePercentageWantBatteryList.add( - Math.max( - dischargePercentageNeededTotalList.get(i), - dischargePercentageAvailableBatteryList.get(i))); + Math.max( + dischargePercentageNeededTotalList.get(i), + dischargePercentageAvailableBatteryList.get(i))); } // System.out.println("chargePercentageWantBatteryList = " + @@ -714,10 +714,10 @@ private Map batteryCalculateForecasts( for (int i = 0; i < numberOfDataPoints; i++) { chargeAndDischargePercentageWantBatteryList.add( - round( - chargePercentageWantBatteryList.get(i) - + dischargePercentageWantBatteryList.get(i), - batteryPercentageRoundingPrecision)); + round( + chargePercentageWantBatteryList.get(i) + + dischargePercentageWantBatteryList.get(i), + batteryPercentageRoundingPrecision)); } // System.out.println("chargeAndDischargePercentageWantBatteryList = " + @@ -742,23 +742,23 @@ private Map batteryCalculateForecasts( // Combine outside limits and running sum energy level percentages if (getToLimitPercentageList.size() > 1) { energyLevelPredictionList.addAll( - getToLimitPercentageList.subList(0, getToLimitPercentageList.size() - 1)); + getToLimitPercentageList.subList(0, getToLimitPercentageList.size() - 1)); } energyLevelPredictionList.addAll(runningSumList); int energyLevelPredictionListSize = energyLevelPredictionList.size(); infoStr.append( - String.format("energyLevelPredictionListOriginal = %s \n", energyLevelPredictionList)); + String.format("energyLevelPredictionListOriginal = %s \n", energyLevelPredictionList)); // Calculate forecast starting within battery percentage limits double energyLevelPredictionMaximum = - Collections.max( - energyLevelPredictionList.subList( - startRunningSumIndex, energyLevelPredictionListSize)); + Collections.max( + energyLevelPredictionList.subList( + startRunningSumIndex, energyLevelPredictionListSize)); double energyLevelPredictionMinimum = - Collections.min( - energyLevelPredictionList.subList( - startRunningSumIndex, energyLevelPredictionListSize)); + Collections.min( + energyLevelPredictionList.subList( + startRunningSumIndex, energyLevelPredictionListSize)); long dtStart = services.getTimerService().getCurrentTimeMillis(); long dt = 0; @@ -768,7 +768,7 @@ private Map batteryCalculateForecasts( int predictionListIndex = startRunningSumIndex; while (energyLevelPredictionMaximum > energyLevelPercentageMaximumBattery - || energyLevelPredictionMinimum < energyLevelPercentageMinimumBattery) { + || energyLevelPredictionMinimum < energyLevelPercentageMinimumBattery) { String chargeOrDischarge = ""; int intervalStartIndex = startRunningSumIndex; @@ -822,12 +822,12 @@ private Map batteryCalculateForecasts( if (chargeOrDischarge.equals("charge")) { double chargeAvailable = 0; double chargeAvailableInterval = - round( - energyLevelPercentageMaximumBattery - - Collections.max( - energyLevelPredictionList.subList( - intervalStartIndex, intervalEndIndex)), - batteryPercentageRoundingPrecision); + round( + energyLevelPercentageMaximumBattery + - Collections.max( + energyLevelPredictionList.subList( + intervalStartIndex, intervalEndIndex)), + batteryPercentageRoundingPrecision); if (chargeAvailableInterval > 0) { for (int i = intervalEndIndex - 1; i >= intervalStartIndex; i--) { @@ -839,11 +839,11 @@ private Map batteryCalculateForecasts( if (dischargePercentageWantBatteryList.get(i) >= 0) { double chargeInUse = - energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); + energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); chargeAvailableLeft = - round( - chargePercentageAvailableBatteryList.get(i) - chargeInUse, - batteryPercentageRoundingPrecision); + round( + chargePercentageAvailableBatteryList.get(i) - chargeInUse, + batteryPercentageRoundingPrecision); } if (chargeAvailableLeft > 0) { @@ -855,8 +855,8 @@ private Map batteryCalculateForecasts( } double chargeNeeded = - energyLevelPercentageMinimumBattery - - energyLevelPredictionList.get(intervalEndIndex); + energyLevelPercentageMinimumBattery + - energyLevelPredictionList.get(intervalEndIndex); double changeValue = Math.min(chargeAvailable, chargeNeeded); if (changeIndex == -1) { @@ -866,20 +866,20 @@ private Map batteryCalculateForecasts( for (int i = changeIndex; i < energyLevelPredictionListSize; i++) { energyLevelPredictionList.set( - i, - round( - energyLevelPredictionList.get(i) + changeValue, - batteryPercentageRoundingPrecision)); + i, + round( + energyLevelPredictionList.get(i) + changeValue, + batteryPercentageRoundingPrecision)); } } else if (chargeOrDischarge.equals("discharge")) { double dischargeAvailable = 0; double dischargeAvailableInterval = - round( - energyLevelPercentageMinimumBattery - - Collections.min( - energyLevelPredictionList.subList( - intervalStartIndex, intervalEndIndex)), - batteryPercentageRoundingPrecision); + round( + energyLevelPercentageMinimumBattery + - Collections.min( + energyLevelPredictionList.subList( + intervalStartIndex, intervalEndIndex)), + batteryPercentageRoundingPrecision); if (dischargeAvailableInterval > 0) { for (int i = intervalEndIndex - 1; i >= intervalStartIndex; i--) { @@ -891,11 +891,11 @@ private Map batteryCalculateForecasts( if (chargePercentageWantBatteryList.get(i) <= 0) { double dischargeInUse = - energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); + energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); dischargeAvailableLeft = - round( - dischargePercentageAvailableBatteryList.get(i) - dischargeInUse, - batteryPercentageRoundingPrecision); + round( + dischargePercentageAvailableBatteryList.get(i) - dischargeInUse, + batteryPercentageRoundingPrecision); } if (dischargeAvailableLeft < 0) { changeIndex = i; @@ -906,8 +906,8 @@ private Map batteryCalculateForecasts( } double dischargeNeeded = - energyLevelPercentageMaximumBattery - - energyLevelPredictionList.get(intervalEndIndex); + energyLevelPercentageMaximumBattery + - energyLevelPredictionList.get(intervalEndIndex); double changeValue = Math.max(dischargeAvailable, dischargeNeeded); if (changeIndex == -1) { @@ -917,45 +917,45 @@ private Map batteryCalculateForecasts( for (int i = changeIndex; i < energyLevelPredictionListSize; i++) { energyLevelPredictionList.set( - i, - round( - energyLevelPredictionList.get(i) + changeValue, - batteryPercentageRoundingPrecision)); + i, + round( + energyLevelPredictionList.get(i) + changeValue, + batteryPercentageRoundingPrecision)); } } if (energyLevelPredictionValueBefore == energyLevelPredictionList.get(intervalEndIndex)) { infoStr.insert( - 0, - String.format( - "Battery energy level percentage calculation error at timestamp = %s", - timestampsMillisList.get(predictionListIndex))); + 0, + String.format( + "Battery energy level percentage calculation error at timestamp = %s", + timestampsMillisList.get(predictionListIndex))); infoStr.append( - String.format( - "energyLevelPredictionListOptimised = %s \n", energyLevelPredictionList)); + String.format( + "energyLevelPredictionListOptimised = %s \n", energyLevelPredictionList)); LOG.warning(infoStr.toString()); break; } else if (dt > timeoutMillis) { infoStr.insert( - 0, - String.format( - "Battery energy level percentage calculation timed out during power limit optimisation at timestamp = %s", - timestampsMillisList.get(predictionListIndex))); + 0, + String.format( + "Battery energy level percentage calculation timed out during power limit optimisation at timestamp = %s", + timestampsMillisList.get(predictionListIndex))); infoStr.append( - String.format( - "energyLevelPredictionListOptimised = %s \n", energyLevelPredictionList)); + String.format( + "energyLevelPredictionListOptimised = %s \n", energyLevelPredictionList)); LOG.warning(infoStr.toString()); break; } energyLevelPredictionMaximum = - Collections.max( - energyLevelPredictionList.subList( - startRunningSumIndex, energyLevelPredictionListSize)); + Collections.max( + energyLevelPredictionList.subList( + startRunningSumIndex, energyLevelPredictionListSize)); energyLevelPredictionMinimum = - Collections.min( - energyLevelPredictionList.subList( - startRunningSumIndex, energyLevelPredictionListSize)); + Collections.min( + energyLevelPredictionList.subList( + startRunningSumIndex, energyLevelPredictionListSize)); dt = services.getTimerService().getCurrentTimeMillis() - dtStart; } @@ -973,24 +973,24 @@ private Map batteryCalculateForecasts( // Get the tariff forecasts from energy optimisation asset for 1 week, timestamps are // ordered from newest to oldest (descending order) List> tariffExportDatapoints = - getTariffDatapoints( - energyOptimisationAsset, - EmsEnergyOptimisationAsset.TARIFF_EXPORT.getName(), - services); + getTariffDatapoints( + energyOptimisationAsset, + EmsEnergyOptimisationAsset.TARIFF_EXPORT.getName(), + services); List> tariffImportDatapoints = - getTariffDatapoints( - energyOptimisationAsset, - EmsEnergyOptimisationAsset.TARIFF_IMPORT.getName(), - services); + getTariffDatapoints( + energyOptimisationAsset, + EmsEnergyOptimisationAsset.TARIFF_IMPORT.getName(), + services); // Overwrite the current tariff forecasts with the day ahead tariff forecasts if (useDayAheadTariffs) { List> tariffExportDayAheadAssetDatapoints = - getTariffDayAheadDatapoints( - dayAheadAsset, EmsDayAheadAsset.TARIFF_EXPORT_DAY_AHEAD.getName(), services); + getTariffDayAheadDatapoints( + dayAheadAsset, EmsDayAheadAsset.TARIFF_EXPORT_DAY_AHEAD.getName(), services); List> tariffImportDayAheadAssetDatapoints = - getTariffDayAheadDatapoints( - dayAheadAsset, EmsDayAheadAsset.TARIFF_IMPORT_DAY_AHEAD.getName(), services); + getTariffDayAheadDatapoints( + dayAheadAsset, EmsDayAheadAsset.TARIFF_IMPORT_DAY_AHEAD.getName(), services); // Combine tariff export forecasts if (!tariffExportDayAheadAssetDatapoints.isEmpty()) { @@ -1027,14 +1027,14 @@ private Map batteryCalculateForecasts( // Calculate optimal charge and discharge zone for each day based on tariffs Map chargeAndDischargeZonesMap = - calculateTariffChargeAndDischargeZones( - tariffImportDatapoints, - tariffExportDatapoints, - BATTERY_TARIFF_OPTIMISATION_WINDOW_DEFAULT); + calculateTariffChargeAndDischargeZones( + tariffImportDatapoints, + tariffExportDatapoints, + BATTERY_TARIFF_OPTIMISATION_WINDOW_DEFAULT); // Charge zone = 1, discharge zone = -1 List chargeAndDischargeZonesList = - new ArrayList<>(Collections.nCopies(energyLevelPredictionListSize, 0)); + new ArrayList<>(Collections.nCopies(energyLevelPredictionListSize, 0)); for (Map.Entry entry : chargeAndDischargeZonesMap.entrySet()) { long timestampMillis = entry.getKey(); @@ -1053,21 +1053,21 @@ private Map batteryCalculateForecasts( // Calculate battery energy level percentage default list double energyLevelPercentageDefault = - batteryCalculateEnergyLevelPercentageDefault( - energyLevelPercentageMaximumBattery, - energyLevelPercentageMinimumBattery, - energyOptimisationAsset); + batteryCalculateEnergyLevelPercentageDefault( + energyLevelPercentageMaximumBattery, + energyLevelPercentageMinimumBattery, + energyOptimisationAsset); List energyLevelPercentageDefaultList = - new ArrayList<>( - Collections.nCopies(energyLevelPredictionListSize, energyLevelPercentageDefault)); + new ArrayList<>( + Collections.nCopies(energyLevelPredictionListSize, energyLevelPercentageDefault)); for (int i = 0; i < energyLevelPredictionListSize; i++) { if (chargeAndDischargeZonesList.get(i) == 1) { energyLevelPercentageDefaultList.set( - i, Double.valueOf(energyLevelPercentageMaximumBattery)); + i, Double.valueOf(energyLevelPercentageMaximumBattery)); } else if (chargeAndDischargeZonesList.get(i) == -1) { energyLevelPercentageDefaultList.set( - i, Double.valueOf(energyLevelPercentageMinimumBattery)); + i, Double.valueOf(energyLevelPercentageMinimumBattery)); } } @@ -1076,107 +1076,107 @@ private Map batteryCalculateForecasts( // Optimise forecast based on tariffs for (int i = 1; - i < chargeAndDischargeZonesList.size() && i < energyLevelPredictionListSize; - i++) { + i < chargeAndDischargeZonesList.size() && i < energyLevelPredictionListSize; + i++) { if (chargeAndDischargeZonesList.get(i) == 1 - && energyLevelPredictionList.get(i) < energyLevelPercentageDefaultList.get(i)) { + && energyLevelPredictionList.get(i) < energyLevelPercentageDefaultList.get(i)) { // Interval from charge zone index till end of forecast Double energyLevelIntervalMaximum = - Collections.max( - energyLevelPredictionList.subList(i, energyLevelPredictionListSize)); + Collections.max( + energyLevelPredictionList.subList(i, energyLevelPredictionListSize)); double chargeSpaceOverall = - round( - energyLevelPercentageMaximumBattery - energyLevelIntervalMaximum, - batteryPercentageRoundingPrecision); + round( + energyLevelPercentageMaximumBattery - energyLevelIntervalMaximum, + batteryPercentageRoundingPrecision); if (chargeSpaceOverall > 0) { double chargeNeeded = - round( - energyLevelPercentageDefaultList.get(i) - energyLevelPredictionList.get(i), - batteryPercentageRoundingPrecision); + round( + energyLevelPercentageDefaultList.get(i) - energyLevelPredictionList.get(i), + batteryPercentageRoundingPrecision); double chargeAvailableLeft = 0; if (dischargePercentageWantBatteryList.get(i) >= 0) { double chargeInUse = - energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); + energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); chargeAvailableLeft = - round( - chargePercentageAvailableBatteryList.get(i) - chargeInUse, - batteryPercentageRoundingPrecision); + round( + chargePercentageAvailableBatteryList.get(i) - chargeInUse, + batteryPercentageRoundingPrecision); } if (chargeAvailableLeft > 0) { chargeAvailableLeft = - Math.min(Math.min(chargeSpaceOverall, chargeAvailableLeft), chargeNeeded); + Math.min(Math.min(chargeSpaceOverall, chargeAvailableLeft), chargeNeeded); chargeSpaceOverall = - round( - chargeSpaceOverall - chargeAvailableLeft, - batteryPercentageRoundingPrecision); + round( + chargeSpaceOverall - chargeAvailableLeft, + batteryPercentageRoundingPrecision); for (int j = i; j < energyLevelPredictionListSize; j++) { energyLevelPredictionList.set( - j, - round( - energyLevelPredictionList.get(j) + chargeAvailableLeft, - batteryPercentageRoundingPrecision)); + j, + round( + energyLevelPredictionList.get(j) + chargeAvailableLeft, + batteryPercentageRoundingPrecision)); } } } // Get the index of the first maximum energyLevelIntervalMaximum = - Collections.max( - energyLevelPredictionList.subList(i, energyLevelPredictionListSize)); + Collections.max( + energyLevelPredictionList.subList(i, energyLevelPredictionListSize)); int energyLevelIntervalMaximumIndex = - energyLevelPredictionList.indexOf(energyLevelIntervalMaximum); + energyLevelPredictionList.indexOf(energyLevelIntervalMaximum); if (chargeSpaceOverall <= 0 && i < energyLevelIntervalMaximumIndex) { double chargeInUse = - energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); + energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); double chargeAvailableLeft = - round( - chargePercentageAvailableBatteryList.get(i) - chargeInUse, - batteryPercentageRoundingPrecision); + round( + chargePercentageAvailableBatteryList.get(i) - chargeInUse, + batteryPercentageRoundingPrecision); int energyLevelIntervalIndex = 0; dt = 0; dtStart = services.getTimerService().getCurrentTimeMillis(); while (chargeAvailableLeft > 0 - && energyLevelIntervalIndex < energyLevelIntervalMaximumIndex) { + && energyLevelIntervalIndex < energyLevelIntervalMaximumIndex) { for (int k = i + 1; k <= energyLevelIntervalMaximumIndex; k++) { energyLevelIntervalIndex = k; double chargeNeeded = - round( - energyLevelPredictionList.get(k) - energyLevelPredictionList.get(i), - batteryPercentageRoundingPrecision); + round( + energyLevelPredictionList.get(k) - energyLevelPredictionList.get(i), + batteryPercentageRoundingPrecision); // Only allow moving charging moment into charge zone if no charge is wanted at // index k if (chargeNeeded > 0 && chargePercentageWantBatteryList.get(k) == 0) { double energyLevelIntervalMaximum2 = - Collections.max(energyLevelPredictionList.subList(i, k)); + Collections.max(energyLevelPredictionList.subList(i, k)); double chargeSpaceInterval = - round( - energyLevelPercentageMaximumBattery - energyLevelIntervalMaximum2, - batteryPercentageRoundingPrecision); + round( + energyLevelPercentageMaximumBattery - energyLevelIntervalMaximum2, + batteryPercentageRoundingPrecision); if (chargeSpaceInterval > 0) { double chargeChange = - Math.min( - Math.min(chargeAvailableLeft, chargeNeeded), chargeSpaceInterval); + Math.min( + Math.min(chargeAvailableLeft, chargeNeeded), chargeSpaceInterval); chargeAvailableLeft = - round( - chargeAvailableLeft - chargeChange, - batteryPercentageRoundingPrecision); + round( + chargeAvailableLeft - chargeChange, + batteryPercentageRoundingPrecision); for (int j = i; j < k; j++) { energyLevelPredictionList.set( - j, - round( - energyLevelPredictionList.get(j) + chargeChange, - batteryPercentageRoundingPrecision)); + j, + round( + energyLevelPredictionList.get(j) + chargeChange, + batteryPercentageRoundingPrecision)); } break; } @@ -1185,13 +1185,13 @@ private Map batteryCalculateForecasts( if (dt > timeoutMillis) { infoStr.insert( - 0, - String.format( - "Battery energy level percentage calculation timed out during tariff charge optimisation at timestamp = %s", - timestampsMillisList.get(i))); + 0, + String.format( + "Battery energy level percentage calculation timed out during tariff charge optimisation at timestamp = %s", + timestampsMillisList.get(i))); infoStr.append( - String.format( - "energyLevelPredictionListOptimised = %s \n", energyLevelPredictionList)); + String.format( + "energyLevelPredictionListOptimised = %s \n", energyLevelPredictionList)); LOG.warning(infoStr.toString()); break; } @@ -1200,106 +1200,106 @@ private Map batteryCalculateForecasts( } } } else if (chargeAndDischargeZonesList.get(i) == -1 - && energyLevelPredictionList.get(i) > energyLevelPercentageDefaultList.get(i)) { + && energyLevelPredictionList.get(i) > energyLevelPercentageDefaultList.get(i)) { // Interval from discharge zone index till end of forecast Double energyLevelIntervalMinimum = - Collections.min( - energyLevelPredictionList.subList(i, energyLevelPredictionListSize)); + Collections.min( + energyLevelPredictionList.subList(i, energyLevelPredictionListSize)); double dischargeSpaceOverall = - round( - energyLevelPercentageMinimumBattery - energyLevelIntervalMinimum, - batteryPercentageRoundingPrecision); + round( + energyLevelPercentageMinimumBattery - energyLevelIntervalMinimum, + batteryPercentageRoundingPrecision); if (dischargeSpaceOverall < 0) { double dischargeNeeded = - round( - energyLevelPercentageDefaultList.get(i) - energyLevelPredictionList.get(i), - batteryPercentageRoundingPrecision); + round( + energyLevelPercentageDefaultList.get(i) - energyLevelPredictionList.get(i), + batteryPercentageRoundingPrecision); double dischargeAvailableLeft = 0; if (chargePercentageWantBatteryList.get(i) <= 0) { double dischargeInUse = - energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); + energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); dischargeAvailableLeft = - round( - dischargePercentageAvailableBatteryList.get(i) - dischargeInUse, - batteryPercentageRoundingPrecision); + round( + dischargePercentageAvailableBatteryList.get(i) - dischargeInUse, + batteryPercentageRoundingPrecision); } if (dischargeAvailableLeft < 0) { dischargeAvailableLeft = - Math.max( - Math.max(dischargeSpaceOverall, dischargeAvailableLeft), dischargeNeeded); + Math.max( + Math.max(dischargeSpaceOverall, dischargeAvailableLeft), dischargeNeeded); dischargeSpaceOverall = - round( - dischargeSpaceOverall - dischargeAvailableLeft, - batteryPercentageRoundingPrecision); + round( + dischargeSpaceOverall - dischargeAvailableLeft, + batteryPercentageRoundingPrecision); for (int j = i; j < energyLevelPredictionListSize; j++) { energyLevelPredictionList.set( - j, - round( - energyLevelPredictionList.get(j) + dischargeAvailableLeft, - batteryPercentageRoundingPrecision)); + j, + round( + energyLevelPredictionList.get(j) + dischargeAvailableLeft, + batteryPercentageRoundingPrecision)); } } } // Get the index of the first minimum energyLevelIntervalMinimum = - Collections.min( - energyLevelPredictionList.subList(i, energyLevelPredictionListSize)); + Collections.min( + energyLevelPredictionList.subList(i, energyLevelPredictionListSize)); int energyLevelIntervalMinimumIndex = - energyLevelPredictionList.indexOf(energyLevelIntervalMinimum); + energyLevelPredictionList.indexOf(energyLevelIntervalMinimum); if (dischargeSpaceOverall >= 0 && i < energyLevelIntervalMinimumIndex) { double dischargeInUse = - energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); + energyLevelPredictionList.get(i) - energyLevelPredictionList.get(i - 1); double dischargeAvailableLeft = - round( - dischargePercentageAvailableBatteryList.get(i) - dischargeInUse, - batteryPercentageRoundingPrecision); + round( + dischargePercentageAvailableBatteryList.get(i) - dischargeInUse, + batteryPercentageRoundingPrecision); int energyLevelIntervalIndex = 0; dt = 0; dtStart = services.getTimerService().getCurrentTimeMillis(); while (dischargeAvailableLeft < 0 - && energyLevelIntervalIndex < energyLevelIntervalMinimumIndex) { + && energyLevelIntervalIndex < energyLevelIntervalMinimumIndex) { for (int k = i + 1; k <= energyLevelIntervalMinimumIndex; k++) { energyLevelIntervalIndex = k; double dischargeNeeded = - round( - energyLevelPredictionList.get(k) - energyLevelPredictionList.get(i), - batteryPercentageRoundingPrecision); + round( + energyLevelPredictionList.get(k) - energyLevelPredictionList.get(i), + batteryPercentageRoundingPrecision); // Only allow moving discharging moment into discharge zone if no discharge is // wanted at index k if (dischargeNeeded < 0 && dischargePercentageWantBatteryList.get(k) == 0) { double energyLevelIntervalMinimum2 = - Collections.min(energyLevelPredictionList.subList(i, k)); + Collections.min(energyLevelPredictionList.subList(i, k)); double dischargeSpaceInterval = - round( - energyLevelPercentageMinimumBattery - energyLevelIntervalMinimum2, - batteryPercentageRoundingPrecision); + round( + energyLevelPercentageMinimumBattery - energyLevelIntervalMinimum2, + batteryPercentageRoundingPrecision); if (dischargeSpaceInterval < 0) { double dischargeChange = - Math.max( - Math.max(dischargeAvailableLeft, dischargeNeeded), - dischargeSpaceInterval); + Math.max( + Math.max(dischargeAvailableLeft, dischargeNeeded), + dischargeSpaceInterval); dischargeAvailableLeft = - round( - dischargeAvailableLeft - dischargeChange, - batteryPercentageRoundingPrecision); + round( + dischargeAvailableLeft - dischargeChange, + batteryPercentageRoundingPrecision); for (int j = i; j < k; j++) { energyLevelPredictionList.set( - j, - round( - energyLevelPredictionList.get(j) + dischargeChange, - batteryPercentageRoundingPrecision)); + j, + round( + energyLevelPredictionList.get(j) + dischargeChange, + batteryPercentageRoundingPrecision)); } break; } @@ -1308,13 +1308,13 @@ private Map batteryCalculateForecasts( if (dt > timeoutMillis) { infoStr.insert( - 0, - String.format( - "Battery energy level percentage calculation timed out during tariff discharge optimisation at timestamp = %s", - timestampsMillisList.get(i))); + 0, + String.format( + "Battery energy level percentage calculation timed out during tariff discharge optimisation at timestamp = %s", + timestampsMillisList.get(i))); infoStr.append( - String.format( - "energyLevelPredictionListOptimised = %s \n", energyLevelPredictionList)); + String.format( + "energyLevelPredictionListOptimised = %s \n", energyLevelPredictionList)); LOG.warning(infoStr.toString()); break; } @@ -1338,7 +1338,7 @@ private Map batteryCalculateForecasts( for (int i = 0; i < energyLevelPredictionList.size() - 1; i++) { double percentageChange = - energyLevelPredictionList.get(i + 1) - energyLevelPredictionList.get(i); + energyLevelPredictionList.get(i + 1) - energyLevelPredictionList.get(i); percentageChangeBatteryList.add(percentageChange); } @@ -1353,13 +1353,13 @@ private Map batteryCalculateForecasts( if (percentageChange > 0) { power = - percentageChange * energyCapacityBattery / (intervalHour * chargeEfficiencyBattery); + percentageChange * energyCapacityBattery / (intervalHour * chargeEfficiencyBattery); } else if (percentageChange < 0) { power = - percentageChange - * energyCapacityBattery - * dischargeEfficiencyBattery - / (10000 * intervalHour); + percentageChange + * energyCapacityBattery + * dischargeEfficiencyBattery + / (10000 * intervalHour); } powerChangeTotalList.add(round(power, 3)); @@ -1370,7 +1370,7 @@ private Map batteryCalculateForecasts( // Update list with power flexible changes for (int i = 0; i < powerChangeTotalList.size() - 1; i++) { totalPowerConsumptionProductionFlexibleList.set( - i, totalPowerConsumptionProductionFlexibleList.get(i) - powerChangeTotalList.get(i)); + i, totalPowerConsumptionProductionFlexibleList.get(i) - powerChangeTotalList.get(i)); } // System.out.println("totalPowerConsumptionProductionFlexibleList = " + @@ -1382,46 +1382,46 @@ private Map batteryCalculateForecasts( // Update energy level percentage forecast starting after current time for (int i = 1; i < numberOfTimestamps; i++) { energyLevelPercentageForecast.add( - new ValueDatapoint<>( - timestampsMillisList.get(i), (int) Math.round(energyLevelPredictionList.get(i)))); + new ValueDatapoint<>( + timestampsMillisList.get(i), (int) Math.round(energyLevelPredictionList.get(i)))); } // Update power set-point starting from current power limit for (int i = 0; i < numberOfTimestamps; i++) { powerSetpointForecast.add( - new ValueDatapoint<>(timestampsMillisList.get(i), powerChangeTotalList.get(i))); + new ValueDatapoint<>(timestampsMillisList.get(i), powerChangeTotalList.get(i))); } services - .getAssetPredictedDatapointService() - .updateValues( - batteryAssetId, - EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE.getName(), - energyLevelPercentageForecast); + .getAssetPredictedDatapointService() + .updateValues( + batteryAssetId, + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE.getName(), + energyLevelPercentageForecast); services - .getAssetPredictedDatapointService() - .updateValues( - batteryAssetId, - EmsElectricityBatteryAsset.POWER_SETPOINT.getName(), - powerSetpointForecast); + .getAssetPredictedDatapointService() + .updateValues( + batteryAssetId, + EmsElectricityBatteryAsset.POWER_SETPOINT.getName(), + powerSetpointForecast); } return batteryEnergyLevelPercentageTargets; } private Map batteryCalculatePowerFlexibleAvailable( - List electricityBatteryAssets) { + List electricityBatteryAssets) { Map powerFlexibleAvailable = new HashMap<>(); for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { boolean allowCharging = electricityBatteryAsset.getAllowCharging().orElse(false); boolean allowDischarging = electricityBatteryAsset.getAllowDischarging().orElse(false); Double energyLevelPercentage = - electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); Integer energyLevelPercentageMaximum = - electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); Integer energyLevelPercentageMinimum = - electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); double chargePowerMaximum = electricityBatteryAsset.getChargePowerMaximum().orElse(0.0); double dischargePowerMaximum = electricityBatteryAsset.getDischargePowerMaximum().orElse(0.0); @@ -1433,30 +1433,30 @@ private Map batteryCalculatePowerFlexibleAvailable( } if (!allowDischarging - || energyLevelPercentageMinimum == null - || energyLevelPercentage == null) { + || energyLevelPercentageMinimum == null + || energyLevelPercentage == null) { dischargePowerMaximum = 0.0; } else if (energyLevelPercentage <= energyLevelPercentageMinimum) { dischargePowerMaximum = 0.0; } powerFlexibleAvailable.put( - electricityBatteryAsset.getId(), - new ChargeDischarge(chargePowerMaximum, dischargePowerMaximum)); + electricityBatteryAsset.getId(), + new ChargeDischarge(chargePowerMaximum, dischargePowerMaximum)); } return powerFlexibleAvailable; } private Map batteryCalculatePowerSetpoints( - EmsEnergyOptimisationAsset energyOptimisationAsset, - List electricityBatteryAssets, - long powerSetpointTimestampMillisLatest, - Map batteryEnergyLevelPercentageTargets, - Services services, - String logPrefixEnergyOptimisation) { + EmsEnergyOptimisationAsset energyOptimisationAsset, + List electricityBatteryAssets, + long powerSetpointTimestampMillisLatest, + Map batteryEnergyLevelPercentageTargets, + Services services, + String logPrefixEnergyOptimisation) { Map powerFlexibleAvailable = - batteryCalculatePowerFlexibleAvailable(electricityBatteryAssets); + batteryCalculatePowerFlexibleAvailable(electricityBatteryAssets); Map powerSetpointsNew; // Check if power net updated since last batteries power set-point update @@ -1467,54 +1467,54 @@ private Map batteryCalculatePowerSetpoints( // disconnect if (powerSetpointTimestampMillisLatest > powerNetTimestampMillis || powerNet == null) { powerSetpointsNew = - batteryCheckPowerSetpointsCurrent(electricityBatteryAssets, powerFlexibleAvailable); + batteryCheckPowerSetpointsCurrent(electricityBatteryAssets, powerFlexibleAvailable); return powerSetpointsNew; } // Check if power limits are present Double powerLimitMaximumProfileTotal = - energyOptimisationAsset.getPowerLimitMaximumProfileTotal().orElse(null); + energyOptimisationAsset.getPowerLimitMaximumProfileTotal().orElse(null); Double powerLimitMinimumProfileTotal = - energyOptimisationAsset.getPowerLimitMinimumProfileTotal().orElse(null); + energyOptimisationAsset.getPowerLimitMinimumProfileTotal().orElse(null); // Calculate debounce power limits Double powerLimitMaximumVirtual = - calculatePowerLimitVirtual(energyOptimisationAsset, powerLimitMaximumProfileTotal, "max"); + calculatePowerLimitVirtual(energyOptimisationAsset, powerLimitMaximumProfileTotal, "max"); Double powerLimitMinimumVirtual = - calculatePowerLimitVirtual(energyOptimisationAsset, powerLimitMinimumProfileTotal, "min"); + calculatePowerLimitVirtual(energyOptimisationAsset, powerLimitMinimumProfileTotal, "min"); // Send warning LOG message when too large fluctuation margins are set if (powerLimitMaximumProfileTotal != null - && powerLimitMinimumProfileTotal != null - && powerLimitMaximumVirtual != null - && powerLimitMinimumVirtual != null - && powerLimitMaximumVirtual < powerLimitMinimumVirtual) { + && powerLimitMinimumProfileTotal != null + && powerLimitMaximumVirtual != null + && powerLimitMinimumVirtual != null + && powerLimitMaximumVirtual < powerLimitMinimumVirtual) { double diffPowerLimit = powerLimitMaximumProfileTotal - powerLimitMinimumProfileTotal; double fluctuationMarginSum = - Math.abs(powerLimitMaximumProfileTotal - powerLimitMaximumVirtual) - + Math.abs(powerLimitMinimumProfileTotal - powerLimitMinimumVirtual); + Math.abs(powerLimitMaximumProfileTotal - powerLimitMaximumVirtual) + + Math.abs(powerLimitMinimumProfileTotal - powerLimitMinimumVirtual); Long powerLimitMaximumProfileTimestampMillis = - energyOptimisationAsset.getPowerLimitMaximumProfileTotalTimestamp().orElse(null); + energyOptimisationAsset.getPowerLimitMaximumProfileTotalTimestamp().orElse(null); DateTimeFormatter formatter = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").withZone(ZoneId.systemDefault()); + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").withZone(ZoneId.systemDefault()); String powerLimitMaximumProfileDateTime = ""; if (powerLimitMaximumProfileTimestampMillis != null) { powerLimitMaximumProfileDateTime = - formatter.format(Instant.ofEpochMilli(powerLimitMaximumProfileTimestampMillis)); + formatter.format(Instant.ofEpochMilli(powerLimitMaximumProfileTimestampMillis)); } LOG.warning( - String.format( - "%s; Failed to perform '%s' energy optimisation method. The difference between '%s' - %s = %s kW is smaller than the fluctuation margin sum = %s kW for timestamp='%s'", - logPrefixEnergyOptimisation, - optimisationMethodName, - EmsEnergyOptimisationAsset.POWER_LIMIT_MAXIMUM_PROFILE_TOTAL.getName(), - EmsEnergyOptimisationAsset.POWER_LIMIT_MINIMUM_PROFILE_TOTAL.getName(), - diffPowerLimit, - fluctuationMarginSum, - powerLimitMaximumProfileDateTime)); + String.format( + "%s; Failed to perform '%s' energy optimisation method. The difference between '%s' - %s = %s kW is smaller than the fluctuation margin sum = %s kW for timestamp='%s'", + logPrefixEnergyOptimisation, + optimisationMethodName, + EmsEnergyOptimisationAsset.POWER_LIMIT_MAXIMUM_PROFILE_TOTAL.getName(), + EmsEnergyOptimisationAsset.POWER_LIMIT_MINIMUM_PROFILE_TOTAL.getName(), + diffPowerLimit, + fluctuationMarginSum, + powerLimitMaximumProfileDateTime)); } // Calculate virtual power consumption @@ -1526,84 +1526,84 @@ private Map batteryCalculatePowerSetpoints( } double powerSetpointsCurrentSum = - powerSetpointsCurrent.values().stream().mapToDouble(Double::doubleValue).sum(); + powerSetpointsCurrent.values().stream().mapToDouble(Double::doubleValue).sum(); double powerConsumptionVirtual = powerNet - powerSetpointsCurrentSum; // Calculate new power set-points without limits Map powerSetpointsNewWithoutLimits = - batteryCalculatePowerSetpointsWithoutLimits( - electricityBatteryAssets, - batteryEnergyLevelPercentageTargets, - energyOptimisationAsset, - powerFlexibleAvailable, - services); + batteryCalculatePowerSetpointsWithoutLimits( + electricityBatteryAssets, + batteryEnergyLevelPercentageTargets, + energyOptimisationAsset, + powerFlexibleAvailable, + services); // Apply power limits and adjust new power set-points double powerSetpointsNewWithoutLimitsSum = - powerSetpointsNewWithoutLimits.values().stream().mapToDouble(Double::doubleValue).sum(); + powerSetpointsNewWithoutLimits.values().stream().mapToDouble(Double::doubleValue).sum(); double powerNetVirtualNew = powerConsumptionVirtual + powerSetpointsNewWithoutLimitsSum; double powerLimitMaximumFluctuation = - calculatePowerFluctuationMargin( - energyOptimisationAsset, powerLimitMaximumProfileTotal, "max"); + calculatePowerFluctuationMargin( + energyOptimisationAsset, powerLimitMaximumProfileTotal, "max"); double powerLimitMinimumFluctuation = - calculatePowerFluctuationMargin( - energyOptimisationAsset, powerLimitMinimumProfileTotal, "min"); + calculatePowerFluctuationMargin( + energyOptimisationAsset, powerLimitMinimumProfileTotal, "min"); if (powerLimitMaximumVirtual != null - && (powerNetVirtualNew + powerLimitMaximumFluctuation) > powerLimitMaximumVirtual) { + && (powerNetVirtualNew + powerLimitMaximumFluctuation) > powerLimitMaximumVirtual) { powerSetpointsNew = - batteryCalculatePowerSetpointsOnLimitBreach( - powerNetVirtualNew, - powerLimitMaximumVirtual, - electricityBatteryAssets, - powerFlexibleAvailable, - powerSetpointsNewWithoutLimits); + batteryCalculatePowerSetpointsOnLimitBreach( + powerNetVirtualNew, + powerLimitMaximumVirtual, + electricityBatteryAssets, + powerFlexibleAvailable, + powerSetpointsNewWithoutLimits); } else if (powerLimitMinimumVirtual != null - && (powerNetVirtualNew - powerLimitMinimumFluctuation) < powerLimitMinimumVirtual) { + && (powerNetVirtualNew - powerLimitMinimumFluctuation) < powerLimitMinimumVirtual) { powerSetpointsNew = - batteryCalculatePowerSetpointsOnLimitBreach( - powerNetVirtualNew, - powerLimitMinimumVirtual, - electricityBatteryAssets, - powerFlexibleAvailable, - powerSetpointsNewWithoutLimits); + batteryCalculatePowerSetpointsOnLimitBreach( + powerNetVirtualNew, + powerLimitMinimumVirtual, + electricityBatteryAssets, + powerFlexibleAvailable, + powerSetpointsNewWithoutLimits); } else { powerSetpointsNew = powerSetpointsNewWithoutLimits; } // Send warning LOG message on power limit breach double powerSetpointsNewSum = - powerSetpointsNew.values().stream().mapToDouble(Double::doubleValue).sum(); + powerSetpointsNew.values().stream().mapToDouble(Double::doubleValue).sum(); double powerNetNewVirtual = powerConsumptionVirtual + powerSetpointsNewSum; if (powerLimitMaximumProfileTotal != null - && powerNetNewVirtual > powerLimitMaximumProfileTotal) { + && powerNetNewVirtual > powerLimitMaximumProfileTotal) { double powerReductionShortage = - round((powerNetNewVirtual - powerLimitMaximumProfileTotal), 3); + round((powerNetNewVirtual - powerLimitMaximumProfileTotal), 3); LOG.warning( - String.format( - "%s; Not enough flexible power to get below power limit maximum; Shortage of %s kW", - logPrefixEnergyOptimisation, powerReductionShortage)); + String.format( + "%s; Not enough flexible power to get below power limit maximum; Shortage of %s kW", + logPrefixEnergyOptimisation, powerReductionShortage)); } else if (powerLimitMinimumProfileTotal != null - && powerNetNewVirtual < powerLimitMinimumProfileTotal) { + && powerNetNewVirtual < powerLimitMinimumProfileTotal) { double powerReductionShortage = - round((powerNetNewVirtual - powerLimitMinimumProfileTotal), 3); + round((powerNetNewVirtual - powerLimitMinimumProfileTotal), 3); LOG.warning( - String.format( - "%s; Not enough flexible power to get above power limit minimum; Shortage of %s kW", - logPrefixEnergyOptimisation, powerReductionShortage)); + String.format( + "%s; Not enough flexible power to get above power limit minimum; Shortage of %s kW", + logPrefixEnergyOptimisation, powerReductionShortage)); } return powerSetpointsNew; } private Map batteryCalculatePowerSetpointsOnLimitBreach( - double power, - double powerLimitVirtual, - List electricityBatteryAssets, - Map powerFlexibleAvailable, - Map powerSetpointsNewWithoutLimits) { + double power, + double powerLimitVirtual, + List electricityBatteryAssets, + Map powerFlexibleAvailable, + Map powerSetpointsNewWithoutLimits) { Map powerSetpointsNew = new HashMap<>(); double powerChangeNeeded = round(power - powerLimitVirtual, 3); @@ -1612,9 +1612,9 @@ private Map batteryCalculatePowerSetpointsOnLimitBreach( double chargePowerAvailable = powerFlexibleAvailable.get(electricityBatteryAssetId).charge; double dischargePowerAvailable = - powerFlexibleAvailable.get(electricityBatteryAssetId).discharge; + powerFlexibleAvailable.get(electricityBatteryAssetId).discharge; double powerSetpointNewWithoutLimits = - powerSetpointsNewWithoutLimits.getOrDefault(electricityBatteryAssetId, 0.0); + powerSetpointsNewWithoutLimits.getOrDefault(electricityBatteryAssetId, 0.0); double chargePowerAvailableTotal = chargePowerAvailable - powerSetpointNewWithoutLimits; double dischargePowerAvailableTotal = dischargePowerAvailable - powerSetpointNewWithoutLimits; @@ -1636,11 +1636,11 @@ private Map batteryCalculatePowerSetpointsOnLimitBreach( } private Map batteryCalculatePowerSetpointsWithoutLimits( - List electricityBatteryAssets, - Map batteryEnergyLevelPercentageTargets, - EmsEnergyOptimisationAsset energyOptimisationAsset, - Map powerFlexibleAvailable, - Services services) { + List electricityBatteryAssets, + Map batteryEnergyLevelPercentageTargets, + EmsEnergyOptimisationAsset energyOptimisationAsset, + Map powerFlexibleAvailable, + Services services) { // Find battery power set-points for a system without power limits Map powerSetpointsNew = new HashMap<>(); @@ -1651,7 +1651,7 @@ private Map batteryCalculatePowerSetpointsWithoutLimits( for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { String electricityBatteryAssetId = electricityBatteryAsset.getId(); Double energyLevelPercentage = - electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); // Set initial power set-point of 0 powerSetpointsNew.put(electricityBatteryAssetId, 0.0); @@ -1665,49 +1665,49 @@ private Map batteryCalculatePowerSetpointsWithoutLimits( // Get battery energy level percentage target Integer energyLevelPercentageTarget = - batteryEnergyLevelPercentageTargets.get(electricityBatteryAssetId); + batteryEnergyLevelPercentageTargets.get(electricityBatteryAssetId); if (energyLevelPercentageTarget == null) { Integer energyLevelPercentageMaximum = - electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); Integer energyLevelPercentageMinimum = - electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); energyLevelPercentageTarget = - batteryCalculateEnergyLevelPercentageDefault( - energyLevelPercentageMaximum, - energyLevelPercentageMinimum, - energyOptimisationAsset); + batteryCalculateEnergyLevelPercentageDefault( + energyLevelPercentageMaximum, + energyLevelPercentageMinimum, + energyOptimisationAsset); } // Get battery power set-point target AssetDatapointAllQuery assetDatapointQueryPredicted = - new AssetDatapointAllQuery(startTimeMillis, endTimeMillis); + new AssetDatapointAllQuery(startTimeMillis, endTimeMillis); List> powerSetpointForecastList = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - electricityBatteryAssetId, - EmsElectricityBatteryAsset.POWER_SETPOINT.getName(), - assetDatapointQueryPredicted); + services + .getAssetPredictedDatapointService() + .queryDatapoints( + electricityBatteryAssetId, + EmsElectricityBatteryAsset.POWER_SETPOINT.getName(), + assetDatapointQueryPredicted); // Calculate battery power set-point target double energyLevelPercentageRounded = round(energyLevelPercentage, 1); double powerNeeded = - round( - (energyLevelPercentageTarget - energyLevelPercentageRounded) * 0.01 * energyCapacity, - 3); + round( + (energyLevelPercentageTarget - energyLevelPercentageRounded) * 0.01 * energyCapacity, + 3); double powerSetpointNeeded = - round(powerNeeded * 60 * BATTERY_POWER_SETPOINT_RESPONSIVENESS_DEFAULT, 3); + round(powerNeeded * 60 * BATTERY_POWER_SETPOINT_RESPONSIVENESS_DEFAULT, 3); if (energyLevelPercentageRounded < energyLevelPercentageTarget - || (powerSetpointCurrent > 0.0 + || (powerSetpointCurrent > 0.0 && energyLevelPercentageRounded < energyLevelPercentageTarget)) { // Start charging or continue charging int chargeEfficiencyPercentage = electricityBatteryAsset.getChargeEfficiency().orElse(100); double chargePowerAvailable = powerFlexibleAvailable.get(electricityBatteryAssetId).charge; double powerSetpointEfficiency = - round(powerSetpointNeeded / (chargeEfficiencyPercentage * 0.01), 3); + round(powerSetpointNeeded / (chargeEfficiencyPercentage * 0.01), 3); double powerSetpointNew = Math.min(powerSetpointEfficiency, chargePowerAvailable); if (!powerSetpointForecastList.isEmpty()) { @@ -1720,16 +1720,16 @@ private Map batteryCalculatePowerSetpointsWithoutLimits( powerSetpointsNew.put(electricityBatteryAssetId, powerSetpointNew); } else if (energyLevelPercentageRounded > energyLevelPercentageTarget - || (powerSetpointCurrent < 0.0 + || (powerSetpointCurrent < 0.0 && energyLevelPercentageRounded > energyLevelPercentageTarget)) { // Start discharging or continue discharging int dischargeEfficiencyPercentage = - electricityBatteryAsset.getDischargeEfficiency().orElse(100); + electricityBatteryAsset.getDischargeEfficiency().orElse(100); double dischargePowerAvailable = - powerFlexibleAvailable.get(electricityBatteryAssetId).discharge; + powerFlexibleAvailable.get(electricityBatteryAssetId).discharge; double powerSetpointEfficiency = - round(powerSetpointNeeded / (dischargeEfficiencyPercentage * 0.01), 3); + round(powerSetpointNeeded / (dischargeEfficiencyPercentage * 0.01), 3); double powerSetpointNew = Math.max(powerSetpointEfficiency, dischargePowerAvailable); if (!powerSetpointForecastList.isEmpty()) { @@ -1748,15 +1748,15 @@ private Map batteryCalculatePowerSetpointsWithoutLimits( } private void batteryCheckConnection( - List electricityBatteryAssets, Services services) { + List electricityBatteryAssets, Services services) { // This method checks if a battery is connected based on if the 'power' and // 'energyLevelPercentage' attributes update within the active time interval String connected = - EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType.connected - .toString(); + EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType.connected + .toString(); String disconnected = - EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType.disconnected - .toString(); + EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType.disconnected + .toString(); long currentTimestampMillis = services.getTimerService().getCurrentTimeMillis(); long activePeriodMillis = ACTIVE_PERIOD_MINUTES * 60000L; @@ -1765,7 +1765,7 @@ private void batteryCheckConnection( String electricityBatteryAssetId = electricityBatteryAsset.getId(); Optional - connectionStatusValue = electricityBatteryAsset.getConnectionStatus(); + connectionStatusValue = electricityBatteryAsset.getConnectionStatus(); String connectionStatusPrevious = ""; if (connectionStatusValue.isPresent()) { @@ -1776,90 +1776,90 @@ private void batteryCheckConnection( long powerTimestampMillisBattery = electricityBatteryAsset.getPowerTimestamp().orElse(0L); Double energyLevelPercentageBattery = - electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); long energyLevelPercentageTimestampMillisBattery = - electricityBatteryAsset.getEnergyLevelPercentageTimestamp().orElse(0L); + electricityBatteryAsset.getEnergyLevelPercentageTimestamp().orElse(0L); String connectionStatusCurrent = disconnected; if ((currentTimestampMillis - powerTimestampMillisBattery) < activePeriodMillis - && powerBattery != null - && (currentTimestampMillis - energyLevelPercentageTimestampMillisBattery) + && powerBattery != null + && (currentTimestampMillis - energyLevelPercentageTimestampMillisBattery) < activePeriodMillis - && energyLevelPercentageBattery != null) { + && energyLevelPercentageBattery != null) { connectionStatusCurrent = connected; } if (connectionStatusCurrent.equals(connected) - && connectionStatusPrevious.equals(disconnected)) { + && connectionStatusPrevious.equals(disconnected)) { services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - electricityBatteryAssetId, - EmsElectricityBatteryAsset.CONNECTION_STATUS, - EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType - .connected), - getClass().getSimpleName()); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + electricityBatteryAssetId, + EmsElectricityBatteryAsset.CONNECTION_STATUS, + EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType + .connected), + getClass().getSimpleName()); } else if (connectionStatusCurrent.equals(disconnected) - && connectionStatusPrevious.equals(connected)) { + && connectionStatusPrevious.equals(connected)) { services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - electricityBatteryAssetId, - EmsElectricityBatteryAsset.CONNECTION_STATUS, - EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType - .disconnected), - getClass().getSimpleName()); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + electricityBatteryAssetId, + EmsElectricityBatteryAsset.CONNECTION_STATUS, + EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType + .disconnected), + getClass().getSimpleName()); services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - electricityBatteryAssetId, - EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE, - null), - getClass().getSimpleName()); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + electricityBatteryAssetId, + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE, + null), + getClass().getSimpleName()); services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - electricityBatteryAssetId, EmsElectricityBatteryAsset.POWER, null), - getClass().getSimpleName()); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + electricityBatteryAssetId, EmsElectricityBatteryAsset.POWER, null), + getClass().getSimpleName()); } else if (connectionStatusCurrent.equals(connected) && connectionStatusPrevious.isEmpty()) { services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - electricityBatteryAssetId, - EmsElectricityBatteryAsset.CONNECTION_STATUS, - EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType - .connected), - getClass().getSimpleName()); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + electricityBatteryAssetId, + EmsElectricityBatteryAsset.CONNECTION_STATUS, + EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType + .connected), + getClass().getSimpleName()); } else if (connectionStatusCurrent.equals(disconnected) - && connectionStatusPrevious.isEmpty()) { + && connectionStatusPrevious.isEmpty()) { services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - electricityBatteryAssetId, - EmsElectricityBatteryAsset.CONNECTION_STATUS, - EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType - .disconnected), - getClass().getSimpleName()); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + electricityBatteryAssetId, + EmsElectricityBatteryAsset.CONNECTION_STATUS, + EmsElectricityBatteryAsset.EmsElectricityBatteryConnectionStatusValueType + .disconnected), + getClass().getSimpleName()); } } } private Map batteryCheckPowerSetpointsCurrent( - List electricityBatteryAssets, - Map powerFlexibleAvailable) { + List electricityBatteryAssets, + Map powerFlexibleAvailable) { Map powerSetpointsNew = new HashMap<>(); for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { String electricityBatteryAssetId = electricityBatteryAsset.getId(); Double energyLevelPercentage = - electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentage().orElse(null); if (energyLevelPercentage == null) { powerSetpointsNew.put(electricityBatteryAssetId, 0.0); @@ -1869,7 +1869,7 @@ private Map batteryCheckPowerSetpointsCurrent( double powerSetpointCurrent = electricityBatteryAsset.getPowerSetpoint().orElse(0.0); double chargePowerAvailable = powerFlexibleAvailable.get(electricityBatteryAssetId).charge; double dischargePowerAvailable = - powerFlexibleAvailable.get(electricityBatteryAssetId).discharge; + powerFlexibleAvailable.get(electricityBatteryAssetId).discharge; // Set initial power set-point new double powerSetpointNew = 0.0; @@ -1898,114 +1898,114 @@ private void batteryCheckSetup(List electricityBatte } String logPrefixBattery = - String.format( - "assetType='%s', assetId='%s', assetName='%s'", - electricityBatteryAsset.getAssetType(), - electricityBatteryAsset.getId(), - electricityBatteryAsset.getAssetName()); + String.format( + "assetType='%s', assetId='%s', assetName='%s'", + electricityBatteryAsset.getAssetType(), + electricityBatteryAsset.getId(), + electricityBatteryAsset.getAssetName()); // Check if the following attributes are connected Map requiredFieldsConnection = new HashMap<>(); requiredFieldsConnection.put( - EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE.getName(), - electricityBatteryAsset.getEnergyLevelPercentage().orElse(null)); + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE.getName(), + electricityBatteryAsset.getEnergyLevelPercentage().orElse(null)); requiredFieldsConnection.put( - EmsElectricityBatteryAsset.POWER.getName(), - electricityBatteryAsset.getPower().orElse(null)); + EmsElectricityBatteryAsset.POWER.getName(), + electricityBatteryAsset.getPower().orElse(null)); List missingFieldsConnection = - requiredFieldsConnection.entrySet().stream() - .filter(entry -> entry.getValue() == null) - .map(Map.Entry::getKey) - .toList(); + requiredFieldsConnection.entrySet().stream() + .filter(entry -> entry.getValue() == null) + .map(Map.Entry::getKey) + .toList(); if (!missingFieldsConnection.isEmpty()) { LOG.warning( - String.format( - "%s; Can't use battery for flexible power. The following attributes are not connected: %s", - logPrefixBattery, - String.join( - ", ", - missingFieldsConnection.stream().map(attr -> "'" + attr + "'").toList()))); + String.format( + "%s; Can't use battery for flexible power. The following attributes are not connected: %s", + logPrefixBattery, + String.join( + ", ", + missingFieldsConnection.stream().map(attr -> "'" + attr + "'").toList()))); } // Check if the following attributes are set Map requiredFieldsSetup = new HashMap<>(); Integer energyLevelPercentageMaximum = - electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentageMaximum().orElse(null); Integer energyLevelPercentageMinimum = - electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); + electricityBatteryAsset.getEnergyLevelPercentageMinimum().orElse(null); Integer chargeEfficiency = electricityBatteryAsset.getChargeEfficiency().orElse(null); Integer dischargeEfficiency = electricityBatteryAsset.getDischargeEfficiency().orElse(null); requiredFieldsSetup.put( - EmsElectricityBatteryAsset.CHARGE_EFFICIENCY.getName(), chargeEfficiency); + EmsElectricityBatteryAsset.CHARGE_EFFICIENCY.getName(), chargeEfficiency); requiredFieldsSetup.put( - EmsElectricityBatteryAsset.CHARGE_POWER_MAXIMUM.getName(), - electricityBatteryAsset.getChargePowerMaximum().orElse(null)); + EmsElectricityBatteryAsset.CHARGE_POWER_MAXIMUM.getName(), + electricityBatteryAsset.getChargePowerMaximum().orElse(null)); requiredFieldsSetup.put( - EmsElectricityBatteryAsset.DISCHARGE_EFFICIENCY.getName(), dischargeEfficiency); + EmsElectricityBatteryAsset.DISCHARGE_EFFICIENCY.getName(), dischargeEfficiency); requiredFieldsSetup.put( - EmsElectricityBatteryAsset.DISCHARGE_POWER_MAXIMUM.getName(), - electricityBatteryAsset.getDischargePowerMaximum().orElse(null)); + EmsElectricityBatteryAsset.DISCHARGE_POWER_MAXIMUM.getName(), + electricityBatteryAsset.getDischargePowerMaximum().orElse(null)); requiredFieldsSetup.put( - EmsElectricityBatteryAsset.ENERGY_CAPACITY.getName(), - electricityBatteryAsset.getEnergyCapacity().orElse(null)); + EmsElectricityBatteryAsset.ENERGY_CAPACITY.getName(), + electricityBatteryAsset.getEnergyCapacity().orElse(null)); requiredFieldsSetup.put( - EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MAXIMUM.getName(), - energyLevelPercentageMaximum); + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MAXIMUM.getName(), + energyLevelPercentageMaximum); requiredFieldsSetup.put( - EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MINIMUM.getName(), - energyLevelPercentageMinimum); + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MINIMUM.getName(), + energyLevelPercentageMinimum); List missingFieldsSetup = - requiredFieldsSetup.entrySet().stream() - .filter(entry -> entry.getValue() == null) - .map(Map.Entry::getKey) - .toList(); + requiredFieldsSetup.entrySet().stream() + .filter(entry -> entry.getValue() == null) + .map(Map.Entry::getKey) + .toList(); if (!missingFieldsSetup.isEmpty()) { LOG.warning( - String.format( - "%s; Can't use battery for flexible power. The following attributes are not set: %s", - logPrefixBattery, - String.join( - ", ", missingFieldsSetup.stream().map(attr -> "'" + attr + "'").toList()))); + String.format( + "%s; Can't use battery for flexible power. The following attributes are not set: %s", + logPrefixBattery, + String.join( + ", ", missingFieldsSetup.stream().map(attr -> "'" + attr + "'").toList()))); } // Check if the following attributes are set correctly if (energyLevelPercentageMaximum != null - && energyLevelPercentageMinimum != null - && energyLevelPercentageMaximum <= energyLevelPercentageMinimum) { + && energyLevelPercentageMinimum != null + && energyLevelPercentageMaximum <= energyLevelPercentageMinimum) { LOG.warning( - String.format( - "%s; Can't use battery for flexible power. '%s' = %s%% is smaller than or equal to '%s'= %s%%)", - logPrefixBattery, - EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MAXIMUM.getName(), - energyLevelPercentageMaximum, - EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MINIMUM.getName(), - energyLevelPercentageMinimum)); + String.format( + "%s; Can't use battery for flexible power. '%s' = %s%% is smaller than or equal to '%s'= %s%%)", + logPrefixBattery, + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MAXIMUM.getName(), + energyLevelPercentageMaximum, + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE_MINIMUM.getName(), + energyLevelPercentageMinimum)); } if (chargeEfficiency != null && chargeEfficiency <= 0) { LOG.warning( - String.format( - "The charge efficiency = %s%% is smaller than or equal to 0", chargeEfficiency)); + String.format( + "The charge efficiency = %s%% is smaller than or equal to 0", chargeEfficiency)); } if (dischargeEfficiency != null && dischargeEfficiency <= 0) { LOG.warning( - String.format( - "The discharge efficiency = %s%% is smaller than or equal to 0", - dischargeEfficiency)); + String.format( + "The discharge efficiency = %s%% is smaller than or equal to 0", + dischargeEfficiency)); } } } private Map batteryGetEnergyLevelPercentageTargetsCurrent( - List electricityBatteryAssets, Services services) { + List electricityBatteryAssets, Services services) { Map batteryEnergyLevelPercentageTargetsCurrent = new HashMap<>(); long intervalMillis = 15 * 60000; @@ -2015,14 +2015,14 @@ private Map batteryGetEnergyLevelPercentageTargetsCurrent( for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { String batteryAssetId = electricityBatteryAsset.getId(); AssetDatapointAllQuery assetDatapointQueryPredicted = - new AssetDatapointAllQuery(currentTimeMillis, endTimeMillis); + new AssetDatapointAllQuery(currentTimeMillis, endTimeMillis); List> energyLevelPercentagePredictedCurrent = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - batteryAssetId, - EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE.getName(), - assetDatapointQueryPredicted); + services + .getAssetPredictedDatapointService() + .queryDatapoints( + batteryAssetId, + EmsElectricityBatteryAsset.ENERGY_LEVEL_PERCENTAGE.getName(), + assetDatapointQueryPredicted); Integer energyLevelPercentageTarget = null; @@ -2038,52 +2038,52 @@ private Map batteryGetEnergyLevelPercentageTargetsCurrent( } private List batteryOrder( - List electricityBatteryAssets) { + List electricityBatteryAssets) { List sorted = new ArrayList<>(electricityBatteryAssets); sorted.sort( - Comparator.comparing( - (EmsElectricityBatteryAsset b) -> - b.getEnergyCapacity().isPresent() - && b.getDischargePowerMaximum().orElse(0.0) != 0.0) - .reversed() - .thenComparing( - b -> - b.getDischargePowerMaximum().orElse(0.0) == 0 - ? null - : round( - b.getEnergyCapacity().orElse(0.0) - / b.getDischargePowerMaximum().orElse(0.0), - 2), - Comparator.nullsLast(Comparator.naturalOrder())) - .thenComparing(EmsElectricityBatteryAsset::getId)); + Comparator.comparing( + (EmsElectricityBatteryAsset b) -> + b.getEnergyCapacity().isPresent() + && b.getDischargePowerMaximum().orElse(0.0) != 0.0) + .reversed() + .thenComparing( + b -> + b.getDischargePowerMaximum().orElse(0.0) == 0 + ? null + : round( + b.getEnergyCapacity().orElse(0.0) + / b.getDischargePowerMaximum().orElse(0.0), + 2), + Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(EmsElectricityBatteryAsset::getId)); return sorted; } private void batteryUpdatePowerSetpoints( - List electricityBatteryAssets, - Map powerSetpointsNew, - Services services) { + List electricityBatteryAssets, + Map powerSetpointsNew, + Services services) { for (EmsElectricityBatteryAsset electricityBatteryAsset : electricityBatteryAssets) { String electricityBatteryAssetId = electricityBatteryAsset.getId(); Double powerSetpointNew = powerSetpointsNew.get(electricityBatteryAssetId); services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - electricityBatteryAssetId, - EmsElectricityBatteryAsset.POWER_SETPOINT, - powerSetpointNew), - getClass().getSimpleName()); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + electricityBatteryAssetId, + EmsElectricityBatteryAsset.POWER_SETPOINT, + powerSetpointNew), + getClass().getSimpleName()); } } private double calculatePowerFluctuationMargin( - EmsEnergyOptimisationAsset energyOptimisationAsset, - Double powerLimitProfileTotal, - String maxOrMin) { + EmsEnergyOptimisationAsset energyOptimisationAsset, + Double powerLimitProfileTotal, + String maxOrMin) { if (powerLimitProfileTotal == null) { return 0.0; } @@ -2096,30 +2096,30 @@ private double calculatePowerFluctuationMargin( } Double powerLimitFluctuationMargin = - (Double) - energyOptimisationAsset - .getAttribute(fluctuationMarginAttributeName) - .flatMap(Attribute::getValue) - .orElse(null); + (Double) + energyOptimisationAsset + .getAttribute(fluctuationMarginAttributeName) + .flatMap(Attribute::getValue) + .orElse(null); if (powerLimitFluctuationMargin == null) { powerLimitFluctuationMargin = - powerLimitProfileTotal * POWER_LIMIT_FLUCTUATION_MARGIN_PERCENTAGE_DEFAULT * 0.01; + powerLimitProfileTotal * POWER_LIMIT_FLUCTUATION_MARGIN_PERCENTAGE_DEFAULT * 0.01; } return round(Math.abs(powerLimitFluctuationMargin), 3); } private Double calculatePowerLimitVirtual( - EmsEnergyOptimisationAsset energyOptimisationAsset, - Double powerLimitProfileTotal, - String maxOrMin) { + EmsEnergyOptimisationAsset energyOptimisationAsset, + Double powerLimitProfileTotal, + String maxOrMin) { if (powerLimitProfileTotal == null) { return null; } double powerLimitFluctuationMargin = - calculatePowerFluctuationMargin(energyOptimisationAsset, powerLimitProfileTotal, maxOrMin); + calculatePowerFluctuationMargin(energyOptimisationAsset, powerLimitProfileTotal, maxOrMin); boolean isMin = "min".equals(maxOrMin); double powerLimitVirtual; @@ -2134,9 +2134,9 @@ private Double calculatePowerLimitVirtual( } private Map calculateTariffChargeAndDischargeZones( - List> tariffImportDatapoints, - List> tariffExportDatapoints, - int window) { + List> tariffImportDatapoints, + List> tariffExportDatapoints, + int window) { Map chargeAndDischargeZonesMap = new HashMap<>(); // Return empty map when there is no tariff forecast present @@ -2146,37 +2146,37 @@ private Map calculateTariffChargeAndDischargeZones( // Find the best import/export tariff window for each day List> tariffImportMovingAverage = - movingAverage(tariffImportDatapoints, window); + movingAverage(tariffImportDatapoints, window); List> tariffExportMovingAverage = - movingAverage(tariffExportDatapoints, window); + movingAverage(tariffExportDatapoints, window); ZoneId zoneId = ZoneId.systemDefault(); Map tariffImportDailyMinimumMap = - IntStream.range(0, tariffImportMovingAverage.size()) - .mapToObj( - i -> { - ValueDatapoint dp = tariffImportMovingAverage.get(i); - return new IndexedDatapoint(dp.getTimestamp(), (Double) dp.getValue(), i); - }) - .collect( - Collectors.toMap( - dp -> Instant.ofEpochMilli(dp.timestamp()).atZone(zoneId).toLocalDate(), - Function.identity(), - BinaryOperator.minBy(Comparator.comparing(IndexedDatapoint::value)))); + IntStream.range(0, tariffImportMovingAverage.size()) + .mapToObj( + i -> { + ValueDatapoint dp = tariffImportMovingAverage.get(i); + return new IndexedDatapoint(dp.getTimestamp(), (Double) dp.getValue(), i); + }) + .collect( + Collectors.toMap( + dp -> Instant.ofEpochMilli(dp.timestamp()).atZone(zoneId).toLocalDate(), + Function.identity(), + BinaryOperator.minBy(Comparator.comparing(IndexedDatapoint::value)))); Map tariffExportDailyMinimumMap = - IntStream.range(0, tariffExportMovingAverage.size()) - .mapToObj( - i -> { - ValueDatapoint dp = tariffExportMovingAverage.get(i); - return new IndexedDatapoint(dp.getTimestamp(), (Double) dp.getValue(), i); - }) - .collect( - Collectors.toMap( - dp -> Instant.ofEpochMilli(dp.timestamp()).atZone(zoneId).toLocalDate(), - Function.identity(), - BinaryOperator.minBy(Comparator.comparing(IndexedDatapoint::value)))); + IntStream.range(0, tariffExportMovingAverage.size()) + .mapToObj( + i -> { + ValueDatapoint dp = tariffExportMovingAverage.get(i); + return new IndexedDatapoint(dp.getTimestamp(), (Double) dp.getValue(), i); + }) + .collect( + Collectors.toMap( + dp -> Instant.ofEpochMilli(dp.timestamp()).atZone(zoneId).toLocalDate(), + Function.identity(), + BinaryOperator.minBy(Comparator.comparing(IndexedDatapoint::value)))); Map chargeZonesMap = new HashMap<>(); Map dischargeZonesMap = new HashMap<>(); @@ -2217,18 +2217,18 @@ private Map calculateTariffChargeAndDischargeZones( } private EmsDayAheadAsset getDayAheadAsset( - EmsEnergyOptimisationAsset energyOptimisationAsset, Services services) { + EmsEnergyOptimisationAsset energyOptimisationAsset, Services services) { // Find assets in database List assets = - services - .getAssetStorageService() - .findAll( - new AssetQuery() - .parents(energyOptimisationAsset.getId()) - .types(EmsDayAheadAsset.class)) - .stream() - .map(asset -> (EmsDayAheadAsset) asset) - .toList(); + services + .getAssetStorageService() + .findAll( + new AssetQuery() + .parents(energyOptimisationAsset.getId()) + .types(EmsDayAheadAsset.class)) + .stream() + .map(asset -> (EmsDayAheadAsset) asset) + .toList(); EmsDayAheadAsset asset = null; @@ -2236,26 +2236,26 @@ private EmsDayAheadAsset getDayAheadAsset( asset = assets.getFirst(); } else if (assets.size() > 1) { String logPrefixEnergyOptimisation = - String.format( - "assetType='%s', assetId='%s', assetName='%s'", - energyOptimisationAsset.getAssetType(), - energyOptimisationAsset.getId(), - energyOptimisationAsset.getAssetName()); + String.format( + "assetType='%s', assetId='%s', assetName='%s'", + energyOptimisationAsset.getAssetType(), + energyOptimisationAsset.getId(), + energyOptimisationAsset.getAssetName()); LOG.warning( - String.format( - "%s; Found %s '%s' assets; Only 1 '%s' asset is allowed; Remove additional '%s' assets", - logPrefixEnergyOptimisation, - assets.size(), - EmsGOPACSAsset.class.getSimpleName(), - EmsGOPACSAsset.class.getSimpleName(), - EmsGOPACSAsset.class.getSimpleName())); + String.format( + "%s; Found %s '%s' assets; Only 1 '%s' asset is allowed; Remove additional '%s' assets", + logPrefixEnergyOptimisation, + assets.size(), + EmsGOPACSAsset.class.getSimpleName(), + EmsGOPACSAsset.class.getSimpleName(), + EmsGOPACSAsset.class.getSimpleName())); } return asset; } private List> getTariffDatapoints( - EmsEnergyOptimisationAsset energyOptimisationAsset, String attributeName, Services services) { + EmsEnergyOptimisationAsset energyOptimisationAsset, String attributeName, Services services) { // Get the start of the day (00:00) in milliseconds long currentTimeMillis = services.getTimerService().getCurrentTimeMillis(); ZoneId zoneId = ZoneId.systemDefault(); @@ -2266,19 +2266,19 @@ private List> getTariffDatapoints( long startTimeMillis = startOfCurrentDayMillis - 24 * 60 * 60000; long endTimeMillis = startOfCurrentDayMillis + 8 * 24 * 60 * 60000; AssetDatapointAllQuery assetDatapointQueryHistoric = - new AssetDatapointAllQuery(startTimeMillis, currentTimeMillis); + new AssetDatapointAllQuery(startTimeMillis, currentTimeMillis); AssetDatapointAllQuery assetDatapointQueryPredicted = - new AssetDatapointAllQuery(currentTimeMillis, endTimeMillis); + new AssetDatapointAllQuery(currentTimeMillis, endTimeMillis); List> tariffHistoric = - services - .getAssetDatapointService() - .queryDatapoints( - energyOptimisationAsset.getId(), attributeName, assetDatapointQueryHistoric); + services + .getAssetDatapointService() + .queryDatapoints( + energyOptimisationAsset.getId(), attributeName, assetDatapointQueryHistoric); List> tariffPredicted = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - energyOptimisationAsset.getId(), attributeName, assetDatapointQueryPredicted); + services + .getAssetPredictedDatapointService() + .queryDatapoints( + energyOptimisationAsset.getId(), attributeName, assetDatapointQueryPredicted); // Combine historic and predicted data-points, timestamps are ordered from newest to oldest // (descending order) @@ -2289,7 +2289,7 @@ private List> getTariffDatapoints( } private List> getTariffDayAheadDatapoints( - Asset asset, String attributeName, Services services) { + Asset asset, String attributeName, Services services) { // Get the start of the day (00:00) in milliseconds long currentTimeMillis = services.getTimerService().getCurrentTimeMillis(); ZoneId zoneId = ZoneId.systemDefault(); @@ -2300,17 +2300,17 @@ private List> getTariffDayAheadDatapoints( long startTimeMillis = startOfCurrentDayMillis - 24 * 60 * 60000; long endTimeMillis = startOfCurrentDayMillis + 2 * 24 * 60 * 60000; AssetDatapointAllQuery assetDatapointQueryHistoric = - new AssetDatapointAllQuery(startTimeMillis, endTimeMillis); + new AssetDatapointAllQuery(startTimeMillis, endTimeMillis); List> tariffHistoric = - services - .getAssetDatapointService() - .queryDatapoints(asset.getId(), attributeName, assetDatapointQueryHistoric); + services + .getAssetDatapointService() + .queryDatapoints(asset.getId(), attributeName, assetDatapointQueryHistoric); return tariffHistoric; } public static List> intervalAverage( - List> dataPoints, long intervalMillis) { + List> dataPoints, long intervalMillis) { // Map Map> valuesPerIntervalMap = new HashMap<>(); @@ -2324,8 +2324,8 @@ public static List> intervalAverage( // Add value to corresponding interval valuesPerIntervalMap - .computeIfAbsent(intervalStartMillis, key -> new ArrayList<>()) - .add(value); + .computeIfAbsent(intervalStartMillis, key -> new ArrayList<>()) + .add(value); } } @@ -2350,10 +2350,10 @@ public static List> intervalAverage( } private List> intervalInterpolate( - List> dataPoints, - long startTimeMillis, - long endTimeMillis, - long intervalMillis) { + List> dataPoints, + long startTimeMillis, + long endTimeMillis, + long intervalMillis) { List> interpolatedList = new ArrayList<>(); if (dataPoints == null || dataPoints.size() < 2) { @@ -2363,11 +2363,11 @@ private List> intervalInterpolate( int idx1 = 0; for (long intervalTimeMillis = startTimeMillis; - intervalTimeMillis <= endTimeMillis; - intervalTimeMillis += intervalMillis) { + intervalTimeMillis <= endTimeMillis; + intervalTimeMillis += intervalMillis) { // Find data-point before and after interval while (idx1 < (dataPoints.size() - 1) - && dataPoints.get(idx1 + 1).getTimestamp() < intervalTimeMillis) { + && dataPoints.get(idx1 + 1).getTimestamp() < intervalTimeMillis) { idx1++; } @@ -2380,7 +2380,7 @@ private List> intervalInterpolate( double valueAfter = (double) dataPoints.get(idx1 + 1).getValue(); double factor = - (double) (intervalTimeMillis - timeBeforeMillis) / (timeAfterMillis - timeBeforeMillis); + (double) (intervalTimeMillis - timeBeforeMillis) / (timeAfterMillis - timeBeforeMillis); double interpolatedValue = valueBefore + factor * (valueAfter - valueBefore); interpolatedList.add(new ValueDatapoint<>(intervalTimeMillis, interpolatedValue)); @@ -2413,7 +2413,7 @@ public List> movingAverage(List> dataPoints, ValueDatapoint original = dataPoints.get(i); ValueDatapoint averaged = - new ValueDatapoint<>(original.getTimestamp(), round(avg, 7)); + new ValueDatapoint<>(original.getTimestamp(), round(avg, 7)); result.add(averaged); } } @@ -2427,22 +2427,22 @@ private double round(double value, int precision) { } private void updateDayAheadAsset( - EmsEnergyOptimisationAsset energyOptimisationAsset, - EmsDayAheadAsset dayAheadAsset, - Services services) { + EmsEnergyOptimisationAsset energyOptimisationAsset, + EmsDayAheadAsset dayAheadAsset, + Services services) { String dayAheadAssetId = dayAheadAsset.getId(); String logPrefixDayAhead = - String.format( - "assetType='%s', assetId='%s', assetName='%s'", - dayAheadAsset.getAssetType(), dayAheadAssetId, dayAheadAsset.getAssetName()); + String.format( + "assetType='%s', assetId='%s', assetName='%s'", + dayAheadAsset.getAssetType(), dayAheadAssetId, dayAheadAsset.getAssetName()); String collectTimeForecasts = dayAheadAsset.getCollectTimeForecasts().orElse(""); if (collectTimeForecasts.isBlank()) { LOG.warning( - String.format( - "%s, attributeName='%s'; Set time to collect day ahead forecasts", - logPrefixDayAhead, EmsDayAheadAsset.COLLECT_TIME_FORECASTS.getName())); + String.format( + "%s, attributeName='%s'; Set time to collect day ahead forecasts", + logPrefixDayAhead, EmsDayAheadAsset.COLLECT_TIME_FORECASTS.getName())); } // Parse the time string @@ -2453,14 +2453,14 @@ private void updateDayAheadAsset( collectTime = LocalTime.parse(collectTimeForecasts, DateTimeFormatter.ofPattern("HH:mm")); } catch (Exception e) { LOG.warning( - String.format( - "%s, attributeName='%s'; Error while parsing collect time; Exception: %s", - logPrefixDayAhead, EmsDayAheadAsset.COLLECT_TIME_FORECASTS.getName(), e)); + String.format( + "%s, attributeName='%s'; Error while parsing collect time; Exception: %s", + logPrefixDayAhead, EmsDayAheadAsset.COLLECT_TIME_FORECASTS.getName(), e)); } } Long lastUpdateForecastsTimestamp = - dayAheadAsset.getLastUpdateForecastsTimestamp().orElse(null); + dayAheadAsset.getLastUpdateForecastsTimestamp().orElse(null); if (collectTime == null || lastUpdateForecastsTimestamp == null) { return; @@ -2470,7 +2470,7 @@ private void updateDayAheadAsset( LocalDate currentDate = LocalDate.now(); LocalDateTime currentCollectDateTime = LocalDateTime.of(currentDate, collectTime); long collectTimeStartMillis = - currentCollectDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + currentCollectDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); long collectTimeEndMillis = collectTimeStartMillis + 15 * 60000; long currentTimeMillis = services.getTimerService().getCurrentTimeMillis(); @@ -2485,75 +2485,75 @@ private void updateDayAheadAsset( // Collect the day ahead tariff forecasts at the desired collect time if (currentTimeMillis >= collectTimeStartMillis - && currentTimeMillis < collectTimeEndMillis - && collectTimeStartMillis > lastUpdateForecastsTimestamp - && currentDate.isEqual(nextUpdateDate)) { + && currentTimeMillis < collectTimeEndMillis + && collectTimeStartMillis > lastUpdateForecastsTimestamp + && currentDate.isEqual(nextUpdateDate)) { // Create asset datapoint query LocalDateTime startOfNextDay = currentDate.plusDays(1).atStartOfDay(); long startTimeMillis = - startOfNextDay.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + startOfNextDay.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); long endTimeMillis = startTimeMillis + 24 * 60 * 60000 - 60000; AssetDatapointAllQuery assetDatapointQuery = - new AssetDatapointAllQuery(startTimeMillis, endTimeMillis); + new AssetDatapointAllQuery(startTimeMillis, endTimeMillis); // Get the tariffs number of data-points from day ahead asset int tariffExportDayAheadSize = - services - .getAssetDatapointService() - .queryDatapoints( - dayAheadAssetId, - EmsDayAheadAsset.TARIFF_EXPORT_DAY_AHEAD.getName(), - assetDatapointQuery) - .size(); + services + .getAssetDatapointService() + .queryDatapoints( + dayAheadAssetId, + EmsDayAheadAsset.TARIFF_EXPORT_DAY_AHEAD.getName(), + assetDatapointQuery) + .size(); int tariffImportDayAheadSize = - services - .getAssetDatapointService() - .queryDatapoints( - dayAheadAssetId, - EmsDayAheadAsset.TARIFF_IMPORT_DAY_AHEAD.getName(), - assetDatapointQuery) - .size(); + services + .getAssetDatapointService() + .queryDatapoints( + dayAheadAssetId, + EmsDayAheadAsset.TARIFF_IMPORT_DAY_AHEAD.getName(), + assetDatapointQuery) + .size(); // Only update the historic data-point table if there are no day ahead tariffs present in the // historic data-point table for current interval if (tariffExportDayAheadSize == 0) { List> tariffExport = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - energyOptimisationAsset.getId(), - EmsEnergyOptimisationAsset.TARIFF_EXPORT.getName(), - assetDatapointQuery); + services + .getAssetPredictedDatapointService() + .queryDatapoints( + energyOptimisationAsset.getId(), + EmsEnergyOptimisationAsset.TARIFF_EXPORT.getName(), + assetDatapointQuery); services - .getAssetDatapointService() - .upsertValues( - dayAheadAssetId, EmsDayAheadAsset.TARIFF_EXPORT_DAY_AHEAD.getName(), tariffExport); + .getAssetDatapointService() + .upsertValues( + dayAheadAssetId, EmsDayAheadAsset.TARIFF_EXPORT_DAY_AHEAD.getName(), tariffExport); } if (tariffImportDayAheadSize == 0) { List> tariffImport = - services - .getAssetPredictedDatapointService() - .queryDatapoints( - energyOptimisationAsset.getId(), - EmsEnergyOptimisationAsset.TARIFF_IMPORT.getName(), - assetDatapointQuery); + services + .getAssetPredictedDatapointService() + .queryDatapoints( + energyOptimisationAsset.getId(), + EmsEnergyOptimisationAsset.TARIFF_IMPORT.getName(), + assetDatapointQuery); services - .getAssetDatapointService() - .upsertValues( - dayAheadAssetId, EmsDayAheadAsset.TARIFF_IMPORT_DAY_AHEAD.getName(), tariffImport); + .getAssetDatapointService() + .upsertValues( + dayAheadAssetId, EmsDayAheadAsset.TARIFF_IMPORT_DAY_AHEAD.getName(), tariffImport); } // Update the 'last update forecasts' datetime field with current update datetime String lastUpdateForecastsNew = currentCollectDateTime.toString(); services - .getAssetProcessingService() - .sendAttributeEvent( - new AttributeEvent( - dayAheadAssetId, - EmsDayAheadAsset.LAST_UPDATE_FORECASTS.getName(), - lastUpdateForecastsNew, - collectTimeStartMillis)); + .getAssetProcessingService() + .sendAttributeEvent( + new AttributeEvent( + dayAheadAssetId, + EmsDayAheadAsset.LAST_UPDATE_FORECASTS.getName(), + lastUpdateForecastsNew, + collectTimeStartMillis)); } }