Skip to content

Commit

Permalink
FEMS Backports (#2509)
Browse files Browse the repository at this point in the history
- [KACO Battery Inverter: quicker control behaviour](851c94d)
- [Max-Ever-Value: update config only once every 24 hours](7fa22cc)
- [EmergencyController & TimeslotPeakshaving: Set PersistencePriority to…](2cc72f4)
- [ToU: calculate essMaxChargePower from usable Capacity](35f10c1)
- [Time-of-Use: reset efficiency factor to 1.2](6081bdb)
- [Time-of-Use: set state machine BALANCING in OFF mode](da53816)
- [Zone selection config for Time-of-Use-Tariff Awattar](ca1f4a7) - closes #2469 
- [Implement Camille Bauer APLUS meter](68cca7c)
- [UI: add TOTAL to history periods](c3372cc)
- [UI: Refactor Time-of-Use-Tariff widgets](796fc08)
- [UI: Adjust Ion-Fab button in modal](6966cb4)
- [UI: Fix Evcs status not being updated](cf9b492)
- [UI: Adjust UserInformation to show companyname](539b0b3)
  • Loading branch information
sfeilmeier authored Feb 1, 2024
1 parent 2ebeea6 commit d2ebc67
Show file tree
Hide file tree
Showing 112 changed files with 1,727 additions and 848 deletions.
2 changes: 2 additions & 0 deletions io.openems.edge.application/EdgeApp.bndrun
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
bnd.identity;id='io.openems.edge.meter.artemes.am2',\
bnd.identity;id='io.openems.edge.meter.bcontrol.em300',\
bnd.identity;id='io.openems.edge.meter.bgetech',\
bnd.identity;id='io.openems.edge.meter.camillebauer.aplus',\
bnd.identity;id='io.openems.edge.meter.carlo.gavazzi.em300',\
bnd.identity;id='io.openems.edge.meter.discovergy',\
bnd.identity;id='io.openems.edge.meter.janitza',\
Expand Down Expand Up @@ -310,6 +311,7 @@
io.openems.edge.meter.artemes.am2;version=snapshot,\
io.openems.edge.meter.bcontrol.em300;version=snapshot,\
io.openems.edge.meter.bgetech;version=snapshot,\
io.openems.edge.meter.camillebauer.aplus;version=snapshot,\
io.openems.edge.meter.carlo.gavazzi.em300;version=snapshot,\
io.openems.edge.meter.discovergy;version=snapshot,\
io.openems.edge.meter.janitza;version=snapshot,\
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.openems.edge.batteryinverter.kaco.blueplanetgridsave;

import static io.openems.edge.common.channel.ChannelUtils.setWriteValueIfNotRead;

import java.time.Duration;
import java.time.Instant;
import java.util.Map;
Expand Down Expand Up @@ -175,6 +177,17 @@ public void run(Battery battery, int setActivePower, int setReactivePower) throw
return;
}

/*
* The WparamRmpTms parameter constrains performance changes using a PT1
* behavior. By default, a 1 second (1000 ms) duration is stored here. This
* duration can be reduced to 0.1 second (100 ms) for quicker control behavior.
* While a complete reduction to 0 is technically possible, it may result in
* overcurrent or overvoltage events, especially in situations involving high
* power changes and multiple devices. This feature is beneficial for FFR use
* cases and aids in preventing battery derating.
*/
setWriteValueIfNotRead(this.getSunSpecChannelOrError(KacoSunSpecModel.S64201.WPARAM_RMP_TMS), 100);

