Skip to content

Commit

Permalink
WIP refactor, tests, functionality
Browse files Browse the repository at this point in the history
refactor to explicitly state the controller supports parallel multi use
extend test documentation
publish state channel to publish the current multi use state
  • Loading branch information
Felix Remmel committed Nov 8, 2024
1 parent d5c4f1d commit 8c21693
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@
@AttributeDefinition(name = "Recharge power", description = "If grid purchase power is below this value battery is recharged.")
int rechargePower();

@AttributeDefinition(name = "Enable multiple ess constraints", description = "A fixed capacity configured by the minSocLimit is used for peakshaving. Additional capacity could be used by other controllers.")
boolean enableMultipleEssConstraints() default false;
@AttributeDefinition(name = "Allow other use cases in a parallel multi use setup", description = "The peak shaving controller uses a fixed capacity configured by the minSocLimit. Other controllers can use the remaining capacity.")
boolean allowParallelMultiUse() default false;

@AttributeDefinition(name = "Minimum SoC required for Peak Shaving", description = "The controller force charges with the available surpluss till this SOC limit")
@AttributeDefinition(name = "Minimum SoC required for Peak Shaving", description = "The controller force charges with the available surpluss till this SOC limit.")
int minSocLimit() default 70;

@AttributeDefinition(name = "SoC hysterersis", description = "SoC hysteresis to avoid switching between force and soft limitation")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package io.openems.edge.controller.symmetric.peakshaving;

import io.openems.edge.common.channel.Doc;
import io.openems.edge.common.channel.StateChannel;
import io.openems.edge.common.channel.value.Value;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.controller.api.Controller;
import io.openems.edge.controller.api.Controller.ChannelId;

public interface ControllerEssPeakShaving extends Controller, OpenemsComponent {

public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
MULTI_USE_STATE(Doc.of(MultiUseState.values()) //
.text("The current state if multi use is allowed"));

;
private final Doc doc;

Expand All @@ -19,5 +25,24 @@ public Doc doc() {
return this.doc;
}
}

/**
* Gets the Channel for {@link ChannelId#RUN_FAILED}.
*
* @return the Channel
*/
public default StateChannel getMultiUseStateChannel() {
return this.channel(ChannelId.MULTI_USE_STATE);
}

