diff --git a/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java b/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java index 2a2b9bd756..64d093c851 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java +++ b/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java @@ -93,4 +93,59 @@ public DummySum withEssMaxDischargePower(int value) { return this.self(); } + /** + * Set {@link Sum.ChannelId#GRID_BUY_ACTIVE_ENERGY}. + * + * @param value the value + * @return myself + */ + public DummySum withGridBuyActiveEnergy(long value) { + withValue(this, Sum.ChannelId.GRID_BUY_ACTIVE_ENERGY, value); + return this.self(); + } + + /** + * Set {@link Sum.ChannelId#GRID_SELL_ACTIVE_ENERGY}. + * + * @param value the value + * @return myself + */ + public DummySum withGridSellActiveEnergy(long value) { + withValue(this, Sum.ChannelId.GRID_SELL_ACTIVE_ENERGY, value); + return this.self(); + } + + /** + * Set {@link Sum.ChannelId#ESS_ACTIVE_CHARGE_ENERGY}. + * + * @param value the value + * @return myself + */ + public DummySum withEssActiveChargeEnergy(long value) { + withValue(this, Sum.ChannelId.ESS_ACTIVE_CHARGE_ENERGY, value); + return this.self(); + } + + /** + * Set {@link Sum.ChannelId#ESS_ACTIVE_DISCHARGE_ENERGY}. + * + * @param value the value + * @return myself + */ + public DummySum withEssActiveDischargeEnergy(long value) { + withValue(this, Sum.ChannelId.ESS_ACTIVE_DISCHARGE_ENERGY, value); + return this.self(); + } + + /** + * Set {@link Sum.ChannelId#CONSUMPTION_ACTIVE_ENERGY}. + * + * @param value the value + * @return myself + */ + public DummySum withConsumptionActiveEnergy(long value) { + withValue(this, Sum.ChannelId.CONSUMPTION_ACTIVE_ENERGY, value); + return this.self(); + } + } diff --git a/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java b/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java index b065cb50c1..16f568b501 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java +++ b/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java @@ -629,6 +629,168 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { CONSUMPTION_ACTIVE_ENERGY(Doc.of(OpenemsType.LONG) // .unit(Unit.CUMULATED_WATT_HOURS) // .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** + * Production to Consumption: Power. + * + * + */ + PRODUCTION_TO_CONSUMPTION_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** + * Production to Consumption: Energy. + * + * + */ + PRODUCTION_TO_CONSUMPTION_ENERGY(Doc.of(OpenemsType.LONG) // + .unit(Unit.CUMULATED_WATT_HOURS) // + .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** + * Production to Grid: Power. + * + * + */ + PRODUCTION_TO_GRID_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** + * Production to Grid: Energy. + * + * + */ + PRODUCTION_TO_GRID_ENERGY(Doc.of(OpenemsType.LONG) // + .unit(Unit.CUMULATED_WATT_HOURS) // + .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** + * Production to ESS: Power. + * + * + */ + PRODUCTION_TO_ESS_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** + * Production to ESS: Energy. + * + * + */ + PRODUCTION_TO_ESS_ENERGY(Doc.of(OpenemsType.LONG) // + .unit(Unit.CUMULATED_WATT_HOURS) // + .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** + * Grid to Consumption: Power. + * + * + */ + GRID_TO_CONSUMPTION_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** + * Grid to Consumption: Energy. + * + * + */ + GRID_TO_CONSUMPTION_ENERGY(Doc.of(OpenemsType.LONG) // + .unit(Unit.CUMULATED_WATT_HOURS) // + .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** + * ESS to Consumption: Power. + * + * + */ + ESS_TO_CONSUMPTION_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** + * ESS to Consumption: Energy. + * + * + */ + ESS_TO_CONSUMPTION_ENERGY(Doc.of(OpenemsType.LONG) // + .unit(Unit.CUMULATED_WATT_HOURS) // + .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** + * Grid to ESS: Power. + * + * + */ + GRID_TO_ESS_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** + * Grid to ESS: Energy. + * + * + */ + GRID_TO_ESS_ENERGY(Doc.of(OpenemsType.LONG) // + .unit(Unit.CUMULATED_WATT_HOURS) // + .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** + * ESS to Grid: Energy. + * + * + */ + ESS_TO_GRID_ENERGY(Doc.of(OpenemsType.LONG) // + .unit(Unit.CUMULATED_WATT_HOURS) // + .persistencePriority(PersistencePriority.VERY_HIGH)), // /** * Is there any Component Info/Warning/Fault that is getting ignored/hidden diff --git a/io.openems.edge.core/src/io/openems/edge/core/predictormanager/PredictorManagerImpl.java b/io.openems.edge.core/src/io/openems/edge/core/predictormanager/PredictorManagerImpl.java index a674023dd0..f63319c01f 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/predictormanager/PredictorManagerImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/predictormanager/PredictorManagerImpl.java @@ -119,8 +119,8 @@ public Prediction getPrediction(ChannelAddress channelAddress) { private Prediction getPredictionSum(Sum.ChannelId channelId) { return switch (channelId) { case CONSUMPTION_ACTIVE_ENERGY, // - CONSUMPTION_ACTIVE_POWER_L1, CONSUMPTION_ACTIVE_POWER_L2, CONSUMPTION_ACTIVE_POWER_L3, - CONSUMPTION_MAX_ACTIVE_POWER, // + CONSUMPTION_ACTIVE_POWER, CONSUMPTION_ACTIVE_POWER_L1, CONSUMPTION_ACTIVE_POWER_L2, + CONSUMPTION_ACTIVE_POWER_L3, CONSUMPTION_MAX_ACTIVE_POWER, // ESS_ACTIVE_CHARGE_ENERGY, ESS_ACTIVE_DISCHARGE_ENERGY, ESS_ACTIVE_POWER, ESS_ACTIVE_POWER_L1, ESS_ACTIVE_POWER_L2, ESS_ACTIVE_POWER_L3, ESS_CAPACITY, ESS_DC_CHARGE_ENERGY, ESS_DC_DISCHARGE_ENERGY, @@ -135,6 +135,11 @@ private Prediction getPredictionSum(Sum.ChannelId channelId) { PRODUCTION_AC_ACTIVE_POWER_L2, PRODUCTION_AC_ACTIVE_POWER_L3, PRODUCTION_DC_ACTIVE_ENERGY, PRODUCTION_MAX_ACTIVE_POWER, // + ESS_TO_CONSUMPTION_POWER, ESS_TO_CONSUMPTION_ENERGY, GRID_TO_CONSUMPTION_POWER, + GRID_TO_CONSUMPTION_ENERGY, GRID_TO_ESS_POWER, GRID_TO_ESS_ENERGY, ESS_TO_GRID_ENERGY, + PRODUCTION_TO_CONSUMPTION_POWER, PRODUCTION_TO_CONSUMPTION_ENERGY, PRODUCTION_TO_ESS_POWER, + PRODUCTION_TO_ESS_ENERGY, PRODUCTION_TO_GRID_POWER, PRODUCTION_TO_GRID_ENERGY, + HAS_IGNORED_COMPONENT_STATES -> EMPTY_PREDICTION; @@ -143,9 +148,6 @@ private Prediction getPredictionSum(Sum.ChannelId channelId) { // ConsumptionActivePower by default this.getPrediction(new ChannelAddress("_sum", "ConsumptionActivePower")); - // TODO - case CONSUMPTION_ACTIVE_POWER -> EMPTY_PREDICTION; - case PRODUCTION_DC_ACTUAL_POWER -> { // Sum up "ActualPower" prediction of all EssDcChargers List chargers = this.componentManager.getEnabledComponentsOfType(EssDcCharger.class); diff --git a/io.openems.edge.core/src/io/openems/edge/core/sum/PowerDistribution.java b/io.openems.edge.core/src/io/openems/edge/core/sum/PowerDistribution.java new file mode 100644 index 0000000000..54eb8407b1 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/sum/PowerDistribution.java @@ -0,0 +1,101 @@ +package io.openems.edge.core.sum; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +import io.openems.edge.common.sum.Sum; + +/** + * Calculates power distribution. + */ +public record PowerDistribution(// + Integer productionToConsumption, /* positive */ + Integer productionToGrid, /* positive */ + Integer productionToEss, /* positive */ + Integer gridToConsumption, /* positive */ + Integer essToConsumption, /* positive */ + Integer gridToEss /* discharge-to-grid negative, charge-from-grid positive */ +) { + + public static final PowerDistribution EMPTY = new PowerDistribution(null, null, null, null, null, null); + + /** + * Creates a {@link PowerDistribution}. + * + * @param grid the gridActivePower; possibly null + * @param production the productionActivePower; possibly null + * @param ess the essActivePower; possibly null + * @return a {@link PowerDistribution}; possibly {@link PowerDistribution#EMPTY} + */ + public static PowerDistribution of(Integer grid, Integer production, Integer ess) { + final PowerDistribution result; + if (grid != null && production != null && ess != null) { + result = of(grid.intValue(), production.intValue(), ess.intValue()); + + } else if (grid != null) { + if (production == null && ess == null) { + result = of(grid.intValue()); + } else { + result = of(grid.intValue(), production != null ? production : 0, ess != null ? ess : 0); + } + + } else { + result = EMPTY; + } + return result; + } + + private static PowerDistribution of(int grid, int production, int ess) { + if (production < 0) { // invalid data + return EMPTY; + } + var consumption = ess + grid + production; + var essToConsumption = ess > 0 // + ? /* discharge */ min(ess, consumption) // + : /* charge */ 0; + var productionToEss = ess > 0 // + ? /* discharge */ 0 // + : /* charge */ min(-ess, production); + var productionToConsumption = min(production - productionToEss, consumption - essToConsumption); + var productionToGrid = max(0, production - productionToConsumption - productionToEss); + var gridToConsumption = max(0, consumption - essToConsumption - productionToConsumption); + var gridToEss = grid - gridToConsumption + productionToGrid; + return new PowerDistribution(productionToConsumption, productionToGrid, productionToEss, gridToConsumption, + essToConsumption, gridToEss); + } + + private static PowerDistribution of(int grid) { + if (grid < 0) { // invalid data + return EMPTY; + } + // Grid Buy to Consumption + return new PowerDistribution(null, null, null, grid, null, null); + } + + protected void updateChannels(SumImpl sum) { + // Power + sum.channel(Sum.ChannelId.PRODUCTION_TO_CONSUMPTION_POWER).setNextValue(this.productionToConsumption); + sum.channel(Sum.ChannelId.PRODUCTION_TO_GRID_POWER).setNextValue(this.productionToGrid); + sum.channel(Sum.ChannelId.PRODUCTION_TO_ESS_POWER).setNextValue(this.productionToEss); + sum.channel(Sum.ChannelId.GRID_TO_CONSUMPTION_POWER).setNextValue(this.gridToConsumption); + sum.channel(Sum.ChannelId.ESS_TO_CONSUMPTION_POWER).setNextValue(this.essToConsumption); + sum.channel(Sum.ChannelId.GRID_TO_ESS_POWER).setNextValue(this.gridToEss); + + // Energy + sum.calculateProductionToConsumptionEnergy.update(this.productionToConsumption); + sum.calculateProductionToGridEnergy.update(this.productionToGrid); + sum.calculateProductionToEssEnergy.update(this.productionToEss); + sum.calculateGridToConsumptionEnergy.update(this.gridToConsumption); + sum.calculateEssToConsumptionEnergy.update(this.essToConsumption); + if (this.gridToEss == null) { + sum.calculateGridToEssEnergy.update(null); + sum.calculateEssToGridEnergy.update(null); + } else if (this.gridToEss > 0) { + sum.calculateGridToEssEnergy.update(this.gridToEss); + sum.calculateEssToGridEnergy.update(0); + } else { + sum.calculateGridToEssEnergy.update(0); + sum.calculateEssToGridEnergy.update(-this.gridToEss); + } + } +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java b/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java index 4910b1f965..385a1d3a1d 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java @@ -45,6 +45,7 @@ import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateActiveTime; +import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; @Designate(ocd = Config.class, factory = false) @@ -65,6 +66,21 @@ public class SumImpl extends AbstractOpenemsComponent implements Sum, OpenemsCom @Reference private ComponentManager componentManager; + protected final CalculateEnergyFromPower calculateProductionToConsumptionEnergy = new CalculateEnergyFromPower(this, + Sum.ChannelId.PRODUCTION_TO_CONSUMPTION_ENERGY); + protected final CalculateEnergyFromPower calculateProductionToGridEnergy = new CalculateEnergyFromPower(this, + Sum.ChannelId.PRODUCTION_TO_GRID_ENERGY); + protected final CalculateEnergyFromPower calculateProductionToEssEnergy = new CalculateEnergyFromPower(this, + Sum.ChannelId.PRODUCTION_TO_ESS_ENERGY); + protected final CalculateEnergyFromPower calculateGridToConsumptionEnergy = new CalculateEnergyFromPower(this, + Sum.ChannelId.GRID_TO_CONSUMPTION_ENERGY); + protected final CalculateEnergyFromPower calculateEssToConsumptionEnergy = new CalculateEnergyFromPower(this, + Sum.ChannelId.ESS_TO_CONSUMPTION_ENERGY); + protected final CalculateEnergyFromPower calculateGridToEssEnergy = new CalculateEnergyFromPower(this, + Sum.ChannelId.GRID_TO_ESS_ENERGY); + protected final CalculateEnergyFromPower calculateEssToGridEnergy = new CalculateEnergyFromPower(this, + Sum.ChannelId.ESS_TO_GRID_ENERGY); + private final EnergyValuesHandler energyValuesHandler; private final Set ignoreStateComponents = new HashSet<>(); @@ -386,7 +402,8 @@ private void calculateChannelValues() { this._setProductionAcActivePowerL3(productionAcActivePowerL3Sum); var productionDcActualPowerSum = productionDcActualPower.calculate(); this._setProductionDcActualPower(productionDcActualPowerSum); - this._setProductionActivePower(TypeUtils.sum(productionAcActivePowerSum, productionDcActualPowerSum)); + var productionActivePower = TypeUtils.sum(productionAcActivePowerSum, productionDcActualPowerSum); + this._setProductionActivePower(productionActivePower); var productionAcActiveEnergySum = productionAcActiveEnergy.calculate(); productionAcActiveEnergySum = this.energyValuesHandler.setValue(Sum.ChannelId.PRODUCTION_AC_ACTIVE_ENERGY, @@ -423,6 +440,10 @@ private void calculateChannelValues() { this.getEssDischargePowerChannel().setNextValue(essDischargePowerSum); this.updateExtremeEverValues(); + + // Power & Energy distribution + PowerDistribution.of(gridActivePowerSum, productionActivePower, essActivePowerSum) // + .updateChannels(this); } /** diff --git a/io.openems.edge.core/test/io/openems/edge/core/sum/PowerDistributionTest.java b/io.openems.edge.core/test/io/openems/edge/core/sum/PowerDistributionTest.java new file mode 100644 index 0000000000..65c8bc9a26 --- /dev/null +++ b/io.openems.edge.core/test/io/openems/edge/core/sum/PowerDistributionTest.java @@ -0,0 +1,47 @@ +package io.openems.edge.core.sum; + +import static io.openems.edge.core.sum.PowerDistribution.EMPTY; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class PowerDistributionTest { + + @Test + public void test() { + assertEquals(EMPTY, PowerDistribution.of(null, null, null)); + assertEquals(EMPTY, PowerDistribution.of(-1, null, null)); + assertEquals(EMPTY, PowerDistribution.of(0, -1, 0)); + { + var sut = PowerDistribution.of(1000, null, null); + assertEquals(1000, sut.gridToConsumption().intValue()); + } + { + var sut = PowerDistribution.of(-1000, 2000, 500); + assertEquals(1000, sut.productionToConsumption().intValue()); + assertEquals(1000, sut.productionToGrid().intValue()); + assertEquals(0, sut.productionToEss().intValue()); + assertEquals(0, sut.gridToConsumption().intValue()); + assertEquals(500, sut.essToConsumption().intValue()); + assertEquals(0, sut.gridToEss().intValue()); + } + { + var sut = PowerDistribution.of(1000, 2000, 500); + assertEquals(2000, sut.productionToConsumption().intValue()); + assertEquals(0, sut.productionToGrid().intValue()); + assertEquals(0, sut.productionToEss().intValue()); + assertEquals(1000, sut.gridToConsumption().intValue()); + assertEquals(500, sut.essToConsumption().intValue()); + assertEquals(0, sut.gridToEss().intValue()); + } + { + var sut = PowerDistribution.of(1000, 2000, -500); + assertEquals(1500, sut.productionToConsumption().intValue()); + assertEquals(0, sut.productionToGrid().intValue()); + assertEquals(500, sut.productionToEss().intValue()); + assertEquals(1000, sut.gridToConsumption().intValue()); + assertEquals(0, sut.essToConsumption().intValue()); + assertEquals(0, sut.gridToEss().intValue()); + } + } +} diff --git a/io.openems.edge.core/test/io/openems/edge/core/sum/SumImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/sum/SumImplTest.java index 2ef7670b28..3174341534 100644 --- a/io.openems.edge.core/test/io/openems/edge/core/sum/SumImplTest.java +++ b/io.openems.edge.core/test/io/openems/edge/core/sum/SumImplTest.java @@ -5,11 +5,17 @@ import static io.openems.edge.common.sum.Sum.ChannelId.CONSUMPTION_MAX_ACTIVE_POWER; import static io.openems.edge.common.sum.Sum.ChannelId.ESS_ACTIVE_POWER; import static io.openems.edge.common.sum.Sum.ChannelId.ESS_DISCHARGE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_TO_CONSUMPTION_POWER; import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; import static io.openems.edge.common.sum.Sum.ChannelId.GRID_MAX_ACTIVE_POWER; import static io.openems.edge.common.sum.Sum.ChannelId.GRID_MIN_ACTIVE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_TO_CONSUMPTION_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_TO_ESS_POWER; import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_ACTIVE_POWER; import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_MAX_ACTIVE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_TO_CONSUMPTION_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_TO_ESS_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_TO_GRID_POWER; import static io.openems.edge.common.sum.Sum.ChannelId.UNMANAGED_CONSUMPTION_ACTIVE_POWER; import org.junit.Test; @@ -93,6 +99,14 @@ public void test() throws OpenemsException, Exception { .output(GRID_MIN_ACTIVE_POWER, -2000) // .output(GRID_MAX_ACTIVE_POWER, 3000) // .output(PRODUCTION_MAX_ACTIVE_POWER, 6666) // - .output(CONSUMPTION_MAX_ACTIVE_POWER, 9666)); + .output(CONSUMPTION_MAX_ACTIVE_POWER, 9666) // + + .output(PRODUCTION_TO_CONSUMPTION_POWER, 6666) // + .output(PRODUCTION_TO_GRID_POWER, 0) // + .output(PRODUCTION_TO_ESS_POWER, 0) // + .output(GRID_TO_CONSUMPTION_POWER, 3000) // + .output(ESS_TO_CONSUMPTION_POWER, 0) // + .output(GRID_TO_ESS_POWER, 0) // + ); } }