Skip to content

Commit 11bfb86

Browse files
[Edge] Victron ESS: keep configured capacity when BMS reports 0 (#3808)
- Fall back to the configured Config.capacity() when the battery reports 0 or null; otherwise use the battery-reported value as before - Switch OSGi Reference for Modbus Bridge to GenerateTargetsFromReferences - Fix duplicated Enum in ChargeStateEss - Fix possible NullPointerException in `_setDcDischargeEnergy()` - Change JUnit tests to Jupiter - Add ComponentTest for `VictronEssImpl` --------- Co-authored-by: Stefan Feilmeier <stefan.feilmeier@fenecon.de>
1 parent e7499cd commit 11bfb86

10 files changed

Lines changed: 76 additions & 49 deletions

File tree

io.openems.edge.victron/src/io/openems/edge/victron/enums/ChargeStateEss.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public enum ChargeStateEss implements OptionsEnum {
1616
ABSORPTION_REPEAT(5, "Absorption Repeat"), //
1717
ABSORPTION_FORCED(6, "Absorption Forced"), //
1818
EQUALIZE(7, "Equalize"), //
19-
BULK_STOPPED(1, "Bulk Stopped"), //
19+
BULK_STOPPED(8, "Bulk Stopped"), //
2020
UNKNOWN(9, "Unknown");
2121

2222
private final int value;

io.openems.edge.victron/src/io/openems/edge/victron/ess/Config.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@
4040
@AttributeDefinition(name = "Max Apparent Power", description = "Maximum apparent power in VA")
4141
int maxApparentPower() default 5000;
4242

43-
@AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
44-
String Modbus_target() default "(enabled=true)";
45-
4643
String webconsole_configurationFactory_nameHint() default "Victron Multiplus 2 ESS [{id}]";
4744

4845
}

io.openems.edge.victron/src/io/openems/edge/victron/ess/VictronEssImpl.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import static org.osgi.service.component.annotations.ReferencePolicy.STATIC;
2020
import static org.osgi.service.component.annotations.ReferencePolicyOption.GREEDY;
2121

22-
import org.osgi.service.cm.ConfigurationAdmin;
2322
import org.osgi.service.component.ComponentContext;
2423
import org.osgi.service.component.annotations.Activate;
2524
import org.osgi.service.component.annotations.Component;
@@ -35,6 +34,7 @@
3534
import io.openems.common.channel.AccessMode;
3635
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
3736
import io.openems.common.exceptions.OpenemsException;
37+
import io.openems.common.referencetarget.GenerateTargetsFromReferences;
3838
import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent;
3939
import io.openems.edge.bridge.modbus.api.BridgeModbus;
4040
import io.openems.edge.bridge.modbus.api.ModbusComponent;
@@ -97,24 +97,24 @@
9797
TOPIC_CYCLE_BEFORE_PROCESS_IMAGE, //
9898
TOPIC_CYCLE_BEFORE_CONTROLLERS //
9999
})
100+
@GenerateTargetsFromReferences("Modbus")
100101
public class VictronEssImpl extends AbstractOpenemsModbusComponent
101102
implements VictronEss, ManagedSinglePhaseEss, SinglePhaseEss, ManagedSymmetricEss, SymmetricEss, AsymmetricEss,
102103
ManagedAsymmetricEss, ModbusComponent, ModbusSlave, EventHandler, OpenemsComponent, TimedataProvider {
103104

104105
@Reference
105106
private Power power;
106107

107-
@Reference
108-
private ConfigurationAdmin cm;
109-
110108
@Reference(policy = STATIC, policyOption = GREEDY, cardinality = MANDATORY)
111109
private volatile Timedata timedata = null;
112110

113111
@Reference
114112
protected ComponentManager componentManager;
115113

116114
@Override
117-
@Reference(policy = STATIC, policyOption = GREEDY, cardinality = MANDATORY)
115+
@Reference(//
116+
policy = STATIC, policyOption = GREEDY, cardinality = MANDATORY, //
117+
target = "(&(id=${config.modbus_id})(enabled=true))")
118118
protected void setModbus(BridgeModbus modbus) {
119119
super.setModbus(modbus);
120120
}
@@ -202,11 +202,7 @@ public VictronEssImpl() {
202202
@Activate
203203
private void activate(ComponentContext context, Config config) throws OpenemsException {
204204
this.config = config;
205-
206-
if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm,
207-
"Modbus", config.modbus_id())) {
208-
return;
209-
}
205+
super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId());
210206

