Skip to content

Commit 580db62

Browse files
sfeilmeiervenu-sagarmichaelgrillsebastianasenlukasrgr
authored
FEMS Backports 2025-03-11 (#3046)
* CI * App: update java version to 21 * Bump java dependency of capacitor to java 21 * Edge * ENTSO-E: fix missing data due to bad response - Provides a more robust and complete dataset by ensuring no timestamps are missing. - Previously, the min/max timestamps were derived only from the extracted response based on the preferred duration. Now, they are computed from the entire dataset, ensuring better coverage of all timestamps. - A new test case was introduced using an XML response that previously caused missing timestamp issues. * Time-of-Use-Tariffs: AppCenter: add max charge from grid setting * Added a property for every ToU app to set the maximale allowed charge from grid power * GoodWe: disable internal BatteryProtection - Disable internal goodwe battery protection called socProtect, that caused several problems. - After setting this register to 1, the maximum voltage values of the write-registers are used. * RabotCharge Time-of-Use-Tariff: fix price calculation * Home Battery: stop on critical min cell voltage * Battery values changed to null when there is no battery communication, because ModbusBridge invalidates all Modbus Channels then. * As a result the system restarted until it was in deep discharge. Use last defined value to check the minCellVoltage. * CleverPV: send EssDischargePower instead of EssActivePower * Clever-PV requires the actual battery charge/discharge power and not AC-side of hybrid inverter * Energy Scheduler v2: continuous improvements - Improve tests and logs - GridOptimizedCharge: fix possible NPE in EnergyScheduler - Improve InitialPopulation: this is crucial for v2. Because simulation is much slower, it is important to start with a good Initial Population. It is now possible for each EnergyScheduleHandler to suggest its own Initial Population (e.g. CHARGE_GRID in cheapest quarter) - Add index to GlobalOptimizationContext.Period, makes it easier to loop over them in a stream - Improve Jenetics Engine configuration: EliteSelector to make sure best Initial Populations survive; Alterers match better with the expected search task - Increase initial wait time to create GlobalOptimizationContext - e.g. for Home systems that require some time to read capacity * EVSE Edge Improvements - Add SURPLUS mode to EVSE Single Controller (works with only one EVSE simultaneously at the moment) - Add `manualEnergySessionLimit` and ENERGY_SESSION channels - only considered in EnergyScheduleHandler at the moment! - Add Energy Scheduler Simulation for manual modes (ZERO, MINIMUM, SURPLUS and FORCE) - EVSE KEBA via Modbus/TCP: - Add PhaseRotation - Calculate/distribute Power per phase * Home Battery: use new register for number of towers - New ATL versions cannot guarantee a value for TOWER_X_BMS_SOFTWARE_VERSION. This makes it impossible to calculate the number of towers based on this values. - The new Register was not present in the initial test version but should have been available since the first container shipment. * **Time-of-Use App Octopus Go** * Energy Scheduler v2 + EVSE: general improvements - Sinexcel: use static imports; drop unused EventTopics - Energy Scheduler v2 - Fix unique ID per EnergyScheduleHandler - EVSE - ClusterCtrl: move logic to testable static method - SingleCtrl: implement SESSION_ENERGY, manualEnergySessionLimit config property and SESSION_LIMIT_REACHED - KEBA: separte hardware wiring from S10 configuration * UI * Refactor Fixdigitaloutput modal with formGroup * Creating unit testable form with formGroup for modals * EVSE UI Improvements: add mode SURPLUS * fix display of dimming in grid * Hiding and showing of dimming in grid history was not applied right * Replace manual component configurator with Estimated Configuration response * Use fallback LIGHT_MODE for them popover selection * Using fallback theme `LIGHT` if popover has been closed without choosing a theme * Change tooltip display state for heatpump * Fixing tooltip displayed heatpump state * Change chartjs hover over pointer style - Improving chartjs hover mode, point out currently selected timestamps respective datasets value - Refactoring currentEdge observable to signal -> resolving lifecycle issue * fix chartjs legend display error Legend Labels not generated --------- Co-authored-by: Stefan Feilmeier <[email protected]> Co-authored-by: Sagar Venu <[email protected]> Co-authored-by: Michael Grill <[email protected]> Co-authored-by: Sebastian Asen <[email protected]> Co-authored-by: Lukas Rieger <[email protected]> Co-authored-by: Kai Jeschek <[email protected]>
1 parent 2089864 commit 580db62

File tree

203 files changed

+4831
-1876
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

203 files changed

+4831
-1876
lines changed

io.openems.common/src/io/openems/common/oem/DummyOpenemsEdgeOem.java

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public SystemUpdateParams getSystemUpdateParams() {
7272
.put("App.TimeOfUseTariff.ENTSO-E", "") //
7373
.put("App.TimeOfUseTariff.GroupeE", "") //
7474
.put("App.TimeOfUseTariff.Hassfurt", "") //
75+
.put("App.TimeOfUseTariff.OctopusGo", "") //
7576
.put("App.TimeOfUseTariff.RabotCharge", "") //
7677
.put("App.TimeOfUseTariff.Stromdao", "") //
7778
.put("App.TimeOfUseTariff.Swisspower", "") //

io.openems.edge.application/EdgeApp.bndrun

+2
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@
199199
bnd.identity;id='io.openems.edge.timeofusetariff.entsoe',\
200200
bnd.identity;id='io.openems.edge.timeofusetariff.groupe',\
201201
bnd.identity;id='io.openems.edge.timeofusetariff.hassfurt',\
202+
bnd.identity;id='io.openems.edge.timeofusetariff.manual',\
202203
bnd.identity;id='io.openems.edge.timeofusetariff.rabotcharge',\
203204
bnd.identity;id='io.openems.edge.timeofusetariff.swisspower',\
204205
bnd.identity;id='io.openems.edge.timeofusetariff.tibber',\
@@ -393,6 +394,7 @@
393394
io.openems.edge.timeofusetariff.entsoe;version=snapshot,\
394395
io.openems.edge.timeofusetariff.groupe;version=snapshot,\
395396
io.openems.edge.timeofusetariff.hassfurt;version=snapshot,\
397+
io.openems.edge.timeofusetariff.manual;version=snapshot,\
396398
io.openems.edge.timeofusetariff.rabotcharge;version=snapshot,\
397399
io.openems.edge.timeofusetariff.swisspower;version=snapshot,\
398400
io.openems.edge.timeofusetariff.tibber;version=snapshot,\

io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java

+35-11
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,8 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId
329329
RACK_NUMBER_OF_BATTERY_BCU(Doc.of(OpenemsType.INTEGER) //
330330
.unit(Unit.NONE) //
331331
.accessMode(AccessMode.READ_ONLY) //
332-
.text("Count Of The Connected BCU")),
332+
.text("Count Of The Connected BCU") //
333+
.onChannelChange(BatteryFeneconHomeImpl::updateNumberOfTowersAndModules)),
333334
RACK_NUMBER_OF_CELLS_IN_SERIES_PER_MODULE(Doc.of(OpenemsType.INTEGER) //
334335
.unit(Unit.NONE) //
335336
.accessMode(AccessMode.READ_ONLY) //
@@ -633,32 +634,27 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId
633634
TOWER_4_BMS_SOFTWARE_VERSION(new IntegerDoc() //
634635
.unit(Unit.NONE) //
635636
.accessMode(AccessMode.READ_ONLY) //
636-
.text("Bms software version of fifth tower") //
637-
.onChannelChange(BatteryFeneconHomeImpl::updateNumberOfTowersAndModules)),
637+
.text("Bms software version of fifth tower")),
638638

639639
TOWER_3_BMS_SOFTWARE_VERSION(new IntegerDoc() //
640640
.unit(Unit.NONE) //
641641
.accessMode(AccessMode.READ_ONLY) //
642-
.text("Bms software version of fourth tower") //
643-
.onChannelChange(BatteryFeneconHomeImpl::updateNumberOfTowersAndModules)),
642+
.text("Bms software version of fourth tower")),
644643

