Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8cebf96
Initial commit
timo-schlegel Aug 5, 2025
236edbb
Increase time for batteryActualPowerChanged() to 20 seconds
timo-schlegel Aug 6, 2025
68979bf
Increase time for batteryActualPowerChanged() to 30 seconds and incre…
timo-schlegel Aug 10, 2025
3e365c5
Reduce the battery charging power by the discharge efficiency factor …
timo-schlegel Aug 11, 2025
4fb5c2d
Fix Smart Mode implementation.
timo-schlegel Aug 11, 2025
83c4f14
Fix Battery Protection in ApplyPowerHandler
timo-schlegel Aug 12, 2025
7410281
Optimization of calculateAndSetActualPvPower()
timo-schlegel Aug 12, 2025
033044e
Optimization of AllowedChargeDischargeHandler
timo-schlegel Aug 12, 2025
0878291
Optimization of calculateAndSetActualPvPower()
timo-schlegel Oct 22, 2025
0aac06c
Optimization of updateDcDischargePowerChannel()
timo-schlegel Jan 11, 2026
719a234
Optimize usage of DISCHARGE_EFFICIENCY_FACTOR in ApplyPowerHandler
timo-schlegel Jan 11, 2026
4bec328
Fix required due to update fork from upstream
timo-schlegel Jan 29, 2026
107ddaf
Add JUnit tests
timo-schlegel Feb 18, 2026
c6f9576
Applied Checkstyle fixes and ran prepare-commit.sh
timo-schlegel Feb 21, 2026
6ae8618
Refactor SunSpec model handling, filter unsupported SolarEdge events,…
timo-schlegel Mar 4, 2026
a9e6adc
Remove obsolete solaredge.ess package
timo-schlegel Mar 4, 2026
58a6723
Add JUnit tests
timo-schlegel Mar 25, 2026
24eedb0
Applied Checkstyle fix
timo-schlegel Mar 25, 2026
6c7e8d8
Minor changes
timo-schlegel Mar 25, 2026
5067420
Merge remote-tracking branch 'upstream/develop' into feature/SolarEdg…
timo-schlegel Mar 26, 2026
6a46c71
Fix compilation error and checkstyle issues after upstream sync
timo-schlegel Mar 26, 2026
5516359
Fix failing SolarEdgeChargerImplTest after upstream sync
timo-schlegel Mar 26, 2026
4ef36a8
Remove explicit AccessMode.READ_ONLY since it is the default for chan…
timo-schlegel Mar 27, 2026
409a700
Fix SunSpec model matching for FilteredSunSpecModel in AbstractSunSpe…
timo-schlegel Mar 29, 2026
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 io.openems.edge.bridge.modbus/bnd.bnd
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ Bundle-Version: 1.0.0.${tstamp}
io.openems.edge.meter.api,\
io.openems.edge.pvinverter.api,\
io.openems.j2mod,\

io.openems.edge.ess.api
-testpath: \
${testpath}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package io.openems.edge.bridge.modbus.sunspec;

import java.util.Arrays;

