Skip to content

Commit

Permalink
Implement the Phoenix Contact EEM-MB370-24DC meter (#2208)
Browse files Browse the repository at this point in the history
  • Loading branch information
sfeilmeier and Michael Grill authored Jun 1, 2023
1 parent 4f33713 commit 7f0e151
Show file tree
Hide file tree
Showing 13 changed files with 362 additions and 0 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 @@ -146,6 +146,7 @@
bnd.identity;id='io.openems.edge.meter.janitza',\
bnd.identity;id='io.openems.edge.meter.kdk',\
bnd.identity;id='io.openems.edge.meter.microcare.sdm630',\
bnd.identity;id='io.openems.edge.meter.phoenixcontact',\
bnd.identity;id='io.openems.edge.meter.plexlog',\
bnd.identity;id='io.openems.edge.meter.pqplus',\
bnd.identity;id='io.openems.edge.meter.schneider.acti9.smartlink',\
Expand Down Expand Up @@ -304,6 +305,7 @@
io.openems.edge.meter.janitza;version=snapshot,\
io.openems.edge.meter.kdk;version=snapshot,\
io.openems.edge.meter.microcare.sdm630;version=snapshot,\
io.openems.edge.meter.phoenixcontact;version=snapshot,\
io.openems.edge.meter.plexlog;version=snapshot,\
io.openems.edge.meter.pqplus;version=snapshot,\
io.openems.edge.meter.schneider.acti9.smartlink;version=snapshot,\
Expand Down
12 changes: 12 additions & 0 deletions io.openems.edge.meter.phoenixcontact/.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-17"/>
<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.meter.phoenixcontact/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/bin_test/
/generated/
23 changes: 23 additions & 0 deletions io.openems.edge.meter.phoenixcontact/.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.meter.phoenixcontact</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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8
16 changes: 16 additions & 0 deletions io.openems.edge.meter.phoenixcontact/bnd.bnd
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Bundle-Name: OpenEMS Edge Meter Phoenix Contact
Bundle-Description: This implementation covers Phoenix Contact meter EEM-MA370-24DC and EEM-MB370-24DC
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

-testpath: \
${testpath},\
com.ghgande.j2mod,\
8 changes: 8 additions & 0 deletions io.openems.edge.meter.phoenixcontact/readme.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
= https://www.phoenixcontact.com/de-de/produkte/energiemessgeraet-eem-mb370-24dc-1127061[PhoenixContact Meter]

Implemented Natures

- SymmetricMeter
- AsymmetricMeter
https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.meter.phonixcontact[Source Code icon:github[]]
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.openems.edge.meter.phoenixcontact;

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

import io.openems.edge.meter.api.MeterType;

@ObjectClassDefinition(//
name = "Meter Phoenix Contact", //
description = "Implements the Phoenix Contact meter" //
)
@interface Config {

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

@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)";

@AttributeDefinition(name = "Meter-Type", description = "What is measured by this Meter?")
MeterType type() default MeterType.PRODUCTION;

String webconsole_configurationFactory_nameHint() default "Meter Phoenix Contact [{id}]";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.openems.edge.meter.phoenixcontact;

import io.openems.edge.common.channel.Doc;
import io.openems.edge.common.component.OpenemsComponent;

public interface PhoenixContactMeter extends OpenemsComponent {

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

private final Doc doc;

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

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package io.openems.edge.meter.phoenixcontact;

import static io.openems.edge.bridge.modbus.api.element.WordOrder.LSWMSW;

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.metatype.annotations.Designate;

import io.openems.common.exceptions.OpenemsException;
import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent;
import io.openems.edge.bridge.modbus.api.BridgeModbus;
import io.openems.edge.bridge.modbus.api.ElementToChannelConverter;
import io.openems.edge.bridge.modbus.api.ModbusComponent;
import io.openems.edge.bridge.modbus.api.ModbusProtocol;
import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement;
import io.openems.edge.bridge.modbus.api.element.FloatDoublewordElement;
import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.taskmanager.Priority;
import io.openems.edge.meter.api.AsymmetricMeter;
import io.openems.edge.meter.api.MeterType;
import io.openems.edge.meter.api.SymmetricMeter;

@Designate(ocd = Config.class, factory = true)
@Component(//
name = "Meter.PhoenixContact", //
immediate = true, //
configurationPolicy = ConfigurationPolicy.REQUIRE //
)
public class PhoenixContactMeterImpl extends AbstractOpenemsModbusComponent
implements SymmetricMeter, AsymmetricMeter, PhoenixContactMeter, ModbusComponent, OpenemsComponent {

@Reference
private ConfigurationAdmin cm;

private MeterType type = MeterType.PRODUCTION;

public PhoenixContactMeterImpl() {
super(//
OpenemsComponent.ChannelId.values(), //
ModbusComponent.ChannelId.values(), //
PhoenixContactMeter.ChannelId.values(), //
SymmetricMeter.ChannelId.values(), //
AsymmetricMeter.ChannelId.values() //
);
}

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

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

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

@Override
protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
final var modbusProtocol = new ModbusProtocol(this, new FC3ReadRegistersTask(0x8006, Priority.HIGH, //
m(AsymmetricMeter.ChannelId.VOLTAGE_L1, new FloatDoublewordElement(0x8006) //
.wordOrder(LSWMSW), ElementToChannelConverter.SCALE_FACTOR_3), //
m(AsymmetricMeter.ChannelId.VOLTAGE_L2, new FloatDoublewordElement(0x8008) //
.wordOrder(LSWMSW), ElementToChannelConverter.SCALE_FACTOR_3), //
m(AsymmetricMeter.ChannelId.VOLTAGE_L3, new FloatDoublewordElement(0x800A) //
.wordOrder(LSWMSW), ElementToChannelConverter.SCALE_FACTOR_3), //
m(SymmetricMeter.ChannelId.FREQUENCY, new FloatDoublewordElement(0x800C) //
.wordOrder(LSWMSW), ElementToChannelConverter.SCALE_FACTOR_3), //
m(AsymmetricMeter.ChannelId.CURRENT_L1, new FloatDoublewordElement(0x800E) //
.wordOrder(LSWMSW), ElementToChannelConverter.SCALE_FACTOR_3), //
m(AsymmetricMeter.ChannelId.CURRENT_L2, new FloatDoublewordElement(0x8010) //
.wordOrder(LSWMSW), ElementToChannelConverter.SCALE_FACTOR_3), //
m(AsymmetricMeter.ChannelId.CURRENT_L3, new FloatDoublewordElement(0x8012) //
.wordOrder(LSWMSW), ElementToChannelConverter.SCALE_FACTOR_3), //
m(SymmetricMeter.ChannelId.CURRENT, new FloatDoublewordElement(0x8014) //
.wordOrder(LSWMSW), ElementToChannelConverter.SCALE_FACTOR_3), //
new DummyRegisterElement(0x8016, 0x8015), //
m(SymmetricMeter.ChannelId.ACTIVE_POWER, new FloatDoublewordElement(0x8016) //
.wordOrder(LSWMSW)), //
new DummyRegisterElement(0x8018, 0x801D), //
m(AsymmetricMeter.ChannelId.ACTIVE_POWER_L1, new FloatDoublewordElement(0x801E) //
.wordOrder(LSWMSW)), //
m(AsymmetricMeter.ChannelId.ACTIVE_POWER_L2, new FloatDoublewordElement(0x8020) //
.wordOrder(LSWMSW)), //
m(AsymmetricMeter.ChannelId.ACTIVE_POWER_L3, new FloatDoublewordElement(0x8022) //
.wordOrder(LSWMSW)), //
new DummyRegisterElement(0x8024, 0x803C), //
m(SymmetricMeter.ChannelId.VOLTAGE, new FloatDoublewordElement(0x803D) //
.wordOrder(LSWMSW), ElementToChannelConverter.SCALE_FACTOR_3) //
), new FC3ReadRegistersTask(0x8100, Priority.HIGH, //
m(SymmetricMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, new FloatDoublewordElement(0x8100) //
.wordOrder(LSWMSW)), //
new DummyRegisterElement(0x8102, 0x8105), //
m(SymmetricMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, new FloatDoublewordElement(0x8106) //
.wordOrder(LSWMSW)) //
));

return modbusProtocol;
}

@Override
public String debugLog() {
return "L:" + this.getActivePower().asString();
}

@Override
public MeterType getMeterType() {
return this.type;
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package io.openems.edge.meter.phoenixcontact;

import io.openems.common.test.AbstractComponentConfig;
import io.openems.common.utils.ConfigUtils;
import io.openems.edge.meter.api.MeterType;
import io.openems.edge.meter.phoenixcontact.Config;

@SuppressWarnings("all")
public class MyConfig extends AbstractComponentConfig implements Config {

protected static class Builder {
private String id;
private String modbusId = null;
private int modbusUnitId;
private MeterType meterType;

private Builder() {
}

public Builder setId(String id) {
this.id = id;
return this;
}

public Builder setModbusId(String modbusId) {
this.modbusId = modbusId;
return this;
}

public Builder setModbusUnitId(int modbusUnitId) {
this.modbusUnitId = modbusUnitId;
return this;
}

public Builder setMeterType(MeterType meterType) {
this.meterType = meterType;
return this;
}

public MyConfig build() {
return new MyConfig(this);
}
}

/**
* Create a Config builder.
*
* @return a {@link Builder}
*/
public static Builder create() {
return new Builder();
}

private final Builder builder;

private MyConfig(Builder builder) {
super(Config.class, builder.id);
this.builder = builder;
}

@Override
public String modbus_id() {
return this.builder.modbusId;
}

@Override
public String Modbus_target() {
return ConfigUtils.generateReferenceTargetFilter(this.id(), this.modbus_id());
}

@Override
public int modbusUnitId() {
return this.builder.modbusUnitId;
}

@Override
public MeterType type() {
return this.builder.meterType;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.openems.edge.meter.phoenixcontact;

import org.junit.Test;

import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.common.test.DummyConfigurationAdmin;
import io.openems.edge.meter.api.MeterType;

public class PhoenixContactMeterImplTest {

private static final String COMPONENT_ID = "meter0";
private static final String MODBUS_ID = "modbus0";

@Test
public void test() throws Exception {
new ComponentTest(new PhoenixContactMeterImpl()) //
.addReference("cm", new DummyConfigurationAdmin())
.addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
.activate(MyConfig.create() //
.setId(COMPONENT_ID) //
.setModbusId(MODBUS_ID) //
.setMeterType(MeterType.PRODUCTION) //
.build())
.next(new TestCase());
}

}

0 comments on commit 7f0e151

Please sign in to comment.