645644
TOWER_2_BMS_SOFTWARE_VERSION(new IntegerDoc() //
646645
.unit(Unit.NONE) //
647646
.accessMode(AccessMode.READ_ONLY) //
648-
.text("Bms software version of third tower") //
649-
.onChannelChange(BatteryFeneconHomeImpl::updateNumberOfTowersAndModules)),
647+
.text("Bms software version of third tower")),
650648

651649
TOWER_1_BMS_SOFTWARE_VERSION(new IntegerDoc() //
652650
.unit(Unit.NONE) //
653651
.accessMode(AccessMode.READ_ONLY) //
654-
.text("Bms software version of second tower") //
655-
.onChannelChange(BatteryFeneconHomeImpl::updateNumberOfTowersAndModules)),
652+
.text("Bms software version of second tower")),
656653

657654
TOWER_0_BMS_SOFTWARE_VERSION(new IntegerDoc() //
658655
.unit(Unit.NONE) //
659656
.accessMode(AccessMode.READ_ONLY) //
660-
.text("Bms software version of first tower") //
661-
.onChannelChange(BatteryFeneconHomeImpl::updateNumberOfTowersAndModules)),
657+
.text("Bms software version of first tower")),
662658

663659
BATTERY_HARDWARE_TYPE(Doc.of(BatteryFeneconHomeHardwareType.values()) //
664660
.onChannelChange(BatteryFeneconHomeImpl::updateNumberOfTowersAndModules)),
@@ -785,4 +781,32 @@ public default Value<Boolean> getLowMinVoltageFaultBatteryStopped() {
785781
public default void _setLowMinVoltageFaultBatteryStopped(boolean value) {
786782
this.getLowMinVoltageFaultBatteryStoppedChannel().setNextValue(value);
787783
}
784+
785+
/**
786+
* Gets the Channel for {@link ChannelId#RACK_NUMBER_OF_BATTERY_BCU}.
787+
*
788+
* @return the Channel
789+
*/
790+
public default IntegerReadChannel getRackNumberOfBatteryBcuChannel() {
791+
return this.channel(ChannelId.RACK_NUMBER_OF_BATTERY_BCU);
792+
}
793+
794+
/**
795+
* Gets the Channel for {@link ChannelId#NUMBER_OF_TOWERS}.
796+
*
797+
* @return the Channel
798+
*/
799+
public default IntegerReadChannel getNumberOfTowersChannel() {
800+
return this.channel(ChannelId.NUMBER_OF_TOWERS);
801+
}
802+
803+
/**
804+
* Internal method to set the 'nextValue' on {@link ChannelId#NUMBER_OF_TOWERS}
805+
* Channel.
806+
*
807+
* @param value the next value
808+
*/
809+
public default void _setNumberOfTowers(Integer value) {
810+
this.getNumberOfTowersChannel().setNextValue(value);
811+
}
788812
}

