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

Migrate Sinexcel implementation to Battery-Inverter #1389

Closed
wants to merge 42 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b36f120
replaces dummy characteristic
wgerbl Jan 19, 2021
55a8b55
implements allowed charge/discharge logic for all soltaro systems inc…
wgerbl Jan 21, 2021
d6d98d5
add constant
wgerbl Jan 21, 2021
d5b6824
Add new features to PolyLine
sfeilmeier Jan 28, 2021
5732585
Add TypeUtils `min` and `multiply` for type Double
sfeilmeier Jan 28, 2021
a2c34ca
Software architecture draft for battery-protection using polylines
sfeilmeier Jan 28, 2021
673f3fc
Merge branch 'develop' into feature/battery-protection
sfeilmeier Jan 28, 2021
0783b03
Reset files to develop
sfeilmeier Jan 28, 2021
9b13c0a
Add Force-Discharge mode
sfeilmeier Jan 28, 2021
414373e
Merge remote-tracking branch 'origin/develop' into feature/battery-pr…
sfeilmeier Jan 30, 2021
30f9e88
Continued work...
sfeilmeier Feb 1, 2021
d73e712
Cleanup and apply autoformat
sfeilmeier Feb 1, 2021
8bb1cb3
Add JUnit tests and alternative configuration with BatteryProtectionD…
sfeilmeier Feb 1, 2021
4253bc3
Avoid NullPointerExceptions
sfeilmeier Feb 1, 2021
0705ab1
Avoid NullPointerExceptions
sfeilmeier Feb 1, 2021
358c810
Apply BatteryProtection to Soltaro Single B
sfeilmeier Feb 1, 2021
8eaa6ee
Add enhanced calculation of Voltage-to-Percent limits
sfeilmeier Feb 2, 2021
4a35464
Add discharge voltage-to-percent ramp for soltaro batteries
sfeilmeier Feb 2, 2021
ce77ac5
Avoid rounding issues on 1 % per Cycle ramp for AllowedCharge/Dischar…
sfeilmeier Feb 2, 2021
3a0a5e3
Fine tuning of Soltaro Battery Protection
sfeilmeier Feb 2, 2021
45e44b4
Fine tuning of Soltaro Battery Protection
sfeilmeier Feb 2, 2021
8b4af9e
Fine tuning of Soltaro Battery Protection
sfeilmeier Feb 2, 2021
9517987
Fine tuning of Soltaro Battery Protection
sfeilmeier Feb 3, 2021
d26abe6
Extend definition of BatteryProtectionDefinition
sfeilmeier Feb 3, 2021
43321ed
Fine tuning of Soltaro Battery Protection: temperature limits
sfeilmeier Feb 3, 2021
71e50bd
Force Charge/Discharge: wait 60 seconds + implement state-machine
sfeilmeier Feb 4, 2021
31a1a96
Soltaro: adjust max charge temperature according to datasheet
sfeilmeier Feb 5, 2021
6f31e15
Updates
sfeilmeier Feb 9, 2021
1906515
Recover old Battery-Protection
sfeilmeier Feb 9, 2021
af3ad85
Add Channels for debugging via Grafana Dashboard
sfeilmeier Feb 9, 2021
b973784
Add readme
sfeilmeier Feb 9, 2021
e148bb2
Merge remote-tracking branch 'origin/develop' into feature/battery-pr…
sfeilmeier Feb 9, 2021
62d2bb1
Apply Checkstyle, update JavaDocs and JUnit test framework
sfeilmeier Feb 9, 2021
06a2c93
Apply Checkstyle
sfeilmeier Feb 9, 2021
a199c14
Fix ForceCharge
sfeilmeier Feb 9, 2021
9017411
Improve State-Machine logging
sfeilmeier Feb 9, 2021
9358e3b
Fix ForceCharge
sfeilmeier Feb 9, 2021
07c25c1
Fix Force-Charge Channel in DischargeCurrentHandler
sfeilmeier Feb 10, 2021
8d8944b
Extract calculation of AllowedCharge/DischargePower + add JUnit tests
sfeilmeier Feb 10, 2021
20d296e
Merge remote-tracking branch 'origin/develop' into feature/battery-pr…
sfeilmeier Feb 10, 2021
be38a0b
Sinexcel: ignore active power < 100 W
sfeilmeier Feb 10, 2021
209380c
Start migration of Sinexcel ESS to BatteryInverter
sfeilmeier Feb 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
2 changes: 1 addition & 1 deletion cnf/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
<message key="name.invalidPattern" value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ClassTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]$)"/>
<message key="name.invalidPattern" value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,9 @@ 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}
* <li>Usually positive, negative for force discharge mode
* </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 @@ -143,8 +140,7 @@ 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}
* <li>Usually positive, negative for force charge mode
* </ul>
*/
DISCHARGE_MAX_CURRENT(Doc.of(OpenemsType.INTEGER) //
Expand Down Expand Up @@ -199,6 +195,8 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
MAX_CELL_VOLTAGE(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.MILLIVOLT)),

