Skip to content

Commit

Permalink
FEMS Backports 2024-12-18 (#2932)
Browse files Browse the repository at this point in the history
- Edge
  - EVCS Api State: remove CHARGING_FINISHED, i.e. "Car is full"
    - Removed "CHARGING_FINISHED" from Status in evcs.api
    - Fix for every part where CHARGING_FINISHED was referenced
  - Fix CI Build: remove OCPP dependency javax.xml.soap
  - Edge: Mennekes Occupied Check
    - Added Check if ActivePower is present when mapping the OcppStatus 1 of the Mennekes Register 104.
  - Controller.Ess.EmergencyCapacityReserve: recharge from grid
    - Config setting + channel in Meta Component
    - new state for in satemachine of emergencyreservesoc
    - change logic of statehandlers to include new state

- UI
  - Add Custom Min Value for Y-Axis in Charts to enhance schedule display
  -  dark mode improvements
    - replace legacy percentagebar
    - delete unused old systemupdate component
    - change header title color
  - Emergency Reserve with Charge-from-Grid

---------

Co-authored-by: Sebastian Asen <47855186+sebastianasen@users.noreply.github.com>
Co-authored-by: Stefan Feilmeier <3515268+sfeilmeier@users.noreply.github.com>
Co-authored-by: Johann Kaufmann <165755282+johannk24@users.noreply.github.com>
Co-authored-by: Sagar Venu <32655208+venu-sagar@users.noreply.github.com>
Co-authored-by: Lukas Rieger <73471197+lukasrgr@users.noreply.github.com>
  • Loading branch information
6 people authored Dec 18, 2024
1 parent e37f9b2 commit 2a1b88a
Show file tree
Hide file tree
Showing 63 changed files with 722 additions and 576 deletions.
1 change: 0 additions & 1 deletion io.openems.edge.application/EdgeApp.bndrun
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,6 @@
io.openems.wrapper.sdnotify;version=snapshot,\
io.reactivex.rxjava3.rxjava;version='[3.1.10,3.1.11)',\
javax.jmdns;version='[3.4.1,3.4.2)',\
javax.xml.soap-api;version='[1.4.0,1.4.1)',\
org.apache.commons.commons-codec;version='[1.17.1,1.17.2)',\
org.apache.commons.commons-compress;version='[1.27.1,1.27.2)',\
org.apache.commons.commons-csv;version='[1.11.0,1.11.1)',\
Expand Down
63 changes: 54 additions & 9 deletions io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package io.openems.edge.common.meta;

import static io.openems.common.channel.PersistencePriority.HIGH;
import static io.openems.common.channel.PersistencePriority.VERY_LOW;
import static io.openems.common.channel.Unit.SECONDS;
import static io.openems.common.types.OpenemsType.BOOLEAN;
import static io.openems.common.types.OpenemsType.LONG;
import static io.openems.common.types.OpenemsType.STRING;

import io.openems.common.OpenemsConstants;
import io.openems.common.channel.AccessMode;
import io.openems.common.channel.PersistencePriority;
import io.openems.common.channel.Unit;
import io.openems.common.oem.OpenemsEdgeOem;
import io.openems.common.types.OpenemsType;
import io.openems.edge.common.channel.BooleanReadChannel;
import io.openems.edge.common.channel.Doc;
import io.openems.edge.common.channel.EnumReadChannel;
import io.openems.edge.common.channel.value.Value;
Expand All @@ -29,8 +34,8 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
* <li>Type: String
* </ul>
*/
VERSION(Doc.of(OpenemsType.STRING) //
.persistencePriority(PersistencePriority.HIGH)),
VERSION(Doc.of(STRING) //
.persistencePriority(HIGH)),
/**
* System Time: seconds since 1st January 1970 00:00:00 UTC.
*
Expand All @@ -39,10 +44,10 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
* <li>Type: Long
* </ul>
*/
SYSTEM_TIME_UTC(Doc.of(OpenemsType.LONG) //
.unit(Unit.SECONDS) //
SYSTEM_TIME_UTC(Doc.of(LONG) //
.unit(SECONDS) //
.text("System Time: seconds since 1st January 1970 00:00:00 UTC") //
.persistencePriority(PersistencePriority.VERY_LOW)),
.persistencePriority(VERY_LOW)),
/**
* Edge currency.
*
Expand All @@ -52,7 +57,18 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
* </ul>
*/
CURRENCY(Doc.of(Currency.values()) //
.persistencePriority(PersistencePriority.HIGH));
.persistencePriority(HIGH)),