// Set Display Information
this.setDisplayInformation(battery);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ public static enum S64201 implements SunSpecPoint {
PointType.UINT16, //
true, //
AccessMode.READ_WRITE, //
Unit.SECONDS, //
"RMP_TMS_SF", //
Unit.MILLISECONDS, //
null, //
new OptionsEnum[0])), //
WPARAM_RMP_DEC_TMM(new PointImpl(//
"S64201_WPARAM_RMP_DEC_TMM", //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public void prepareTest() throws Exception {
addChannel.invoke(sut, KacoSunSpecModel.S64201.CURRENT_STATE.getChannelId());
addChannel.invoke(sut, KacoSunSpecModel.S64201.WATCHDOG.getChannelId());
addChannel.invoke(sut, KacoSunSpecModel.S64201.W_SET_PCT.getChannelId());
addChannel.invoke(sut, KacoSunSpecModel.S64201.WPARAM_RMP_TMS.getChannelId());

test.activate(MyConfig.create() //
.setId(BATTERY_INVERTER_ID) //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ public DummyComponentContext() {
*
* @param key the property key
* @param value the property value
* @return myself
*/
public void addProperty(String key, Object value) {
public DummyComponentContext addProperty(String key, Object value) {
this.properties.put(key, value);
return this;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ public Dictionary<String, Object> getProperties() {
*
* @param key the property key
* @param value the property value
* @return myself
*/
public void addProperty(String key, Object value) {
public DummyConfiguration addProperty(String key, Object value) {
this.properties.put(key, value);
return this;
}

@Override
Expand Down Expand Up @@ -186,9 +188,11 @@ public void addConfig(AbstractComponentConfig config)
*
* @param key the PID
* @param configuration the {@link DummyConfiguration}.
* @return myself
*/
public void addConfiguration(String key, DummyConfiguration configuration) {
public DummyConfigurationAdmin addConfiguration(String key, DummyConfiguration configuration) {
this.configurations.put(key, configuration);
return this;
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.openems.edge.controller.ess.emergencycapacityreserve;

import io.openems.common.channel.Level;
import io.openems.common.channel.PersistencePriority;
import io.openems.common.channel.Unit;
import io.openems.common.types.OpenemsType;
import io.openems.edge.common.channel.Channel;
Expand Down Expand Up @@ -36,13 +37,15 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
* Holds target power to reach.
*/
DEBUG_TARGET_POWER(Doc.of(OpenemsType.FLOAT) //
.unit(Unit.WATT).text("The debug target power to reach")), //
.unit(Unit.WATT) //
.text("The debug target power to reach")), //

/**
* Holds power to increase/decrease ramp for every cycle.
*/
DEBUG_RAMP_POWER(Doc.of(OpenemsType.FLOAT) //
.unit(Unit.WATT).text("The debug ramp power to decrease power")), //
.unit(Unit.WATT) //
.text("The debug ramp power to decrease power")), //

/**
* Configured reserve SoC is out of range [5,100].
Expand All @@ -55,7 +58,8 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
*/
ACTUAL_RESERVE_SOC(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.PERCENT) //
.text("The reserve soc value")); //
.text("The reserve soc value") //
.persistencePriority(PersistencePriority.HIGH)); //

private final Doc doc;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,8 @@
High Risk: High dependence on predictions; Battery is scheduled to charge/discharge completely based on predictions.""")
RiskLevel riskLevel() default RiskLevel.MEDIUM;

// TODO consider making this Capacity-dependent (i.e. C-rate)
@AttributeDefinition(name = "Max Charge Power in CHARGE State [W]", description = "ESS Max Charge Power in CHARGE State")
int essMaxChargePower() default 5_000;

// TODO This will eventually be moved globally/to a 'PowerOptimizer" Controller; should be per Phase (fuse)
// TODO This will eventually be moved globally/to a 'PowerOptimizer" Controller;
// should be per Phase (fuse)
@AttributeDefinition(name = "Max Charge Power from the grid [W]", description = "Maximum charge power from the grid")
int maxChargePowerFromGrid() default 24_000;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE;
import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils.calculateCharge;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils.essMaxChargePower;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils.getEssMinSoc;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils.postprocessRunState;

import java.time.ZonedDateTime;
import java.util.List;
Expand Down Expand Up @@ -44,6 +46,8 @@
import io.openems.edge.controller.ess.timeofusetariff.optimizer.Period;
import io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils;
import io.openems.edge.ess.api.ManagedSymmetricEss;
import io.openems.edge.ess.power.api.Phase;
import io.openems.edge.ess.power.api.Pwr;
import io.openems.edge.predictor.api.manager.PredictorManager;
import io.openems.edge.timedata.api.Timedata;
import io.openems.edge.timedata.api.TimedataProvider;
Expand Down Expand Up @@ -119,7 +123,6 @@ public TimeOfUseTariffControllerImpl() {
.ctrlEmergencyCapacityReserves(this.ctrlEmergencyCapacityReserves) //
.ctrlLimitTotalDischarges(this.ctrlLimitTotalDischarges) //
.controlMode(this.config.controlMode()) //
.essMaxChargePower(this.config.essMaxChargePower()) //
.maxChargePowerFromGrid(this.config.maxChargePowerFromGrid()) //
.build());
}
Expand Down Expand Up @@ -193,7 +196,7 @@ private StateMachine getCurrentPeriodState() {
*/
private void modeAutomatic() throws OpenemsNamedException {
// Evaluate current state
final var state = Utils.postprocessRunState(
final var state = postprocessRunState(
getEssMinSoc(this.ctrlLimitTotalDischarges, this.ctrlEmergencyCapacityReserves), //
this.ess.getSoc().get(), //
this.getCurrentPeriodState());
Expand All @@ -205,14 +208,15 @@ private void modeAutomatic() throws OpenemsNamedException {

// Get and apply ActivePower Less-or-Equals Set-Point
var activePower = switch (state) {
case CHARGE ->
calculateCharge(this.ess, this.sum, this.config.essMaxChargePower(), this.config.maxChargePowerFromGrid());
case CHARGE -> calculateCharge(this.ess, this.sum, essMaxChargePower(this.optimizer.getParams(), this.ess),
this.config.maxChargePowerFromGrid());
case DELAY_DISCHARGE -> 0;
case BALANCING -> null;
};

if (activePower != null) {
this.ess.setActivePowerLessOrEquals(activePower);
this.ess.setActivePowerLessOrEquals(this.ess.getPower().fitValueIntoMinMaxPower(this.id(), this.ess,
Phase.ALL, Pwr.ACTIVE, activePower));
}
}

Expand All @@ -226,7 +230,7 @@ private void modeOff() {
this.calculateDelayedTime.update(false);

// Default State Machine.
this._setStateMachine(null);
this._setStateMachine(BALANCING);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public record Context(//
List<ControllerEssEmergencyCapacityReserve> ctrlEmergencyCapacityReserves, //
List<ControllerEssLimitTotalDischarge> ctrlLimitTotalDischarges, //
ControlMode controlMode, //
int essMaxChargePower, int maxChargePowerFromGrid) {
int maxChargePowerFromGrid) {

public static class Builder {
private Clock clock;
Expand All @@ -28,7 +28,6 @@ public static class Builder {
private List<ControllerEssEmergencyCapacityReserve> ctrlEmergencyCapacityReserves;
private List<ControllerEssLimitTotalDischarge> ctrlLimitTotalDischarges;
private ControlMode controlMode;
private int essMaxChargePower;
private int maxChargePowerFromGrid;

/**
Expand Down Expand Up @@ -111,17 +110,6 @@ public Builder controlMode(ControlMode controlMode) {
return this;
}

/**
* The essMaxChargePower.
*
* @param essMaxChargePower the essMaxChargePower
* @return myself
*/
public Builder essMaxChargePower(int essMaxChargePower) {
this.essMaxChargePower = essMaxChargePower;
return this;
}

/**
* The maxChargePowerFromGrid.
*
Expand All @@ -141,7 +129,7 @@ public Builder maxChargePowerFromGrid(int maxChargePowerFromGrid) {
public Context build() {
return new Context(this.clock, this.predictorManager, this.timeOfUseTariff, this.ess,
this.ctrlEmergencyCapacityReserves, this.ctrlLimitTotalDischarges, this.controlMode,
this.essMaxChargePower, this.maxChargePowerFromGrid);
this.maxChargePowerFromGrid);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.openems.edge.controller.ess.timeofusetariff.optimizer;

import static java.lang.Math.max;
import static java.lang.Math.min;

import java.time.ZonedDateTime;
Expand Down Expand Up @@ -48,7 +49,6 @@ public static class Builder {
private int essMaxSocEnergy;
private int essInitialEnergy;
private int essMaxEnergyPerPeriod;
private int essMaxChargePerPeriod;
private int maxBuyFromGrid;
private int[] productions = new int[0];
private int[] consumptions = new int[0];
Expand Down Expand Up @@ -86,11 +86,6 @@ protected Builder essMaxEnergyPerPeriod(int essMaxEnergyPerPeriod) {
return this;
}

protected Builder essMaxChargePerPeriod(int essMaxChargePerPeriod) {
this.essMaxChargePerPeriod = essMaxChargePerPeriod;
return this;
}

protected Builder maxBuyFromGrid(int maxBuyFromGrid) {
this.maxBuyFromGrid = maxBuyFromGrid;
return this;
Expand Down Expand Up @@ -133,11 +128,13 @@ public Params build() {
var numberOfPeriods = min(this.productions.length, min(this.consumptions.length, this.prices.length));
var maxPrice = IntStream.range(0, this.prices.length).mapToObj(i -> this.prices[i]).max(Float::compare)
.orElse(null);
// Set ESS Max-Charge in CHARGE mode to 1/4 of usable energy (i.e. C-Rate 0.25)
var essMaxChargePerPeriod = max(0, (this.essMaxSocEnergy - this.essMinSocEnergy) / 4);
return new Params(numberOfPeriods, //
this.time, //
this.essTotalEnergy, this.essMinSocEnergy, this.essMaxSocEnergy, this.essInitialEnergy,
this.essMaxEnergyPerPeriod, //
this.essMaxChargePerPeriod, this.maxBuyFromGrid, //
essMaxChargePerPeriod, this.maxBuyFromGrid, //
this.productions, this.consumptions, //
this.prices, maxPrice, //
this.states, //
Expand All @@ -164,7 +161,6 @@ protected String toString(boolean full) {
.append(", essMaxSocEnergy=").append(this.essMaxSocEnergy) //
.append(", essInitialEnergy=").append(this.essInitialEnergy) //
.append(", essMaxEnergyPerPeriod=").append(this.essMaxEnergyPerPeriod) //
.append(", essMaxChargePerPeriod=").append(this.essMaxChargePerPeriod) //
.append(", maxBuyFromGrid=").append(this.maxBuyFromGrid) //
.append(", states=").append(Arrays.toString(this.states)) //
.append(", maxPrice=").append(this.maxPrice);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
public class Simulator {

/** Used to incorporate charge/discharge efficiency. */
public static final double EFFICIENCY_FACTOR = 1.15;
public static final double EFFICIENCY_FACTOR = 1.2;

protected static double calculateCost(Params p, StateMachine[] states) {
return calculateCost(p, states, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ public static Params createSimulatorParams(Context context, TreeMap<ZonedDateTim
.essMaxSocEnergy(essMaxSocEnergy) //
.essInitialEnergy(essSocEnergy) //
.essMaxEnergyPerPeriod(toEnergy(max(dischargePower, abs(chargePower)))) //
.essMaxChargePerPeriod(toEnergy(context.essMaxChargePower())) //
.maxBuyFromGrid(toEnergy(context.maxChargePowerFromGrid())) //
.productions(stream(interpolateArray(predictionProduction)).map(v -> toEnergy(v)).toArray()) //
.consumptions(stream(interpolateArray(predictionConsumption)).map(v -> toEnergy(v)).toArray()) //
Expand Down Expand Up @@ -351,7 +350,7 @@ protected static int calculateStateChargeEnergy(int essMaxChargePerPeriod, int m
*
* @param ess the {@link ManagedSymmetricEss}
* @param sum the {@link Sum}
* @param essMaxChargePower ESS Max Charge Power in CHARGE State
* @param essMaxChargePower ESS Max Charge Power in CHARGE State (positive)
* @param maxChargePowerFromGrid the configured max charge from grid power
* @return the set-point or null
*/
Expand Down Expand Up @@ -679,4 +678,26 @@ protected static void logSchedule(Params params, TreeMap<ZonedDateTime, Period>
}
System.out.println(b.toString());
}

/**
* Calculates the ESS Max-Charge power during automatic mode.
*
* @param params the {@link Params}, or null
* @param ess the {@link ManagedSymmetricEss}
* @return the power in [W]
*/
public static int essMaxChargePower(Params params, ManagedSymmetricEss ess) {
if (params != null) {
return params.essMaxChargePerPeriod();
}
var capacity = ess.getCapacity();
if (capacity.isDefined()) {
return capacity.get() / 4;
}
var maxApparentPower = ess.getMaxApparentPower();
if (maxApparentPower.isDefined()) {
return maxApparentPower.get() / 4;
}
return 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,6 @@ public ControlMode controlMode() {
return this.builder.controlMode;
}

@Override
public int essMaxChargePower() {
return this.builder.essMaxChargePower;
}

@Override
public int maxChargePowerFromGrid() {
return this.builder.maxChargePowerFromGrid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ public class RunOptimizerFromLogApp {
private static final int ESS_MAX_SOC = 90;

/** The ESS Max Power [W]. */
private static final int ESS_MAX_POWER = 10000;
/** ESS Max Charge Power in CHARGE State [W]. */
private static final int ESS_MAX_CHARGE = 5000;
private static final int ESS_MAX_POWER = 5000;

/** The Max Buy-from-Grid Power [W]. */
private static final int MAX_BUY_FROM_GRID = 24_000;
Expand Down Expand Up @@ -75,7 +73,6 @@ public static void main(String[] args) {
.essMaxSocEnergy(Math.round(ESS_MAX_SOC / 100F * ESS_CAPACITY)) //
.essInitialEnergy(periods.get(0).essInitial()) //
.essMaxEnergyPerPeriod(toEnergy(ESS_MAX_POWER)) //
.essMaxChargePerPeriod(toEnergy(ESS_MAX_CHARGE)) //
.maxBuyFromGrid(toEnergy(MAX_BUY_FROM_GRID)) //
.productions(periods.stream().mapToInt(Period::production).toArray()) //
.consumptions(periods.stream().mapToInt(Period::consumption).toArray()) //
Expand Down
Loading

0 comments on commit d2ebc67

Please sign in to comment.