211207
// Set initial values from config
212208
this._setMaxApparentPower(config.maxApparentPower());
@@ -746,7 +742,6 @@ public SinglePhase getPhase() {
746742
* Negative values for Charge; positive for Discharge.
747743
*/
748744
private void calculateEnergy() {
749-
750745
var activeAcPower = this.getActivePower().get();
751746
if (activeAcPower == null) {
752747
// Not available
@@ -766,18 +761,23 @@ private void calculateEnergy() {
766761
this.calculateDischargeEnergy.update(0);
767762
}
768763

769-
// Capacity Channel is also needed for ESS
764+
// Capacity Channel is also needed for ESS. Some BMS report the installed
765+
// capacity as 0 or null; in that case fall back to the configured capacity
766+
// rather than overwriting the channel with 0, which would zero downstream
767+
// consumers such as the Time-of-Use optimizer's usable-energy calculation.
770768
if (this.battery != null) {
771-
this._setCapacity(this.battery.getCapacity().get());
769+
final var batteryCapacity = this.battery.getCapacity().get();
770+
setValue(this, SymmetricEss.ChannelId.CAPACITY, batteryCapacity != null && batteryCapacity > 0 //
771+
? batteryCapacity //
772+
: this.config.capacity());
773+
774+
this._setDcDischargeEnergy(this.battery.getDcDischargeEnergy().get());
775+
this._setDcChargeEnergy(this.battery.getDcChargeEnergy().get());
772776
}
773777

774778
if (this.batteryInverter != null) {
775779
this._setDcDischargePower(this.batteryInverter.getActivePower().get());
776780
}
777-
778-
this._setDcDischargeEnergy(this.battery.getDcDischargeEnergy().get());
779-
this._setDcChargeEnergy(this.battery.getDcChargeEnergy().get());
780-
781781
}
782782

783783
@Override

io.openems.edge.victron/test/io/openems/edge/dccharger/victron/VictronDcChargerImplTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package io.openems.edge.dccharger.victron;
22

3-
import static org.junit.Assert.assertNotNull;
3+
import static org.junit.jupiter.api.Assertions.assertNotNull;
44

5-
import org.junit.Test;
5+
import org.junit.jupiter.api.Test;
66

77
import io.openems.common.test.DummyConfigurationAdmin;
88
import io.openems.common.types.ChannelAddress;
@@ -130,7 +130,7 @@ public void test() throws Exception {
130130
public void testChannelIds() {
131131
var channelIds = VictronDcCharger.ChannelId.values();
132132
for (var channelId : channelIds) {
133-
assertNotNull("ChannelId " + channelId.name() + " should have a doc", channelId.doc());
133+
assertNotNull(channelId.doc(), "ChannelId " + channelId.name() + " should have a doc");
134134
}
135135
}
136136

io.openems.edge.victron/test/io/openems/edge/victron/battery/VictronCanBusBatteryImplTest.java renamed to io.openems.edge.victron/test/io/openems/edge/victron/battery/VictronBatteryImplTest.java

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
package io.openems.edge.victron.battery;
22

3-
import static org.junit.Assert.assertEquals;
4-
import static org.junit.Assert.assertNotNull;
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
55

6-
import org.junit.Test;
6+
import org.junit.jupiter.api.Test;
77

88
import io.openems.common.test.DummyConfigurationAdmin;
99
import io.openems.common.types.ChannelAddress;
1010
import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
1111
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
1212
import io.openems.edge.common.test.ComponentTest;
1313

14-
public class VictronCanBusBatteryImplTest {
14+
public class VictronBatteryImplTest {
1515

1616
private static final String BATTERY_ID = "battery0";
1717
private static final String MODBUS_ID = "modbus0";
@@ -21,9 +21,14 @@ public class VictronCanBusBatteryImplTest {
2121
private static final ChannelAddress BATTERY_VOLTAGE = new ChannelAddress(BATTERY_ID, "Voltage");
2222
private static final ChannelAddress BATTERY_CURRENT = new ChannelAddress(BATTERY_ID, "Current");
2323

24-
@Test
25-
public void test() throws Exception {
26-
new ComponentTest(new VictronBatteryImpl()) //
24+
/**
25+
* Creates a {@link ComponentTest} for a {@link VictronBatteryImpl}.
26+
*
27+
* @return a test
28+
* @throws Exception on error
29+
*/
30+
public static ComponentTest createVictronBattery() throws Exception {
31+
return new ComponentTest(new VictronBatteryImpl()) //
2732
.addReference("cm", new DummyConfigurationAdmin()) //
2833
.addReference("setModbus", new DummyModbusBridge(MODBUS_ID) //
2934
.withRegisters(259,
@@ -203,7 +208,12 @@ public void test() throws Exception {
203208
.setModbusId(MODBUS_ID) //
204209
.setEssId(ESS_ID) //
205210
.setDebugMode(false) //
206-
.build()) //
211+
.build());
212+
}
213+
214+
@Test
215+
public void test() throws Exception {
216+
createVictronBattery() //
207217
.next(new TestCase()) // First cycle to read registers
208218
.next(new TestCase() //
209219
.output(BATTERY_SOC, 85) //
@@ -215,7 +225,7 @@ public void test() throws Exception {
215225
public void testChannelIds() {
216226
var channelIds = VictronBattery.ChannelId.values();
217227
for (var channelId : channelIds) {
218-
assertNotNull("ChannelId " + channelId.name() + " should have a doc", channelId.doc());
228+
assertNotNull(channelId.doc(), "ChannelId " + channelId.name() + " should have a doc");
219229
}
220230
}
221231

io.openems.edge.victron/test/io/openems/edge/victron/batteryinverter/statemachine/StateMachineTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package io.openems.edge.victron.batteryinverter.statemachine;
22

3-
import static org.junit.Assert.assertEquals;
4-
import static org.junit.Assert.assertNotNull;
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
55

6-
import org.junit.Test;
6+
import org.junit.jupiter.api.Test;
77

88
/**
99
* Tests for the Victron State Machine.
@@ -69,7 +69,7 @@ public void testGetStateHandler() {
6969
// Test that all states return a non-null handler
7070
for (var state : StateMachine.State.values()) {
7171
var handler = stateMachine.getStateHandler(state);
72-
assertNotNull("Handler for state " + state + " should not be null", handler);
72+
assertNotNull(handler, "Handler for state " + state + " should not be null");
7373
}
7474
}
7575

io.openems.edge.victron/test/io/openems/edge/victron/ess/MyConfig.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.openems.edge.victron.ess;
22

33
import io.openems.common.test.AbstractComponentConfig;
4-
import io.openems.common.utils.ConfigUtils;
54
import io.openems.edge.common.type.Phase.SingleOrAllPhase;
65

76
@SuppressWarnings("all")
@@ -113,11 +112,6 @@ public String modbus_id() {
113112
return this.builder.modbusId;
114113
}
115114

116-
@Override
117-
public String Modbus_target() {
118-
return ConfigUtils.generateReferenceTargetFilter(this.id(), this.modbus_id());
119-
}
120-
121115
@Override
122116
public int modbusUnitId() {
123117
return this.builder.modbusUnitId;

io.openems.edge.victron/test/io/openems/edge/victron/ess/VictronEssImplTest.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@
33
import static org.junit.Assert.assertEquals;
44
import static org.junit.Assert.assertNotNull;
55

6-
import org.junit.Test;
6+
import org.junit.jupiter.api.Test;
77

8+
import io.openems.common.exceptions.OpenemsException;
9+
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
10+
import io.openems.edge.common.test.ComponentTest;
811
import io.openems.edge.common.type.Phase.SingleOrAllPhase;
12+
import io.openems.edge.ess.api.SymmetricEss;
13+
import io.openems.edge.victron.battery.VictronBatteryImplTest;
914

1015
/**
1116
* Tests for {@link VictronEssImpl}.
@@ -15,6 +20,27 @@ public class VictronEssImplTest {
1520
private static final String ESS_ID = "ess0";
1621
private static final String MODBUS_ID = "modbus0";
1722

23+
@Test
24+
public void test() throws OpenemsException, Exception {
25+
new ComponentTest(new VictronEssImpl()) //
26+
.addReference("battery", VictronBatteryImplTest.createVictronBattery().sut) //
27+
.activate(MyConfig.create() //
28+
.setId(ESS_ID) //
29+
.setAlias("Victron ESS") //
30+
.setEnabled(true) //
31+
.setModbusId(MODBUS_ID) //
32+
.setModbusUnitId(227) //
33+
.setPhase(SingleOrAllPhase.ALL) //
34+
.setDebugMode(false) //
35+
.setReadOnlyMode(false) //
36+
.setCapacity(10000) //
37+
.setMaxApparentPower(5000) //
38+
.build()) //
39+
.next(new TestCase() //
40+
.output(SymmetricEss.ChannelId.CAPACITY, 10000)) //
41+
.deactivate();
42+
}
43+
1844
@Test
1945
public void testChannelIdCount() {
2046
// Verify that all ChannelIds are defined

io.openems.edge.victron/test/io/openems/edge/victron/meter/grid/VictronMeterImplTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package io.openems.edge.victron.meter.grid;
22

3-
import static org.junit.Assert.assertEquals;
4-
import static org.junit.Assert.assertNotNull;
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
55

6-
import org.junit.Test;
6+
import org.junit.jupiter.api.Test;
77

88
import io.openems.common.test.DummyConfigurationAdmin;
99
import io.openems.common.types.ChannelAddress;

io.openems.edge.victron/test/io/openems/edge/victron/pvinverter/VictronPvInverterImplTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package io.openems.edge.victron.pvinverter;
22

3-
import static org.junit.Assert.assertNotNull;
3+
import static org.junit.jupiter.api.Assertions.assertNotNull;
44

5-
import org.junit.Test;
5+
import org.junit.jupiter.api.Test;
66

77
import io.openems.common.test.DummyConfigurationAdmin;
88
import io.openems.common.types.ChannelAddress;

0 commit comments

Comments
 (0)