// TODO FORCE_CHARGE_ACTIVE and FORCE_DISCHARGE_ACTIVE channels are
// deprecated/obsolete by BatteryProtection channels
/**
* Force charge active.
*
Expand Down Expand Up @@ -235,6 +233,7 @@ public Doc doc() {

/**
* Gets the ModbusSlaveNatureTable.
*
* @param accessMode the {@link AccessMode}
* @return ModbusSlaveNatureTable
*/
Expand Down Expand Up @@ -821,4 +820,5 @@ public default void _setForceDischargeActive(Boolean value) {
public default void _setForceDischargeActive(boolean value) {
this.getForceDischargeActiveChannel().setNextValue(value);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
package io.openems.edge.battery.protection;

import io.openems.common.channel.Unit;
import io.openems.common.types.OpenemsType;
import io.openems.edge.battery.api.Battery;
import io.openems.edge.battery.protection.currenthandler.ChargeMaxCurrentHandler;
import io.openems.edge.battery.protection.currenthandler.DischargeMaxCurrentHandler;
import io.openems.edge.battery.protection.force.AbstractForceChargeDischarge;
import io.openems.edge.common.channel.Doc;
import io.openems.edge.common.component.ClockProvider;
import io.openems.edge.common.type.TypeUtils;

/**
* This utility class provides algorithms to calculate maximum allowed charge
* and discharge currents for batteries.
*
* <p>
* The logic uses:
*
* <ul>
* <li>Allowed Current Limit provided by Battery Management System
* <li>Voltage-to-Percent characteristics based on Min- and Max-Cell-Voltage
* <li>Temperature-to-Percent characteristics based on Min- and
* Max-Cell-Temperature
* <li>Linear max increase limit (e.g. 0.5 A per second)
* <li>Force Charge/Discharge mode (e.g. -1 A to enforce charge/discharge)
* </ul>
*/
public class BatteryProtection {

public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
/**
* Charge Current limit provided by the Battery/BMS.
*
* <ul>
* <li>Interface: BatteryProtection
* <li>Type: Integer
* <li>Unit: Ampere
* </ul>
*/
BP_CHARGE_BMS(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.AMPERE)),
/**
* Discharge Current limit provided by the Battery/BMS.
*
* <ul>
* <li>Interface: BatteryProtection
* <li>Type: Integer
* <li>Unit: Ampere
* </ul>
*/
BP_DISCHARGE_BMS(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.AMPERE)),
/**
* Charge Current limit derived from Min-Cell-Voltage.
*
* <ul>
* <li>Interface: BatteryProtection
* <li>Type: Integer
* <li>Unit: Ampere
* </ul>
*/
BP_CHARGE_MIN_VOLTAGE(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.AMPERE)),
/**
* Discharge Current limit derived from Min-Cell-Voltage.
*
* <ul>
* <li>Interface: BatteryProtection
* <li>Type: Integer
* <li>Unit: Ampere
* </ul>
*/
BP_DISCHARGE_MIN_VOLTAGE(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.AMPERE)),
/**
* Charge Current limit derived from Max-Cell-Voltage.
*
* <ul>
* <li>Interface: BatteryProtection
* <li>Type: Integer
* <li>Unit: Ampere
* </ul>
*/
BP_CHARGE_MAX_VOLTAGE(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.AMPERE)),
/**
* Discharge Current limit derived from Max-Cell-Voltage.
*
* <ul>
* <li>Interface: BatteryProtection
* <li>Type: Integer
* <li>Unit: Ampere
* </ul>
*/
BP_DISCHARGE_MAX_VOLTAGE(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.AMPERE)),
/**
* Charge Current limit derived from Min-Cell-Temperature.
*
* <ul>
* <li>Interface: BatteryProtection
* <li>Type: Integer
* <li>Unit: Ampere
* </ul>
*/
BP_CHARGE_MIN_TEMPERATURE(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.AMPERE)),
/**
* Discharge Current limit derived from Min-Cell-Temperature.
*
* <ul>
* <li>Interface: BatteryProtection
* <li>Type: Integer
* <li>Unit: Ampere
* </ul>
*/
BP_DISCHARGE_MIN_TEMPERATURE(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.AMPERE)),
/**
* Charge Current limit derived from Max-Cell-Temperature.
*
* <ul>
* <li>Interface: BatteryProtection
* <li>Type: Integer
* <li>Unit: Ampere
* </ul>
*/
BP_CHARGE_MAX_TEMPERATURE(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.AMPERE)),
/**
* Discharge Current limit derived from Max-Cell-Temperature.
*
* <ul>
* <li>Interface: BatteryProtection
* <li>Type: Integer
* <li>Unit: Ampere
* </ul>
*/
BP_DISCHARGE_MAX_TEMPERATURE(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.AMPERE)),
/**
* Charge Max-Increase Current limit.
*
* <ul>
* <li>Interface: BatteryProtection
* <li>Type: Integer
* <li>Unit: Ampere
* </ul>
*/
BP_CHARGE_INCREASE(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.AMPERE)),
/**
* Discharge Max-Increase Current limit.
*
* <ul>
* <li>Interface: BatteryProtection
* <li>Type: Integer
* <li>Unit: Ampere
* </ul>
*/
BP_DISCHARGE_INCREASE(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.AMPERE)),
/**
* Force-Discharge State.
*
* <ul>
* <li>Interface: BatteryProtection
* <li>Type: Integer
* <li>Unit: Ampere
* </ul>
*/
BP_FORCE_DISCHARGE(Doc.of(AbstractForceChargeDischarge.State.values())), //
/**
* Force-Charge State.
*
* <ul>
* <li>Interface: BatteryProtection
* <li>Type: Integer
* <li>Unit: Ampere
* </ul>
*/
BP_FORCE_CHARGE(Doc.of(AbstractForceChargeDischarge.State.values())) //
;

