Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Battery Protection Algorithm #1329

Merged
merged 25 commits into from
Jan 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
78babef
extends Battery API
wgerbl Dec 1, 2020
8d2c49d
implemented for version b, test implmentation with different cell cha…
wgerbl Dec 8, 2020
351c3fe
Debug-Log-Controller: allow wildcards for ignore components + non-con…
sfeilmeier Dec 11, 2020
10dd800
Improve ApiWorker logs
sfeilmeier Dec 11, 2020
50eaf2e
Autoformat, remove unnecessary logs, add Javadoc
sfeilmeier Dec 11, 2020
8897dea
Generic-Ess: apply ramp for allowed charge/discharge power
sfeilmeier Dec 11, 2020
b84188a
Battery-Protection: set "0" limit when no data is available
sfeilmeier Dec 11, 2020
20c8fe0
Soltaro Single B: reduce retry attempts to avoid race condition with …
sfeilmeier Dec 11, 2020
c819a5e
KACO battery inverter: do not set possibly negative charge/discharge …
sfeilmeier Dec 11, 2020
5ba1a2c
ManagedSymmetricEss: avoid "Given LowLimit is higher than HighLimit" …
sfeilmeier Dec 11, 2020
5d62fcc
Merge remote-tracking branch 'origin/develop' into feature/battery_pr…
sfeilmeier Dec 13, 2020
dc97bc3
Improve Logging in StateMachines
sfeilmeier Dec 14, 2020
80b81c3
adapt values
wgerbl Dec 21, 2020
f38b0bf
adds test, fix that system charges until final discharge cell voltage…
wgerbl Dec 22, 2020
8eda1e7
force charge stops at 10 mV to final discharge cell voltage
wgerbl Dec 22, 2020
a168528
Merge branch 'develop' into feature/battery_protection
wgerbl Dec 25, 2020
0d04e54
Fix Fenecon Mini State-Machine
sfeilmeier Dec 27, 2020
9c9e377
Soltaro Single B: Downgrade Errors
sfeilmeier Dec 27, 2020
c5ac1e8
Improve ESS Sinexcel Battery-Handling
sfeilmeier Dec 27, 2020
3576a93
Generic-ESS: change execution order to avoid race condition
sfeilmeier Dec 27, 2020
220dbc9
Soltaro Single B: improve debuglog
sfeilmeier Dec 27, 2020
5ee794a
ESS SInexcel: identify state as "Automatic Standby-Mode"
sfeilmeier Dec 27, 2020
7e3c815
ESS Sinexcel: identify state as "Automatic Standby-Mode"
sfeilmeier Dec 27, 2020
6b0acd9
Merge branch 'develop' into feature/battery_protection
sfeilmeier Jan 11, 2021
087ca81
Code cleanup
sfeilmeier Jan 11, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions io.openems.common/src/io/openems/common/types/ChannelAddress.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.openems.common.exceptions.OpenemsError;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.utils.StringUtils;

