Skip to content

Commit

Permalink
Add Fronius PV Inverter (via Modbus/SunSpec) (#207)
Browse files Browse the repository at this point in the history
- Implement Fronius PV Inverter bundle
- AbstractOpenemsSunSpecComponent readNextBlock() function change to complete when all expected blocks have already been read.
- Add Factory ID in isProducer() method to be displayed in the UI.

Co-authored-by: Sebastian Asen <[email protected]>
Co-authored-by: Stefan Feilmeier <[email protected]>
Reviewed-on: https://git.intranet.fenecon.de/FENECON/fems/pulls/207
Reviewed-by: Stefan Feilmeier <[email protected]>
Co-authored-by: sebastian asen <[email protected]>
Co-committed-by: sebastian asen <[email protected]>
  • Loading branch information
sebastianasen and sfeilmeier committed Mar 3, 2022
1 parent ef17b06 commit 77ca9f1
Show file tree
Hide file tree
Showing 14 changed files with 351 additions and 42 deletions.
2 changes: 2 additions & 0 deletions io.openems.edge.application/EdgeApp.bndrun
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
bnd.identity;id='io.openems.edge.predictor.persistencemodel',\
bnd.identity;id='io.openems.edge.predictor.similardaymodel',\
bnd.identity;id='io.openems.edge.pvinverter.cluster',\
bnd.identity;id='io.openems.edge.pvinverter.fronius',\
bnd.identity;id='io.openems.edge.pvinverter.kaco.blueplanet',\
bnd.identity;id='io.openems.edge.pvinverter.sma',\
bnd.identity;id='io.openems.edge.pvinverter.solarlog',\
Expand Down Expand Up @@ -275,6 +276,7 @@
io.openems.edge.predictor.similardaymodel;version=snapshot,\
io.openems.edge.pvinverter.api;version=snapshot,\
io.openems.edge.pvinverter.cluster;version=snapshot,\
io.openems.edge.pvinverter.fronius;version=snapshot,\
io.openems.edge.pvinverter.kaco.blueplanet;version=snapshot,\
io.openems.edge.pvinverter.sma;version=snapshot,\
io.openems.edge.pvinverter.solarlog;version=snapshot,\
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -77,14 +78,18 @@ protected boolean activate(ComponentContext context, String id, String alias, bo
throws OpenemsException {
this.readFromCommonBlockNo = readFromCommonBlockNo;

var expectedBlocks = this.activeModels.keySet().stream() //
.map(m -> m.getBlockId()) //
.collect(Collectors.toSet());

// Start the SunSpec read procedure...
this.isSunSpec().thenAccept(isSunSpec -> {
if (!isSunSpec) {
throw new IllegalArgumentException("This modbus device is not SunSpec!");
}

try {
this.readNextBlock(40_002).thenRun(() -> {
this.readNextBlock(40_002, expectedBlocks).thenRun(() -> {
this.isSunSpecInitializationCompleted = true;
this.onSunSpecInitializationCompleted();
});
Expand Down Expand Up @@ -129,12 +134,28 @@ private CompletableFuture<Boolean> isSunSpec() throws OpenemsException {
/**
* Reads the next SunSpec block.
*
* @param startAddress the startAddress
* @param startAddress the startAddress
* @param remainingBlocks the remaining blocks expected to read
* @return a future that completes once reading the block finished
* @throws OpenemsException on error
*/
private CompletableFuture<Void> readNextBlock(int startAddress) throws OpenemsException {
private CompletableFuture<Void> readNextBlock(int startAddress, Set<Integer> remainingBlocks)
throws OpenemsException {
final CompletableFuture<Void> finished = new CompletableFuture<Void>();

// Finish if all expected Blocks have been read
if (remainingBlocks.isEmpty()) {
finished.complete(null);
}

/*
* Try to read block by block until all required blocks have been read or an
* END_OF_MAP register has been found.
*
* It may still happen that a device does not have a valid END_OF_MAP register
* and that some blocks are not read - especially when one component is used for
* multiple devices like single and three phase inverter.
*/
this.readElementsOnceTyped(new UnsignedWordElement(startAddress), new UnsignedWordElement(startAddress + 1))
.thenAccept(values -> {
int blockId = values.get(0);
Expand Down Expand Up @@ -164,6 +185,7 @@ private CompletableFuture<Void> readNextBlock(int startAddress) throws OpenemsEx
Priority priority = activeEntry.getValue();
try {
this.addBlock(startAddress, sunSpecModel, priority);
remainingBlocks.remove(activeEntry.getKey().getBlockId());
} catch (OpenemsException e) {
this.logWarn(this.log, "Error while adding SunSpec-Model [" + blockId
+ "] starting at [" + startAddress + "]: " + e.getMessage());
Expand All @@ -180,7 +202,9 @@ private CompletableFuture<Void> readNextBlock(int startAddress) throws OpenemsEx
// Read next block recursively
int nextBlockStartAddress = startAddress + 2 + length;
try {
final CompletableFuture<Void> readNextBlockFuture = this.readNextBlock(nextBlockStartAddress);

final CompletableFuture<Void> readNextBlockFuture = this.readNextBlock(nextBlockStartAddress,
remainingBlocks);
// Announce finished when next block (recursively) is finished
readNextBlockFuture.thenRun(() -> {
finished.complete(null);
Expand Down Expand Up @@ -250,7 +274,6 @@ public boolean isSunSpecInitializationCompleted() {
* @param startAddress the address to start reading from
* @param model the SunSpecModel
* @param priority the reading priority
* @return future that gets completed when the Block elements are read
* @throws OpenemsException on error
*/
protected void addBlock(int startAddress, SunSpecModel model, Priority priority) throws OpenemsException {
Expand Down
12 changes: 12 additions & 0 deletions io.openems.edge.pvinverter.fronius/.classpath
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="aQute.bnd.classpath.container"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
<classpathentry kind="src" output="bin" path="src"/>
<classpathentry kind="src" output="bin_test" path="test">
<attributes>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="bin"/>
</classpath>
2 changes: 2 additions & 0 deletions io.openems.edge.pvinverter.fronius/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/generated/
/bin_test/
23 changes: 23 additions & 0 deletions io.openems.edge.pvinverter.fronius/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>io.openems.edge.pvinverter.fronius</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>bndtools.core.bndbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>bndtools.core.bndnature</nature>
</natures>
</projectDescription>
17 changes: 17 additions & 0 deletions io.openems.edge.pvinverter.fronius/bnd.bnd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Bundle-Name: OpenEMS Edge PV Inverter Fronius
Bundle-Vendor: FENECON GmbH
Bundle-License: https://opensource.org/licenses/EPL-2.0
Bundle-Version: 1.0.0.${tstamp}

-buildpath: \
${buildpath},\
io.openems.common,\
io.openems.edge.bridge.modbus,\
io.openems.edge.common,\
io.openems.edge.meter.api,\
io.openems.edge.pvinverter.api,\
io.openems.edge.pvinverter.sunspec

-testpath: \
${testpath},\
com.ghgande.j2mod
Binary file not shown.
12 changes: 12 additions & 0 deletions io.openems.edge.pvinverter.fronius/readme.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
= Fronius PV inverter

Implementation of the Fronius PV inverters.

Tested on
- [Fronius Symo]

Implemented Natures:
- SymmetricMeter
- ManagedSymmetricPvInverter

https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.pvinverter.fronius[Source Code icon:github[]]
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.openems.edge.pvinverter.fronius;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

@ObjectClassDefinition(name = "PV-Inverter Fronius", //
description = "Implements the Fronius PV inverter.")
@interface Config {

@AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component")
String id() default "pvInverter0";

@AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID")
String alias() default "";

@AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?")
boolean enabled() default true;

@AttributeDefinition(name = "Modbus-ID", description = "ID of Modbus bridge.")
String modbus_id() default "modbus0";

@AttributeDefinition(name = "Modbus Unit-ID", description = "The Unit-ID of the Modbus device.")
int modbusUnitId() default 1;

@AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
String Modbus_target() default "(enabled=true)";

String webconsole_configurationFactory_nameHint() default "PV-Inverter Fronius [{id}]";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package io.openems.edge.pvinverter.fronius;

import java.util.Map;

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.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
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 org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import org.osgi.service.metatype.annotations.Designate;

import com.google.common.collect.ImmutableMap;

import io.openems.common.channel.AccessMode;
import io.openems.common.exceptions.OpenemsException;
import io.openems.edge.bridge.modbus.api.BridgeModbus;
import io.openems.edge.bridge.modbus.api.ModbusComponent;
import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel;
import io.openems.edge.bridge.modbus.sunspec.SunSpecModel;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.event.EdgeEventConstants;
import io.openems.edge.common.modbusslave.ModbusSlave;
import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable;
import io.openems.edge.common.modbusslave.ModbusSlaveTable;
import io.openems.edge.common.taskmanager.Priority;
import io.openems.edge.meter.api.SymmetricMeter;
import io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter;
import io.openems.edge.pvinverter.sunspec.AbstractSunSpecPvInverter;
import io.openems.edge.pvinverter.sunspec.SunSpecPvInverter;

@Designate(ocd = Config.class, factory = true)
@Component(//
name = "PV-Inverter.Fronius", //
immediate = true, //
configurationPolicy = ConfigurationPolicy.REQUIRE, //
property = { //
EventConstants.EVENT_TOPIC + "=" + EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE, //
"type=PRODUCTION" //
})
public class FroniusPvInverter extends AbstractSunSpecPvInverter implements SunSpecPvInverter,
ManagedSymmetricPvInverter, SymmetricMeter, ModbusComponent, OpenemsComponent, EventHandler, ModbusSlave {

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

/*
* This is depending on the specific inverter.
*/
.put(DefaultSunSpecModel.S_111, Priority.LOW) // from 40070
.put(DefaultSunSpecModel.S_112, Priority.LOW) // from 40070
.put(DefaultSunSpecModel.S_113, Priority.HIGH) // from 40070
.build();

private static final int READ_FROM_MODBUS_BLOCK = 1;

@Reference
protected ConfigurationAdmin cm;

public FroniusPvInverter() throws OpenemsException {
super(//
ACTIVE_MODELS, //
OpenemsComponent.ChannelId.values(), //
ModbusComponent.ChannelId.values(), //
SymmetricMeter.ChannelId.values(), //
ManagedSymmetricPvInverter.ChannelId.values(), //
SunSpecPvInverter.ChannelId.values() //
);
}

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

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

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

@Override
public void handleEvent(Event event) {
super.handleEvent(event);
}

@Override
public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) {
return new ModbusSlaveTable(//
OpenemsComponent.getModbusSlaveNatureTable(accessMode), //
SymmetricMeter.getModbusSlaveNatureTable(accessMode), //
ManagedSymmetricPvInverter.getModbusSlaveNatureTable(accessMode), //
ModbusSlaveNatureTable.of(FroniusPvInverter.class, accessMode, 100) //
.build());
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.openems.edge.pvinverter.fronius;

import org.junit.Test;

import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.common.test.DummyConfigurationAdmin;

public class FroniusPvInverterTest {

private static final String PV_INVERTER_ID = "pvInverter0";
private static final String MODBUS_ID = "modbus0";

@Test
public void test() throws Exception {
new ComponentTest(new FroniusPvInverter()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
.activate(MyConfig.create() //
.setId(PV_INVERTER_ID) //
.setModbusId(MODBUS_ID) //
.setModbusUnitId(1) //
.build()) //
;
}
}
Loading

0 comments on commit 77ca9f1

Please sign in to comment.