private final Doc doc;

private ChannelId(Doc doc) {
this.doc = doc;
}

@Override
public Doc doc() {
return this.doc;
}
}

public static class Builder {

private final Battery battery;

private ChargeMaxCurrentHandler chargeMaxCurrentHandler;
private DischargeMaxCurrentHandler dischargeMaxCurrentHandler;

protected Builder(Battery battery) {
this.battery = battery;
}

/**
* Applies all values from a {@link BatteryProtectionDefinition}.
*
* @param def the {@link BatteryProtectionDefinition}
* @param clockProvider a {@link ClockProvider}
* @return a {@link Builder}
*/
public Builder applyBatteryProtectionDefinition(BatteryProtectionDefinition def, ClockProvider clockProvider) {
return this //
.setChargeMaxCurrentHandler(
ChargeMaxCurrentHandler.create(clockProvider, def.getInitialBmsMaxEverChargeCurrent()) //
.setVoltageToPercent(def.getChargeVoltageToPercent()) //
.setTemperatureToPercent(def.getChargeTemperatureToPercent()) //
.setMaxIncreasePerSecond(def.getMaxIncreaseAmperePerSecond()) //
.setForceDischarge(def.getForceDischargeParams()) //
.build()) //
.setDischargeMaxCurrentHandler(
DischargeMaxCurrentHandler.create(clockProvider, def.getInitialBmsMaxEverDischargeCurrent()) //
.setVoltageToPercent(def.getDischargeVoltageToPercent())
.setTemperatureToPercent(def.getDischargeTemperatureToPercent()) //
.setMaxIncreasePerSecond(def.getMaxIncreaseAmperePerSecond()) //
.setForceCharge(def.getForceChargeParams()) //
.build()) //
;
}

/**
* Sets the {@link ChargeMaxCurrentHandler}.
*
* @param chargeMaxCurrentHandler the {@link ChargeMaxCurrentHandler}
* @return a {@link Builder}
*/
public Builder setChargeMaxCurrentHandler(ChargeMaxCurrentHandler chargeMaxCurrentHandler) {
this.chargeMaxCurrentHandler = chargeMaxCurrentHandler;
return this;
}

/**
* Sets the {@link DischargeMaxCurrentHandler}.
*
* @param dischargeMaxCurrentHandler the {@link DischargeMaxCurrentHandler}
* @return a {@link Builder}
*/
public Builder setDischargeMaxCurrentHandler(DischargeMaxCurrentHandler dischargeMaxCurrentHandler) {
this.dischargeMaxCurrentHandler = dischargeMaxCurrentHandler;
return this;
}

/**
* Builds the {@link BatteryProtection} instance.
*
* @return a {@link BatteryProtection}
*/
public BatteryProtection build() {
return new BatteryProtection(this.battery, this.chargeMaxCurrentHandler, this.dischargeMaxCurrentHandler);
}
}

