Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/reverse power relay #2684

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/bin_test/
/generated/
17 changes: 17 additions & 0 deletions io.openems.edge.controller.pvinverter.reversepowerrelay/bnd.bnd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Bundle-Name: OpenEMS Edge Controller io.openems.edge.controller.pvinverter.reversepowerrelay
Bundle-Vendor: OpenEMS Association e.V.
Bundle-License: https://opensource.org/licenses/EPL-2.0
Bundle-Version: 1.0.0.${tstamp}

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

-testpath: \
${testpath},\
org.mockito.mockito-core,\
io.openems.edge.io.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
= io.openems.edge.controller.pvinverter.reversepowerrelay

Limits pv production according to current state of reverse power relay (short: rpr, german: "Rundsteuerempfaenger").
Usually rpr as four defined states which are defined by it�s relays:
- K1 -> 0% limit pv production to 0
- K2 -> 30% limit pv production to 30%
- K3 -> 60% limit pv production to 60%
- K4 -> 100% no limitation

You have to define a network input which has rpr output as input, e.g. a Siemens Logo!

Be careful: if there is an undefined state (e.g. two relays active at the same time or no relay active at all) then pv production is limited to 0%!
Usually only K4 should be active and your pv is not limited

Tested with Siemens LOGO!8 and managed symmetric pv inverter.
Remember to configure a high priority for this controller.

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

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

@ObjectClassDefinition(//
name = "Controller PV-Inverter Reverse Power Relay (Rundsteuerempf�nger)", //
description = "Defines a reverse power relay to limit PV inverter.")
@interface Config {

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

@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 = "PV-Inverter-ID", description = "ID of PV-Inverter device.")
String pvInverter_id();

@AttributeDefinition(name = "Input Channel 0%", description = "Address of the input channel. If this channel is active the inverter is limited to 0%. i.e. 'myRelay/Input1")
String inputChannelAddress0Percent();

@AttributeDefinition(name = "Input Channel 30%", description = "Address of the input channel. If this channel is active the inverter is limited to 30%. i.e. 'myRelay/Input2")
String inputChannelAddress30Percent();

@AttributeDefinition(name = "Input Channel 60%", description = "Address of the input channel. If this channel is active the inverter is limited to 60%. i.e. 'myRelay/Input3")
String inputChannelAddress60Percent();

@AttributeDefinition(name = "Input Channel 100%", description = "Address of the input channel. If this channel is active the inverter is limited to 100%. i.e. 'myRelay/Input4")
String inputChannelAddress100Percent();

@AttributeDefinition(name = "Power Limit 30% step [W]", description = "")
int powerLimit30();

@AttributeDefinition(name = "Power Limit 60% step [W]", description = "")
int powerLimit60();

String webconsole_configurationFactory_nameHint() default "Controller PV-Inverter Reverse Power Relay Limitation [{id}]";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.openems.edge.controller.pvinverter.reversepowerrelay;

import io.openems.edge.common.channel.Doc;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.controller.api.Controller;

public interface ReversePowerRelay extends Controller, OpenemsComponent {

public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
;

private final Doc doc;

private ChannelId(Doc doc) {
this.doc = doc;
}

Check warning on line 16 in io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelay.java

View check run for this annotation

Codecov / codecov/patch

io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelay.java#L14-L16

Added lines #L14 - L16 were not covered by tests

@Override
public Doc doc() {
return this.doc;

Check warning on line 20 in io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelay.java

View check run for this annotation

Codecov / codecov/patch

io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelay.java#L20

Added line #L20 was not covered by tests
}
}

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

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.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.component.AbstractOpenemsComponent;
import io.openems.edge.common.component.ComponentManager;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.controller.api.Controller;
import io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter;
import io.openems.edge.common.channel.BooleanReadChannel;

