Skip to content

Commit a6d051b

Browse files
sfeilmeiermichaelgrillhuseyinsahtsebastianasenpooran-c
authored
FEMS Backport 2023-09-25 (OpenEMS#2368)
* SolarLog: fix setting/calculation of active production energy Added missing event subscription for "TOPIC_CYCLE_AFTER_PROCESS_IMAGE" to calculate ActiveProductionEnergy & removed reading this value via modbus * App Center: Refactor App.Api.ModbusTcp * ElementToChannelConverter: Add SET_NULL_FOR_DEFAULT * GoodWe BatteryInverter: validate compatible FENECON Home Battery - Check relation between goodwe inverter and fenecon home battery - Calculate Number of Modules for GoodWe depending on the battery values * AppCenter: Home 20 & 30 Implementation of Apps for Home 20 & 30 * GoodWe BatteryInverter: improve Warning/Error State descriptions - Fixes mapping of Registers and bits. - New format of the description: [Fault English | Fault German | Suggested solution German] * KACO: fit ActivePower set point percentage -100 to 100 The filter is from -100 to +100 factor. * FENECON Home: fix switching Battery Start Up Relay ON This fixes a bug, that keeps a Home Battery from getting started via the Battery Start Up Relay. The `ModbusCommunicationFailed` State has level FAULT. Because of this `battery.hasFaults()` is never true when the battery is turned off, so the State-Machine immediately goes to ERROR state and never enters GO_RUNNING when the `ModbusCommunicationFailed` State is set. It still works in many cases after a full cold-start (e.g. by restarting OpenEMS service), though, because then `ModbusCommunicationFailed` State is not set _yet_. This fix handles `ModbusCommunicationFailed` State separately in UNDEFINED state and waits for modbus communication in GO_RUNNING. In case of an overall timeout - i.e. the battery cannot be started - `MaxStartAttemptsFailed` is still triggered. In the process of refactoring the GO_RUNNING handler, I implemented a full Sub-State-Machine including nice debug log messages and a Mermaid chart. I also touched the OpenEMS testing framework to force set a Channel (to cirumvent channel debounce settings) * Modbus Bridge: hide error logs on first try (on configured low LogVerbosity) This avoids unnecessary logs and resemble the logging behavour of the previous Modbus bridge * UI: Fix notch color for all OEM's Replace notch color with OEM's background-color * App Center: return created dependencies Necessarry for selecting AC-Meter in Home IBN and update it. * Meter SDM630: calculate active production/consumption energy manually * Battery: standardize debugLog * UI: Splitting index into login and overview * Solves Community Issue [OpenEMS](https://community.openems.io/t/setup-ide-for-openems-ui-not-working/1923/5?u=stefan.feilmeier) * Due to lifecycle issues and routing issues after login, the `login` and the `edges-overview` get split into two components. * If only one `edge` assigned to the user -> the user logs in, the overview wont be shown, but because this is happening in the same component, routing forward creates a problem in `live`. The currentData is subscribed but not shown. Only on reload it worked again. * This also fits the purpose of reducing unnecessary requests. --------- Co-authored-by: Michael Grill <[email protected]> Co-authored-by: Hueseyin Sahutoglu <[email protected]> Co-authored-by: Sebastian Asen <[email protected]> Co-authored-by: Pooran Chandrashekaraiah <[email protected]> Co-authored-by: Anas Shetla <[email protected]> Co-authored-by: Mohammadmahdi Ataei <[email protected]>
1 parent a286584 commit a6d051b

File tree

83 files changed

+4687
-736
lines changed

Some content is hidden

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

83 files changed

+4687
-736
lines changed

cnf/pom.xml

+6-6
Original file line numberDiff line numberDiff line change
@@ -315,12 +315,6 @@
315315
<artifactId>jsoup</artifactId>
316316
<version>1.16.1</version>
317317
</dependency>
318-
<dependency>
319-
<!-- Used by Apache Felix Web Management Console -->
320-
<groupId>org.owasp.encoder</groupId>
321-
<artifactId>encoder</artifactId>
322-
<version>1.2.3</version>
323-
</dependency>
324318
<dependency>
325319
<groupId>org.osgi</groupId>
326320
<artifactId>osgi.annotation</artifactId>
@@ -396,6 +390,12 @@
396390
<artifactId>org.osgi.util.promise</artifactId>
397391
<version>1.3.0</version>
398392
</dependency>
393+
<dependency>
394+
<!-- Used by Apache Felix Web Management Console -->
395+
<groupId>org.owasp.encoder</groupId>
396+
<artifactId>encoder</artifactId>
397+
<version>1.2.3</version>
398+
</dependency>
399399
<dependency>
400400
<!-- Used by io.openems.backend.metadata.odoo -->
401401
<groupId>org.postgresql</groupId>

io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/OnRequest.java

-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import io.openems.common.jsonrpc.response.EdgeRpcResponse;
4343
import io.openems.common.jsonrpc.response.GetEdgeResponse;
4444
import io.openems.common.jsonrpc.response.GetEdgesResponse;
45-
import io.openems.common.jsonrpc.response.GetEdgesResponse.EdgeMetadata;
4645
import io.openems.common.session.Role;
4746
import io.openems.common.utils.JsonUtils;
4847

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.openems.common.function;
2+
3+
import java.util.function.Consumer;
4+
5+
/**
6+
* This interface is similar to the java.util interface {@link Consumer}.
7+
* Difference is, that it limits the type to a primitive, non-nullable boolean.
8+
*/
9+
@FunctionalInterface
10+
public interface BooleanConsumer {
11+
12+
/**
13+
* Performs this operation on the given argument.
14+
*
15+
* @param value the input argument
16+
*/
17+
public void accept(boolean value);
18+
19+
}

io.openems.edge.battery.api/src/io/openems/edge/battery/api/Battery.java

+23
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable;
1414
import io.openems.edge.common.modbusslave.ModbusType;
1515
import io.openems.edge.common.startstop.StartStoppable;
16+
import io.openems.edge.common.statemachine.AbstractStateMachine;
1617

1718
/**
1819
* Represents a Battery.
@@ -734,4 +735,26 @@ public default void _setMaxCellVoltage(Integer value) {
734735
public default void _setMaxCellVoltage(int value) {
735736
this.getMaxCellVoltageChannel().setNextValue(value);
736737
}
738+
739+
/**
740+
* Generates a default DebugLog message for {@link Battery} implementations with
741+
* a State-Machine.
742+
*
743+
* @param battery the {@link Battery}
744+
* @param stateMachine the actual StateMachine (extends
745+
* {@link AbstractStateMachine})
746+
* @return a debug log String
747+
*/
748+
public static String generateDebugLog(Battery battery, AbstractStateMachine<?, ?> stateMachine) {
749+
return new StringBuilder() //
750+
.append(stateMachine.debugLog()) //
751+
.append("|SoC:").append(battery.getSoc()) //
752+
.append("|Actual:").append(battery.getVoltage()) //
753+
.append(";").append(battery.getCurrent()) //
754+
.append("|Charge:").append(battery.getChargeMaxVoltage()) //
755+
.append(";").append(battery.getChargeMaxCurrent()) //
756+
.append("|Discharge:").append(battery.getDischargeMinVoltage()) //
757+
.append(";").append(battery.getDischargeMaxCurrent()) //
758+
.toString();
759+
}
737760
}

io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130Impl.java

+1-10
Original file line numberDiff line numberDiff line change
@@ -172,16 +172,7 @@ private void handleStateMachine() {
172172

173173
@Override
174174
public String debugLog() {
175-
return new StringBuilder() //
176-
.append(this.stateMachine.debugLog()) //
177-
.append("|SoC:").append(this.getSoc()) //
178-
.append("|Actual:").append(this.getVoltage()) //
179-
.append(";").append(this.getCurrent()) //
180-
.append("|Charge:").append(this.getChargeMaxVoltage()) //
181-
.append(";").append(this.getChargeMaxCurrent()) //
182-
.append("|Discharge:").append(this.getDischargeMinVoltage()) //
183-
.append(";").append(this.getDischargeMaxCurrent()) //
184-
.toString();
175+
return Battery.generateDebugLog(this, this.stateMachine);
185176
}
186177

187178
@Override

io.openems.edge.battery.fenecon.commercial/src/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImpl.java

+1-10
Original file line numberDiff line numberDiff line change
@@ -1202,16 +1202,7 @@ protected synchronized void updateSoc() {
12021202

12031203
@Override
12041204
public String debugLog() {
1205-
return new StringBuilder() //
1206-
.append(this.stateMachine.debugLog()) //
1207-
.append("|SoC:").append(this.getSoc()) //
1208-
.append("|Actual:").append(this.getVoltage()) //
1209-
.append(";").append(this.getCurrent()) //
1210-
.append("|Charge:").append(this.getChargeMaxVoltage()) //
1211-
.append(";").append(this.getChargeMaxCurrent()) //
1212-
.append("|Discharge:").append(this.getDischargeMinVoltage()) //
1213-
.append(";").append(this.getDischargeMaxCurrent()) //
1214-
.toString();
1205+
return Battery.generateDebugLog(this, this.stateMachine);
12151206
}
12161207

12171208
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# State-Machine for GO_RUNNING
2+
3+
```mermaid
4+
graph TD
5+
UNDEFINED -->|bmsControl==UNDEFINED| INITIAL_WAIT_FOR_BMS_CONTROL
6+
UNDEFINED -->|bmsControl==false| START_UP_RELAY_ON
7+
UNDEFINED -->|bmsControl==true| START_UP_RELAY_OFF
8+
9+
INITIAL_WAIT_FOR_BMS_CONTROL -->|bmsControl==false or timeout| START_UP_RELAY_ON
10+
INITIAL_WAIT_FOR_BMS_CONTROL -->|bmsControl==true| START_UP_RELAY_OFF
11+
12+
START_UP_RELAY_ON -->|startUpRelay==true| START_UP_RELAY_HOLD
13+
START_UP_RELAY_ON -->|timeout| RETRY_MODBUS_COMMUNICATION
14+
15+
START_UP_RELAY_HOLD -->|wait 10s| START_UP_RELAY_OFF
16+
START_UP_RELAY_OFF -->|startUpRelay!=true| RETRY_MODBUS_COMMUNICATION
17+
18+
RETRY_MODBUS_COMMUNICATION --> WAIT_FOR_BMS_CONTROL
19+
20+
WAIT_FOR_BMS_CONTROL -->|bmsControl==true| WAIT_FOR_MODBUS_COMMUNICATION
21+
WAIT_FOR_MODBUS_COMMUNICATION -->|modbusCommunicationFailed==false| FINISHED
22+
```
23+
24+
View using Mermaid, e.g. https://mermaid-js.github.io/mermaid-live-editor

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

+36-14
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.openems.edge.common.channel.Channel;
1212
import io.openems.edge.common.channel.Doc;
1313
import io.openems.edge.common.channel.IntegerDoc;
14+
import io.openems.edge.common.channel.IntegerReadChannel;
1415
import io.openems.edge.common.channel.value.Value;
1516
import io.openems.edge.common.component.OpenemsComponent;
1617
import io.openems.edge.common.startstop.StartStop;
@@ -29,21 +30,17 @@ public default Channel<Boolean> getBmsControlChannel() {
2930

3031
/**
3132
* Gets the BmsControl, see {@link ChannelId#BMS_CONTROL}.
33+
*
34+
* <ul>
35+
* <li>true: is started
36+
* <li>false: is not started
37+
* <li>null: undefined (e.g. Modbus Communication Failed)
38+
* </ul>
3239
*
3340
* @return the Channel {@link Value}
3441
*/
35-
public default Value<Boolean> getBmsControl() {
36-
return this.getBmsControlChannel().value();
37-
}
38-
39-
/**
40-
* Internal method to set the 'nextValue' on {@link ChannelId#BMS_CONTROL}
41-
* Channel.
42-
*
43-
* @param value the next value
44-
*/
45-
public default void _setBmsControl(Boolean value) {
46-
this.getBmsControlChannel().setNextValue(value);
42+
public default Boolean getBmsControl() {
43+
return this.getBmsControlChannel().value().get();
4744
}
4845

4946
/**
@@ -64,6 +61,25 @@ public default BatteryFeneconHomeHardwareType getBatteryHardwareType() {
6461
return this.getBatteryHardwareTypeChannel().value().asEnum();
6562
}
6663

64+
/**
65+
* Gets the Channel for {@link ChannelId#NUMBER_OF_MODULES_PER_TOWER}.
66+
*
67+
* @return the Channel
68+
*/
69+
public default IntegerReadChannel getNumberOfModulesPerTowerChannel() {
70+
return this.channel(BatteryFeneconHome.ChannelId.NUMBER_OF_MODULES_PER_TOWER);
71+
}
72+
73+
/**
74+
* Gets the number of modules per tower. See
75+
* {@link ChannelId#NUMBER_OF_MODULES_PER_TOWER}.
76+
*
77+
* @return the Channel {@link Value}
78+
*/
79+
public default Value<Integer> getNumberOfModulesPerTower() {
80+
return this.getNumberOfModulesPerTowerChannel().value();
81+
}
82+
6783
/**
6884
* Gets the target Start/Stop mode from config or StartStop-Channel.
6985
*
@@ -633,8 +649,14 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId
633649
BATTERY_HARDWARE_TYPE(Doc.of(BatteryFeneconHomeHardwareType.values()) //
634650
.<BatteryFeneconHomeImpl>onChannelChange(BatteryFeneconHomeImpl::updateNumberOfTowersAndModules)),
635651

636-
BMS_CONTROL(Doc.of(OpenemsType.BOOLEAN) //
637-
.text("BMS CONTROL(1: Shutdown, 0: no action)")),
652+
/**
653+
* true: started; false: not-started.
654+
*
655+
* <p>
656+
* NOTE that Modbus Bit is inverted: 1: is-not-started; 0: is-started
657+
*/
658+
BMS_CONTROL(Doc.of(OpenemsType.BOOLEAN)),
659+
638660
STATE_MACHINE(Doc.of(State.values()) //
639661
.text("Current State of State-Machine")), //
640662
RUN_FAILED(Doc.of(Level.FAULT) //

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

+73-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.openems.edge.battery.fenecon.home;
22

3+
import static io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent.BitConverter.INVERT;
34
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1;
45

56
import java.util.Objects;
@@ -59,6 +60,7 @@
5960
import io.openems.edge.common.component.OpenemsComponent;
6061
import io.openems.edge.common.event.EdgeEventConstants;
6162
import io.openems.edge.common.modbusslave.ModbusSlave;
63+
import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable;
6264
import io.openems.edge.common.modbusslave.ModbusSlaveTable;
6365
import io.openems.edge.common.startstop.StartStop;
6466
import io.openems.edge.common.startstop.StartStoppable;
@@ -81,8 +83,9 @@ public class BatteryFeneconHomeImpl extends AbstractOpenemsModbusComponent imple
8183
private static final String SERIAL_NUMBER_PREFIX_BMS = "519100001009";
8284
private static final String SERIAL_NUMBER_PREFIX_MODULE = "519110001210";
8385

86+
protected final StateMachine stateMachine = new StateMachine(State.UNDEFINED);
87+
8488
private final Logger log = LoggerFactory.getLogger(BatteryFeneconHomeImpl.class);
85-
private final StateMachine stateMachine = new StateMachine(State.UNDEFINED);
8689
private final AtomicReference<StartStop> startStopTarget = new AtomicReference<>(StartStop.UNDEFINED);
8790

8891
@Reference
@@ -98,7 +101,7 @@ protected void setModbus(BridgeModbus modbus) {
98101
}
99102

100103
private Config config;
101-
private BatteryProtection batteryProtection; // set in constructor
104+
private BatteryProtection batteryProtection;
102105

103106
public BatteryFeneconHomeImpl() {
104107
super(//
@@ -161,14 +164,14 @@ private void handleStateMachine() {
161164
this._setStartStop(StartStop.UNDEFINED);
162165

163166
// Prepare Context
164-
BooleanWriteChannel batteryStartUpRelayChannel;
165-
try {
166-
batteryStartUpRelayChannel = this.componentManager
167-
.getChannel(ChannelAddress.fromString(this.config.batteryStartUpRelay()));
168-
} catch (IllegalArgumentException | OpenemsNamedException e1) {
169-
batteryStartUpRelayChannel = null;
170-
}
171-
var context = new Context(this, batteryStartUpRelayChannel);
167+
var batteryStartUpRelayChannel = this.getBatteryStartUpRelayChannel();
168+
var batteryStartUpRelay = batteryStartUpRelayChannel != null ? batteryStartUpRelayChannel.value().get() : null;
169+
var context = new Context(this, this.componentManager.getClock(), //
170+
batteryStartUpRelay,
171+
(value) -> setBatteryStartUpRelay(batteryStartUpRelayChannel, value, this::logInfo, this::logWarn), //
172+
this.getBmsControl(), //
173+
this.getModbusCommunicationFailed(), //
174+
() -> this.retryModbusCommunication());
172175

173176
// Call the StateMachine
174177
try {
@@ -328,7 +331,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
328331
m(BatteryFeneconHome.ChannelId.NUMBER_OF_MODULES_PER_TOWER, new UnsignedWordElement(10024))), //
329332
new FC3ReadRegistersTask(44000, Priority.HIGH, //
330333
m(new BitsWordElement(44000, this) //
331-
.bit(0, BatteryFeneconHome.ChannelId.BMS_CONTROL)) //
334+
.bit(0, BatteryFeneconHome.ChannelId.BMS_CONTROL, INVERT)) //
332335
));
333336
}
334337

@@ -418,24 +421,16 @@ private ChannelIdImpl generateTowerChannel(int tower, String channelIdSuffix, Le
418421

419422
@Override
420423
public String debugLog() {
421-
return new StringBuilder() //
422-
.append(this.stateMachine.debugLog()) //
423-
.append("|SoC:").append(this.getSoc()) //
424-
.append("|Actual:").append(this.getVoltage()) //
425-
.append(";").append(this.getCurrent()) //
426-
.append("|Charge:").append(this.getChargeMaxVoltage()) //
427-
.append(";").append(this.getChargeMaxCurrent()) //
428-
.append("|Discharge:").append(this.getDischargeMinVoltage()) //
429-
.append(";").append(this.getDischargeMaxCurrent()) //
430-
.toString();
424+
return Battery.generateDebugLog(this, this.stateMachine);
431425
}
432426

433427
@Override
434428
public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) {
435429
return new ModbusSlaveTable(//
436430
OpenemsComponent.getModbusSlaveNatureTable(accessMode), //
437-
Battery.getModbusSlaveNatureTable(accessMode) //
438-
);
431+
Battery.getModbusSlaveNatureTable(accessMode), //
432+
ModbusSlaveNatureTable.of(BatteryFeneconHome.class, accessMode, 100) //
433+
.build());
439434
}
440435

441436
@Override
@@ -918,6 +913,61 @@ private static int extractNumber(int value, int length, int position) {
918913
return (1 << length) - 1 & value >> position - 1;
919914
}
920915

916+
private void logInfo(String message) {
917+
this.logInfo(this.log, message);
918+
}
919+
920+
private void logWarn(String message) {
921+
this.logWarn(this.log, message);
922+
}
923+
924+
/**
925+
* Gets the Battery-Start-Up-Relay Channel.
926+
*
927+
* @return {@link BooleanWriteChannel} or null
928+
*/
929+
private BooleanWriteChannel getBatteryStartUpRelayChannel() {
930+
try {
931+
var channel = this.componentManager
932+
.<BooleanWriteChannel>getChannel(ChannelAddress.fromString(this.config.batteryStartUpRelay()));
933+
return channel;
934+
} catch (Exception e) {
935+
this.logWarn("Unable to get Battery-Start-Up-Relay: " + e.getMessage());
936+
return null;
937+
}
938+
}
939+
940+
/**
941+
* Switch Battery-Start-Up-Relay ON or OFF.
942+
*
943+
* @param batteryStartUpRelayChannel the Battery-Start-Up-Relay
944+
* {@link BooleanWriteChannel}; or null
945+
* @param value true to switch the relay on; <br/>
946+
* false to switch the relay off
947+
* @param logInfo Consumer for log messages
948+
* @param logWarn Consumer for warn messages
949+
*/
950+
private static void setBatteryStartUpRelay(BooleanWriteChannel batteryStartUpRelayChannel, boolean value,
951+
Consumer<String> logInfo, Consumer<String> logWarn) {
952+
var valueString = value ? "ON" : "OFF";
953+
954+
// Validate availability of batteryStartUpRelay, otherwise ignore
955+
if (batteryStartUpRelayChannel == null) {
956+
logWarn.accept("Switching Battery Start Up Relay " + valueString + " failed. Relay is missing");
957+
return;
958+
}
959+
960+
// Switch StartUpRelay
961+
try {
962+
batteryStartUpRelayChannel.setNextWriteValue(value);
963+
logInfo.accept("Switching Battery Start Up Relay " + valueString //
964+
+ " [" + batteryStartUpRelayChannel.address() + "]");
965+
} catch (OpenemsNamedException e) {
966+
logWarn.accept("Switching Battery Start Up Relay " + valueString //
967+
+ " failed [" + batteryStartUpRelayChannel.address() + "]: " + e.getMessage());
968+
}
969+
}
970+
921971
/**
922972
* Generates prefix for Channel-IDs for Cell Temperature and Voltage channels.
923973
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@org.osgi.annotation.versioning.Version("1.0.0")
2+
@org.osgi.annotation.bundle.Export
3+
package io.openems.edge.battery.fenecon.home;

0 commit comments

Comments
 (0)