/**
* Is it allowed to charge the ESS from Grid?.
*
* <ul>
* <li>Interface: Meta
* <li>Type: Boolean
* </ul>
*/
IS_ESS_CHARGE_FROM_GRID_ALLOWED(Doc.of(BOOLEAN) //
.persistencePriority(HIGH));

private final Doc doc;

Expand Down Expand Up @@ -116,4 +132,33 @@ public default Currency getCurrency() {
public default void _setCurrency(Currency value) {
this.getCurrencyChannel().setNextValue(value);
}

/**
* Gets the Channel for {@link ChannelId#IS_ESS_CHARGE_FROM_GRID_ALLOWED}.
*
* @return the Channel
*/
public default BooleanReadChannel getIsEssChargeFromGridAllowedChannel() {
return this.channel(ChannelId.IS_ESS_CHARGE_FROM_GRID_ALLOWED);
}

/**
* Gets whether charging the ESS from grid is allowed. See
* {@link ChannelId#IS_ESS_CHARGE_FROM_GRID_ALLOWED}.
*
* @return the Channel {@link Value}
*/
public default boolean getIsEssChargeFromGridAllowed() {
return this.getIsEssChargeFromGridAllowedChannel().value().orElse(false);
}

/**
* Internal method to set the 'nextValue' on
* {@link ChannelId#IS_ESS_CHARGE_FROM_GRID_ALLOWED} Channel.
*
* @param value the next value
*/
public default void _setIsEssChargeFromGridAllowed(boolean value) {
this.getIsEssChargeFromGridAllowedChannel().setNextValue(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,14 @@ public DummyMeta withCurrency(Currency value) {
return this.self();
}

/**
* Set {@link Meta.ChannelId#IS_ESS_CHARGE_FROM_GRID_ALLOWED}.
*
* @param value the value
* @return myself
*/
public DummyMeta withIsEssChargeFromGridAllowed(boolean value) {
TestUtils.withValue(this, Meta.ChannelId.IS_ESS_CHARGE_FROM_GRID_ALLOWED, value);
return this.self();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@

```mermaid
graph TD
NO_LIMIT["NO_LIMIT<br>SoC > 21 %<br>No Limit<br>[infinity, infinity]<br>1 %p/sec"] -->|discharge, SoC <= 21 %| ABOVE_RESERVE_SOC["ABOVE_RESERVE_SOC<br>[infinity, max(50 % mAP, DC-PV)]<br>1 %p/sec"]
ABOVE_RESERVE_SOC -->|discharge, SoC <= 20 %| AT_RESERVE_SOC["AT_RESERVE_SOC<br>[infinity, max(0, DC-PV)]<br>1 %p/sec"]
AT_RESERVE_SOC -->|discharge, SoC < 20| UNDER_RESERVE_SOC["UNDER_RESERVE_SOC<br>[infinity, 0]<br>5 %p/sec"]
UNDER_RESERVE_SOC -->|discharge, SoC <= 16| FORCE_CHARGE["FORCE_CHARGE<br>Force-Charge with AC-PV<br>[infinity, AC-PV * -1]<br>1 %p/sec"]
FORCE_CHARGE -->|charge, SoC >= 20| AT_RESERVE_SOC
AT_RESERVE_SOC -->|charge, SoC > 20| ABOVE_RESERVE_SOC
NO_LIMIT -->|discharge, SoC <= 21 %| ABOVE_RESERVE_SOC
ABOVE_RESERVE_SOC -->|discharge, SoC <= 20 %| AT_RESERVE_SOC
ABOVE_RESERVE_SOC -->|charge, SoC > 21| NO_LIMIT
AT_RESERVE_SOC -->|discharge, SoC < 20| BELOW_RESERVE_SOC["BELOW_RESERVE_SOC<br>[infinity, 0]<br>5 %p/sec"]
AT_RESERVE_SOC -->|charge, <br>if last state FORCE_CHARGE_GRID: <br> SoC > 21,<br>else: SoC > 20| ABOVE_RESERVE_SOC
BELOW_RESERVE_SOC -->|discharge, SoC <= 19| FORCE_CHARGE_PV
BELOW_RESERVE_SOC -->|discharge, SoC < 18| FORCE_CHARGE_GRID
FORCE_CHARGE_PV -->|charge, SoC >= 20| AT_RESERVE_SOC
FORCE_CHARGE_PV --> |discharge, SoC < 18| FORCE_CHARGE_GRID
FORCE_CHARGE_GRID -->|charge, SoC >= 21| AT_RESERVE_SOC
UNDEFINED["UNDEFINED<br>Initial State<br>[Start of Process]"] -->|SoC < 18 %| FORCE_CHARGE_GRID["FORCE_CHARGE_GRID<br>Force-Charge with Grid Power<br>[infinity, max[10 % mAP if SoC >= 17%, else 50 % mAP]]<br>1 %p/sec"]
UNDEFINED -->|SoC == 19 %| FORCE_CHARGE_PV["FORCE_CHARGE_PV<br>Force-Charge with AC-PV<br>[infinity, AC-PV * -1]<br>1 %p/sec"]
UNDEFINED -->|SoC == 20 %| AT_RESERVE_SOC["AT_RESERVE_SOC<br>[infinity, max(0, DC-PV)]<br>1 %p/sec"]
UNDEFINED -->|SoC == 21 %| ABOVE_RESERVE_SOC["ABOVE_RESERVE_SOC<br>[infinity, max(50 % mAP, DC-PV)]<br>1 %p/sec"]
UNDEFINED -->|SoC > 21 %| NO_LIMIT["NO_LIMIT<br>SoC > 21 %<br>No Limit<br>[infinity, infinity]<br>1 %p/sec"]
DESCRIPTION["Example ReserveSoc = 20%<br>ReserveSoc in [5;100]<br>%p = Percentage Point<br>mAP = MaxApparentPower<br>Ramp increase/decrease by 1 %p/sec of MaxApparentPower"]
```

View using Mermaid, e.g. https://mermaid-js.github.io/mermaid-live-editor
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.openems.edge.controller.ess.emergencycapacityreserve;

import static io.openems.common.channel.PersistencePriority.HIGH;
import static io.openems.common.channel.Unit.PERCENT;
import static io.openems.common.channel.Unit.WATT;
import static io.openems.common.types.OpenemsType.FLOAT;
import static io.openems.common.types.OpenemsType.INTEGER;

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;
import io.openems.edge.common.channel.Doc;
import io.openems.edge.common.channel.FloatReadChannel;
Expand All @@ -23,28 +26,31 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
* Current state of the StateMachine.
*/
STATE_MACHINE(Doc.of(State.values()) //
.text("Current State of State-Machine")), //
.text("Current State of State-Machine") //
.persistencePriority(HIGH)), //

/**
* Holds {@link ManagedSymmetricEss.ChannelId#SET_ACTIVE_POWER_LESS_OR_EQUALS}
* for debug purpose.
*/
DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.WATT) //
DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS(Doc.of(INTEGER) //
.unit(WATT) //
.text("The debug SetActivePowerLessOrEquals")), //

/**
* Holds target power to reach.
*/
DEBUG_TARGET_POWER(Doc.of(OpenemsType.FLOAT) //
.unit(Unit.WATT) //
.text("The debug target power to reach")), //
DEBUG_TARGET_POWER(Doc.of(FLOAT) //
.unit(WATT) //
.text("The debug target power to reach") //
.persistencePriority(HIGH) //
), //

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

/**
Expand All @@ -56,10 +62,10 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
/**
* Holds the actual reserve soc value. Holds null if reserve soc is disabled.
*/
ACTUAL_RESERVE_SOC(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.PERCENT) //
ACTUAL_RESERVE_SOC(Doc.of(INTEGER) //
.unit(PERCENT) //
.text("The reserve soc value") //
.persistencePriority(PersistencePriority.HIGH)); //
.persistencePriority(HIGH)); //

private final Doc doc;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.openems.edge.common.component.ComponentManager;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.filter.RampFilter;
import io.openems.edge.common.meta.Meta;
import io.openems.edge.common.sum.Sum;
import io.openems.edge.controller.api.Controller;
import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.Context;
Expand All @@ -49,7 +50,7 @@ public class ControllerEssEmergencyCapacityReserveImpl extends AbstractOpenemsCo

private final Logger log = LoggerFactory.getLogger(ControllerEssEmergencyCapacityReserveImpl.class);
private final EnergyScheduleHandler energyScheduleHandler;
private final StateMachine stateMachine = new StateMachine(State.NO_LIMIT);
private final StateMachine stateMachine = new StateMachine(State.UNDEFINED);
private final RampFilter rampFilter = new RampFilter();

@Reference
Expand All @@ -61,6 +62,9 @@ public class ControllerEssEmergencyCapacityReserveImpl extends AbstractOpenemsCo
@Reference
private Sum sum;

@Reference
private Meta meta;

@Reference
private ManagedSymmetricEss ess;

Expand Down Expand Up @@ -173,8 +177,8 @@ private Context handleStateMachine() {
if (socToUse == null || !maxApparentPower.isDefined()) {
this.stateMachine.forceNextState(State.NO_LIMIT);
}

var context = new Context(this, this.sum, maxApparentPower.get(), socToUse, this.config.reserveSoc());
var context = new Context(this, this.sum, maxApparentPower.get(), socToUse, this.config.reserveSoc(),
this.meta.getIsEssChargeFromGridAllowed());
try {
this.stateMachine.run(context);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ protected State runAndGetNextState(Context context) throws OpenemsNamedException
return State.BELOW_RESERVE_SOC;
}

int reserveSocBuffer = switch (context.getLastActiveState()) {
case FORCE_CHARGE_GRID -> 1;
default -> 0;
};

// SoC is under configured reserveSoC
if (soc > reserveSoc) {
if (soc > reserveSoc + reserveSocBuffer) {
return State.ABOVE_RESERVE_SOC;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ protected State runAndGetNextState(Context context) throws OpenemsNamedException
var reserveSoc = context.reserveSoc;
int soc = context.soc;

// SoC is 4% under configured reserveSoC
if (soc <= reserveSoc - 4 || soc <= 0) {
return State.FORCE_CHARGE;
// SoC is atleast 2% under configured reserveSoC and gridcharging is enabled
if ((soc <= reserveSoc - 2) && context.isEssChargeFromGridAllowed) {
return State.FORCE_CHARGE_GRID;
}

// Enter FORCE_CHARGE_PV sooner when grid charge is allowed
if (soc <= reserveSoc - 1 || soc <= 0) {
return State.FORCE_CHARGE_PV;
}

// SoC is greater then configured reserveSoC
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import io.openems.edge.common.statemachine.AbstractContext;
import io.openems.edge.common.sum.Sum;
import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve;
import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.StateMachine.State;

public class Context extends AbstractContext<ControllerEssEmergencyCapacityReserve> {

protected final Sum sum;
protected final boolean isEssChargeFromGridAllowed;

/**
* MaxApparentPower is guaranteed to be not-null in any State other than
Expand All @@ -21,14 +23,16 @@ public class Context extends AbstractContext<ControllerEssEmergencyCapacityReser

private Float targetPower;
private float rampPower;
private State lastActiveState;

public Context(ControllerEssEmergencyCapacityReserve emergencyCapacityReserve, Sum sum, Integer maxApparentPower,
Integer soc, int reserveSoc) {
Integer soc, int reserveSoc, boolean isEssChargeFromGridAllowed) {
super(emergencyCapacityReserve);
this.sum = sum;
this.maxApparentPower = maxApparentPower;
this.soc = soc;
this.reserveSoc = reserveSoc;
this.isEssChargeFromGridAllowed = isEssChargeFromGridAllowed;
}

public Float getTargetPower() {
Expand All @@ -47,6 +51,10 @@ public float getRampPower() {
return this.rampPower;
}

public State getLastActiveState() {
return this.lastActiveState == null ? State.UNDEFINED : this.lastActiveState;
}

protected void setRampPower(Double rampPower) {
this.rampPower = rampPower == null ? null : rampPower.floatValue();
}
Expand All @@ -59,4 +67,8 @@ protected void setRampPower(int rampPower) {
this.rampPower = rampPower;
}

public void setLastActiveState(State state) {
this.lastActiveState = state;
}

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

import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.edge.common.statemachine.StateHandler;
import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.StateMachine.State;

public class ForceChargeGridHandler extends StateHandler<State, Context> {

@Override
protected State runAndGetNextState(Context context) throws OpenemsNamedException {
var reserveSoc = context.reserveSoc;
int soc = context.soc;

// leave grid charge logic when grid charge gets disabled
if (!context.isEssChargeFromGridAllowed) {
if (soc <= reserveSoc - 1) {
return State.BELOW_RESERVE_SOC;
}
return State.AT_RESERVE_SOC;
}

float targetPower;

if (soc <= reserveSoc - 4 || soc <= 0) {
targetPower = context.maxApparentPower * -0.5f;
} else {
targetPower = context.maxApparentPower * -0.1f;
}

// calculate target and ramp power
context.setTargetPower(targetPower);
context.setRampPower(context.maxApparentPower * 0.01);

// SoC is greater or equals then configured reserveSoC or 100
if (soc >= reserveSoc + 1 || soc == 100) {
return State.AT_RESERVE_SOC;
}

return State.FORCE_CHARGE_GRID;
}

}
Loading

0 comments on commit 2a1b88a

Please sign in to comment.