public class ChannelAddress implements Comparable<ChannelAddress> {

Expand Down Expand Up @@ -79,4 +80,39 @@ public boolean equals(Object obj) {
ChannelAddress other = (ChannelAddress) obj;
return this.toString().equals(other.toString());
}

/**
* Match two ChannelAddresses, considering wildcards.
*
* <ul>
* <li>if {@link #equals(Object)} is true -> return 0
* <li>if both {@link ChannelAddress}es match via wildcards -> return value > 1;
* bigger values represent a better match
* <li>if both {@link ChannelAddress}es do not match -> return -1
* </ul>
*
* <p>
* See {@link StringUtils#matchWildcard(String, String)} for implementation
* details.
*
* @param source the source {@link ChannelAddress}
* @param pattern the pattern {@link ChannelAddress}
* @return an integer value representing the degree of matching
*/
public static int match(ChannelAddress source, ChannelAddress pattern) {
int componentIdMatch = StringUtils.matchWildcard(source.componentId, pattern.componentId);
int channelIdMatch = StringUtils.matchWildcard(source.channelId, pattern.channelId);
if (componentIdMatch < 0 || channelIdMatch < 0) {
return -1;
} else if (componentIdMatch == 0 && channelIdMatch == 0) {
return 0;
}
if (componentIdMatch == 0) {
return Integer.MAX_VALUE / 2 + channelIdMatch;
} else if (channelIdMatch == 0) {
return Integer.MAX_VALUE / 2 + componentIdMatch;
} else {
return componentIdMatch + channelIdMatch;
}
}
}
33 changes: 33 additions & 0 deletions io.openems.common/src/io/openems/common/utils/StringUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,37 @@ public static String toShortString(JsonElement j, int length) {
public static String capitalizeFirstLetter(String s) {
return s.substring(0, 1).toUpperCase() + s.substring(1);
}

/**
* Match two Strings, considering wildcards.
*
* <ul>
* <li>if {@link #equals(Object)} is true -> return 0
* <li>if 'pattern' matches 'source' -> return value > 1; bigger values
* represent a better match
* <li>if both Strings do not match -> return -1
* </ul>
*
* <p>
* Implementation note: only one wildcard is considered. Either the entire
* string is "*" or the wildcard is at the beginning or at the end of the
* pattern String. The the JUnit test for details.
*
* @param source the String to be evaluated
* @param pattern the pattern String, i.e. "meter*"
* @return an integer value representing the degree of matching
*/
public static int matchWildcard(String source, String pattern) {
if (source.equals(pattern)) {
return 0;
} else if (pattern.equals("*")) {
return 1;
} else if (pattern.startsWith("*") && source.endsWith(pattern.substring(1))) {
return pattern.length();
} else if (pattern.endsWith("*") && source.startsWith(pattern.substring(0, pattern.length() - 1))) {
return pattern.length();
} else {
return -1;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import org.osgi.annotation.versioning.ProviderType;

import io.openems.common.channel.AccessMode;
import io.openems.common.channel.Level;
import io.openems.common.channel.Unit;
import io.openems.common.types.OpenemsType;
import io.openems.edge.common.channel.Doc;
import io.openems.edge.common.channel.IntegerReadChannel;
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.common.modbusslave.ModbusSlaveNatureTable;
Expand Down Expand Up @@ -113,8 +115,12 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
* <li>Interface: Battery
* <li>Type: Integer
* <li>Unit: A
* <li>Usually positive, negative for force discharge mode; see
* {@link ChannelId#FORCE_DISCHARGE_ACTIVE}
* </ul>
*/
// TODO every Battery-Inverter implementation needs to be adjusted accordingly!
// Usually this register might be UINT16 and not accept negative values!
CHARGE_MAX_CURRENT(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.AMPERE)),

Expand All @@ -137,6 +143,8 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
* <li>Interface: Battery
* <li>Type: Integer
* <li>Unit: A
* <li>Usually positive, negative for force charge mode; see
* {@link ChannelId#FORCE_CHARGE_ACTIVE}
* </ul>
*/
DISCHARGE_MAX_CURRENT(Doc.of(OpenemsType.INTEGER) //
Expand Down Expand Up @@ -189,7 +197,29 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
* </ul>
*/
MAX_CELL_VOLTAGE(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.MILLIVOLT));
.unit(Unit.MILLIVOLT)),

/**
* Force charge active.
*
* <ul>
* <li>Interface: Battery
* <li>Indicates that battery is in force charge mode
* </ul>
*/
FORCE_CHARGE_ACTIVE(Doc.of(Level.INFO).text("Force charge mode is active")), //

/**
* Force discharge active.
*
* <ul>
* <li>Interface: Battery
* <li>Indicates that battery is in force discharge mode
* </ul>
*/
FORCE_DISCHARGE_ACTIVE(Doc.of(Level.INFO).text("Force discharge mode is active")), //

;

private final Doc doc;

Expand Down Expand Up @@ -710,4 +740,80 @@ public default void _setMaxCellVoltage(Integer value) {
public default void _setMaxCellVoltage(int value) {
this.getMaxCellVoltageChannel().setNextValue(value);
}

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

/**
* Gets the State. See {@link ChannelId#FORCE_CHARGE_ACTIVE}.
*
* @return the Channel {@link Value}
*/
public default Value<Boolean> getForceChargeActive() {
return this.getForceChargeActiveChannel().value();
}

/**
* Internal method to set the 'nextValue' on
* {@link ChannelId#FORCE_CHARGE_ACTIVE} Channel.
*
* @param value the next value
*/
public default void _setForceChargeActive(Boolean value) {
this.getForceChargeActiveChannel().setNextValue(value);
}

/**
* Internal method to set the 'nextValue' on
* {@link ChannelId#FORCE_CHARGE_ACTIVE} Channel.
*
* @param value the next value
*/
public default void _setForceChargeActive(boolean value) {
this.getForceChargeActiveChannel().setNextValue(value);
}

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

/**
* Gets the State. See {@link ChannelId#FORCE_DISCHARGE_ACTIVE}.
*
* @return the Channel {@link Value}
*/
public default Value<Boolean> getForceDischargeActive() {
return this.getForceDischargeActiveChannel().value();
}

/**
* Internal method to set the 'nextValue' on
* {@link ChannelId#FORCE_DISCHARGE_ACTIVE} Channel.
*
* @param value the next value
*/
public default void _setForceDischargeActive(Boolean value) {
this.getForceDischargeActiveChannel().setNextValue(value);
}

/**
* Internal method to set the 'nextValue' on
* {@link ChannelId#FORCE_DISCHARGE_ACTIVE} Channel.
*
* @param value the next value
*/
public default void _setForceDischargeActive(boolean value) {
this.getForceDischargeActiveChannel().setNextValue(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,40 @@ public void setStartStop(StartStop value) throws OpenemsNamedException {
this._setStartStop(value);
}

public DummyBattery withCapacity(int value) {
this._setCapacity(value);
this.getCapacityChannel().nextProcessImage();
return this;
}

public DummyBattery withVoltage(int value) {
this._setVoltage(value);
this.getVoltageChannel().nextProcessImage();
return this;
}

public DummyBattery withDischargeMaxCurrent(int value) {
this._setDischargeMaxCurrent(value);
this.getDischargeMaxCurrentChannel().nextProcessImage();
return this;
}

public DummyBattery withChargeMaxCurrent(int value) {
this._setChargeMaxCurrent(value);
this.getChargeMaxCurrentChannel().nextProcessImage();
return this;
}

public DummyBattery withMinCellVoltage(int value) {
this._setMinCellVoltage(value);
this.getMinCellVoltageChannel().nextProcessImage();
return this;
}

public DummyBattery withMaxCellVoltage(int value) {
this._setMaxCellVoltage(value);
this.getMaxCellVoltageChannel().nextProcessImage();
return this;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

import io.openems.edge.battery.bydcommercial.BatteryBoxC130;
import io.openems.edge.battery.bydcommercial.Config;
import io.openems.edge.common.statemachine.AbstractContext;

public class Context extends AbstractContext<BatteryBoxC130> {

public class Context {
protected final BatteryBoxC130 component;
protected final Config config;

public Context(BatteryBoxC130 component, Config config) {
super();
this.component = component;
public Context(BatteryBoxC130 parent, Config config) {
super(parent);
this.config = config;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.time.Instant;

import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.edge.battery.bydcommercial.BatteryBoxC130;
import io.openems.edge.battery.bydcommercial.enums.PowerCircuitControl;
import io.openems.edge.battery.bydcommercial.statemachine.StateMachine.State;
import io.openems.edge.common.statemachine.StateHandler;
Expand All @@ -17,18 +18,20 @@ protected void onEntry(Context context) throws OpenemsNamedException {
this.entryAt = Instant.now();

// Try to stop system
context.component._setPowerCircuitControl(PowerCircuitControl.SWITCH_OFF);
context.getParent()._setPowerCircuitControl(PowerCircuitControl.SWITCH_OFF);
}

@Override
protected void onExit(Context context) throws OpenemsNamedException {
context.component._setMaxStartAttempts(false);
context.component._setMaxStopAttempts(false);
BatteryBoxC130 battery = context.getParent();

battery._setMaxStartAttempts(false);
battery._setMaxStopAttempts(false);
}

@Override
public State runAndGetNextState(Context context) {
System.out.println("Stuck in ERROR_HANDLING: " + context.component.getStateChannel().listStates());
System.out.println("Stuck in ERROR_HANDLING: " + context.getParent().getStateChannel().listStates());

if (Duration.between(this.entryAt, Instant.now()).getSeconds() > 120) {
// Try again
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.time.Instant;

import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.edge.battery.bydcommercial.BatteryBoxC130;
import io.openems.edge.battery.bydcommercial.enums.PowerCircuitControl;
import io.openems.edge.battery.bydcommercial.statemachine.StateMachine.State;
import io.openems.edge.battery.bydcommercial.utils.Constants;
Expand All @@ -18,12 +19,13 @@ public class GoRunningHandler extends StateHandler<State, Context> {
protected void onEntry(Context context) throws OpenemsNamedException {
this.lastAttempt = Instant.MIN;
this.attemptCounter = 0;
context.component._setMaxStartAttempts(false);
context.getParent()._setMaxStartAttempts(false);
}

@Override
public State runAndGetNextState(Context context) throws OpenemsNamedException {
PowerCircuitControl preChargeControl = context.component.getPowerCircuitControl();
BatteryBoxC130 battery = context.getParent();
PowerCircuitControl preChargeControl = battery.getPowerCircuitControl();

if (preChargeControl == PowerCircuitControl.SWITCH_ON) {
return State.RUNNING;
Expand All @@ -36,12 +38,12 @@ public State runAndGetNextState(Context context) throws OpenemsNamedException {

if (this.attemptCounter > Constants.RETRY_COMMAND_MAX_ATTEMPTS) {
// Too many tries
context.component._setMaxStartAttempts(true);
battery._setMaxStartAttempts(true);
return State.UNDEFINED;

} else {
// Trying to switch on
context.component.setPowerCircuitControl(PowerCircuitControl.PRE_CHARGING_1);
battery.setPowerCircuitControl(PowerCircuitControl.PRE_CHARGING_1);
this.lastAttempt = Instant.now();
this.attemptCounter++;
return State.GO_RUNNING;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.time.Instant;

import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.edge.battery.bydcommercial.BatteryBoxC130;
import io.openems.edge.battery.bydcommercial.enums.PowerCircuitControl;
import io.openems.edge.battery.bydcommercial.statemachine.StateMachine.State;
import io.openems.edge.battery.bydcommercial.utils.Constants;
Expand All @@ -22,7 +23,8 @@ protected void onEntry(Context context) {

@Override
public State runAndGetNextState(Context context) throws OpenemsNamedException {
PowerCircuitControl powerCircuitControl = context.component.getPowerCircuitControl();
BatteryBoxC130 battery = context.getParent();
PowerCircuitControl powerCircuitControl = battery.getPowerCircuitControl();

if (powerCircuitControl == PowerCircuitControl.SWITCH_OFF) {
return State.STOPPED;
Expand All @@ -35,12 +37,12 @@ public State runAndGetNextState(Context context) throws OpenemsNamedException {

if (this.attemptCounter > Constants.RETRY_COMMAND_MAX_ATTEMPTS) {
// Too many tries
context.component._setMaxStopAttempts(true);
battery._setMaxStopAttempts(true);
return State.UNDEFINED;

} else {
// Trying to switch off
context.component.setPowerCircuitControl(PowerCircuitControl.SWITCH_OFF);
battery.setPowerCircuitControl(PowerCircuitControl.SWITCH_OFF);
this.lastAttempt = Instant.now();
this.attemptCounter++;
return State.GO_STOPPED;
Expand Down
Loading