io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java

+40-80
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import static io.openems.edge.bridge.modbus.api.ModbusUtils.FunctionCode.FC3;
77

88
import java.time.Instant;
9-
import java.util.List;
109
import java.util.concurrent.atomic.AtomicReference;
1110
import java.util.function.Consumer;
1211

@@ -59,7 +58,6 @@
5958
import io.openems.edge.common.channel.ChannelId.ChannelIdImpl;
6059
import io.openems.edge.common.channel.ChannelUtils;
6160
import io.openems.edge.common.channel.Doc;
62-
import io.openems.edge.common.channel.IntegerReadChannel;
6361
import io.openems.edge.common.channel.internal.OpenemsTypeDoc;
6462
import io.openems.edge.common.component.ComponentManager;
6563
import io.openems.edge.common.component.OpenemsComponent;
@@ -88,6 +86,7 @@ public class BatteryFeneconHomeImpl extends AbstractOpenemsModbusComponent imple
8886
public static final int DEFAULT_CRITICAL_MIN_VOLTAGE = 2800;
8987
protected static final int TIMEOUT = 600; // [10 minutes in seconds]
9088
private Instant timeCriticalMinVoltage;
89+
private Integer lastKnownMinVoltage;
9190

9291
protected final StateMachine stateMachine = new StateMachine(State.UNDEFINED);
9392