@Designate(ocd = Config.class, factory = true)
@Component(//
name = "Controller.PvInverter.ReversePowerRelay", //
immediate = true, //
configurationPolicy = ConfigurationPolicy.REQUIRE //
)
public class ReversePowerRelayImpl extends AbstractOpenemsComponent
implements ReversePowerRelay, Controller, OpenemsComponent {

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

@Reference
private ComponentManager componentManager;

private String pvInverterId;

private int powerLimit30Percent = 0;
private int powerLimit60Percent = 0;

private ChannelAddress inputChannelAddress0Percent = null;
private ChannelAddress inputChannelAddress30Percent = null;
private ChannelAddress inputChannelAddress60Percent = null;
private ChannelAddress inputChannelAddress100Percent = null;

private ManagedSymmetricPvInverter pvInverter;

public ReversePowerRelayImpl() {
super(//
OpenemsComponent.ChannelId.values(), //
Controller.ChannelId.values(), //
ReversePowerRelay.ChannelId.values() //
);
}

@Activate
private void activate(ComponentContext context, Config config) {
super.activate(context, config.id(), config.alias(), config.enabled());
this.pvInverterId = config.pvInverter_id();

this.powerLimit30Percent = config.powerLimit30();
this.powerLimit60Percent = config.powerLimit60();

try {
this.inputChannelAddress0Percent = ChannelAddress.fromString(config.inputChannelAddress0Percent());
this.inputChannelAddress30Percent = ChannelAddress.fromString(config.inputChannelAddress30Percent());

this.inputChannelAddress60Percent = ChannelAddress.fromString(config.inputChannelAddress60Percent());
this.inputChannelAddress100Percent = ChannelAddress.fromString(config.inputChannelAddress100Percent());

} catch (OpenemsNamedException e) {
this.log.error("Error parsing channel addresses", e);

Check warning on line 72 in io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java

View check run for this annotation

Codecov / codecov/patch

io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java#L71-L72

Added lines #L71 - L72 were not covered by tests
}
}

@Deactivate
protected void deactivate() {
// Reset limit
ManagedSymmetricPvInverter pvInverter;
try {
pvInverter = this.componentManager.getComponent(this.pvInverterId);
pvInverter.setActivePowerLimit(null);
} catch (OpenemsNamedException e) {
this.logError(this.log, e.getMessage());
}
super.deactivate();
}

Check warning on line 87 in io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java

View check run for this annotation

Codecov / codecov/patch

io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java#L81-L87

Added lines #L81 - L87 were not covered by tests

private void setPvLimit(Integer powerLimit) {

try {
this.pvInverter = this.componentManager.getComponent(this.pvInverterId);
if (this.pvInverter != null) {
this.pvInverter.setActivePowerLimit(powerLimit);
if (powerLimit != null) {
this.log.warn("Setting PV limit: " + powerLimit + "W for " + this.pvInverterId);
} else {
this.log.info("No limit for " + this.pvInverterId);
}

}
} catch (OpenemsNamedException e) {
this.log.error("Error setting PV limit", e);

Check warning on line 103 in io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java

View check run for this annotation

Codecov / codecov/patch

io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java#L102-L103

Added lines #L102 - L103 were not covered by tests
}

}

private Boolean getChannelValue(ChannelAddress address) throws OpenemsNamedException {
if (address == null) {
return null;

Check warning on line 110 in io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java

View check run for this annotation

Codecov / codecov/patch

io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java#L110

Added line #L110 was not covered by tests
}
try {
BooleanReadChannel channel = this.componentManager.getChannel(address);
return channel.value().orElse(null);
} catch (OpenemsNamedException e) {
this.log.error("Error reading channel value", e);
return null;

Check warning on line 117 in io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java

View check run for this annotation

Codecov / codecov/patch

io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java#L115-L117

Added lines #L115 - L117 were not covered by tests
}
}

@Override
public void run() throws OpenemsNamedException {

try {
Boolean value0Percent = this.getChannelValue(this.inputChannelAddress0Percent);
Boolean value30Percent = this.getChannelValue(this.inputChannelAddress30Percent);
Boolean value60Percent = this.getChannelValue(this.inputChannelAddress60Percent);
Boolean value100Percent = this.getChannelValue(this.inputChannelAddress100Percent);

if (value0Percent == null || value30Percent == null || value60Percent == null || value100Percent == null) {
this.log.warn("Skipping logic in run() due to null channel values");
this.setPvLimit(0);
return;
}
//
if (value0Percent == true) {
this.setPvLimit(0);

Check warning on line 137 in io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java

View check run for this annotation

Codecov / codecov/patch

io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java#L137

Added line #L137 was not covered by tests
} else if (value0Percent == false && value30Percent == true && value60Percent == false
&& value100Percent == false) {
this.setPvLimit(this.powerLimit30Percent);
} else if (value0Percent == false && value30Percent == false && value60Percent == true
&& value100Percent == false) {
this.setPvLimit(this.powerLimit60Percent);
} else if (value0Percent == false && value30Percent == false && value60Percent == false
&& value100Percent == true) {
this.setPvLimit(null);
} else {
this.setPvLimit(0);
}

} catch (OpenemsNamedException e) {
this.log.error("No values from modbus channels yet", e);
return;

Check warning on line 153 in io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java

View check run for this annotation

Codecov / codecov/patch

io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java#L151-L153

Added lines #L151 - L153 were not covered by tests
}

}

public String getPvInverterId() {
return this.pvInverterId;

Check warning on line 159 in io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java

View check run for this annotation

Codecov / codecov/patch

io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java#L159

Added line #L159 was not covered by tests
}

public int getPowerLimit30Percent() {
return this.powerLimit30Percent;

Check warning on line 163 in io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java

View check run for this annotation

Codecov / codecov/patch

io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java#L163

Added line #L163 was not covered by tests
}

public int getPowerLimit60Percent() {
return this.powerLimit60Percent;

Check warning on line 167 in io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java

View check run for this annotation

Codecov / codecov/patch

io.openems.edge.controller.pvinverter.reversepowerrelay/src/io/openems/edge/controller/pvinverter/reversepowerrelay/ReversePowerRelayImpl.java#L167

Added line #L167 was not covered by tests
}
}
Loading