diff --git a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg104/Config.java b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg104/Config.java new file mode 100644 index 0000000000..809dd3fb6e --- /dev/null +++ b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg104/Config.java @@ -0,0 +1,38 @@ +package io.openems.edge.meter.janitza.umg104; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +import io.openems.common.types.MeterType; + +@ObjectClassDefinition(// + name = "Meter Janitza UMG 104", // + description = "Implements the Janitza UMG 104 power analyser.") +@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. Defaults to '1' for Modbus/TCP.") + int modbusUnitId() default 1; + + @AttributeDefinition(name = "Invert Power", description = "Inverts all Power values, inverts current values, swaps production and consumptioon energy, i.e. Power is multiplied with -1.") + boolean invert() default false; + + @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 Janitza UMG 104 [{id}]"; +} \ No newline at end of file diff --git a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg104/MeterJanitzaUmg104.java b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg104/MeterJanitzaUmg104.java new file mode 100644 index 0000000000..d5b359e3aa --- /dev/null +++ b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg104/MeterJanitzaUmg104.java @@ -0,0 +1,31 @@ +package io.openems.edge.meter.janitza.umg104; + +import io.openems.common.channel.Unit; +import io.openems.common.types.OpenemsType; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.meter.api.ElectricityMeter; + +public interface MeterJanitzaUmg104 extends ElectricityMeter, OpenemsComponent, ModbusSlave { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + ROTATION_FIELD(Doc.of(OpenemsType.INTEGER)), + + INTERNAL_TEMPERATURE(Doc.of(OpenemsType.FLOAT) // + .unit(Unit.DEGREE_CELSIUS)), // + ; + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } + +} diff --git a/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg104/MeterJanitzaUmg104Impl.java b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg104/MeterJanitzaUmg104Impl.java new file mode 100644 index 0000000000..f328e10d40 --- /dev/null +++ b/io.openems.edge.meter.janitza/src/io/openems/edge/meter/janitza/umg104/MeterJanitzaUmg104Impl.java @@ -0,0 +1,175 @@ +package io.openems.edge.meter.janitza.umg104; + +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.INVERT_IF_TRUE; +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_3; + +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.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.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.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; +import io.openems.edge.common.taskmanager.Priority; +import io.openems.edge.meter.api.ElectricityMeter; + +/** + * Implements the Janitza UMG 104 power analyzer. + * + *
+ * https://www.janitza.de/umg-104-pro.html + */ +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "Meter.Janitza.UMG104", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class MeterJanitzaUmg104Impl extends AbstractOpenemsModbusComponent + implements MeterJanitzaUmg104, 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 MeterType meterType = MeterType.PRODUCTION; + /** Invert power values. */ + private boolean invert = false; + + public MeterJanitzaUmg104Impl() { + super(// + OpenemsComponent.ChannelId.values(), // + ModbusComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // + MeterJanitzaUmg104.ChannelId.values() // + ); + + // Automatically calculate sum values from L1/L2/L3 + ElectricityMeter.calculateSumCurrentFromPhases(this); + ElectricityMeter.calculateAverageVoltageFromPhases(this); + } + + @Activate + private void activate(ComponentContext context, Config config) throws OpenemsException { + this.meterType = config.type(); + this.invert = config.invert(); + + if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, + "Modbus", config.modbus_id())) { + return; + } + } + + @Override + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + @Override + public MeterType getMeterType() { + return this.meterType; + } + + @Override + protected ModbusProtocol defineModbusProtocol() { + var modbusProtocol = new ModbusProtocol(this, // + new FC3ReadRegistersTask(1317, Priority.HIGH, // + m(ElectricityMeter.ChannelId.VOLTAGE_L1, new FloatDoublewordElement(1317), // + SCALE_FACTOR_3), // + m(ElectricityMeter.ChannelId.VOLTAGE_L2, new FloatDoublewordElement(1319), // + SCALE_FACTOR_3), // + m(ElectricityMeter.ChannelId.VOLTAGE_L3, new FloatDoublewordElement(1321), // + SCALE_FACTOR_3), // + new DummyRegisterElement(1323, 1324), // + m(ElectricityMeter.ChannelId.CURRENT_L1, new FloatDoublewordElement(1325), // + SCALE_FACTOR_3), // + m(ElectricityMeter.ChannelId.CURRENT_L2, new FloatDoublewordElement(1327), // + SCALE_FACTOR_3), // + m(ElectricityMeter.ChannelId.CURRENT_L3, new FloatDoublewordElement(1329), // + SCALE_FACTOR_3), // + new DummyRegisterElement(1331, 1332), // + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, new FloatDoublewordElement(1333), // + INVERT_IF_TRUE(this.invert)), // + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, new FloatDoublewordElement(1335), // + INVERT_IF_TRUE(this.invert)), // + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, new FloatDoublewordElement(1337), // + INVERT_IF_TRUE(this.invert)), // + new DummyRegisterElement(1339, 1340), // + m(ElectricityMeter.ChannelId.REACTIVE_POWER_L1, new FloatDoublewordElement(1341), // + INVERT_IF_TRUE(this.invert)), // + m(ElectricityMeter.ChannelId.REACTIVE_POWER_L2, new FloatDoublewordElement(1343), // + INVERT_IF_TRUE(this.invert)), // + m(ElectricityMeter.ChannelId.REACTIVE_POWER_L3, new FloatDoublewordElement(1345), // + INVERT_IF_TRUE(this.invert)), // + new DummyRegisterElement(1347, 1368), // + m(ElectricityMeter.ChannelId.ACTIVE_POWER, new FloatDoublewordElement(1369), // + INVERT_IF_TRUE(this.invert)), // + m(ElectricityMeter.ChannelId.REACTIVE_POWER, new FloatDoublewordElement(1371), // + INVERT_IF_TRUE(this.invert))), // + new FC3ReadRegistersTask(1439, Priority.LOW, // + m(ElectricityMeter.ChannelId.FREQUENCY, new FloatDoublewordElement(1439), // + SCALE_FACTOR_3), + new DummyRegisterElement(1441, 1448), + m(MeterJanitzaUmg104.ChannelId.ROTATION_FIELD, new FloatDoublewordElement(1449), // + SCALE_FACTOR_3), + new DummyRegisterElement(1451, 1460), + m(MeterJanitzaUmg104.ChannelId.INTERNAL_TEMPERATURE, new FloatDoublewordElement(1461), // + SCALE_FACTOR_3) + )); + + if (this.invert) { + modbusProtocol.addTask(new FC3ReadRegistersTask(9851, Priority.LOW, // + m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, new FloatDoublewordElement(9851)), + new DummyRegisterElement(9853, 9862), + m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, new FloatDoublewordElement(9863)))); + } else { + modbusProtocol.addTask(new FC3ReadRegistersTask(9851, Priority.LOW, // + m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, new FloatDoublewordElement(9851)), + new DummyRegisterElement(9853, 9862), + m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, new FloatDoublewordElement(9863)))); + } + + return modbusProtocol; + } + + @Override + public String debugLog() { + return "L:" + this.getActivePower().asString(); + } + + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(// + OpenemsComponent.getModbusSlaveNatureTable(accessMode), // + ElectricityMeter.getModbusSlaveNatureTable(accessMode) // + ); + } +} diff --git a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg104/MeterJanitzaUmg104ImplTest.java b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg104/MeterJanitzaUmg104ImplTest.java new file mode 100644 index 0000000000..187eb07aa1 --- /dev/null +++ b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg104/MeterJanitzaUmg104ImplTest.java @@ -0,0 +1,25 @@ +package io.openems.edge.meter.janitza.umg104; + +import static io.openems.common.types.MeterType.GRID; + +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 MeterJanitzaUmg104ImplTest { + + @Test + public void test() throws Exception { + new ComponentTest(new MeterJanitzaUmg104Impl()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .activate(MyConfig.create() // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // + .build()) // + ; + } +} \ No newline at end of file diff --git a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg104/MyConfig.java b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg104/MyConfig.java new file mode 100644 index 0000000000..c04162b5ed --- /dev/null +++ b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg104/MyConfig.java @@ -0,0 +1,86 @@ +package io.openems.edge.meter.janitza.umg104; + +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 boolean invert; + + 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 Builder setInvert(boolean invert) { + this.invert = invert; + 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; + } + + @Override + public boolean invert() { + return this.builder.invert; + } + +} \ No newline at end of file