@@ -149,8 +148,8 @@ public void handleEvent(Event event) {
149148
this.batteryProtection.apply();
150149
break;
151150
case EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE:
152-
this.checkCriticalMinVoltage();
153151
this.handleStateMachine();
152+
this.checkCriticalMinVoltage();
154153
break;
155154
}
156155
}
@@ -465,44 +464,21 @@ public StartStop getStartStopTarget() {
465464
* associated channel value could be read with the aid of
466465
* {@link ChannelUtils#getValues}. However, startup time is once again involved
467466
* in this process. This indicates that the last callback will have been made
468-
* before the record is set. Furthermore, there is no certainty that the
469-
* "software version channel value change" will occur, making it unlikely for
470-
* this to trigger a callback.
467+
* before the record is set.
471468
*/
472469
protected synchronized void updateNumberOfTowersAndModules() {
473470
Channel<Integer> numberOfModulesPerTowerChannel = this
474471
.channel(BatteryFeneconHome.ChannelId.NUMBER_OF_MODULES_PER_TOWER);
475472
var numberOfModulesPerTowerOpt = numberOfModulesPerTowerChannel.value();
476473

477-
// Were all required registers read?
478474
if (!numberOfModulesPerTowerOpt.isDefined()) {
479475
return;
480476
}
481477

482-
// Evaluate the total number of towers by reading the software versions of
483-
// towers 2 and 3: they are '0' when the respective tower is not available.
484-
final var softwareVersionlist = List.of(//
485-
BatteryFeneconHome.ChannelId.TOWER_0_BMS_SOFTWARE_VERSION, //
486-
BatteryFeneconHome.ChannelId.TOWER_1_BMS_SOFTWARE_VERSION, //
487-
BatteryFeneconHome.ChannelId.TOWER_2_BMS_SOFTWARE_VERSION, //
488-
BatteryFeneconHome.ChannelId.TOWER_3_BMS_SOFTWARE_VERSION, //
489-
BatteryFeneconHome.ChannelId.TOWER_4_BMS_SOFTWARE_VERSION//
490-
) //
491-
.stream() //
492-
.map(c -> {
493-
IntegerReadChannel channel = this.channel(c);
494-
return channel.value().get();
495-
}) //
496-
.toList();
497-
498-
final var numberOfTowers = calculateTowerNumberFromSoftwareVersion(softwareVersionlist);
499-
500-
// Write 'TOWER_NUMBER' Debug Channel
501-
Channel<?> numberOfTowersChannel = this.channel(BatteryFeneconHome.ChannelId.NUMBER_OF_TOWERS);
502-
numberOfTowersChannel.setNextValue(numberOfTowers);
503-
if (numberOfTowers == null) {
504-
return;
505-
}
478+
// Register for NumberOfBatteryBcu was not existing in the very first firmware -
479+
// at least one tower is used.
480+
final var numberOfTowers = this.getRackNumberOfBatteryBcuChannel().getNextValue().orElse(1);
481+
this._setNumberOfTowers(numberOfTowers);
506482

507483
final var moduleMaxVoltage = this.getBatteryHardwareType().moduleMaxVoltage;
508484
final var moduleMinVoltage = this.getBatteryHardwareType().moduleMinVoltage;
@@ -523,21 +499,6 @@ protected synchronized void updateNumberOfTowersAndModules() {
523499
}
524500
}
525501

526-
protected static Integer calculateTowerNumberFromSoftwareVersion(List<Integer> versionList) {
527-
var numberOfTowers = 0;
528-
for (var version : versionList) {
529-
if (version == null) {
530-
return null;
531-
}
532-
if (version == 0 || version == 256) {
533-
// Ensure number of towers is never '0' if registers are not null.
534-
return Math.max(1, numberOfTowers);
535-
}
536-
numberOfTowers++;
537-
}
538-
return numberOfTowers;
539-
}
540-
541502
private int lastNumberOfTowers = 0;
542503
private int lastNumberOfModulesPerTower = 0;
543504

@@ -1064,55 +1025,54 @@ public ModbusProtocol getDefinedModbusProtocol() {
10641025
}
10651026

10661027
private void checkCriticalMinVoltage() {
1067-
1068-
final var subState = getMinVoltageSubState(DEFAULT_CRITICAL_MIN_VOLTAGE,
1069-
this.getMinCellVoltage().orElse(Integer.MAX_VALUE), this.getCurrent().orElse(0));
1028+
final Integer currentMinVoltage;
1029+
if (this.getMinCellVoltage().isDefined()) {
1030+
currentMinVoltage = this.getMinCellVoltage().get();
1031+
this.lastKnownMinVoltage = currentMinVoltage;
1032+
} else {
1033+
currentMinVoltage = this.lastKnownMinVoltage;
1034+
}
1035+
final var subState = getMinVoltageSubState(DEFAULT_CRITICAL_MIN_VOLTAGE, currentMinVoltage,
1036+
this.getCurrent().get());
10701037
var now = Instant.now(this.componentManager.getClock());
10711038

1039+
boolean batWillStoppWarning = false;
1040+
boolean batIsStoppingFault = false;
1041+
boolean batStoppedFault = false;
1042+
10721043
switch (subState) {
1073-
case ABOVE_LIMIT -> {
1074-
this._setLowMinVoltageFault(false);
1075-
this._setLowMinVoltageWarning(false);
1076-
this._setLowMinVoltageFaultBatteryStopped(false);
1077-
this.timeCriticalMinVoltage = null;
1078-
}
1044+
case ABOVE_LIMIT -> this.timeCriticalMinVoltage = null;
10791045
case BELOW_LIMIT -> {
1080-
10811046
if (this.stateMachine.getCurrentState() == StateMachine.State.STOPPED) {
1082-
this._setLowMinVoltageFaultBatteryStopped(true);
1083-
this._setLowMinVoltageFault(false);
1084-
this._setLowMinVoltageWarning(false);
1085-
return;
1086-
}
1087-
1088-
this._setLowMinVoltageFaultBatteryStopped(false);
1089-
1090-
if (this.timeCriticalMinVoltage == null) {
1091-
this.timeCriticalMinVoltage = now;
1092-
}
1093-
1094-
if (this.timeCriticalMinVoltage.isBefore(now.minusSeconds(TIMEOUT))) {
1095-
this._setLowMinVoltageFault(true);
1096-
this._setLowMinVoltageWarning(false);
1097-
return;
1047+
batStoppedFault = true;
1048+
} else {
1049+
if (this.timeCriticalMinVoltage == null) {
1050+
this.timeCriticalMinVoltage = now;
1051+
}
1052+
batIsStoppingFault = this.timeCriticalMinVoltage.isBefore(now.minusSeconds(TIMEOUT));
1053+
batWillStoppWarning = !batIsStoppingFault;
10981054
}
1099-
this._setLowMinVoltageWarning(true);
1100-
this._setLowMinVoltageFault(false);
11011055
}
11021056
case BELOW_LIMIT_CHARGING -> {
1103-
this._setLowMinVoltageFaultBatteryStopped(false);
1104-
this._setLowMinVoltageWarning(true);
1105-
this._setLowMinVoltageFault(false);
1057+
batWillStoppWarning = true;
11061058
this.timeCriticalMinVoltage = null;
11071059
}
11081060
}
1061+
1062+
this._setLowMinVoltageWarning(batWillStoppWarning);
1063+
this._setLowMinVoltageFault(batIsStoppingFault);
1064+
this._setLowMinVoltageFaultBatteryStopped(batStoppedFault);
11091065
}
11101066

1111-
protected static MinVoltageSubState getMinVoltageSubState(int minVoltageLimit, int currentMinVoltage, int current) {
1067+
protected static MinVoltageSubState getMinVoltageSubState(int minVoltageLimit, Integer currentMinVoltage,
1068+
Integer current) {
1069+
if (currentMinVoltage == null) {
1070+
return MinVoltageSubState.ABOVE_LIMIT;
1071+
}
11121072
if (currentMinVoltage > minVoltageLimit) {
11131073
return MinVoltageSubState.ABOVE_LIMIT;
11141074
}
1115-
if (current < 0) {
1075+
if (current != null && current < 0) {
11161076
return MinVoltageSubState.BELOW_LIMIT_CHARGING;
11171077
}
11181078
return MinVoltageSubState.BELOW_LIMIT;
@@ -1123,4 +1083,4 @@ protected static enum MinVoltageSubState {
11231083
BELOW_LIMIT, //
11241084
BELOW_LIMIT_CHARGING; //
11251085
}
1126-
}
1086+
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.openems.edge.battery.fenecon.home.statemachine;
22

3-
import io.openems.edge.battery.fenecon.home.BatteryFeneconHomeImpl;
43
import io.openems.edge.battery.fenecon.home.statemachine.StateMachine.State;
54
import io.openems.edge.common.startstop.StartStop;
65
import io.openems.edge.common.statemachine.StateHandler;
@@ -11,23 +10,7 @@ public class StoppedHandler extends StateHandler<State, Context> {
1110
public State runAndGetNextState(Context context) {
1211
final var battery = context.getParent();
1312

14-
final int currentMinVoltage;
15-
if (battery.getMinCellVoltage().isDefined()) {
16-
currentMinVoltage = battery.getMinCellVoltage().get();
17-
} else {
18-
currentMinVoltage = battery.getMinCellVoltageChannel().getPastValues().lastEntry().getValue()
19-
.orElse(Integer.MAX_VALUE);
20-
}
21-
22-
if (currentMinVoltage < BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE) {
23-
battery._setLowMinVoltageFaultBatteryStopped(true);
24-
} else {
25-
battery._setLowMinVoltageFaultBatteryStopped(false);
26-
}
27-
28-
// Mark as started
2913
battery._setStartStop(StartStop.STOP);
3014
return State.STOPPED;
3115
}
32-
3316
}

0 commit comments

Comments
 (0)