/**
* Internal method to set the 'nextValue' on {@link ChannelId#RUN_FAILED}
* Channel.
*
* @param value the next value
*/
public default void setMultiUseState(MultiUseState state) {
this.getRunFailedChannel().setNextValue(state);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.openems.edge.controller.symmetric.peakshaving;

import java.util.Optional;

import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
Expand All @@ -16,6 +18,10 @@
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.controller.api.Controller;
import io.openems.edge.ess.api.ManagedSymmetricEss;
import io.openems.edge.ess.api.PowerConstraint;
import io.openems.edge.ess.power.api.Phase;
import io.openems.edge.ess.power.api.Pwr;
import io.openems.edge.ess.power.api.Relationship;
import io.openems.edge.meter.api.ElectricityMeter;

@Designate(ocd = Config.class, factory = true)
Expand All @@ -36,7 +42,7 @@ public class ControllerEssPeakShavingImpl extends AbstractOpenemsComponent

private Config config;

private BehaviourState previousBehaviour = BehaviourState.FIXED_LIMITATION;
private MultiUseState multiUseBehavior = MultiUseState.NONE;

public ControllerEssPeakShavingImpl() {
super(//
Expand Down Expand Up @@ -80,76 +86,48 @@ public void run() throws OpenemsNamedException {

var soc = ess.getSoc().orElse(0);

var behaviour = BehaviourState.FIXED_LIMITATION;
if (this.config.enableMultipleEssConstraints()) {
if (this.config.allowParallelMultiUse()) {

behaviour = getSocSubstate(soc, this.config.minSocLimit(), this.config.socHysteresis(),
this.previousBehaviour);
this.previousBehaviour = behaviour;
this.multiUseBehavior = this.multiUseBehavior.getMultiUseBehavior(soc, this.config.minSocLimit(),
this.config.socHysteresis());
}

// Calculate 'real' grid-power (without current ESS charge/discharge)
var gridPower = meter.getActivePower().getOrError() /* current buy-from/sell-to grid */
+ ess.getActivePower().getOrError() /* current charge/discharge Ess */;

int calculatedPower;
Optional<Integer> peakShaveCompensationPower = Optional.empty();
if (gridPower >= this.config.peakShavingPower()) {
/*
* Peak-Shaving
*/
calculatedPower = gridPower - this.config.peakShavingPower();

} else if (gridPower <= this.config.rechargePower()) {
/*
* Recharge
*/
calculatedPower = gridPower - this.config.rechargePower();

} else {
/*
* Do nothing
*/
calculatedPower = 0;
// we've to do peak shaving
peakShaveCompensationPower = Optional.of(gridPower - this.config.peakShavingPower());
}

switch (behaviour) {
case FIXED_LIMITATION -> {

/*
* set result
*/
ess.setActivePowerEqualsWithPid(calculatedPower);

this.setMultiUseState(this.multiUseBehavior);
switch (this.multiUseBehavior) {
case NONE -> {
ess.setActivePowerEqualsWithPid(peakShaveCompensationPower.orElseGet(() -> {
// If peak shaving is not required, recharge has to happen.
return gridPower - this.config.rechargePower();
}));
ess.setReactivePowerEquals(0);
}

case SOFT_LIMITATION -> {
ess.setActivePowerGreaterOrEquals(calculatedPower);
}
}

ess.setReactivePowerEquals(0);
}

protected static enum BehaviourState {
FIXED_LIMITATION, //
SOFT_LIMITATION;
}

protected static BehaviourState getSocSubstate(int soc, int minSoc, int socBuffer,
BehaviourState previousBehaviour) {

return switch (previousBehaviour) {
case FIXED_LIMITATION -> {
if (soc >= minSoc + socBuffer) {
yield BehaviourState.SOFT_LIMITATION;
case PARALLEL -> {
// If peak shaving does not happen do nothing, and allow follow up controllers
// to set any value. If peak shaving has to happen don't allow other controllers
// to do anything to guarantee quick reaction of battery.
if (peakShaveCompensationPower.isPresent()) {
ess.setActivePowerGreaterOrEquals(peakShaveCompensationPower.get());
ess.setReactivePowerEquals(0);
} else {
// one can utilize the battery within the boundaries configured for this
// controller
log.info("Running in parallel mode that allows everything");
PowerConstraint.apply(ess, "Parallel multi use constraints of peak shaving", Phase.ALL, Pwr.ACTIVE,
Relationship.GREATER_OR_EQUALS, -this.config.rechargePower());
}
yield BehaviourState.FIXED_LIMITATION;
}
case SOFT_LIMITATION -> {
if (soc <= minSoc) {
yield BehaviourState.FIXED_LIMITATION;
}
yield BehaviourState.SOFT_LIMITATION;
}
};

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package io.openems.edge.controller.symmetric.peakshaving;

import io.openems.common.types.OptionsEnum;

enum MultiUseState implements OptionsEnum {
/**
* If no multi use is active, this controller will set a fixed value for active
* power, which no follow up controller can override.
*/
NONE(0, "No multi use is currently allowed"),
/**
* If parallel multi use is active, this controller sets a minimum value, which
* allows follow up controllers to override another value, which applies to the
* given constraints.
*/
PARALLEL(1, "SoC based parallel multi use is allowed");

private final int value;
private final String name;

private MultiUseState(int value, String name) {
this.value = value;
this.name = name;
}

@Override
public int getValue() {
return this.value;
}

@Override
public String getName() {
return this.name;
}

@Override
public OptionsEnum getUndefined() {
return NONE;
}

/**
* Returns the desired multi use behavior based on the input values.
*
* @param soc The current state of charge.
* @param minSoc The required state of charge for this peak shaving multi use
* case.
* @param socBuffer The hysteresis buffer to add on top of the SoC before
* allowing parallel multi use.
* @return The new multi use state.
*/
public MultiUseState getMultiUseBehavior(int soc, int minSoc, int socBuffer) {

return switch (this) {
case NONE -> {
if (soc >= minSoc + socBuffer) {
yield MultiUseState.PARALLEL;
}
yield MultiUseState.NONE;
}
case PARALLEL -> {
if (soc <= minSoc) {
yield MultiUseState.NONE;
}
yield MultiUseState.PARALLEL;
}
};
}
}
Loading

0 comments on commit 8c21693

Please sign in to comment.