diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun
index 26bb687f19a..30957061722 100644
--- a/io.openems.edge.application/EdgeApp.bndrun
+++ b/io.openems.edge.application/EdgeApp.bndrun
@@ -164,6 +164,7 @@
bnd.identity;id='io.openems.edge.meter.carlo.gavazzi',\
bnd.identity;id='io.openems.edge.meter.discovergy',\
bnd.identity;id='io.openems.edge.meter.eastron',\
+ bnd.identity;id='io.openems.edge.meter.hager',\
bnd.identity;id='io.openems.edge.meter.janitza',\
bnd.identity;id='io.openems.edge.meter.kdk',\
bnd.identity;id='io.openems.edge.meter.phoenixcontact',\
@@ -356,6 +357,7 @@
io.openems.edge.meter.carlo.gavazzi;version=snapshot,\
io.openems.edge.meter.discovergy;version=snapshot,\
io.openems.edge.meter.eastron;version=snapshot,\
+ io.openems.edge.meter.hager;version=snapshot,\
io.openems.edge.meter.janitza;version=snapshot,\
io.openems.edge.meter.kdk;version=snapshot,\
io.openems.edge.meter.phoenixcontact;version=snapshot,\
diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java
index 10b7a380eab..25799ae4805 100644
--- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java
+++ b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java
@@ -33,8 +33,9 @@ protected WebsocketClient(ControllerApiBackendImpl parent, String name, URI serv
this.onNotification = new OnNotification(parent);
this.onError = new OnError(parent);
this.onClose = (ws, code, reason, remote) -> {
- this.log.error("Disconnected from OpenEMS Backend [" + serverUri.toString() //
- + (proxy != AbstractWebsocketClient.NO_PROXY ? " via Proxy" : "") + "]");
+ this.log.error("Disconnected from OpenEMS Backend [ uri={}, proxy={}" //
+ + ", code={}, reason={}, remote={} ]", //
+ serverUri.toString(), proxy, code, reason, remote);
this.parent.getUnableToSendChannel().setNextValue(true);
};
}
diff --git a/io.openems.edge.meter.hager/.classpath b/io.openems.edge.meter.hager/.classpath
new file mode 100644
index 00000000000..6b893954e43
--- /dev/null
+++ b/io.openems.edge.meter.hager/.classpath
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/io.openems.edge.meter.hager/.gitignore b/io.openems.edge.meter.hager/.gitignore
new file mode 100644
index 00000000000..9e0adcc107c
--- /dev/null
+++ b/io.openems.edge.meter.hager/.gitignore
@@ -0,0 +1 @@
+/generated/
diff --git a/io.openems.edge.meter.hager/.project b/io.openems.edge.meter.hager/.project
new file mode 100644
index 00000000000..0f0db1fa9e5
--- /dev/null
+++ b/io.openems.edge.meter.hager/.project
@@ -0,0 +1,23 @@
+
+
+ io.openems.edge.meter.hager
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ bndtools.core.bndbuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ bndtools.core.bndnature
+
+
diff --git a/io.openems.edge.meter.hager/.settings/org.eclipse.core.resources.prefs b/io.openems.edge.meter.hager/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 00000000000..99f26c0203a
--- /dev/null
+++ b/io.openems.edge.meter.hager/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/=UTF-8
diff --git a/io.openems.edge.meter.hager/.settings/org.eclipse.jdt.core.prefs b/io.openems.edge.meter.hager/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 00000000000..9a7984bb6c3
--- /dev/null
+++ b/io.openems.edge.meter.hager/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=21
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=21
diff --git a/io.openems.edge.meter.hager/bnd.bnd b/io.openems.edge.meter.hager/bnd.bnd
new file mode 100644
index 00000000000..99534c518f7
--- /dev/null
+++ b/io.openems.edge.meter.hager/bnd.bnd
@@ -0,0 +1,16 @@
+Bundle-Name: OpenEMS Edge Meter Hager
+Bundle-Vendor:
+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.j2mod,\
+
+
+-testpath: \
+ ${testpath}
diff --git a/io.openems.edge.meter.hager/src/io/openems/edge/meter/hager/ecr380d/Config.java b/io.openems.edge.meter.hager/src/io/openems/edge/meter/hager/ecr380d/Config.java
new file mode 100644
index 00000000000..0d8748531c4
--- /dev/null
+++ b/io.openems.edge.meter.hager/src/io/openems/edge/meter/hager/ecr380d/Config.java
@@ -0,0 +1,35 @@
+package io.openems.edge.meter.hager.ecr380d;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+import io.openems.common.types.MeterType;
+
+@ObjectClassDefinition(//
+ name = "Meter Hager ECR380D", //
+ description = "Implements the Hager ECR380D 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 = "Meter-Type", description = "What is measured by this Meter?")
+ MeterType type() default MeterType.PRODUCTION;
+
+ @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();
+
+ @AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
+ String Modbus_target() default "(enabled=true)";
+
+ String webconsole_configurationFactory_nameHint() default "Meter Hager ECR380D [{id}]";
+}
diff --git a/io.openems.edge.meter.hager/src/io/openems/edge/meter/hager/ecr380d/HagerEcr380dMeter.java b/io.openems.edge.meter.hager/src/io/openems/edge/meter/hager/ecr380d/HagerEcr380dMeter.java
new file mode 100644
index 00000000000..e58107cdbeb
--- /dev/null
+++ b/io.openems.edge.meter.hager/src/io/openems/edge/meter/hager/ecr380d/HagerEcr380dMeter.java
@@ -0,0 +1,162 @@
+package io.openems.edge.meter.hager.ecr380d;
+
+import io.openems.common.channel.AccessMode;
+import io.openems.common.channel.PersistencePriority;
+import io.openems.common.channel.Unit;
+import io.openems.common.types.OpenemsType;
+import io.openems.edge.bridge.modbus.api.ElementToChannelConverter;
+import io.openems.edge.bridge.modbus.api.ElementToChannelScaleFactorConverter;
+import io.openems.edge.bridge.modbus.api.element.ModbusRegisterElement;
+import io.openems.edge.bridge.modbus.api.element.SignedWordElement;
+import io.openems.edge.bridge.modbus.api.element.StringWordElement;
+import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement;
+import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement;
+import io.openems.edge.common.channel.Doc;
+import io.openems.edge.common.component.OpenemsComponent;
+import io.openems.edge.meter.api.ElectricityMeter;
+
+/**
+ * Hager ECR380D Modbus-Register and scaling described at Modbus
+ * table Summary – rev. 1.14.
+ *
+ */
+public interface HagerEcr380dMeter extends ElectricityMeter, OpenemsComponent {
+
+ /**
+ * Chapter 5. Device Identification (start 0x1000)
+ */
+ public static final int DEVICE_START_ADDRESS = 0x1000;
+ /**
+ * Chapter 6. Instantaneous Measures (Start 0xB000)
+ */
+ public static final int INSTANTANEOUS_MEASURES_START_ADDRESS = 0xB000;
+ /**
+ * Chapter 7. Energies (kWh, ΣT) – Start 0xB060
+ */
+ public static final int ENERGY_START_ADDRESS = 0xB060;
+ /**
+ * Chapter 12. Energies per Phase (kWh)
+ */
+ public static final int ENERGY_PER_PHASE_START_ADDRESS = 0xB180;
+
+ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
+ // 5. Device Identification (start 0x1000)
+ VENDOR_NAME(Doc.of(OpenemsType.STRING).accessMode(AccessMode.READ_ONLY).persistencePriority(PersistencePriority.VERY_LOW).text("Vendor Name"), new StringWordElement(DEVICE_START_ADDRESS | 0x0000, 16), ElementToChannelConverter.DIRECT_1_TO_1),
+ PRODUCT_CODE(Doc.of(OpenemsType.STRING).accessMode(AccessMode.READ_ONLY).persistencePriority(PersistencePriority.VERY_LOW).text("Product Code"), new StringWordElement(DEVICE_START_ADDRESS | 0x0010, 16), ElementToChannelConverter.DIRECT_1_TO_1),
+ SW_VERSION(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).persistencePriority(PersistencePriority.VERY_LOW).text("Software version"), new StringWordElement(DEVICE_START_ADDRESS | 0x0020, 2), ElementToChannelConverter.DIRECT_1_TO_1),
+ VENDOR_URL(Doc.of(OpenemsType.STRING).accessMode(AccessMode.READ_ONLY).persistencePriority(PersistencePriority.VERY_LOW).text("Product Name"), new StringWordElement(DEVICE_START_ADDRESS | 0x0022, 16), ElementToChannelConverter.DIRECT_1_TO_1),
+ PRODUCT_NAME(Doc.of(OpenemsType.STRING).accessMode(AccessMode.READ_ONLY).persistencePriority(PersistencePriority.VERY_LOW).text("Product Name"), new StringWordElement(DEVICE_START_ADDRESS | 0x0032, 16), ElementToChannelConverter.DIRECT_1_TO_1),
+ MODEL_NAME(Doc.of(OpenemsType.STRING).accessMode(AccessMode.READ_ONLY).persistencePriority(PersistencePriority.VERY_LOW).text("Model Name"), new StringWordElement(DEVICE_START_ADDRESS | 0x0042, 16), ElementToChannelConverter.DIRECT_1_TO_1),
+ APPLICATION_NAME(Doc.of(OpenemsType.STRING).accessMode(AccessMode.READ_ONLY).persistencePriority(PersistencePriority.VERY_LOW).text("User application name"), new StringWordElement(DEVICE_START_ADDRESS | 0x0052, 16), ElementToChannelConverter.DIRECT_1_TO_1),
+ HW_VERSION(Doc.of(OpenemsType.STRING).accessMode(AccessMode.READ_ONLY).persistencePriority(PersistencePriority.VERY_LOW).text("Hardware version"), new StringWordElement(DEVICE_START_ADDRESS | 0x0062, 2), ElementToChannelConverter.DIRECT_1_TO_1),
+ PRODUCTION_CODE_SERIAL_NUMBER(Doc.of(OpenemsType.STRING).accessMode(AccessMode.READ_ONLY).persistencePriority(PersistencePriority.VERY_LOW).text("Production code serial number"), new StringWordElement(DEVICE_START_ADDRESS | 0x0064, 16), ElementToChannelConverter.DIRECT_1_TO_1),
+ PRODUCTION_SITE_CODE(Doc.of(OpenemsType.STRING).accessMode(AccessMode.READ_ONLY).persistencePriority(PersistencePriority.VERY_LOW).text("Production site code"), new StringWordElement(DEVICE_START_ADDRESS | 0x0074, 2), ElementToChannelConverter.DIRECT_1_TO_1),
+ PRODUCTION_DAY_OF_YEAR(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).persistencePriority(PersistencePriority.VERY_LOW).text("Production day of the year"), new UnsignedWordElement(DEVICE_START_ADDRESS | 0x0076), ElementToChannelConverter.DIRECT_1_TO_1),
+ PRODUCTION_YEAR(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).persistencePriority(PersistencePriority.VERY_LOW).text("Production year"), new UnsignedWordElement(DEVICE_START_ADDRESS | 0x0077), ElementToChannelConverter.DIRECT_1_TO_1),
+
+ // 6. Instantaneous Measures (Start 0xB000)
+ // V_L1_NEUTRAL(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT).persistencePriority(PersistencePriority.HIGH).text("Power L1 to N"),new UnsignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0000), ElementToChannelScaleFactorConverter.DIVIDE(100.0d)),
+ // V_L2_NEUTRAL(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT).persistencePriority(PersistencePriority.HIGH).text("Power L2 to N"), new UnsignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0001), ElementToChannelScaleFactorConverter.DIVIDE(100.0d)),
+ // V_L3_NEUTRAL(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT).persistencePriority(PersistencePriority.HIGH).text("Power L3 to N"), new UnsignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0002), ElementToChannelScaleFactorConverter.DIVIDE(100.0d)),
+ V_L1_L2(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.MILLIVOLT).persistencePriority(PersistencePriority.HIGH).text("Power L1 to L2"), new UnsignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0003), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)),
+ V_L2_L3(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.MILLIVOLT).persistencePriority(PersistencePriority.HIGH).text("Power L2 to L3"), new UnsignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0004), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)),
+ V_L3_L1(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.MILLIVOLT).persistencePriority(PersistencePriority.HIGH).text("Power L3 to L1"), new UnsignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0005), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)),
+ // FREQUENCY(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.HERTZ).persistencePriority(PersistencePriority.HIGH).text("Frequency"), new UnsignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0006), ElementToChannelScaleFactorConverter.DIVIDE(100.0d)),
+ /* 0x0007+8 not defined */
+ // I_L1(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.MILLIAMPERE).persistencePriority(PersistencePriority.HIGH).text("Current L1"), new UnsignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0009), ElementToChannelConverter.DIRECT_1_TO_1),
+ // I_L2(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.MILLIAMPERE).persistencePriority(PersistencePriority.HIGH).text("Current L2"), new UnsignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x000B), ElementToChannelConverter.DIRECT_1_TO_1),
+ // I_L3(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.MILLIAMPERE).persistencePriority(PersistencePriority.HIGH).text("Current L3"), new UnsignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x000D), ElementToChannelConverter.DIRECT_1_TO_1),
+ I_NEUTRAL(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.MILLIAMPERE).persistencePriority(PersistencePriority.HIGH).text("Current neutral"), new UnsignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x000F), ElementToChannelConverter.DIRECT_1_TO_1),
+
+ // P_SUM(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.WATT).persistencePriority(PersistencePriority.HIGH).text("Power Sum(L1,L2,L3)"), new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0011), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), // kW/100, S32
+ // Q_SUM(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE_REACTIVE).persistencePriority(PersistencePriority.HIGH).text("Power reactive Sum(L1,L2,L3)"), new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0013), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), // kvar/100, S32
+ S_SUM(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE).persistencePriority(PersistencePriority.HIGH).text("Power apparent Sum(L1,L2,L3)"), new UnsignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0015), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), // kVA/100, U32
+ PF_SUM_IEC(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.NONE).persistencePriority(PersistencePriority.HIGH).text("Power factor IEC Sum(L1,L2,L3)"), new SignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0017), ElementToChannelConverter.DIRECT_1_TO_1), // promille, S16, -1000..+1000
+ PF_SUM_IEEE(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.NONE).persistencePriority(PersistencePriority.HIGH).text("Power factor IEEE Sum(L1,L2,L3)"), new SignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0018), ElementToChannelConverter.DIRECT_1_TO_1), // promille, S16, -1000..+1000
+
+ // P_L1(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.WATT).persistencePriority(PersistencePriority.HIGH).text("Power L1"), new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0019), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), // kW/100, S32
+ // P_L2(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.WATT).persistencePriority(PersistencePriority.HIGH).text("Power L2"), new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x001B), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), // kW/100, S32
+ // P_L3(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.WATT).persistencePriority(PersistencePriority.HIGH).text("Power L3"), new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x001D), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), // kW/100, S32
+
+ // Q_L1(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE_REACTIVE).persistencePriority(PersistencePriority.HIGH).text("Power reactive L1"), new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x001F), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), // kvar/100, S32
+ // Q_L2(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE_REACTIVE).persistencePriority(PersistencePriority.HIGH).text("Power reactive L2"), new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0021), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), // kvar/100, S32
+ // Q_L3(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE_REACTIVE).persistencePriority(PersistencePriority.HIGH).text("Power reactive L3"), new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0023), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), // kvar/100, S32
+
+ S_L1(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE).persistencePriority(PersistencePriority.HIGH).text("Power apparent L1"), new UnsignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0025), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), // kVA/100, U32
+ S_L2(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE).persistencePriority(PersistencePriority.HIGH).text("Power apparent L2"), new UnsignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0027), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), // kVA/100, U32
+ S_L3(Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE).persistencePriority(PersistencePriority.HIGH).text("Power apparent L3"), new UnsignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0029), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), // kVA/100, U32
+
+ PF_L1_IEC(Doc.of(OpenemsType.FLOAT).accessMode(AccessMode.READ_ONLY).unit(Unit.PERCENT).persistencePriority(PersistencePriority.HIGH).text("Power factor IEC L1"), new SignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x002B), ElementToChannelScaleFactorConverter.DIVIDE(1000.0d)), // promille, S16, -1000..+1000
+ PF_L2_IEC(Doc.of(OpenemsType.FLOAT).accessMode(AccessMode.READ_ONLY).unit(Unit.PERCENT).persistencePriority(PersistencePriority.HIGH).text("Power factor IEC L2"), new SignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x002C), ElementToChannelScaleFactorConverter.DIVIDE(1000.0d)), // promille, S16, -1000..+1000
+ PF_L3_IEC(Doc.of(OpenemsType.FLOAT).accessMode(AccessMode.READ_ONLY).unit(Unit.PERCENT).persistencePriority(PersistencePriority.HIGH).text("Power factor IEC L3"), new SignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x002D), ElementToChannelScaleFactorConverter.DIVIDE(1000.0d)), // promille, S16, -1000..+1000
+
+ PF_L1_IEEE(Doc.of(OpenemsType.FLOAT).accessMode(AccessMode.READ_ONLY).unit(Unit.PERCENT).persistencePriority(PersistencePriority.HIGH).text("Power factor IEEE L1"), new SignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x002E), ElementToChannelScaleFactorConverter.DIVIDE(1000.0d)), // promille, S16, -1000..+1000
+ PF_L2_IEEE(Doc.of(OpenemsType.FLOAT).accessMode(AccessMode.READ_ONLY).unit(Unit.PERCENT).persistencePriority(PersistencePriority.HIGH).text("Power factor IEEE L2"), new SignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x002F), ElementToChannelScaleFactorConverter.DIVIDE(1000.0d)), // promille, S16, -1000..+1000
+ PF_L3_IEEE(Doc.of(OpenemsType.FLOAT).accessMode(AccessMode.READ_ONLY).unit(Unit.PERCENT).persistencePriority(PersistencePriority.HIGH).text("Power factor IEEE L3"), new SignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0030), ElementToChannelScaleFactorConverter.DIVIDE(1000.0d)), // promille, S16, -1000..+1000
+
+ // 7. Energies (kWh, ΣT) – Start 0xB060
+ // EA_PLUS_SUM(Doc.of(OpenemsType.LONG).unit(Unit.WATT_HOURS).persistencePriority(PersistencePriority.HIGH).text("Imported Energy"), new UnsignedDoublewordElement(ENERGY_START_ADDRESS | 0x0000), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)), // Ea+ (ΣT) kWh, U32
+ ER_PLUS_SUM(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE_REACTIVE_HOURS).persistencePriority(PersistencePriority.HIGH).text("Imported reactive Energy"), new UnsignedDoublewordElement(ENERGY_START_ADDRESS | 0x0002), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)), // Ea+ (ΣT) kWh, U32
+ // EA_MINUS_SUM(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.WATT_HOURS).persistencePriority(PersistencePriority.HIGH).text("Exported Energy"), new UnsignedDoublewordElement(ENERGY_START_ADDRESS | 0x0004), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)), // Ea- (ΣT) kWh, U32
+ ER_MINUS_SUM(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE_REACTIVE_HOURS).persistencePriority(PersistencePriority.HIGH).text("Exported reactive Energy"), new UnsignedDoublewordElement(ENERGY_START_ADDRESS | 0x0006), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)), // Ea- (ΣT) kWh, U32
+ EA_PLUS_DETAILED_SUM(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.WATT_HOURS).persistencePriority(PersistencePriority.HIGH).text("Imported Energy"), new UnsignedDoublewordElement(ENERGY_START_ADDRESS | 0x0008), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)), // Ea+ (ΣT) kWh, U32
+ EA_MINUS_DETAILED_SUM(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.WATT_HOURS).persistencePriority(PersistencePriority.HIGH).text("Exported Energy"), new UnsignedDoublewordElement(ENERGY_START_ADDRESS | 0x000A), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)), // Ea- (ΣT) kWh, U32
+
+ // 12. Energies per Phase (kWh)
+ // EA_PLUS_L1(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.WATT_HOURS).persistencePriority(PersistencePriority.HIGH).text("Imported Energy L1"), new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x0000), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)),
+ // EA_PLUS_L2(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.WATT_HOURS).persistencePriority(PersistencePriority.HIGH).text("Imported Energy L2"), new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x0002), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)),
+ // EA_PLUS_L3(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.WATT_HOURS).persistencePriority(PersistencePriority.HIGH).text("Imported Energy L3"), new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x0004), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)),
+ // EA_MINUS_L1(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.WATT_HOURS).persistencePriority(PersistencePriority.HIGH).text("Exported Energy L1"), new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x0006), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)),
+ // EA_MINUS_L2(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.WATT_HOURS).persistencePriority(PersistencePriority.HIGH).text("Exported Energy L2"), new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x0008), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)),
+ // EA_MINUS_L3(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.WATT_HOURS).persistencePriority(PersistencePriority.HIGH).text("Exported Energy L3"), new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x000A), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)),
+
+ ER_PLUS_L1(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE_REACTIVE_HOURS).persistencePriority(PersistencePriority.HIGH).text("Imported reactive Energy L1"), new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x000C), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)),
+ ER_PLUS_L2(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE_REACTIVE_HOURS).persistencePriority(PersistencePriority.HIGH).text("Imported reactive Energy L2"), new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x000E), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)),
+ ER_PLUS_L3(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE_REACTIVE_HOURS).persistencePriority(PersistencePriority.HIGH).text("Imported reactive Energy L3"), new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x0010), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)),
+ ER_MINUS_L1(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE_REACTIVE_HOURS).persistencePriority(PersistencePriority.HIGH).text("Exported reactive Energy L1"), new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x0012), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)),
+ ER_MINUS_L2(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE_REACTIVE_HOURS).persistencePriority(PersistencePriority.HIGH).text("Exported reactive Energy L2"), new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x0014), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)),
+ ER_MINUS_L3(Doc.of(OpenemsType.LONG).accessMode(AccessMode.READ_ONLY).unit(Unit.VOLT_AMPERE_REACTIVE_HOURS).persistencePriority(PersistencePriority.HIGH).text("Exported reactive Energy L3"), new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x0016), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)),
+
+ ;
+
+ private final Doc doc;
+ private final ModbusRegisterElement, ?> address;
+ private final ElementToChannelConverter converter;
+
+ ChannelId(final Doc doc, final ModbusRegisterElement, ?> address, ElementToChannelConverter converter) {
+ this.doc = doc;
+ this.address = address;
+ this.converter = converter;
+ }
+
+ /**
+ * Accessor method for the {@link Doc} instance of this channel.
+ *
+ * @return the Doc instance
+ * @see ChannelId#doc()
+ */
+ @Override
+ public Doc doc() {
+ return this.doc;
+ }
+
+ /**
+ * Accessor method for the modbus register number of this channel.
+ *
+ * @return the modbus register number for this channel
+ */
+ public ModbusRegisterElement, ?> address() {
+ return this.address;
+ }
+
+ /**
+ * Accessor method for the modbus channel converter of this channel.
+ *
+ * @return the converter for this channel
+ */
+ public ElementToChannelConverter converter() {
+ return this.converter;
+ }
+ }
+}
\ No newline at end of file
diff --git a/io.openems.edge.meter.hager/src/io/openems/edge/meter/hager/ecr380d/HagerEcr380dMeterImpl.java b/io.openems.edge.meter.hager/src/io/openems/edge/meter/hager/ecr380d/HagerEcr380dMeterImpl.java
new file mode 100644
index 00000000000..eb2d77183c6
--- /dev/null
+++ b/io.openems.edge.meter.hager/src/io/openems/edge/meter/hager/ecr380d/HagerEcr380dMeterImpl.java
@@ -0,0 +1,252 @@
+package io.openems.edge.meter.hager.ecr380d;
+
+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.Modified;
+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.channel.AccessMode;
+import io.openems.common.exceptions.OpenemsException;
+import io.openems.common.types.MeterType;
+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.ElementToChannelScaleFactorConverter;
+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.ModbusRegisterElement;
+import io.openems.edge.bridge.modbus.api.element.SignedDoublewordElement;
+import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement;
+import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement;
+import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask;
+import io.openems.edge.common.channel.value.Value;
+import io.openems.edge.common.component.OpenemsComponent;
+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.modbusslave.ModbusType;
+import io.openems.edge.common.taskmanager.Priority;
+import io.openems.edge.meter.api.ElectricityMeter;
+
+/**
+ * Hager ECR380D
+ * energy meter via Modbus RTU.
+ *
+ * @see HagerEcr380dMeter for detailed mapping information
+ */
+@Designate(ocd = Config.class, factory = true)
+@Component(//
+ name = "Meter.Hager.ECR380D", //
+ immediate = true, //
+ configurationPolicy = ConfigurationPolicy.REQUIRE //
+)
+public class HagerEcr380dMeterImpl extends AbstractOpenemsModbusComponent //
+ implements HagerEcr380dMeter, ElectricityMeter, ModbusComponent, OpenemsComponent, ModbusSlave {
+
+ @Reference
+ private ConfigurationAdmin cm;
+
+ @Override
+ @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY)
+ protected void setModbus(BridgeModbus modbus) {
+ super.setModbus(modbus);
+ }
+
+ private Config config;
+
+ public HagerEcr380dMeterImpl() {
+ super(OpenemsComponent.ChannelId.values(), //
+ ModbusComponent.ChannelId.values(), //
+ ElectricityMeter.ChannelId.values(), //
+ HagerEcr380dMeter.ChannelId.values()//
+ );
+ }
+
+ @Activate
+ @Modified
+ protected void activate(final ComponentContext context, final Config config) throws OpenemsException {
+ this.config = config;
+
+ super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, "Modbus", config.modbus_id());
+ }
+
+
+ @Deactivate
+ @Override
+ protected void deactivate() {
+ super.deactivate();
+ }
+
+ @Override
+ public String debugLog() {
+ final Value p = this.getActivePower();
+ return "P=" + (p.isDefined() ? p.get() + " W" : "-");
+ }
+
+ @Override
+ public MeterType getMeterType() {
+ return this.config.type();
+ }
+
+ /**
+ * Calculate a List of registers, this modbus slave provides to the
+ * outside world.
+ *
+ * @param accessMode the filter which registers are available
+ * @return the table of registers, this implementation present to clients
+ */
+ @Override
+ public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) {
+ return new ModbusSlaveTable(//
+ OpenemsComponent.getModbusSlaveNatureTable(accessMode), //
+ ElectricityMeter.getModbusSlaveNatureTable(accessMode), //
+ ModbusSlaveNatureTable.of(HagerEcr380dMeter.class, accessMode, 0x002C) //
+ .channel(0x0000, HagerEcr380dMeter.ChannelId.V_L1_L2, ModbusType.UINT32) //
+ .channel(0x0002, HagerEcr380dMeter.ChannelId.V_L2_L3, ModbusType.UINT32) //
+ .channel(0x0004, HagerEcr380dMeter.ChannelId.V_L3_L1, ModbusType.UINT32) //
+ .channel(0x0006, HagerEcr380dMeter.ChannelId.ER_PLUS_L1, ModbusType.UINT32) //
+ .channel(0x0008, HagerEcr380dMeter.ChannelId.I_NEUTRAL, ModbusType.UINT64) //
+ .channel(0x000C, HagerEcr380dMeter.ChannelId.ER_PLUS_SUM, ModbusType.UINT64) //
+ .channel(0x0010, HagerEcr380dMeter.ChannelId.ER_MINUS_SUM, ModbusType.UINT64) //
+ .channel(0x0014, HagerEcr380dMeter.ChannelId.ER_PLUS_L1, ModbusType.UINT64) //
+ .channel(0x0018, HagerEcr380dMeter.ChannelId.ER_PLUS_L2, ModbusType.UINT64) //
+ .channel(0x001C, HagerEcr380dMeter.ChannelId.ER_PLUS_L3, ModbusType.UINT64) //
+ .channel(0x0020, HagerEcr380dMeter.ChannelId.ER_MINUS_L1, ModbusType.UINT64) //
+ .channel(0x0024, HagerEcr380dMeter.ChannelId.ER_MINUS_L2, ModbusType.UINT64) //
+ .channel(0x0028, HagerEcr380dMeter.ChannelId.ER_MINUS_L3, ModbusType.UINT64) //
+ .build()
+ );
+ }
+
+ /**
+ * Calculate the read commands used to read values from the device.
+ *
+ * @return the modbus protocol to requests value from the device
+ */
+ @Override
+ protected ModbusProtocol defineModbusProtocol() {
+ final ModbusProtocol modbusProtocol = new ModbusProtocol(this);
+
+ modbusProtocol.addTask(this.getInstantaneousMeasuresTask());
+
+ modbusProtocol.addTask(this.getEnergyTask());
+
+ modbusProtocol.addTask(this.getEnergyByPhaseTask());
+
+ modbusProtocol.addTask(this.getDeviceTask());
+
+ return modbusProtocol;
+ }
+
+ private FC3ReadRegistersTask getDeviceTask() {
+ return new FC3ReadRegistersTask(//
+ DEVICE_START_ADDRESS, //
+ Priority.LOW,
+ this.m(HagerEcr380dMeter.ChannelId.VENDOR_NAME), //
+ this.m(HagerEcr380dMeter.ChannelId.PRODUCT_CODE), //
+ this.m(HagerEcr380dMeter.ChannelId.SW_VERSION), //
+ this.m(HagerEcr380dMeter.ChannelId.VENDOR_URL), //
+ this.m(HagerEcr380dMeter.ChannelId.PRODUCT_NAME), //
+ this.m(HagerEcr380dMeter.ChannelId.MODEL_NAME), //
+ this.m(HagerEcr380dMeter.ChannelId.APPLICATION_NAME), //
+ this.m(HagerEcr380dMeter.ChannelId.HW_VERSION), //
+ this.m(HagerEcr380dMeter.ChannelId.PRODUCTION_CODE_SERIAL_NUMBER), //
+ this.m(HagerEcr380dMeter.ChannelId.PRODUCTION_SITE_CODE), //
+ this.m(HagerEcr380dMeter.ChannelId.PRODUCTION_DAY_OF_YEAR), //
+ this.m(HagerEcr380dMeter.ChannelId.PRODUCTION_YEAR) //
+ );
+ }
+
+ private FC3ReadRegistersTask getInstantaneousMeasuresTask() {
+ return new FC3ReadRegistersTask(//
+ INSTANTANEOUS_MEASURES_START_ADDRESS, //
+ Priority.HIGH,
+ this.m(ElectricityMeter.ChannelId.VOLTAGE_L1, new UnsignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0000), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)),
+ this.m(ElectricityMeter.ChannelId.VOLTAGE_L2, new UnsignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0001), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)),
+ this.m(ElectricityMeter.ChannelId.VOLTAGE_L3, new UnsignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0002), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)),
+ this.m(HagerEcr380dMeter.ChannelId.V_L1_L2), //
+ this.m(HagerEcr380dMeter.ChannelId.V_L2_L3), //
+ this.m(HagerEcr380dMeter.ChannelId.V_L3_L1), //
+ this.m(ElectricityMeter.ChannelId.FREQUENCY, new UnsignedWordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0006), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), //
+ new DummyRegisterElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0007, INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0008), //
+ this.m(ElectricityMeter.ChannelId.CURRENT_L1, new UnsignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0009), ElementToChannelConverter.DIRECT_1_TO_1), //
+ this.m(ElectricityMeter.ChannelId.CURRENT_L2, new UnsignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x000B), ElementToChannelConverter.DIRECT_1_TO_1), //
+ this.m(ElectricityMeter.ChannelId.CURRENT_L3, new UnsignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x000D), ElementToChannelConverter.DIRECT_1_TO_1), //
+ this.m(HagerEcr380dMeter.ChannelId.I_NEUTRAL), //
+ this.m(ElectricityMeter.ChannelId.ACTIVE_POWER, new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0011), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), //
+ this.m(ElectricityMeter.ChannelId.REACTIVE_POWER, new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0013), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), //
+ this.m(HagerEcr380dMeter.ChannelId.S_SUM), //
+ this.m(HagerEcr380dMeter.ChannelId.PF_SUM_IEC), //
+ this.m(HagerEcr380dMeter.ChannelId.PF_SUM_IEEE), //
+ this.m(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0019), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), //
+ this.m(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x001B), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), //
+ this.m(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x001D), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), //
+ this.m(ElectricityMeter.ChannelId.REACTIVE_POWER_L1, new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x001F), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), //
+ this.m(ElectricityMeter.ChannelId.REACTIVE_POWER_L2, new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0021), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), //
+ this.m(ElectricityMeter.ChannelId.REACTIVE_POWER_L3, new SignedDoublewordElement(INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0023), ElementToChannelScaleFactorConverter.MULTIPLY(10.0d)), //
+ this.m(HagerEcr380dMeter.ChannelId.S_L1), //
+ this.m(HagerEcr380dMeter.ChannelId.S_L2), //
+ this.m(HagerEcr380dMeter.ChannelId.S_L3), //
+ this.m(HagerEcr380dMeter.ChannelId.PF_L1_IEC), //
+ this.m(HagerEcr380dMeter.ChannelId.PF_L2_IEC), //
+ this.m(HagerEcr380dMeter.ChannelId.PF_L3_IEC), //
+ this.m(HagerEcr380dMeter.ChannelId.PF_L1_IEEE), //
+ this.m(HagerEcr380dMeter.ChannelId.PF_L2_IEEE), //
+ this.m(HagerEcr380dMeter.ChannelId.PF_L3_IEEE) //
+ );
+ }
+
+ private FC3ReadRegistersTask getEnergyTask() {
+ return new FC3ReadRegistersTask(//
+ ENERGY_START_ADDRESS, //
+ Priority.HIGH, //
+ this.m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, new UnsignedDoublewordElement(ENERGY_START_ADDRESS | 0x0000), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)), //
+ this.m(HagerEcr380dMeter.ChannelId.ER_PLUS_SUM), //
+ this.m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, new UnsignedDoublewordElement(ENERGY_START_ADDRESS | 0x0004), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)), //
+ this.m(HagerEcr380dMeter.ChannelId.ER_MINUS_SUM), //
+ this.m(HagerEcr380dMeter.ChannelId.EA_PLUS_DETAILED_SUM), //
+ this.m(HagerEcr380dMeter.ChannelId.EA_MINUS_DETAILED_SUM) //
+ );
+ }
+
+ private FC3ReadRegistersTask getEnergyByPhaseTask() {
+ return new FC3ReadRegistersTask(//
+ ENERGY_PER_PHASE_START_ADDRESS, //
+ Priority.HIGH, //
+ this.m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY_L1, new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x0000), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)), //
+ this.m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY_L2, new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x0002), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)), //
+ this.m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY_L3, new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x0004), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)), //
+ this.m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY_L1, new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x0006), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)), //
+ this.m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY_L2, new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x0008), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)), //
+ this.m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY_L3, new UnsignedDoublewordElement(ENERGY_PER_PHASE_START_ADDRESS | 0x000A), ElementToChannelScaleFactorConverter.MULTIPLY(1000.0d)), //
+ this.m(HagerEcr380dMeter.ChannelId.ER_PLUS_L1),
+ this.m(HagerEcr380dMeter.ChannelId.ER_PLUS_L2),
+ this.m(HagerEcr380dMeter.ChannelId.ER_PLUS_L3),
+ this.m(HagerEcr380dMeter.ChannelId.ER_MINUS_L1),
+ this.m(HagerEcr380dMeter.ChannelId.ER_MINUS_L2),
+ this.m(HagerEcr380dMeter.ChannelId.ER_MINUS_L3)
+
+ );
+ }
+
+ /**
+ * Helper method to create a modbus register by a {@link HagerEcr380dMeter.ChannelId}.
+ *
+ * @param channelId The channel description
+ * @return the element parameter
+ */
+ private ModbusRegisterElement,?> m(HagerEcr380dMeter.ChannelId channelId) {
+ return channelId.converter() != null //
+ ? this.m(channelId, channelId.address(), channelId.converter()) //
+ : this.m(channelId, channelId.address());
+ }
+}
diff --git a/io.openems.edge.meter.hager/src/io/openems/edge/meter/hager/ecr380d/package-info.java b/io.openems.edge.meter.hager/src/io/openems/edge/meter/hager/ecr380d/package-info.java
new file mode 100644
index 00000000000..c151ae42842
--- /dev/null
+++ b/io.openems.edge.meter.hager/src/io/openems/edge/meter/hager/ecr380d/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * Energy meter: Hager ECR 380 D.
+ *
+ * @author hrohmer
+ */
+@org.osgi.annotation.versioning.Version("1.0.0")
+package io.openems.edge.meter.hager.ecr380d;
\ No newline at end of file
diff --git a/io.openems.edge.meter.hager/test/io/openems/edge/meter/hager/ecr380d/HagerEcr380dMeterTest.java b/io.openems.edge.meter.hager/test/io/openems/edge/meter/hager/ecr380d/HagerEcr380dMeterTest.java
new file mode 100644
index 00000000000..35491eabab8
--- /dev/null
+++ b/io.openems.edge.meter.hager/test/io/openems/edge/meter/hager/ecr380d/HagerEcr380dMeterTest.java
@@ -0,0 +1,225 @@
+package io.openems.edge.meter.hager.ecr380d;
+
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import io.openems.common.types.MeterType;
+import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
+import io.openems.edge.common.test.AbstractComponentTest.TestCase;
+import io.openems.edge.meter.api.ElectricityMeter;
+import io.openems.edge.common.test.ComponentTest;
+
+/**
+ * minimal smoke test with {@link DummyModbusBridge}.
+ */
+public class HagerEcr380dMeterTest {
+
+ private static final String CID = "meter0";
+ private static final String MID = "modbus0";
+ private static final MyConfig CONFIG = MyConfig.create() //
+ .setId(CID)
+ .setModbusId(MID)
+ .setType(MeterType.GRID)
+ .build();
+
+ private ComponentTest componentTest;
+
+ @Before
+ public void before() throws Exception {
+ final DummyModbusBridge bridge = new DummyModbusBridge(MID) //
+ .withRegister(HagerEcr380dMeter.DEVICE_START_ADDRESS | 0x0020, 0x0203)
+ .withRegister(HagerEcr380dMeter.DEVICE_START_ADDRESS | 0x0021, 0x0000)
+ .withRegister(HagerEcr380dMeter.DEVICE_START_ADDRESS | 0x0062, 0x0401)
+ .withRegister(HagerEcr380dMeter.DEVICE_START_ADDRESS | 0x0063, 0x0000)
+ .withRegister(HagerEcr380dMeter.DEVICE_START_ADDRESS | 0x0076, 0x0001)
+ .withRegister(HagerEcr380dMeter.DEVICE_START_ADDRESS | 0x0077, 0x07E9)
+
+ .withRegisters(HagerEcr380dMeter.INSTANTANEOUS_MEASURES_START_ADDRESS | 0x0000, //
+ 23000, //
+ 23150, //
+ 22950, //
+ 39500, //
+ 39600, //
+ 39000, //
+ 5010, //
+ 0x0000, //
+ 0x0000, //
+ 0x0000, 0x4E20,//
+ 0x0000, 0x9C40,//
+ 0x0000, 0xEA60,//
+ 0x0000, 0x2710,//
+ 0x0027, 0x1F3C,// P_SUM
+ 0x0008, 0xF494,// Q_SUM
+ 0x0001, 0x2624,// S_SUM
+ 0x024B, // PF_SUM_IEC
+ 0x035C, // PF_SUM_IEEE
+ 0x0003, 0x5E80, // ACTIVE_POWER_L1
+ 0xFFFC, 0xA180, // ACTIVE_POWER_L2
+ 0x0001, 0xAF40, // ACTIVE_POWER_L3
+ 0x0003, 0x5E80, // REACTIVE_POWER_L1
+ 0x0001, 0xAF40, // REACTIVE_POWER_L2
+ 0xFFFE, 0x50C0, // REACTIVE_POWER_L3
+ 0x0003, 0x5E80, // S_L1
+ 0x0001, 0xAF40, // S_L2
+ 0x0000, 0x0000, // S_L3
+ 0x0000, // PF_L1_IEC
+ 0x0000, // PF_L1_IEC
+ 0x0000, // PF_L2_IEC
+ 0x0000, // PF_L1_IEEE
+ 0x0000, // PF_L2_IEEE
+ 0x0000 // PF_L3_IEEE
+ ) //
+ .withRegisters(HagerEcr380dMeter.ENERGY_START_ADDRESS | 0x0000, //
+ 0x0098, 0x967F,// ACTIVE_CONSUMPTION_ENERGY
+ 0x0032, 0xDCD5,// ER_PLUS_SUM
+ 0x0009, 0xFBF1,// ACTIVE_PRODUCTION_ENERGY
+ 0x0012, 0xD687,// ER_MINUS_SUM
+ 0x0023, 0xCACE,// EA_PLUS_DETAILED_SUM
+ 0x0011, 0x201E) // EA_MINUS_DETAILED_SUM
+ .withRegisters(HagerEcr380dMeter.ENERGY_PER_PHASE_START_ADDRESS | 0x0000, //
+ 0x0098, 0x961B,// ACTIVE_CONSUMPTION_ENERGY_L1
+ 0x0098, 0x95B7,// ACTIVE_CONSUMPTION_ENERGY_L2
+ 0x0098, 0x9553,// ACTIVE_CONSUMPTION_ENERGY_L3
+ 0x0098, 0x9297,// ACTIVE_PRODUCTION_ENERGY_L1
+ 0x0098, 0x8EAF,// ACTIVE_PRODUCTION_ENERGY_L2
+ 0x0098, 0x8AC7,// ACTIVE_PRODUCTION_ENERGY_L3
+ 0x0010, 0xF703,// ER_PLUS_L1
+ 0x0010, 0xF69F,// ER_PLUS_L2
+ 0x0010, 0xF63B,// ER_PLUS_L2
+ 0x0054, 0xC68F,// ER_MINUS_L1
+ 0x0054, 0xC62B,// ER_MINUS_L1
+ 0x0054, 0xC5C7); // ER_MINUS_L1
+ this.withRegister(bridge, HagerEcr380dMeter.DEVICE_START_ADDRESS | 0x0000, "Hager ");
+ this.withRegister(bridge, HagerEcr380dMeter.DEVICE_START_ADDRESS | 0x0010, "ECR380D ");
+ this.withRegister(bridge, HagerEcr380dMeter.DEVICE_START_ADDRESS | 0x0022, "http://www.hager.de ");
+ this.withRegister(bridge, HagerEcr380dMeter.DEVICE_START_ADDRESS | 0x0032, "3P Meter 80A 4M ");
+ this.withRegister(bridge, HagerEcr380dMeter.DEVICE_START_ADDRESS | 0x0042, "AGARDIO RJ45 MID ");
+ this.withRegister(bridge, HagerEcr380dMeter.DEVICE_START_ADDRESS | 0x0052, "APL ");
+ this.withRegister(bridge, HagerEcr380dMeter.DEVICE_START_ADDRESS | 0x0064, "12345678 ");
+ this.withRegister(bridge, HagerEcr380dMeter.DEVICE_START_ADDRESS | 0x0074, "DE ");
+
+ this.componentTest = new ComponentTest(new HagerEcr380dMeterImpl())
+ .addReference("cm", new io.openems.edge.common.test.DummyConfigurationAdmin())
+ .addReference("setModbus", bridge);
+ }
+
+ @After
+ public void after() throws Exception {
+ if (this.componentTest != null) {
+ this.componentTest.deactivate();
+ }
+ this.componentTest = null;
+ }
+
+ @Test
+ public void activateDeactivateTest() throws Exception {
+ this.componentTest.activate(CONFIG)
+ .next(new TestCase())
+ .deactivate();
+
+ assertTrue(true);
+ }
+
+ @Test
+ public void testVoltage() throws Exception {
+ this.componentTest.activate(CONFIG)
+ .next(new TestCase()//
+ .output(ElectricityMeter.ChannelId.VOLTAGE_L1, 230000) //
+ .output(ElectricityMeter.ChannelId.VOLTAGE_L2, 231500) //
+ .output(ElectricityMeter.ChannelId.VOLTAGE_L3, 229500) //
+ .output(HagerEcr380dMeter.ChannelId.V_L1_L2, 395000) //
+ .output(HagerEcr380dMeter.ChannelId.V_L2_L3, 396000) //
+ .output(HagerEcr380dMeter.ChannelId.V_L3_L1, 390000) //
+ )
+ .deactivate();
+ }
+
+ @Test
+ public void testFrequency() throws Exception {
+ this.componentTest.activate(CONFIG)
+ .next(new TestCase()//
+ .output(ElectricityMeter.ChannelId.FREQUENCY, 50100) //
+ )
+ .deactivate();
+ }
+
+ @Test
+ public void testCurrent() throws Exception {
+ this.componentTest.activate(CONFIG)
+ .next(new TestCase()//
+ .output(ElectricityMeter.ChannelId.CURRENT_L1, 20000) //
+ .output(ElectricityMeter.ChannelId.CURRENT_L2, 40000) //
+ .output(ElectricityMeter.ChannelId.CURRENT_L3, 60000) //
+ .output(HagerEcr380dMeter.ChannelId.I_NEUTRAL, 10000) //
+ )
+ .deactivate();
+ }
+
+ @Test
+ public void testPower() throws Exception {
+ this.componentTest.activate(CONFIG)
+ .next(new TestCase()//
+ .output(ElectricityMeter.ChannelId.ACTIVE_POWER, 25639000) //
+ .output(ElectricityMeter.ChannelId.REACTIVE_POWER, 5869000) //
+ .output(HagerEcr380dMeter.ChannelId.S_SUM, 753000) //
+ .output(HagerEcr380dMeter.ChannelId.PF_SUM_IEC, 587) //
+ .output(HagerEcr380dMeter.ChannelId.PF_SUM_IEEE, 860) //
+ .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, 2208000) //
+ .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, -2208000) //
+ .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, 1104000) //
+ .output(ElectricityMeter.ChannelId.REACTIVE_POWER_L1, 2208000)
+ .output(ElectricityMeter.ChannelId.REACTIVE_POWER_L2, 1104000)
+ .output(ElectricityMeter.ChannelId.REACTIVE_POWER_L3, -1104000)
+ .output(HagerEcr380dMeter.ChannelId.S_L1, 2208000) //
+ .output(HagerEcr380dMeter.ChannelId.S_L2, 1104000) //
+ .output(HagerEcr380dMeter.ChannelId.S_L3, 0) //
+ )
+ .deactivate();
+ }
+
+ @Test
+ public void testEnergy() throws Exception {
+ this.componentTest.activate(CONFIG)
+ .next(new TestCase()//
+ .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, 9999999000L) //
+ .output(HagerEcr380dMeter.ChannelId.ER_PLUS_SUM, 3333333000L) //
+ .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, 654321000L) //
+ .output(HagerEcr380dMeter.ChannelId.ER_MINUS_SUM, 1234567000L) //
+ .output(HagerEcr380dMeter.ChannelId.EA_PLUS_DETAILED_SUM, 2345678000L) //
+ .output(HagerEcr380dMeter.ChannelId.EA_MINUS_DETAILED_SUM, 1122334000L) //
+ )
+ .deactivate();
+ }
+
+ @Test
+ public void testEnergyPerPhase() throws Exception {
+ this.componentTest.activate(CONFIG)
+ .next(new TestCase()//
+ .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY_L1, 9999899000L) //
+ .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY_L2, 9999799000L) //
+ .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY_L3, 9999699000L) //
+ .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY_L1, 9998999000L) //
+ .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY_L2, 9997999000L) //
+ .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY_L3, 9996999000L) //
+ .output(HagerEcr380dMeter.ChannelId.ER_PLUS_L1, 1111811000L) //
+ .output(HagerEcr380dMeter.ChannelId.ER_PLUS_L2, 1111711000L) //
+ .output(HagerEcr380dMeter.ChannelId.ER_PLUS_L3, 1111611000L) //
+ .output(HagerEcr380dMeter.ChannelId.ER_MINUS_L1, 5555855000L) //
+ .output(HagerEcr380dMeter.ChannelId.ER_MINUS_L2, 5555755000L) //
+ .output(HagerEcr380dMeter.ChannelId.ER_MINUS_L3, 5555655000L) //
+ )
+ .deactivate();
+ }
+
+
+ private void withRegister(DummyModbusBridge bridge, int address, String value) {
+ final byte[] registers = value.getBytes();
+ for (int i = 0; i < registers.length; i += 2) {
+ bridge.withRegister(address++, registers[i], registers[i + 1]);
+ }
+ }
+}
diff --git a/io.openems.edge.meter.hager/test/io/openems/edge/meter/hager/ecr380d/MyConfig.java b/io.openems.edge.meter.hager/test/io/openems/edge/meter/hager/ecr380d/MyConfig.java
new file mode 100644
index 00000000000..4e54c59145c
--- /dev/null
+++ b/io.openems.edge.meter.hager/test/io/openems/edge/meter/hager/ecr380d/MyConfig.java
@@ -0,0 +1,75 @@
+package io.openems.edge.meter.hager.ecr380d;
+
+import io.openems.common.test.AbstractComponentConfig;
+import io.openems.common.types.MeterType;
+import io.openems.common.utils.ConfigUtils;
+
+@SuppressWarnings("all")
+public class MyConfig extends AbstractComponentConfig implements Config {
+
+ protected static class Builder {
+ private String id;
+ private String modbusId;
+ private int modbusUnitId;
+ private MeterType type;
+
+ private Builder() {
+ }
+
+ public Builder setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder setModbusId(String modbusId) {
+ this.modbusId = modbusId;
+ return this;
+ }
+
+ public Builder setType(MeterType type) {
+ this.type = type;
+ 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.type;
+ }
+
+}
\ No newline at end of file