/**
* Create a {@link BatteryProtection} using builder pattern.
*
* @param battery the {@link Battery}
* @return a {@link Builder}
*/
public static Builder create(Battery battery) {
return new Builder(battery);
}

private final Battery battery;
private final ChargeMaxCurrentHandler chargeMaxCurrentHandler;
private final DischargeMaxCurrentHandler dischargeMaxCurrentHandler;

protected BatteryProtection(Battery battery, ChargeMaxCurrentHandler chargeMaxCurrentHandler,
DischargeMaxCurrentHandler dischargeMaxCurrentHandler) {
TypeUtils.assertNull("BatteryProtection algorithm is missing data", battery, chargeMaxCurrentHandler,
dischargeMaxCurrentHandler);
this.battery = battery;
this.chargeMaxCurrentHandler = chargeMaxCurrentHandler;
this.dischargeMaxCurrentHandler = dischargeMaxCurrentHandler;
}

/**
* Apply the logic on the {@link Battery}.
*
* <ul>
* <li>Set CHARGE_MAX_CURRENT Channel
* <li>Set DISCHARGE_MAX_CURRENT Channel
* <li>Set FORCE_DISCHARGE_ACTIVE State-Channel if Charge-Max-Current < 0
* <li>Set FORCE_CHARGE_ACTIVE State-Channel if Discharge-Max-Current < 0
* <li>SET
* </ul>
*/
public void apply() {
// Use MaxCurrentHandlers to calculate max charge and discharge currents.
// These methods also write debug information to BatteryProtection-Channels, so
// it is feasible to always execute them, even if battery is not started.
int chargeMaxCurrent = this.chargeMaxCurrentHandler.calculateCurrentLimit(this.battery);
int dischargeMaxCurrent = this.dischargeMaxCurrentHandler.calculateCurrentLimit(this.battery);

// Set max charge and discharge currents
this.battery._setChargeMaxCurrent(chargeMaxCurrent);
this.battery._setDischargeMaxCurrent(dischargeMaxCurrent);
}
}
Loading