/**
* Represents a Filtered SunSpec Model.
*/
public class FilteredSunSpecModel implements SunSpecModel {

private final SunSpecModel delegate;
private final SunSpecPoint[] points;

public FilteredSunSpecModel(SunSpecModel delegate, SunSpecPoint[] points) {
this.delegate = delegate;
this.points = points;
}

@Override
public String name() {
return this.delegate.name();
}

@Override
public String label() {
return this.delegate.label();
}

@Override
public SunSpecPoint[] points() {
return this.points;
}

/**
* Creates a {@link FilteredSunSpecModel} that delegates to the given
* {@link SunSpecModel} but excludes the specified {@link SunSpecPoint}s
* from the returned {@link SunSpecModel#points()}.
*
* <p>
* This can be used if a device implements a standard SunSpec model but
* does not support some points (e.g. vendor-specific event registers).
* The returned model behaves like the original model except that the
* excluded points are not exposed and therefore not mapped to Modbus
* registers.
*
* @param delegate the original {@link SunSpecModel}
* @param excluded the {@link SunSpecPoint}s that should be removed
* @return a {@link FilteredSunSpecModel} without the specified points
*/
public static FilteredSunSpecModel withoutPoints(SunSpecModel delegate, SunSpecPoint... excluded) {
var excludedSet = Arrays.asList(excluded);
var filtered = Arrays.stream(delegate.points())
.filter(p -> !excludedSet.contains(p))
.toArray(SunSpecPoint[]::new);
return new FilteredSunSpecModel(delegate, filtered);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package io.openems.edge.bridge.modbus.sunspec.ess;

import java.util.Map;
import java.util.Optional;

import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Deactivate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.openems.common.exceptions.OpenemsException;
import io.openems.edge.bridge.modbus.sunspec.AbstractOpenemsSunSpecComponent;
import io.openems.edge.bridge.modbus.sunspec.SunSpecModel;
import io.openems.edge.bridge.modbus.sunspec.SunSpecPoint;
import io.openems.edge.common.channel.Channel;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.taskmanager.Priority;
import io.openems.edge.ess.api.ManagedSymmetricEss;
import io.openems.edge.ess.api.SymmetricEss;

public abstract class AbstractSunSpecEss extends AbstractOpenemsSunSpecComponent
implements ManagedSymmetricEss, SymmetricEss, OpenemsComponent {

private final Logger log = LoggerFactory.getLogger(AbstractSunSpecEss.class);

public AbstractSunSpecEss(Map<SunSpecModel, Priority> activeModels,
io.openems.edge.common.channel.ChannelId[] firstInitialChannelIds,
io.openems.edge.common.channel.ChannelId[]... furtherInitialChannelIds) throws OpenemsException {
super(activeModels, firstInitialChannelIds, furtherInitialChannelIds);
}

/**
* Make sure to call this method from the inheriting OSGi Component.
*
* @param context ComponentContext of this component. Receive it
* from parameter for @Activate
* @param id ID of this component. Typically 'config.id()'
* @param alias Human-readable name of this Component. Typically
* 'config.alias()'. Defaults to 'id' if empty
* @param enabled Whether the component should be enabled.
* Typically 'config.enabled()'
* @param unitId Unit-ID of the Modbus target
* @param cm An instance of ConfigurationAdmin. Receive it
* using @Reference
* @param modbusReference The name of the @Reference setter method for the
* Modbus bridge - e.g. 'Modbus' if you have a
* setModbus()-method
* @param modbusId The ID of the Modbus bridge. Typically
* 'config.modbus_id()'
* @param readFromCommonBlockNo the starting block number
* @return true if the target filter was updated. You may use it to abort the
* activate() method.
* @throws OpenemsException on error
*/
@Override
protected boolean activate(ComponentContext context, String id, String alias, boolean enabled, int unitId,
ConfigurationAdmin cm, String modbusReference, String modbusId, int readFromCommonBlockNo)
throws OpenemsException {
return super.activate(context, id, alias, enabled, unitId, cm, modbusReference, modbusId,
readFromCommonBlockNo);
}

/**
* Make sure to call this method from the inheriting OSGi Component.
*/
@Override
@Deactivate
protected void deactivate() {
super.deactivate();
}

@Override
public String debugLog() {
return new StringBuilder() //
.append("SoC:").append(this.getSoc().asString()) //
.append("|ESS ActivePower:").append(this.getActivePower().asString()) //
.toString();
}

@Override
protected void onSunSpecInitializationCompleted() {
this.logInfo(this.log, "SunSpec initialization finished. " + this.channels().size() + " Channels available.");
}

@Override
protected <T extends Channel<?>> Optional<T> getSunSpecChannel(SunSpecPoint point) {
return super.getSunSpecChannel(point);
}

@Override
protected <T extends Channel<?>> T getSunSpecChannelOrError(SunSpecPoint point) throws OpenemsException {
return super.getSunSpecChannelOrError(point);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@org.osgi.annotation.versioning.Version("1.0.0")
@org.osgi.annotation.bundle.Export
package io.openems.edge.bridge.modbus.sunspec.ess;
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ protected void addBlock(int startAddress, SunSpecModel model, Priority priority)

// Can we evaluate the InverterType from this Block?
Stream.of(InverterType.values()) //
.filter(type -> type.blocks.stream().anyMatch(t -> t.equals(model))) //
.filter(type -> type.blocks.stream().anyMatch(t -> t.equals(model) || t.name().equals(model.name()))) //
.findFirst() //
.ifPresent(type -> this.inverterType = type);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package io.openems.edge.bridge.modbus.sunspec.ess;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

import java.util.Map;

import org.junit.Test;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;

import com.google.common.collect.ImmutableMap;

import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.exceptions.OpenemsException;
import io.openems.common.test.DummyConfigurationAdmin;
import io.openems.edge.bridge.modbus.api.BridgeModbus;
import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel;
import io.openems.edge.bridge.modbus.sunspec.SunSpecModel;
import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
import io.openems.edge.common.channel.Channel;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.taskmanager.Priority;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.ess.api.ManagedSymmetricEss;
import io.openems.edge.ess.api.SymmetricEss;
import io.openems.edge.ess.power.api.Power;

public class AbstractSunSpecEssTest {

private class SunSpecEssImpl extends AbstractSunSpecEss implements ManagedSymmetricEss, SymmetricEss, OpenemsComponent {

private static final int READ_FROM_MODBUS_BLOCK = 1;

private static final Map<SunSpecModel, Priority> ACTIVE_MODELS = ImmutableMap.<SunSpecModel, Priority>builder()
.put(DefaultSunSpecModel.S_1, Priority.LOW) //
.put(DefaultSunSpecModel.S_101, Priority.LOW) //
.build();

@Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY)
protected void setModbus(BridgeModbus modbus) {
super.setModbus(modbus);
}

@Reference
private ConfigurationAdmin cm;

public SunSpecEssImpl() throws OpenemsNamedException {
super(//
ACTIVE_MODELS, //
OpenemsComponent.ChannelId.values(), //
SymmetricEss.ChannelId.values(), //
ManagedSymmetricEss.ChannelId.values() //
);
}

@Activate
private void activate(ComponentContext context, Config config) throws OpenemsNamedException {
if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm,
"Modbus", config.modbus_id(), READ_FROM_MODBUS_BLOCK)) {
return;
}
}

@Override
@Deactivate
protected void deactivate() {
super.deactivate();
}

@Override
public Power getPower() {
return null;
}

@Override
public void applyPower(int activePower, int reactivePower) throws OpenemsNamedException {
return;
}

@Override
public int getPowerPrecision() {
return 1;
}
}

@Test
public void testDebugLog() throws Exception {
var sut = new SunSpecEssImpl();
assertNotNull(sut.debugLog());
}

@Test
public void testOnSunSpecInitializationCompleted() throws Exception {
var sut = new SunSpecEssImpl();
sut.onSunSpecInitializationCompleted();
}

@Test
public void testGetSunSpecChannel() throws Exception {
var sut = new SunSpecEssImpl();
new ComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("setModbus", new DummyModbusBridge("modbus0")
.withRegisters(40000, 0x5375, 0x6e53) // isSunSpec
.withRegisters(40002, 1, 66) // Block 1
.withRegisters(40070, 101, 50) // Block 101
.withRegisters(40122, 0xFFFF, 0)) // END_OF_MAP
.activate(MyConfig.create() //
.setId("ess0") //
.setModbusId("modbus0") //
.setModbusUnitId(1) //
.build())
.next(new TestCase()) //
.next(new TestCase()) //
.next(new TestCase().also(t -> {
// read DefaultSunSpecModel.S_1
assertFalse(sut.isSunSpecInitializationCompleted());
})) //
.next(new TestCase().also(t -> {
// read DefaultSunSpecModel.S_101
assertTrue(sut.isSunSpecInitializationCompleted());
})) //
.next(new TestCase().also(t -> {
// Test getSunSpecChannel() returns Channel for known point
assertTrue(sut.<Channel<?>>getSunSpecChannel(DefaultSunSpecModel.S101.DCW).isPresent());
})) //
.next(new TestCase().also(t -> {
// Test getSunSpecChannel() returns Optional.empty for unknown point
assertTrue(sut.<Channel<?>>getSunSpecChannel(DefaultSunSpecModel.S103.DCW).isEmpty());
})) //
.deactivate();
}

@Test
public void testGetSunSpecChannelOrError() throws Exception {
var sut = new SunSpecEssImpl();
new ComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("setModbus", new DummyModbusBridge("modbus0")
.withRegisters(40000, 0x5375, 0x6e53) // isSunSpec
.withRegisters(40002, 1, 66) // Block 1
.withRegisters(40070, 101, 50) // Block 101
.withRegisters(40122, 0xFFFF, 0)) // END_OF_MAP
.activate(MyConfig.create() //
.setId("ess0") //
.setModbusId("modbus0") //
.setModbusUnitId(1) //
.build())
.next(new TestCase()) //
.next(new TestCase()) //
.next(new TestCase().also(t -> {
// read DefaultSunSpecModel.S_1
assertFalse(sut.isSunSpecInitializationCompleted());
})) //
.next(new TestCase().also(t -> {
// read DefaultSunSpecModel.S_101
assertTrue(sut.isSunSpecInitializationCompleted());
})) //
.next(new TestCase().also(t -> {
// Test getSunSpecChannelOrError() throws an error if Channel is not available.
assertThrows(OpenemsException.class, () -> sut.<Channel<?>>getSunSpecChannelOrError(DefaultSunSpecModel.S103.DCW));
}))
.deactivate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.openems.edge.bridge.modbus.sunspec.ess;

@interface Config {
String id();

String alias();

boolean enabled();

boolean readOnly();

String modbus_id();

int modbusUnitId();

int readFromModbusBlock();

String Modbus_target();
}
Loading
Loading