-
Notifications
You must be signed in to change notification settings - Fork 563
[Edge] Implement Inepro Pro380ModCT meter #3570
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
base: develop
Are you sure you want to change the base?
Changes from all commits
af7e458
f6f7639
ae8f110
72b6b3f
3ac05be
d3a5ddb
00b0669
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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-21"/> | ||
| <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> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| /bin_test/ | ||
| /generated/ |
| 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.inepro</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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| Bundle-Name: OpenEMS Edge io.openems.edge.meter.inepro | ||
| Bundle-Vendor: VEV Platform Services France | ||
| 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},\ | ||
| io.openems.common |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package io.openems.edge.meter.inepro.pro380modct; | ||
|
|
||
| import org.osgi.service.metatype.annotations.AttributeDefinition; | ||
| import org.osgi.service.metatype.annotations.ObjectClassDefinition; | ||
|
|
||
| import io.openems.common.types.MeterType; | ||
|
|
||
| @ObjectClassDefinition(// | ||
| name = "Meter inepro Pro380ModCT", // | ||
| description = "Implements the inepro Pro380ModCT meter.") | ||
| public @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"; | ||
|
|
||
tomflorentin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| @AttributeDefinition(name = "Modbus Unit-ID", description = "The Unit-ID of the Modbus device.") | ||
| int modbusUnitId() default 1; | ||
|
|
||
| @AttributeDefinition(name = "Meter-Type", description = "What is measured by this Meter?") | ||
| MeterType type() default MeterType.CONSUMPTION_METERED; | ||
|
|
||
| @AttributeDefinition(name = "Invert Power", description = "Inverts ALL Power values, inverts current values, swaps production and consumption 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.inepro.Pro380ModCT [{id}]"; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package io.openems.edge.meter.inepro.pro380modct; | ||
|
|
||
| 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 Pro380modct extends ElectricityMeter, OpenemsComponent, ModbusSlave { | ||
|
|
||
| public enum ChannelId implements io.openems.edge.common.channel.ChannelId { | ||
| APPARENT_POWER_L1(Doc.of(OpenemsType.INTEGER)// | ||
| .unit(Unit.VOLT_AMPERE)), // | ||
| APPARENT_POWER_L2(Doc.of(OpenemsType.INTEGER)// | ||
| .unit(Unit.VOLT_AMPERE)), // | ||
| APPARENT_POWER_L3(Doc.of(OpenemsType.INTEGER)// | ||
| .unit(Unit.VOLT_AMPERE)), // | ||
| APPARENT_POWER(Doc.of(OpenemsType.INTEGER)// | ||
| .unit(Unit.VOLT_AMPERE)); | ||
|
|
||
| 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,229 @@ | ||
| package io.openems.edge.meter.inepro.pro380modct; | ||
|
|
||
| import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_3; | ||
| import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_3_AND_INVERT_IF_TRUE; | ||
|
|
||
| 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; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also please apply Ctrl+Shift+O after all for Sorting the imports |
||
| 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.element.WordOrder; | ||
| import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; | ||
| import io.openems.common.channel.AccessMode; | ||
| import io.openems.edge.common.component.OpenemsComponent; | ||
| import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; | ||
| import io.openems.edge.common.modbusslave.ModbusSlaveTable; | ||
| import io.openems.edge.common.taskmanager.Priority; | ||
| import io.openems.edge.meter.api.ElectricityMeter; | ||
| 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.Deactivate; | ||
| import org.osgi.service.component.annotations.ReferenceCardinality; | ||
| import org.osgi.service.component.annotations.ReferencePolicyOption; | ||
| import org.osgi.service.component.annotations.ConfigurationPolicy; | ||
| import org.osgi.service.component.annotations.Reference; | ||
| import org.osgi.service.component.annotations.ReferencePolicy; | ||
| import org.osgi.service.metatype.annotations.Designate; | ||
|
|
||
| @Designate(ocd = Config.class, factory = true) | ||
| @Component(// | ||
| name = "Meter.inepro.Pro380ModCT", // | ||
| immediate = true, // | ||
| configurationPolicy = ConfigurationPolicy.REQUIRE // | ||
| ) | ||
|
|
||
|
|
||
| public class Pro380modctImpl extends AbstractOpenemsModbusComponent | ||
| implements Pro380modct, ElectricityMeter, ModbusComponent, OpenemsComponent { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ModbusSlave is missing - It would still be inherited from the Interface, but Best Practice would list it here |
||
|
|
||
| @Reference | ||
| private ConfigurationAdmin cm; | ||
|
|
||
| @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) | ||
| protected void setModbus(BridgeModbus modbus) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Method overrides AbstractOpenemsModbusComponent.setModbus(). The @OverRide annotation provides compile-time safety and documents intent. |
||
| super.setModbus(modbus); | ||
| } | ||
|
|
||
| private Config config = null; | ||
| private boolean invert; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Creates a second source of truth. KDK accesses this.config.invert() directly. Since this.config is assigned before super.activate() calls defineModbusProtocol(), direct access is safe. |
||
|
|
||
| public Pro380modctImpl() { | ||
| super(// | ||
| OpenemsComponent.ChannelId.values(), // | ||
| ModbusComponent.ChannelId.values(), // | ||
| ElectricityMeter.ChannelId.values(), // | ||
| Pro380modct.ChannelId.values() // | ||
| ); | ||
| ElectricityMeter.calculateSumCurrentFromPhases(this); | ||
| ElectricityMeter.calculateAverageVoltageFromPhases(this); | ||
| } | ||
|
|
||
| @Activate | ||
| private void activate(ComponentContext context, Config config) throws OpenemsException { | ||
| this.config = config; | ||
| this.invert = config.invert(); | ||
| if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, | ||
| "Modbus", config.modbus_id())) { | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { | ||
| return new ModbusSlaveTable(// | ||
| OpenemsComponent.getModbusSlaveNatureTable(accessMode), // | ||
| ElectricityMeter.getModbusSlaveNatureTable(accessMode), // | ||
| ModbusSlaveNatureTable.of(Pro380modct.class, accessMode, 100) // | ||
| .build()); | ||
| } | ||
|
|
||
| @Override | ||
| @Deactivate | ||
| protected void deactivate() { | ||
| super.deactivate(); | ||
| } | ||
|
|
||
| @Override | ||
| protected ModbusProtocol defineModbusProtocol() { | ||
tomflorentin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| var modbusProtocol = new ModbusProtocol(this, // | ||
| new FC3ReadRegistersTask(0x5002, Priority.HIGH, // | ||
| m(ElectricityMeter.ChannelId.VOLTAGE_L1, // | ||
| new FloatDoublewordElement(0x5002).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| m(ElectricityMeter.ChannelId.VOLTAGE_L2, // | ||
| new FloatDoublewordElement(0x5004).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| m(ElectricityMeter.ChannelId.VOLTAGE_L3, // | ||
| new FloatDoublewordElement(0x5006).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| m(ElectricityMeter.ChannelId.FREQUENCY, // | ||
| new FloatDoublewordElement(0x5008).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| new DummyRegisterElement(0x500A, 0x500B), // Current* (PRO1 only) | ||
| m(ElectricityMeter.ChannelId.CURRENT_L1, // | ||
| new FloatDoublewordElement(0x500C).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)), // | ||
| m(ElectricityMeter.ChannelId.CURRENT_L2, // | ||
| new FloatDoublewordElement(0x500E).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)), // | ||
| m(ElectricityMeter.ChannelId.CURRENT_L3, // | ||
| new FloatDoublewordElement(0x5010).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_POWER, // | ||
| new FloatDoublewordElement(0x5012).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, // | ||
| new FloatDoublewordElement(0x5014).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, // | ||
| new FloatDoublewordElement(0x5016).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, // | ||
| new FloatDoublewordElement(0x5018).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)), // | ||
| m(ElectricityMeter.ChannelId.REACTIVE_POWER, // | ||
| new FloatDoublewordElement(0x501A).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)), // | ||
| m(ElectricityMeter.ChannelId.REACTIVE_POWER_L1, // | ||
| new FloatDoublewordElement(0x501C).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)), // | ||
| m(ElectricityMeter.ChannelId.REACTIVE_POWER_L2, // | ||
| new FloatDoublewordElement(0x501E).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)), // | ||
| m(ElectricityMeter.ChannelId.REACTIVE_POWER_L3, // | ||
| new FloatDoublewordElement(0x5020).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)), // | ||
| m(Pro380modct.ChannelId.APPARENT_POWER, // | ||
| new FloatDoublewordElement(0x5022).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)), // | ||
| m(Pro380modct.ChannelId.APPARENT_POWER_L1, // | ||
| new FloatDoublewordElement(0x5024).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)), // | ||
| m(Pro380modct.ChannelId.APPARENT_POWER_L2, // | ||
| new FloatDoublewordElement(0x5026).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)), // | ||
| m(Pro380modct.ChannelId.APPARENT_POWER_L3, // | ||
| new FloatDoublewordElement(0x5028).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3_AND_INVERT_IF_TRUE(this.invert)) // | ||
| ) // | ||
| ); | ||
|
|
||
| if (this.invert) { | ||
| modbusProtocol.addTask(new FC3ReadRegistersTask(0x600C, Priority.LOW, // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, // | ||
| new FloatDoublewordElement(0x600C).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| new DummyRegisterElement(0x600E, 0x6011), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY_L1, // | ||
| new FloatDoublewordElement(0x6012).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY_L2, // | ||
| new FloatDoublewordElement(0x6014).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY_L3, // | ||
| new FloatDoublewordElement(0x6016).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, // | ||
| new FloatDoublewordElement(0x6018).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| new DummyRegisterElement(0x601A, 0x601D), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY_L1, // | ||
| new FloatDoublewordElement(0x601E).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY_L2, // | ||
| new FloatDoublewordElement(0x6020).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY_L3, // | ||
| new FloatDoublewordElement(0x6022).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3) // | ||
| )); | ||
| } else { // not invert | ||
| modbusProtocol.addTask(new FC3ReadRegistersTask(0x600C, Priority.LOW, // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, // | ||
| new FloatDoublewordElement(0x600C).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| new DummyRegisterElement(0x600E, 0x6011), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY_L1, // | ||
| new FloatDoublewordElement(0x6012).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY_L2, // | ||
| new FloatDoublewordElement(0x6014).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY_L3, // | ||
| new FloatDoublewordElement(0x6016).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, // | ||
| new FloatDoublewordElement(0x6018).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| new DummyRegisterElement(0x601A, 0x601D), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY_L1, // | ||
| new FloatDoublewordElement(0x601E).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY_L2, // | ||
| new FloatDoublewordElement(0x6020).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3), // | ||
| m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY_L3, // | ||
| new FloatDoublewordElement(0x6022).wordOrder(WordOrder.MSWLSW), // | ||
| SCALE_FACTOR_3) // | ||
| )); | ||
| } | ||
|
|
||
| return modbusProtocol; | ||
| } | ||
|
|
||
tomflorentin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| @Override | ||
| public String debugLog() { | ||
tomflorentin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return "L:" + this.getActivePower().asString(); | ||
| } | ||
|
|
||
tomflorentin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| @Override | ||
| public MeterType getMeterType() { | ||
| return this.config.type(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Config should be package-private - it's an internal implementation detail, not part of the public api