From 519c1f07dde0806f9fdfd0c3691f6b15e65fa381 Mon Sep 17 00:00:00 2001 From: Thomas Sicking Date: Mon, 31 Aug 2026 11:35:55 +0200 Subject: [PATCH 01/15] Implementation of Sungrow components --- io.openems.edge.sungrow/.classpath | 12 + io.openems.edge.sungrow/.gitignore | 2 + io.openems.edge.sungrow/.project | 23 + .../org.eclipse.core.resources.prefs | 2 + io.openems.edge.sungrow/bnd.bnd | 19 + io.openems.edge.sungrow/readme.adoc | 3 + .../edge/sungrow/dccharger/Config.java | 28 + .../dccharger/SungrowVirtualDcCharger.java | 118 +++++ .../edge/sungrow/ess/ApplyPowerHandler.java | 73 +++ .../io/openems/edge/sungrow/ess/Config.java | 36 ++ .../openems/edge/sungrow/ess/EssSungrow.java | 493 ++++++++++++++++++ .../edge/sungrow/ess/EssSungrowImpl.java | 423 +++++++++++++++ .../ess/enums/ChargeDischargeCommand.java | 35 ++ .../edge/sungrow/ess/enums/ControlMode.java | 23 + .../edge/sungrow/ess/enums/EmsMode.java | 35 ++ .../edge/sungrow/ess/enums/EnableDisable.java | 34 ++ .../edge/sungrow/ess/enums/SystemState.java | 43 ++ .../io/openems/edge/sungrow/meter/Config.java | 25 + .../meter/SungrowVirtualGridMeter.java | 112 ++++ .../edge/sungrow/pvinverter/Config.java | 38 ++ .../sungrow/pvinverter/PvInverterSungrow.java | 131 +++++ .../pvinverter/PvInverterSungrowImpl.java | 222 ++++++++ .../edge/sungrow/pvinverter/WorkState.java | 44 ++ 23 files changed, 1974 insertions(+) create mode 100644 io.openems.edge.sungrow/.classpath create mode 100644 io.openems.edge.sungrow/.gitignore create mode 100644 io.openems.edge.sungrow/.project create mode 100644 io.openems.edge.sungrow/.settings/org.eclipse.core.resources.prefs create mode 100644 io.openems.edge.sungrow/bnd.bnd create mode 100644 io.openems.edge.sungrow/readme.adoc create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/Config.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowVirtualDcCharger.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/ApplyPowerHandler.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/Config.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrow.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/ChargeDischargeCommand.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/ControlMode.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/EmsMode.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/EnableDisable.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/SystemState.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowVirtualGridMeter.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/Config.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrow.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/WorkState.java diff --git a/io.openems.edge.sungrow/.classpath b/io.openems.edge.sungrow/.classpath new file mode 100644 index 00000000000..b4cffd0fe60 --- /dev/null +++ b/io.openems.edge.sungrow/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/io.openems.edge.sungrow/.gitignore b/io.openems.edge.sungrow/.gitignore new file mode 100644 index 00000000000..c2b941a96de --- /dev/null +++ b/io.openems.edge.sungrow/.gitignore @@ -0,0 +1,2 @@ +/bin_test/ +/generated/ diff --git a/io.openems.edge.sungrow/.project b/io.openems.edge.sungrow/.project new file mode 100644 index 00000000000..1787bf0ae68 --- /dev/null +++ b/io.openems.edge.sungrow/.project @@ -0,0 +1,23 @@ + + + io.openems.edge.sungrow + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/io.openems.edge.sungrow/.settings/org.eclipse.core.resources.prefs b/io.openems.edge.sungrow/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..99f26c0203a --- /dev/null +++ b/io.openems.edge.sungrow/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/io.openems.edge.sungrow/bnd.bnd b/io.openems.edge.sungrow/bnd.bnd new file mode 100644 index 00000000000..427415df54f --- /dev/null +++ b/io.openems.edge.sungrow/bnd.bnd @@ -0,0 +1,19 @@ +Bundle-Name: OpenEMS Edge io.openems.edge.sungrow +Bundle-Vendor: OpenEMS Association e.V. +Bundle-License: https://opensource.org/licenses/EPL-2.0 +Bundle-Version: 1.0.0.${tstamp} + +-buildpath: \ + ${buildpath},\ + io.openems.j2mod,\ + io.openems.common,\ + io.openems.edge.bridge.modbus,\ + io.openems.edge.common,\ + io.openems.edge.ess.api,\ + io.openems.edge.meter.api,\ + io.openems.edge.pvinverter.api,\ + io.openems.edge.timedata.api,\ + io.openems.j2mod,\ + +-testpath: \ + ${testpath} diff --git a/io.openems.edge.sungrow/readme.adoc b/io.openems.edge.sungrow/readme.adoc new file mode 100644 index 00000000000..6a9b74281ef --- /dev/null +++ b/io.openems.edge.sungrow/readme.adoc @@ -0,0 +1,3 @@ += Sungrow + +https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.sungrow[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/Config.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/Config.java new file mode 100644 index 00000000000..3f93f4431c2 --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/Config.java @@ -0,0 +1,28 @@ +package io.openems.edge.sungrow.dccharger; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(// + name = "Ess Sungrow Virtual DC charger", // + description = "Implements virtual DC charger from Sungrow Hybrid ESS.") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "charger0"; + + @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 = "Core-ID", description = "ID of the Sungrow Ess Component.") + String core_id() default "ess0"; + + @AttributeDefinition(name = "Core target filter", description = "This is auto-generated by 'Core-ID'.") + String core_target() default "(enabled=true)"; + + String webconsole_configurationFactory_nameHint() default "Ess Sungrow Virtual DC charger [{id}]"; + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowVirtualDcCharger.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowVirtualDcCharger.java new file mode 100644 index 00000000000..c32c7667f67 --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowVirtualDcCharger.java @@ -0,0 +1,118 @@ +package io.openems.edge.sungrow.dccharger; + +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.event.Event; +import org.osgi.service.event.EventHandler; +import org.osgi.service.event.propertytypes.EventTopics; +import org.osgi.service.metatype.annotations.Designate; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.ess.dccharger.api.EssDcCharger; +import io.openems.edge.sungrow.ess.EssSungrow; +import io.openems.edge.timedata.api.Timedata; +import io.openems.edge.timedata.api.TimedataProvider; +import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "Ess.Sungrow.DcCharger", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +@EventTopics({ // + EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // +}) +public class SungrowVirtualDcCharger extends AbstractOpenemsComponent + implements EssDcCharger, TimedataProvider, OpenemsComponent, EventHandler { + + protected Config config = null; + + @Reference + private ConfigurationAdmin cm; + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + private EssSungrow core; + + @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) + private volatile Timedata timedata = null; + + private final CalculateEnergyFromPower calculateActualEnergy = new CalculateEnergyFromPower(this, + EssDcCharger.ChannelId.ACTUAL_ENERGY); + + public SungrowVirtualDcCharger() { + super(// + OpenemsComponent.ChannelId.values(), // + EssDcCharger.ChannelId.values() // + ); + } + + @Activate + private void activate(ComponentContext context, Config config) throws OpenemsException { + this.config = config; + super.activate(context, config.id(), config.alias(), config.enabled()); + + if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "core", config.core_id())) { + return; + } + + this.mapChannelValues(); + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + private void mapChannelValues() { + this.core.getTotalDcPowerChannel().onSetNextValue(value -> { + if (value.isDefined()) { + this._setActualPower(value.get()); + } + }); + this.core.getMppt1VoltageChannel().onSetNextValue(value -> { + if (value.isDefined()) { + this._setVoltage(value.get()); + } + }); + this.core.getMppt1CurrentChannel().onSetNextValue(value -> { + if (value.isDefined()) { + this._setCurrent(value.get()); + } + }); + } + + @Override + public void handleEvent(Event event) { + if (!this.isEnabled()) { + return; + } + switch (event.getTopic()) { + case EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE -> { + this.calculateActualEnergy.update(this.getActualPower().get()); + } + } + } + + @Override + public String debugLog() { + return "L:" + this.getActualPower().asString(); + } + + @Override + public Timedata getTimedata() { + return this.timedata; + } + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/ApplyPowerHandler.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/ApplyPowerHandler.java new file mode 100644 index 00000000000..0d981aa0262 --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/ApplyPowerHandler.java @@ -0,0 +1,73 @@ +package io.openems.edge.sungrow.ess; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.sungrow.ess.enums.ChargeDischargeCommand; +import io.openems.edge.sungrow.ess.enums.ControlMode; +import io.openems.edge.sungrow.ess.enums.EmsMode; + +/** + * Handler to apply the set active power to the Sungrow ESS depending on the configured {@link ControlMode}. + */ +public class ApplyPowerHandler { + + /** + * Applies the desired active power setpoint by setting the appropiate EMS_MODE + * and Charge/Discharge power. + * + * @param parent the {@link EssSungrowImpl} + * @param setActivePower the active power setpoint + * @param controlMode the configured {@link ContolMode} + * @param gridActivePower the grid active power + * @throws OpenemsNamedException on write error + */ + public synchronized void apply(EssSungrowImpl parent, int setActivePower, ControlMode controlMode, + Value gridActivePower) throws OpenemsNamedException { + parent.channel(EssSungrow.ChannelId.SMART_MODE_NOT_WORKING_WITH_PID_FILTER) // + .setNextValue(parent.power.isPidEnabled() && controlMode.equals(ControlMode.SMART)); + + var result = switch (controlMode) { + case INTERNAL -> handleInternalMode(); + case REMOTE -> handleRemoteMode(setActivePower); + case SMART -> handleSmartMode(setActivePower, parent.getActivePower(), gridActivePower); + default -> handleInternalMode(); + }; + + parent.getEmsModeChannel().setNextWriteValue(result.emsMode); + if (result.setActivePower > 0) { + parent.getChargeDischargeCommandChannel().setNextWriteValue(ChargeDischargeCommand.DISCHARGE); + parent.getChargeDischargePowerChannel().setNextWriteValue(result.setActivePower); + } else { + parent.getChargeDischargeCommandChannel().setNextWriteValue(ChargeDischargeCommand.CHARGE); + parent.getChargeDischargePowerChannel().setNextWriteValue(-result.setActivePower); + } + } + + private static record Result(EmsMode emsMode, int setActivePower) { + } + + private static Result handleInternalMode() { + return new Result(EmsMode.SELF_CONSUMPTION, 0); + } + + private static Result handleRemoteMode(int setActivePower) { + return new Result(EmsMode.EXTERNAL_EMS_MODE, setActivePower); + } + + private static Result handleSmartMode(int setActivePower, Value essActivePower, + Value gridActivePower) { + // Fallback to internal mode if a value is undefined + if (!gridActivePower.isDefined() || !essActivePower.isDefined()) { + return handleInternalMode(); + } + + // Is balancing to zero active? + var diffBalancing = setActivePower - (gridActivePower.get() + essActivePower.get()); + // avoid rounding errors + if (Math.abs(diffBalancing) <= 1) { + return handleInternalMode(); + } + + return handleRemoteMode(setActivePower); + } +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/Config.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/Config.java new file mode 100644 index 00000000000..687527d7342 --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/Config.java @@ -0,0 +1,36 @@ +package io.openems.edge.sungrow.ess; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +import io.openems.edge.sungrow.ess.enums.ControlMode; + +@ObjectClassDefinition(// + name = "ESS Sungrow", // + description = "Implements the Sungrow SH10RT Hybrid Ess") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "ess0"; + + @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 = "Control mode", description = "Sets the Control mode") + ControlMode controlMode() default ControlMode.SMART; + + @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)"; + + String webconsole_configurationFactory_nameHint() default "ESS Sungrow [{id}]"; + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrow.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrow.java new file mode 100644 index 00000000000..4da6a05fbaf --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrow.java @@ -0,0 +1,493 @@ +package io.openems.edge.sungrow.ess; + +import io.openems.common.channel.AccessMode; +import io.openems.common.channel.Level; +import io.openems.common.channel.PersistencePriority; +import io.openems.common.channel.Unit; +import io.openems.common.types.OpenemsType; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.EnumWriteChannel; +import io.openems.edge.common.channel.IntegerReadChannel; +import io.openems.edge.common.channel.IntegerWriteChannel; +import io.openems.edge.common.channel.LongReadChannel; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.sungrow.ess.enums.ChargeDischargeCommand; +import io.openems.edge.sungrow.ess.enums.EmsMode; +import io.openems.edge.sungrow.ess.enums.EnableDisable; +import io.openems.edge.sungrow.ess.enums.SystemState; + +public interface EssSungrow extends OpenemsComponent { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + SERIAL_NUMBER(Doc.of(OpenemsType.STRING) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), // + DAILY_OUTPUT_ENERGY(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT_HOURS) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), // + INSIDE_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.DEZIDEGREE_CELSIUS) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), // + MPPT1_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.MILLIVOLT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), + MPPT1_CURRENT(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.MILLIAMPERE) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), // + MPPT2_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.MILLIVOLT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), // + MPPT2_CURRENT(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.MILLIAMPERE) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), // + TOTAL_DC_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.HIGH)), // + VOLTAGE_L1(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.MILLIVOLT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), // + VOLTAGE_L2(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.MILLIVOLT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), + VOLTAGE_L3(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.MILLIVOLT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), // + POWER_FACTOR(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.THOUSANDTH) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), // + GRID_FREQUENCY(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.MILLIHERTZ) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.MEDIUM)), // + EXPORT_LIMIT_MIN(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), // + EXPORT_LIMIT_MAX(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), // + BDC_RATED_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), // + CHARGE_MAX_CURRENT(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.AMPERE) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.HIGH)), // + DISCHARGE_MAX_CURRENT(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.AMPERE) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.HIGH)), // + SYSTEM_STATE(Doc.of(SystemState.values()) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), // + POWER_GENERATED_FROM_PV(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), // + BATTERY_CHARGING(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), // + BATTERY_DISCHARGING(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), // + POSITIVE_LOAD_POWER(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), // + FEED_IN_POWER(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), // + IMPORT_POWER_FROM_GRID(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), // + NEGATIVE_LOAD_POWER(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), // + DAILY_PV_GENERATION(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT_HOURS) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), // + TOTAL_PV_GENERATION(Doc.of(OpenemsType.LONG) // + .unit(Unit.WATT_HOURS) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), // + DAILY_EXPORT_POWER_FROM_PV(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), // + TOTAL_EXPORT_ENERGY_FROM_PV(Doc.of(OpenemsType.LONG) // + .unit(Unit.WATT_HOURS) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), + LOAD_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.MEDIUM)), // + EXPORT_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.MEDIUM)), + DAILY_BATTERY_CHARGE_ENERGY_FROM_PV(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT_HOURS) + // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), + TOTAL_BATTERY_CHARGE_ENERGY_FROM_PV(Doc.of(OpenemsType.LONG) // + .unit(Unit.WATT_HOURS) + // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), + CO2_REDUCTION(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.NONE) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), + DAILY_DIRECT_ENERGY_CONSUMPTION(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT_HOURS) + // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), + TOTAL_DIRECT_ENERGY_CONSUMPTION(Doc.of(OpenemsType.LONG) // + .unit(Unit.WATT_HOURS) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), + BATTERY_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.VOLT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.HIGH)), + BATTERY_CURRENT(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.AMPERE) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.HIGH)), + BATTERY_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.HIGH)), + SOH(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.PERCENT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), + BATTERY_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.DEZIDEGREE_CELSIUS) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.MEDIUM)), + DAILY_BATTERY_DISCHARGE_ENERGY(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT_HOURS) + // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), + SELF_CONSUMPTION_OF_TODAY(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.PERCENT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), + CURRENT_L1(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.MILLIAMPERE) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), + CURRENT_L2(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.MILLIAMPERE) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), + CURRENT_L3(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.MILLIAMPERE) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), + DAILY_IMPORT_ENERGY(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT_HOURS) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), + TOTAL_IMPORT_ENERGY(Doc.of(OpenemsType.LONG) // + .unit(Unit.WATT_HOURS) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), + DAILY_CHARGE_ENERGY(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT_HOURS) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), + DAILY_EXPORT_ENERGY(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT_HOURS) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW)), + TOTAL_EXPORT_ENERGY(Doc.of(OpenemsType.LONG) // + .unit(Unit.WATT_HOURS) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW)), + + EMS_MODE(Doc.of(EmsMode.values()) // + .accessMode(AccessMode.READ_WRITE) // + .persistencePriority(PersistencePriority.HIGH)), + CHARGE_DISCHARGE_COMMAND(Doc.of(ChargeDischargeCommand.values()) // + .accessMode(AccessMode.READ_WRITE) // + .persistencePriority(PersistencePriority.HIGH)), + CHARGE_DISCHARGE_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .accessMode(AccessMode.READ_WRITE) // + .persistencePriority(PersistencePriority.HIGH)), + MAX_SOC(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.PERCENT) // + .accessMode(AccessMode.READ_WRITE) // + .persistencePriority(PersistencePriority.VERY_LOW)), + MIN_SOC(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.PERCENT) // + .accessMode(AccessMode.READ_WRITE) // + .persistencePriority(PersistencePriority.VERY_LOW)), + EXPORT_POWER_LIMIT(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .accessMode(AccessMode.READ_WRITE) // + .persistencePriority(PersistencePriority.VERY_LOW)), + OFF_GRID_OPTION(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE) // + .persistencePriority(PersistencePriority.VERY_LOW)), + HEARTBEAT(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.NONE) // + .accessMode(AccessMode.READ_WRITE) // + .persistencePriority(PersistencePriority.HIGH)), // + DEBUG_HEARTBEAT( + Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.HIGH)), // + METER_COMM_DETECTION(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE) // + .persistencePriority(PersistencePriority.VERY_LOW)), + EXPORT_POWER_LIMITATION(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE) // + .persistencePriority(PersistencePriority.VERY_LOW)), + RESERVED_SOC_FOR_BACKUP(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.PERCENT) // + .accessMode(AccessMode.READ_WRITE) // + .persistencePriority(PersistencePriority.VERY_LOW)), // + SMART_MODE_NOT_WORKING_WITH_PID_FILTER(Doc.of(Level.WARNING) // + .text("SMART mode does not work correctly with active PID filter")) // + ; + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } + + + /** + * Gets the Channel for {@link ChannelId#MPPT1_VOLTAGE}. + * + * @return the Channel + */ + public default IntegerReadChannel getMppt1VoltageChannel() { + return this.channel(ChannelId.MPPT1_VOLTAGE); + } + + /** + * Gets the Channel for {@link ChannelId#MPPT1_CURRENT}. + * + * @return the Channel + */ + public default IntegerReadChannel getMppt1CurrentChannel() { + return this.channel(ChannelId.MPPT1_CURRENT); + } + + /** + * Gets the Channel for {@link ChannelId#MPPT2_VOLTAGE}. + * + * @return the Channel + */ + public default IntegerReadChannel getMppt2VoltageChannel() { + return this.channel(ChannelId.MPPT2_VOLTAGE); + } + + /** + * Gets the Channel for {@link ChannelId#MPPT2_CURRENT}. + * + * @return the Channel + */ + public default IntegerReadChannel getMppt2CurrentChannel() { + return this.channel(ChannelId.MPPT2_CURRENT); + } + + /** + * Gets the Channel for {@link ChannelId#TOTAL_DC_POWER}. + * + * @return the Channel + */ + public default IntegerReadChannel getTotalDcPowerChannel() { + return this.channel(ChannelId.TOTAL_DC_POWER); + } + + /** + * Gets the {@link ChannelId#TOTAL_DC_POWER}. + * + * @return the Channel {@link Value} + */ + public default Value getTotalDcPower() { + return this.getTotalDcPowerChannel().value(); + } + + /** + * Gets the Channel for {@link ChannelId#VOLTAGE_L1}. + * + * @return the Channel + */ + public default IntegerReadChannel getVoltageL1Channel() { + return this.channel(ChannelId.VOLTAGE_L1); + } + + /** + * Gets the Channel for {@link ChannelId#VOLTAGE_L2}. + * + * @return the Channel + */ + public default IntegerReadChannel getVoltageL2Channel() { + return this.channel(ChannelId.VOLTAGE_L2); + } + + /** + * Gets the Channel for {@link ChannelId#VOLTAGE_L3}. + * + * @return the Channel + */ + public default IntegerReadChannel getVoltageL3Channel() { + return this.channel(ChannelId.VOLTAGE_L3); + } + + /** + * Gets the Channel for {@link ChannelId#GRID_FREQUENCY}. + * + * @return the Channel + */ + public default IntegerReadChannel getGridFrequencyChannel() { + return this.channel(ChannelId.GRID_FREQUENCY); + } + + /** + * Gets the Channel for {@link ChannelId#CHARGE_MAX_CURRENT}. + * + * @return the Channel + */ + public default IntegerReadChannel getChargeMaxCurrentChannel() { + return this.channel(ChannelId.CHARGE_MAX_CURRENT); + } + + /** + * Gets the {@link ChannelId#CHARGE_MAX_CURRENT}. + * + * @return the Channel {@link Value} + */ + public default Value getChargeMaxCurrent() { + return this.getChargeMaxCurrentChannel().value(); + } + + /** + * Gets the Channel for {@link ChannelId#DISCHARGE_MAX_CURRENT}. + * + * @return the Channel + */ + public default IntegerReadChannel getDischargeMaxCurrentChannel() { + return this.channel(ChannelId.DISCHARGE_MAX_CURRENT); + } + + /** + * Gets the {@link ChannelId#DISCHARGE_MAX_CURRENT}. + * + * @return the Channel {@link Value} + */ + public default Value getDischargeMaxCurrent() { + return this.getDischargeMaxCurrentChannel().value(); + } + + /** + * Gets the Channel for {@link ChannelId#EXPORT_POWER}. + * + * @return the Channel + */ + public default IntegerReadChannel getExportPowerChannel() { + return this.channel(ChannelId.EXPORT_POWER); + } + + /** + * Gets the Channel for {@link ChannelId#BATTERY_VOLTAGE}. + * + * @return the Channel + */ + public default IntegerReadChannel getBatteryVoltageChannel() { + return this.channel(ChannelId.BATTERY_VOLTAGE); + } + + /** + * Gets the {@link ChannelId#BATTERY_VOLTAGE}. + * + * @return the Channel {@link Value} + */ + public default Value getBatteryVoltage() { + return this.getBatteryVoltageChannel().value(); + } + + /** + * Gets the Channel for {@link ChannelId#TOTAL_IMPORT_ENERGY}. + * + * @return the Channel + */ + public default LongReadChannel getTotalImportEnergyChannel() { + return this.channel(ChannelId.TOTAL_IMPORT_ENERGY); + } + + /** + * Gets the Channel for {@link ChannelId#TOTAL_EXPORT_ENERGY}. + * + * @return the Channel + */ + public default LongReadChannel getTotalExportEnergyChannel() { + return this.channel(ChannelId.TOTAL_EXPORT_ENERGY); + } + + /** + * Gets the Channel for {@link ChannelId#EMS_MODE}. + * + * @return the Channel + */ + public default EnumWriteChannel getEmsModeChannel() { + return this.channel(ChannelId.EMS_MODE); + } + + /** + * Gets the Channel for {@link ChannelId#CHARGE_DISCHARGE_COMMAND}. + * + * @return the Channel + */ + public default EnumWriteChannel getChargeDischargeCommandChannel() { + return this.channel(ChannelId.CHARGE_DISCHARGE_COMMAND); + } + + /** + * Gets the Channel for {@link ChannelId#CHARGE_DISCHARGE_POWER}. + * + * @return the Channel + */ + public default IntegerWriteChannel getChargeDischargePowerChannel() { + return this.channel(ChannelId.CHARGE_DISCHARGE_POWER); + } + + /** + * Gets the Channel for {@link ChannelId#HEARTBEAT}. + * + * @return the Channel + */ + public default IntegerWriteChannel getHeartbeatChannel() { + return this.channel(ChannelId.HEARTBEAT); + } +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java new file mode 100644 index 00000000000..c321a26c2ac --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java @@ -0,0 +1,423 @@ +package io.openems.edge.sungrow.ess; + +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_1; +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2; +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1; + +import java.util.function.Consumer; + +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.OpenemsError.OpenemsNamedException; +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.ModbusComponent; +import io.openems.edge.bridge.modbus.api.ModbusProtocol; +import io.openems.edge.bridge.modbus.api.element.BitsWordElement; +import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; +import io.openems.edge.bridge.modbus.api.element.SignedDoublewordElement; +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.bridge.modbus.api.element.WordOrder; +import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask; +import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; +import io.openems.edge.bridge.modbus.api.task.FC4ReadInputRegistersTask; +import io.openems.edge.bridge.modbus.api.task.FC6WriteRegisterTask; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.sum.GridMode; +import io.openems.edge.common.sum.Sum; +import io.openems.edge.common.taskmanager.Priority; +import io.openems.edge.common.type.TypeUtils; +import io.openems.edge.ess.api.HybridEss; +import io.openems.edge.ess.api.ManagedSymmetricEss; +import io.openems.edge.ess.api.SymmetricEss; +import io.openems.edge.ess.power.api.Power; +import io.openems.edge.timedata.api.Timedata; +import io.openems.edge.timedata.api.TimedataProvider; +import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "Ess.Sungrow", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class EssSungrowImpl extends AbstractOpenemsModbusComponent implements EssSungrow, SymmetricEss, + ManagedSymmetricEss, HybridEss, ModbusComponent, TimedataProvider, OpenemsComponent { + + @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) + private volatile Timedata timedata = null; + + private final CalculateEnergyFromPower calculateActiveChargeEnergy = new CalculateEnergyFromPower(this, + SymmetricEss.ChannelId.ACTIVE_CHARGE_ENERGY); + private final CalculateEnergyFromPower calculateActiveDischargeEnergy = new CalculateEnergyFromPower(this, + SymmetricEss.ChannelId.ACTIVE_DISCHARGE_ENERGY); + private final CalculateEnergyFromPower calculateDcChargeEnergy = new CalculateEnergyFromPower(this, + HybridEss.ChannelId.DC_CHARGE_ENERGY); + private final CalculateEnergyFromPower calculateDcDischargeEnergy = new CalculateEnergyFromPower(this, + HybridEss.ChannelId.DC_DISCHARGE_ENERGY); + + private final ApplyPowerHandler applyPowerHandler = new ApplyPowerHandler(); + + @Reference + protected Power power; + + @Reference + private Sum sum; + + private Config config = null; + + private int heartbeat = 500; + + public EssSungrowImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + ModbusComponent.ChannelId.values(), // + SymmetricEss.ChannelId.values(), // + ManagedSymmetricEss.ChannelId.values(), // + HybridEss.ChannelId.values(), // + EssSungrow.ChannelId.values() // + ); + } + + @Reference + protected ConfigurationAdmin cm; + + @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.config = config; + if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, + "Modbus", config.modbus_id())) { + return; + } + + // NOTE: This should normally be read from the device + this._setGridMode(GridMode.ON_GRID); + + this.installPowerListeners(); + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + /** + * Installs listeners to calculate power values from existing values. + */ + private void installPowerListeners() { + this.installDcDischargePowerListener(); + this.installAllowedChargePowerListener(); + this.installAllowedDischargePowerListener(); + } + + /** + * Installs a listener calculating the DC discharge power from the active power + * and pv power. + */ + private void installDcDischargePowerListener() { + final Consumer> dcDischarge = ignore -> { + this._setDcDischargePower(TypeUtils.subtract(// + this.getActivePower().get(), this.getTotalDcPower().get())); + }; + this.getActivePowerChannel().onSetNextValue(dcDischarge); + this.getTotalDcPowerChannel().onSetNextValue(dcDischarge); + } + + /** + * Installs a listener calculating the allowed charge power from the battery + * voltage and the charge max current. + */ + private void installAllowedChargePowerListener() { + final Consumer> allowedCharge = ignore -> { + this._setAllowedChargePower(// + // set to 0 if either value is undefined + -this.getBatteryVoltage().orElse(0) * this.getChargeMaxCurrent().orElse(0)); + }; + this.getBatteryVoltageChannel().onSetNextValue(allowedCharge); + this.getChargeMaxCurrentChannel().onSetNextValue(allowedCharge); + } + + /** + * Installs a listener calculating the allowed discharge power from the battery + * voltage and the discharge max current. + */ + private void installAllowedDischargePowerListener() { + final Consumer> allowedDischarge = ignore -> { + this._setAllowedDischargePower(// + // set to 0 if either value is undefined + this.getBatteryVoltage().orElse(0) * this.getDischargeMaxCurrent().orElse(0)); + }; + this.getBatteryVoltageChannel().onSetNextValue(allowedDischarge); + this.getDischargeMaxCurrentChannel().onSetNextValue(allowedDischarge); + } + + @Override + protected ModbusProtocol defineModbusProtocol() { + + return new ModbusProtocol(this, // + new FC4ReadInputRegistersTask(4989, Priority.HIGH, // + m(EssSungrow.ChannelId.SERIAL_NUMBER, new StringWordElement(4989, 10)), // + new DummyRegisterElement(4999), // Device type code + m(SymmetricEss.ChannelId.MAX_APPARENT_POWER, new UnsignedWordElement(5000), // + SCALE_FACTOR_2), // + new DummyRegisterElement(5001), // Output type + m(EssSungrow.ChannelId.DAILY_OUTPUT_ENERGY, new UnsignedWordElement(5002), // + SCALE_FACTOR_2), // + new DummyRegisterElement(5003, 5006), // + m(EssSungrow.ChannelId.INSIDE_TEMPERATURE, new SignedWordElement(5007)), // + new DummyRegisterElement(5008, 5009), // + m(EssSungrow.ChannelId.MPPT1_VOLTAGE, new UnsignedWordElement(5010), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.MPPT1_CURRENT, new UnsignedWordElement(5011), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.MPPT2_VOLTAGE, new UnsignedWordElement(5012), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.MPPT2_CURRENT, new UnsignedWordElement(5013), // + SCALE_FACTOR_2), // + new DummyRegisterElement(5014, 5015), // + m(EssSungrow.ChannelId.TOTAL_DC_POWER, // + new UnsignedDoublewordElement(5016).wordOrder(WordOrder.LSWMSW)), // + m(EssSungrow.ChannelId.VOLTAGE_L1, new UnsignedWordElement(5018), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.VOLTAGE_L2, new UnsignedWordElement(5019), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.VOLTAGE_L3, new UnsignedWordElement(5020), // + SCALE_FACTOR_2), // + new DummyRegisterElement(5021, 5031), // + m(SymmetricEss.ChannelId.REACTIVE_POWER, // + new SignedDoublewordElement(5032).wordOrder(WordOrder.LSWMSW)), // + m(EssSungrow.ChannelId.POWER_FACTOR, new SignedWordElement(5034)), // + m(EssSungrow.ChannelId.GRID_FREQUENCY, new UnsignedWordElement(5035), // + SCALE_FACTOR_1)), // + + new FC4ReadInputRegistersTask(5621, Priority.LOW, // + m(EssSungrow.ChannelId.EXPORT_LIMIT_MIN, new UnsignedWordElement(5621), // + SCALE_FACTOR_1), // + m(EssSungrow.ChannelId.EXPORT_LIMIT_MAX, new UnsignedWordElement(5622), // + SCALE_FACTOR_1), // + new DummyRegisterElement(5623, 5626), // + m(EssSungrow.ChannelId.BDC_RATED_POWER, new UnsignedWordElement(5627), // + SCALE_FACTOR_2), // + new DummyRegisterElement(5628, 5633), // + m(EssSungrow.ChannelId.CHARGE_MAX_CURRENT, new UnsignedWordElement(5634)), // + m(EssSungrow.ChannelId.DISCHARGE_MAX_CURRENT, new UnsignedWordElement(5635)) // + ), // + + new FC4ReadInputRegistersTask(12999, Priority.HIGH, // + m(EssSungrow.ChannelId.SYSTEM_STATE, new UnsignedWordElement(12999)), // + m(new BitsWordElement(13000, this) // + .bit(0, EssSungrow.ChannelId.POWER_GENERATED_FROM_PV) // + .bit(1, EssSungrow.ChannelId.BATTERY_CHARGING) // + .bit(2, EssSungrow.ChannelId.BATTERY_DISCHARGING) // + .bit(3, EssSungrow.ChannelId.POSITIVE_LOAD_POWER) // + .bit(4, EssSungrow.ChannelId.FEED_IN_POWER) // + .bit(5, EssSungrow.ChannelId.IMPORT_POWER_FROM_GRID) // + .bit(7, EssSungrow.ChannelId.NEGATIVE_LOAD_POWER) // + ), // + m(EssSungrow.ChannelId.DAILY_PV_GENERATION, new UnsignedWordElement(13001), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.TOTAL_PV_GENERATION, // + new UnsignedDoublewordElement(13002).wordOrder(WordOrder.LSWMSW), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.DAILY_EXPORT_POWER_FROM_PV, new UnsignedWordElement(13004), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.TOTAL_EXPORT_ENERGY_FROM_PV, // + new UnsignedDoublewordElement(13005).wordOrder(WordOrder.LSWMSW), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.LOAD_POWER, + new SignedDoublewordElement(13007).wordOrder(WordOrder.LSWMSW)), // + m(EssSungrow.ChannelId.EXPORT_POWER, // + new SignedDoublewordElement(13009).wordOrder(WordOrder.LSWMSW)), // + m(EssSungrow.ChannelId.DAILY_BATTERY_CHARGE_ENERGY_FROM_PV, new UnsignedWordElement(13011), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.TOTAL_BATTERY_CHARGE_ENERGY_FROM_PV, // + new UnsignedDoublewordElement(13012).wordOrder(WordOrder.LSWMSW), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.CO2_REDUCTION, // + new UnsignedDoublewordElement(13014).wordOrder(WordOrder.LSWMSW), // + SCALE_FACTOR_MINUS_1), // + m(EssSungrow.ChannelId.DAILY_DIRECT_ENERGY_CONSUMPTION, new UnsignedWordElement(13016), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.TOTAL_DIRECT_ENERGY_CONSUMPTION, // + new UnsignedDoublewordElement(13017).wordOrder(WordOrder.LSWMSW), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.BATTERY_VOLTAGE, new UnsignedWordElement(13019), // + SCALE_FACTOR_MINUS_1), // + m(EssSungrow.ChannelId.BATTERY_CURRENT, new UnsignedWordElement(13020), // + SCALE_FACTOR_MINUS_1), // + m(EssSungrow.ChannelId.BATTERY_POWER, new UnsignedWordElement(13021)), // + m(SymmetricEss.ChannelId.SOC, new UnsignedWordElement(13022), // + SCALE_FACTOR_MINUS_1), // + m(EssSungrow.ChannelId.SOH, new UnsignedWordElement(13023), // + SCALE_FACTOR_MINUS_1), // + m(EssSungrow.ChannelId.BATTERY_TEMPERATURE, new SignedWordElement(13024)), // + m(EssSungrow.ChannelId.DAILY_BATTERY_DISCHARGE_ENERGY, new UnsignedWordElement(13025), // + SCALE_FACTOR_2), // + new DummyRegisterElement(13026, 13027), // + m(EssSungrow.ChannelId.SELF_CONSUMPTION_OF_TODAY, new UnsignedWordElement(13028), // + SCALE_FACTOR_MINUS_1), // + // NOTE: Grid mode is not read properly + new DummyRegisterElement(13029), + m(EssSungrow.ChannelId.CURRENT_L1, new SignedWordElement(13030), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.CURRENT_L2, new SignedWordElement(13031), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.CURRENT_L3, new SignedWordElement(13032), // + SCALE_FACTOR_2), // + m(SymmetricEss.ChannelId.ACTIVE_POWER, new SignedDoublewordElement(13033)) + .wordOrder(WordOrder.LSWMSW), // + m(EssSungrow.ChannelId.DAILY_IMPORT_ENERGY, new UnsignedWordElement(13035), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.TOTAL_IMPORT_ENERGY, + new UnsignedDoublewordElement(13036).wordOrder(WordOrder.LSWMSW), // + SCALE_FACTOR_2), // + m(SymmetricEss.ChannelId.CAPACITY, new UnsignedWordElement(13038), // + SCALE_FACTOR_1), // + m(EssSungrow.ChannelId.DAILY_CHARGE_ENERGY, new UnsignedWordElement(13039), // + SCALE_FACTOR_2), // + new DummyRegisterElement(13040, 13043), // + m(EssSungrow.ChannelId.DAILY_EXPORT_ENERGY, new UnsignedWordElement(13044), // + SCALE_FACTOR_2), // + m(EssSungrow.ChannelId.TOTAL_EXPORT_ENERGY, + new UnsignedDoublewordElement(13045).wordOrder(WordOrder.LSWMSW), // + SCALE_FACTOR_2) // + ), // + + new FC3ReadRegistersTask(13049, Priority.HIGH, // + m(EssSungrow.ChannelId.EMS_MODE, new UnsignedWordElement(13049)), // + m(EssSungrow.ChannelId.CHARGE_DISCHARGE_COMMAND, new UnsignedWordElement(13050)), // + m(EssSungrow.ChannelId.CHARGE_DISCHARGE_POWER, new UnsignedWordElement(13051)), // + new DummyRegisterElement(13052, 13056), // + m(EssSungrow.ChannelId.MAX_SOC, new UnsignedWordElement(13057), // + SCALE_FACTOR_MINUS_1), // + m(EssSungrow.ChannelId.MIN_SOC, new UnsignedWordElement(13058), // + SCALE_FACTOR_MINUS_1), // + new DummyRegisterElement(13059, 13078), // + m(EssSungrow.ChannelId.HEARTBEAT, new UnsignedWordElement(13079)), // + new DummyRegisterElement(13080, 13084), // + m(EssSungrow.ChannelId.METER_COMM_DETECTION, new UnsignedWordElement(13085)), // + m(EssSungrow.ChannelId.EXPORT_POWER_LIMITATION, new UnsignedWordElement(13086)), // + new DummyRegisterElement(13087, 13098), // + m(EssSungrow.ChannelId.RESERVED_SOC_FOR_BACKUP, new UnsignedWordElement(13099)) // + ), // + + new FC16WriteRegistersTask(13049, // + m(EssSungrow.ChannelId.EMS_MODE, new UnsignedWordElement(13049)), // + m(EssSungrow.ChannelId.CHARGE_DISCHARGE_COMMAND, new UnsignedWordElement(13050)), // + m(EssSungrow.ChannelId.CHARGE_DISCHARGE_POWER, new UnsignedWordElement(13051)) // + ), // + new FC6WriteRegisterTask(13079, // + m(EssSungrow.ChannelId.HEARTBEAT, new UnsignedWordElement(13079)) // + ) // + + ); + } + + @Override + public Integer getSurplusPower() { + return null; + } + + @Override + public Power getPower() { + return this.power; + } + + @Override + public void applyPower(int activePower, int reactivePower) throws OpenemsNamedException { + + this.calculateEnergy(); + this.updateHeartbeat(); + this.applyPowerHandler.apply(this, activePower, this.config.controlMode(), this.sum.getGridActivePower()); + } + + /** + * Calculate the Energy values from ActivePower. + */ + private void calculateEnergy() { + // AC + var activePower = this.getActivePower().get(); + if (activePower == null) { + // Not available + this.calculateActiveChargeEnergy.update(null); + this.calculateActiveDischargeEnergy.update(null); + } else if (activePower > 0) { + // Buy-From-Inverter + this.calculateActiveChargeEnergy.update(0); + this.calculateActiveDischargeEnergy.update(activePower); + } else { + // Sell-To-Inverter + this.calculateActiveChargeEnergy.update(activePower * -1); + this.calculateActiveDischargeEnergy.update(0); + } + // DC + var dcPower = this.getDcDischargePower().get(); + if (dcPower == null) { + // Not available + this.calculateDcChargeEnergy.update(null); + this.calculateDcDischargeEnergy.update(null); + } else if (dcPower > 0) { + // Discharging battery + this.calculateDcChargeEnergy.update(0); + this.calculateDcDischargeEnergy.update(dcPower); + } else { + // Charging battery + this.calculateDcChargeEnergy.update(dcPower * -1); + this.calculateDcDischargeEnergy.update(0); + } + } + + /** + * Toggles the heartbeat value between 500 and 600 and sets a debug channel. + * + * @throws OpenemsNamedException on write error + */ + private void updateHeartbeat() throws OpenemsNamedException { + if (this.heartbeat == 500) { + this.heartbeat = 600; + } else { + this.heartbeat = 500; + } + this.getHeartbeatChannel().setNextWriteValue(this.heartbeat); + this.channel(EssSungrow.ChannelId.DEBUG_HEARTBEAT).setNextValue(this.heartbeat); + } + + @Override + public int getPowerPrecision() { + return 1; + } + + @Override + public String debugLog() { + return new StringBuilder() // + .append("SoC:").append(this.getSoc()) // + .append("|Active Power:").append(this.getActivePower().toString()) // + .append("|DC Discharge Power:").append(this.getDcDischargePower().toString()).toString(); + } + + @Override + public Timedata getTimedata() { + return this.timedata; + } + +} diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/ChargeDischargeCommand.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/ChargeDischargeCommand.java new file mode 100644 index 00000000000..44e97611983 --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/ChargeDischargeCommand.java @@ -0,0 +1,35 @@ +package io.openems.edge.sungrow.ess.enums; + +import io.openems.common.types.OptionsEnum; + +public enum ChargeDischargeCommand implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + CHARGE(0xAA, "Charge"), // + DISCHARGE(0xBB, "Discharge"), // + STOP(0xCC, "Stop") // + ; + + private final int value; + private final String name; + + private ChargeDischargeCommand(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/ControlMode.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/ControlMode.java new file mode 100644 index 00000000000..cd74fdd8f29 --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/ControlMode.java @@ -0,0 +1,23 @@ +package io.openems.edge.sungrow.ess.enums; + +public enum ControlMode { + + /** + * Uses the internal 'SELF_CONSUMPTION' mode of the Sungrow inverter. Allows no remote + * control of Set-Points. Requires a Sungrow Meter at the grid junction + * point. + */ + INTERNAL, + /** + * Uses the internal 'SELF_CONSUMPTION' mode of the Sungrow inverter but smartly switches to + * other modes if required. Requires a Sungrow Meter at the grid junction + * point. + */ + SMART, + /** + * Full control of the Sungrow inverter by OpenEMS. Slower than internal 'SELF_CONSUMPTION' + * mode, but does not require a Sungrow Meter at the grid junction point. + */ + REMOTE; + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/EmsMode.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/EmsMode.java new file mode 100644 index 00000000000..f142b647378 --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/EmsMode.java @@ -0,0 +1,35 @@ +package io.openems.edge.sungrow.ess.enums; + +import io.openems.common.types.OptionsEnum; + +public enum EmsMode implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + SELF_CONSUMPTION(0, "Self-consumption mode"), // + FORCED_MODE(2, "Forced Mode"), // + EXTERNAL_EMS_MODE(3, "External EMS mode") // + ; + + private final int value; + private final String name; + + private EmsMode(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/EnableDisable.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/EnableDisable.java new file mode 100644 index 00000000000..e0338017366 --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/EnableDisable.java @@ -0,0 +1,34 @@ +package io.openems.edge.sungrow.ess.enums; + +import io.openems.common.types.OptionsEnum; + +public enum EnableDisable implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + ENABLE(0xAA, "Enable"), // + DISABLE(0x55, "Disable") // + ; + + private final int value; + private final String name; + + private EnableDisable(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/SystemState.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/SystemState.java new file mode 100644 index 00000000000..5ae672b7977 --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/SystemState.java @@ -0,0 +1,43 @@ +package io.openems.edge.sungrow.ess.enums; + +import io.openems.common.types.OptionsEnum; + +public enum SystemState implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + STOP(0x0002, "Stop"), // + STANDBY(0x0008, "Standby"), // + INITIAL_STANDBY(0x0010, "Initial Standby"), // + STARTUP(0x0020, "Startup"), // + RUNNING(0x0040, "Running"), // + FAULT(0x0100, "Fault"), // + RUNNING_IN_MAINTAIN_MODE(0x0400, "Running in maintain mode"), // + RUNNING_IN_FORCED_MODE(0x0800, "Running in forced mode"), // + RUNNING_IN_OFF_GRID_MODE(0x1000, "Running in off-grid mode"), // + RESTARTING(0x2501, "Restarting"), // + RUNNING_IN_EXTERNAL_EMS_MODE(0x4000, "Running in External EMS mode") // + ; + + private final int value; + private final String name; + + private SystemState(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java new file mode 100644 index 00000000000..8ca15deadb4 --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java @@ -0,0 +1,25 @@ +package io.openems.edge.sungrow.meter; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(// + name = "Ess Sungrow Virtual Grid Meter", // + description = "Implements virtual meter from Sungrow Hybrid ESS.") +@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 = "Core ID", description = "ID of the Sungrow Ess Component.") + String core_id() default "ess0"; + + String webconsole_configurationFactory_nameHint() default "Ess Sungrow Virtual Grid Meter [{id}]"; + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowVirtualGridMeter.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowVirtualGridMeter.java new file mode 100644 index 00000000000..508b2258437 --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowVirtualGridMeter.java @@ -0,0 +1,112 @@ +package io.openems.edge.sungrow.meter; + +import static io.openems.edge.meter.api.ElectricityMeter.calculatePhasesFromActivePower; +import static io.openems.edge.meter.api.ElectricityMeter.calculateSumCurrentFromPhases; +import static io.openems.edge.meter.api.ElectricityMeter.calculateAverageVoltageFromPhases; +import static io.openems.edge.meter.api.ElectricityMeter.calculateCurrentsFromActivePowerAndVoltage; + +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.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.meter.api.ElectricityMeter; +import io.openems.edge.sungrow.ess.EssSungrow; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "Ess.Sungrow.Gridmeter", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class SungrowVirtualGridMeter extends AbstractOpenemsComponent implements ElectricityMeter, OpenemsComponent { + + protected Config config = null; + + @Reference + private ConfigurationAdmin cm; + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + private EssSungrow ess; + + public SungrowVirtualGridMeter() { + super(// + OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values() // + ); + + // Provide phase powers + calculatePhasesFromActivePower(this); + + // Provide missing voltages + calculateAverageVoltageFromPhases(this); + + // Provide Currents from Power and Voltage + calculateSumCurrentFromPhases(this); + calculateCurrentsFromActivePowerAndVoltage(this); + } + + @Activate + private void activate(ComponentContext context, Config config) throws OpenemsException, OpenemsNamedException { + this.config = config; + super.activate(context, config.id(), config.alias(), config.enabled()); + + if (!this.config.enabled()) { + return; + } + + this.mapChannelValues(); + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + private void mapChannelValues() throws OpenemsException { + this.ess.getExportPowerChannel().onSetNextValue(newValue -> { + this._setActivePower(newValue.isDefined() ? -newValue.get() : null); + }); + this.ess.getTotalExportEnergyChannel().onSetNextValue(newValue -> { + this._setActiveConsumptionEnergy(newValue.get()); + }); + this.ess.getTotalImportEnergyChannel().onSetNextValue(newValue -> { + this._setActiveProductionEnergy(newValue.get()); + }); + this.ess.getGridFrequencyChannel().onSetNextValue(newValue -> { + this._setFrequency(newValue.get()); + }); + this.ess.getVoltageL1Channel().onSetNextValue(newValue -> { + this._setVoltageL1(newValue.get()); + }); + this.ess.getVoltageL2Channel().onSetNextValue(newValue -> { + this._setVoltageL2(newValue.get()); + }); + this.ess.getVoltageL3Channel().onSetNextValue(newValue -> { + this._setVoltageL3(newValue.get()); + }); + } + + @Override + public MeterType getMeterType() { + return MeterType.GRID; + } + + @Override + public String debugLog() { + return "L:" + this.getActivePower().asString(); + } + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/Config.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/Config.java new file mode 100644 index 00000000000..fb1986ff953 --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/Config.java @@ -0,0 +1,38 @@ +package io.openems.edge.sungrow.pvinverter; + +import io.openems.common.types.MeterType; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(// + name = "PV-Inverter Sungrow", // + description = "Implements the Sungrow PV inverter") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "pvInverter0"; + + @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 = "Read-Only mode", description = "Set to true, if PV Inverter should only be read.") + boolean readOnly() 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 = "Always Production") + MeterType type() default MeterType.PRODUCTION; + + String webconsole_configurationFactory_nameHint() default "PV-Inverter Sungrow [{id}]"; + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrow.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrow.java new file mode 100644 index 00000000000..756d9a96a2c --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrow.java @@ -0,0 +1,131 @@ +package io.openems.edge.sungrow.pvinverter; + +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.common.channel.Doc; +import io.openems.edge.common.component.OpenemsComponent; + +public interface PvInverterSungrow extends OpenemsComponent { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + SERIAL_NUMBER(Doc.of(OpenemsType.STRING) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW) // + ), // + + NOMINAL_OUTPUT_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW) // + ), // + + DAILY_ENERGY(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT_HOURS) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.MEDIUM) // + ), TOTAL_RUNNING_TIME(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.HOUR) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW) // + ), + + INTERNAL_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.DEZIDEGREE_CELSIUS) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW) // + ), + + APPARENT_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.VOLT_AMPERE) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.MEDIUM) // + ), // + + DC_VOLTAGE_1(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.VOLT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW) // + ), // + + DC_CURRENT_1(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.MILLIAMPERE) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW) // + ), // + + DC_VOLTAGE_2(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.VOLT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW) // + ), // + + DC_CURRENT_2(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.MILLIAMPERE) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW) // + ), // + + DC_VOLTAGE_3(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.VOLT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW) // + ), // + + DC_CURRENT_3(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.MILLIAMPERE) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW) // + ), // + + DC_POWER(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.WATT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.HIGH) // + ), + + POWER_FACTOR(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.THOUSANDTH) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW) // + ), + + WORK_STATE(Doc.of(WorkState.values()) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW) // + ), + + NEGATIVE_VOLTAGE_TO_THE_GROUND(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.VOLT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.VERY_LOW) // + ), + + BUS_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.VOLT) // + .accessMode(AccessMode.READ_ONLY) // + .persistencePriority(PersistencePriority.LOW) // + ), + + POWER_LIMITATION_SWITCH(Doc.of(OpenemsType.BOOLEAN) // + .unit(Unit.ON_OFF) // + .accessMode(AccessMode.READ_WRITE) // + ), POWER_LIMITATION_SETTING(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.PERCENT) // + .accessMode(AccessMode.READ_WRITE) // + ); + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java new file mode 100644 index 00000000000..7f85bcfb9f4 --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java @@ -0,0 +1,222 @@ +package io.openems.edge.sungrow.pvinverter; + +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_1; +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2; +import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1; +import static io.openems.edge.common.event.EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE; + +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.event.Event; +import org.osgi.service.event.EventHandler; +import org.osgi.service.event.propertytypes.EventTopics; +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.SignedDoublewordElement; +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.bridge.modbus.api.element.WordOrder; +import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; +import io.openems.edge.bridge.modbus.api.task.FC4ReadInputRegistersTask; +import io.openems.edge.bridge.modbus.api.task.FC6WriteRegisterTask; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.taskmanager.Priority; +import io.openems.edge.meter.api.ElectricityMeter; +import io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter; +import io.openems.edge.timedata.api.Timedata; +import io.openems.edge.timedata.api.TimedataProvider; +import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "PV-Inverter.Sungrow", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +@EventTopics({ // + TOPIC_CYCLE_AFTER_PROCESS_IMAGE // +}) +public class PvInverterSungrowImpl extends AbstractOpenemsModbusComponent implements ManagedSymmetricPvInverter, + ElectricityMeter, PvInverterSungrow, ModbusComponent, TimedataProvider, OpenemsComponent, EventHandler { + + private static final ElementToChannelConverter ON_OFF_CONVERTER = new ElementToChannelConverter(// + // Element to Channel + value -> // + (Integer) value == 0xAA, // + // Channel to Element + value -> { // + if ((Boolean) value) { + return 0xAA; + } + return 0xEE; + }); + + @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) + private volatile Timedata timedata = null; + + private final CalculateEnergyFromPower calculateEnergy = new CalculateEnergyFromPower(this, + ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY); + + public PvInverterSungrowImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + ModbusComponent.ChannelId.values(), // + ManagedSymmetricPvInverter.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // + PvInverterSungrow.ChannelId.values() // + ); + } + + @Reference + protected ConfigurationAdmin cm; + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + protected void setModbus(BridgeModbus modbus) { + super.setModbus(modbus); + } + + private boolean readOnly = true; // oEMS add + + @Activate + void activate(ComponentContext context, Config config) throws OpenemsException { + this.readOnly = config.readOnly(); // oEMS add + if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, + "Modbus", config.modbus_id())) { + return; + } + this.getActivePowerChannel().onSetNextValue(value -> { // + var powerPerPhase = value.orElse(0) / 3; + this._setActivePowerL1(powerPerPhase); + this._setActivePowerL2(powerPerPhase); + this._setActivePowerL3(powerPerPhase); + }); + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + @Override + public void handleEvent(Event event) { + if (!this.isEnabled()) { + return; + } + switch (event.getTopic()) { + case TOPIC_CYCLE_AFTER_PROCESS_IMAGE -> this.calculateEnergy.update(this.getActivePower().get()); + } + } + + @Override + protected ModbusProtocol defineModbusProtocol() { + + var protocol = new ModbusProtocol(this); + protocol.addTasks(// + new FC4ReadInputRegistersTask(4989, Priority.HIGH, // + m(PvInverterSungrow.ChannelId.SERIAL_NUMBER, new StringWordElement(4989, 10)), // + new DummyRegisterElement(4999), // Device Type Code + m(PvInverterSungrow.ChannelId.NOMINAL_OUTPUT_POWER, new UnsignedWordElement(5000), // + SCALE_FACTOR_2), // + new DummyRegisterElement(5001), // Outout type + m(PvInverterSungrow.ChannelId.DAILY_ENERGY, new UnsignedWordElement(5002), // + SCALE_FACTOR_2), // + new DummyRegisterElement(5003, 5004), // + m(PvInverterSungrow.ChannelId.TOTAL_RUNNING_TIME, + new UnsignedDoublewordElement(5005).wordOrder(WordOrder.LSWMSW)), // + m(PvInverterSungrow.ChannelId.INTERNAL_TEMPERATURE, new SignedWordElement(5007)), // + m(PvInverterSungrow.ChannelId.APPARENT_POWER, + new UnsignedDoublewordElement(5008).wordOrder(WordOrder.LSWMSW)), // + m(PvInverterSungrow.ChannelId.DC_VOLTAGE_1, new UnsignedWordElement(5010), // + SCALE_FACTOR_MINUS_1), // + m(PvInverterSungrow.ChannelId.DC_CURRENT_1, new UnsignedWordElement(5011), // + SCALE_FACTOR_2), // + m(PvInverterSungrow.ChannelId.DC_VOLTAGE_2, new UnsignedWordElement(5012), // + SCALE_FACTOR_MINUS_1), // + m(PvInverterSungrow.ChannelId.DC_CURRENT_2, new UnsignedWordElement(5013), // + SCALE_FACTOR_2), // + m(PvInverterSungrow.ChannelId.DC_VOLTAGE_3, new UnsignedWordElement(5014), // + SCALE_FACTOR_MINUS_1), // + m(PvInverterSungrow.ChannelId.DC_CURRENT_3, new UnsignedWordElement(5015), // + SCALE_FACTOR_2), // + m(PvInverterSungrow.ChannelId.DC_POWER, + new UnsignedDoublewordElement(5016).wordOrder(WordOrder.LSWMSW)), // + m(ElectricityMeter.ChannelId.VOLTAGE_L1, new UnsignedWordElement(5018), // + SCALE_FACTOR_2), // + m(ElectricityMeter.ChannelId.VOLTAGE_L2, new UnsignedWordElement(5019), // + SCALE_FACTOR_2), // + m(ElectricityMeter.ChannelId.VOLTAGE_L3, new UnsignedWordElement(5020), // + SCALE_FACTOR_2), // + m(ElectricityMeter.ChannelId.CURRENT_L1, new UnsignedWordElement(5021), // + SCALE_FACTOR_2), // + m(ElectricityMeter.ChannelId.CURRENT_L2, new UnsignedWordElement(5022), // + SCALE_FACTOR_2), // + m(ElectricityMeter.ChannelId.CURRENT_L3, new UnsignedWordElement(5023), // + SCALE_FACTOR_2), // + new DummyRegisterElement(5024, 5029), // Reserved + m(ElectricityMeter.ChannelId.ACTIVE_POWER, + new UnsignedDoublewordElement(5030).wordOrder(WordOrder.LSWMSW)), // + m(ElectricityMeter.ChannelId.REACTIVE_POWER, + new SignedDoublewordElement(5032).wordOrder(WordOrder.LSWMSW)), // + m(PvInverterSungrow.ChannelId.POWER_FACTOR, new SignedWordElement(5034)), // + new DummyRegisterElement(5035, 5036), // 5035: Frequency, read from another register + m(PvInverterSungrow.ChannelId.WORK_STATE, new UnsignedWordElement(5037)) // + ), + + new FC4ReadInputRegistersTask(5145, Priority.LOW, // + m(PvInverterSungrow.ChannelId.NEGATIVE_VOLTAGE_TO_THE_GROUND, new SignedWordElement(5145), // + SCALE_FACTOR_MINUS_1), // + m(PvInverterSungrow.ChannelId.BUS_VOLTAGE, new UnsignedWordElement(5146), // + SCALE_FACTOR_MINUS_1), // + m(ElectricityMeter.ChannelId.FREQUENCY, new UnsignedWordElement(5147), // + SCALE_FACTOR_1) // + ), + + new FC3ReadRegistersTask(5006, Priority.LOW, // + m(PvInverterSungrow.ChannelId.POWER_LIMITATION_SWITCH, new UnsignedWordElement(5006), // + ON_OFF_CONVERTER), // + m(PvInverterSungrow.ChannelId.POWER_LIMITATION_SETTING, new UnsignedWordElement(5007), // + SCALE_FACTOR_MINUS_1)), // + + new FC3ReadRegistersTask(5038, Priority.HIGH, // + m(ManagedSymmetricPvInverter.ChannelId.ACTIVE_POWER_LIMIT, new UnsignedWordElement(5038), // + SCALE_FACTOR_2))); // + if (!this.readOnly) { // oEMS add + protocol.addTask(new FC6WriteRegisterTask(5038, m(ManagedSymmetricPvInverter.ChannelId.ACTIVE_POWER_LIMIT, // + new UnsignedWordElement(5038), SCALE_FACTOR_2))); + } + return protocol; + } + + @Override + public String debugLog() { + return "L:" + this.getActivePower().asString(); + } + + @Override + public Timedata getTimedata() { + return this.timedata; + } + + // oEMS add + @Override + public boolean isReadOnly() { + return this.readOnly; + } +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/WorkState.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/WorkState.java new file mode 100644 index 00000000000..7a1acfe27fb --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/WorkState.java @@ -0,0 +1,44 @@ +package io.openems.edge.sungrow.pvinverter; + +import io.openems.common.types.OptionsEnum; + +public enum WorkState implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + RUN(0x0, "Run"), // + STOP(0x8000, "Stop"), // + KEY_STOP(0x1300, "Key stop"), // + EMERGENCY_STOP(0x1500, "Emergency stop"), // + STANDBY(0x1400, "Standby"), // + INITIAL_STANDBY(0x1200, "Initial standby"), // + STARTING(0x1600, "Starting"), // + ALARM_RUN(0x9100, "Alarm run"), // + DERATING_RUN(0x8100, "Derating run"), // + DISPATCH_RUN(0x8200, "Dispatch run"), // + FAULT(0x5500, "Fault"), // + COMMUNICATE_FAULT(0x2500, "Communicate fault") // + ; + + private int value; + private String name; + + private WorkState(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + +} \ No newline at end of file From a8241920cf3428a7e38dae9ca9c896a01467c624 Mon Sep 17 00:00:00 2001 From: Thomas Sicking Date: Mon, 31 Aug 2026 11:59:02 +0200 Subject: [PATCH 02/15] Fixed merge problems --- .../edge/ess/api/ManagedSymmetricEss.java | 22 ++++++++++++++++++- .../io/openems/edge/ess/api/SymmetricEss.java | 10 +++++++++ .../edge/sungrow/ess/ApplyPowerHandler.java | 2 +- .../pvinverter/PvInverterSungrowImpl.java | 7 +----- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java index b0e600eb9c3..49a346c268f 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java @@ -284,7 +284,27 @@ public default IntegerReadChannel getAllowedChargePowerChannel() { public default Value getAllowedChargePower() { return this.getAllowedChargePowerChannel().value(); } - + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#ALLOWED_CHARGE_POWER} Channel. + * + * @param value the next value + */ + public default void _setAllowedChargePower(int value) { + this.getAllowedChargePowerChannel().setNextValue(value); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#ALLOWED_CHARGE_POWER} Channel. + * + * @param value the next value + */ + public default void _setAllowedChargePower(Integer value) { + this.getAllowedChargePowerChannel().setNextValue(value); + } + /** * Gets the Channel for {@link ChannelId#ALLOWED_DISCHARGE_POWER}. * diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java index 418c4bff280..12cba9a6cce 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java @@ -320,6 +320,16 @@ public default Channel getGridModeChannel() { public default GridMode getGridMode() { return this.getGridModeChannel().value().asEnum(); } + + /* + * Internal method to set the 'nextValue' on {@link ChannelId#GRID_MODE} + * Channel. + * + * @param value the next value + */ + public default void _setGridMode(GridMode value) { + this.getGridModeChannel().setNextValue(value); + } /** * Returns true if {@link GridMode} is {@link GridMode#ON_GRID} or diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/ApplyPowerHandler.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/ApplyPowerHandler.java index 0d981aa0262..047fc94e472 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/ApplyPowerHandler.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/ApplyPowerHandler.java @@ -24,7 +24,7 @@ public class ApplyPowerHandler { public synchronized void apply(EssSungrowImpl parent, int setActivePower, ControlMode controlMode, Value gridActivePower) throws OpenemsNamedException { parent.channel(EssSungrow.ChannelId.SMART_MODE_NOT_WORKING_WITH_PID_FILTER) // - .setNextValue(parent.power.isPidEnabled() && controlMode.equals(ControlMode.SMART)); + .setNextValue(parent.power.isFilterEnabled() && controlMode.equals(ControlMode.SMART)); var result = switch (controlMode) { case INTERNAL -> handleInternalMode(); diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java index 7f85bcfb9f4..0711bd60936 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java @@ -214,9 +214,4 @@ public Timedata getTimedata() { return this.timedata; } - // oEMS add - @Override - public boolean isReadOnly() { - return this.readOnly; - } -} \ No newline at end of file +} From 94cb8d65eb7402d5ed8ee2cfa308eadc5d44cd34 Mon Sep 17 00:00:00 2001 From: Thomas Sicking Date: Mon, 31 Aug 2026 12:26:22 +0200 Subject: [PATCH 03/15] Added JUnit tests --- .../edge/sungrow/dccharger/Config.java | 14 +-- ...alDcCharger.java => SungrowDcCharger.java} | 14 +-- .../io/openems/edge/sungrow/meter/Config.java | 12 +-- ...alGridMeter.java => SungrowGridMeter.java} | 9 +- .../edge/sungrow/dccharger/MyConfig.java | 57 +++++++++++++ .../SungrowVirtualDcChargerTest.java | 25 ++++++ .../edge/sungrow/ess/EssSungrowEssTest.java | 29 +++++++ .../io/openems/edge/sungrow/ess/MyConfig.java | 80 +++++++++++++++++ .../openems/edge/sungrow/meter/MyConfig.java | 52 ++++++++++++ .../meter/SungrowVirtualGridMeterTest.java | 25 ++++++ .../edge/sungrow/pvinverter/MyConfig.java | 85 +++++++++++++++++++ .../pvinverter/PvInverterSungrowImplTest.java | 27 ++++++ 12 files changed, 402 insertions(+), 27 deletions(-) rename io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/{SungrowVirtualDcCharger.java => SungrowDcCharger.java} (90%) rename io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/{SungrowVirtualGridMeter.java => SungrowGridMeter.java} (94%) create mode 100644 io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/MyConfig.java create mode 100644 io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/SungrowVirtualDcChargerTest.java create mode 100644 io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/EssSungrowEssTest.java create mode 100644 io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/MyConfig.java create mode 100644 io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java create mode 100644 io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/SungrowVirtualGridMeterTest.java create mode 100644 io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/MyConfig.java create mode 100644 io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImplTest.java diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/Config.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/Config.java index 3f93f4431c2..dc45e71e099 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/Config.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/Config.java @@ -4,8 +4,8 @@ import org.osgi.service.metatype.annotations.ObjectClassDefinition; @ObjectClassDefinition(// - name = "Ess Sungrow Virtual DC charger", // - description = "Implements virtual DC charger from Sungrow Hybrid ESS.") + name = "ESS Sungrow DC charger", // + description = "Implements DC charger from Sungrow Hybrid ESS.") @interface Config { @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") @@ -17,12 +17,12 @@ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; - @AttributeDefinition(name = "Core-ID", description = "ID of the Sungrow Ess Component.") - String core_id() default "ess0"; + @AttributeDefinition(name = "ESS-ID", description = "ID of the Sungrow Ess Component.") + String ess_id() default "ess0"; - @AttributeDefinition(name = "Core target filter", description = "This is auto-generated by 'Core-ID'.") - String core_target() default "(enabled=true)"; + @AttributeDefinition(name = "ESS target filter", description = "This is auto-generated by 'ESS-ID'.") + String ess_target() default "(enabled=true)"; - String webconsole_configurationFactory_nameHint() default "Ess Sungrow Virtual DC charger [{id}]"; + String webconsole_configurationFactory_nameHint() default "ESS Sungrow DC charger [{id}]"; } \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowVirtualDcCharger.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcCharger.java similarity index 90% rename from io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowVirtualDcCharger.java rename to io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcCharger.java index c32c7667f67..a36b0280b24 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowVirtualDcCharger.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcCharger.java @@ -34,7 +34,7 @@ @EventTopics({ // EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // }) -public class SungrowVirtualDcCharger extends AbstractOpenemsComponent +public class SungrowDcCharger extends AbstractOpenemsComponent implements EssDcCharger, TimedataProvider, OpenemsComponent, EventHandler { protected Config config = null; @@ -43,7 +43,7 @@ public class SungrowVirtualDcCharger extends AbstractOpenemsComponent private ConfigurationAdmin cm; @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) - private EssSungrow core; + private EssSungrow ess; @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) private volatile Timedata timedata = null; @@ -51,7 +51,7 @@ public class SungrowVirtualDcCharger extends AbstractOpenemsComponent private final CalculateEnergyFromPower calculateActualEnergy = new CalculateEnergyFromPower(this, EssDcCharger.ChannelId.ACTUAL_ENERGY); - public SungrowVirtualDcCharger() { + public SungrowDcCharger() { super(// OpenemsComponent.ChannelId.values(), // EssDcCharger.ChannelId.values() // @@ -63,7 +63,7 @@ private void activate(ComponentContext context, Config config) throws OpenemsExc this.config = config; super.activate(context, config.id(), config.alias(), config.enabled()); - if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "core", config.core_id())) { + if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "ess", config.ess_id())) { return; } @@ -76,17 +76,17 @@ protected void deactivate() { } private void mapChannelValues() { - this.core.getTotalDcPowerChannel().onSetNextValue(value -> { + this.ess.getTotalDcPowerChannel().onSetNextValue(value -> { if (value.isDefined()) { this._setActualPower(value.get()); } }); - this.core.getMppt1VoltageChannel().onSetNextValue(value -> { + this.ess.getMppt1VoltageChannel().onSetNextValue(value -> { if (value.isDefined()) { this._setVoltage(value.get()); } }); - this.core.getMppt1CurrentChannel().onSetNextValue(value -> { + this.ess.getMppt1CurrentChannel().onSetNextValue(value -> { if (value.isDefined()) { this._setCurrent(value.get()); } diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java index 8ca15deadb4..e80f17157a4 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java @@ -4,8 +4,8 @@ import org.osgi.service.metatype.annotations.ObjectClassDefinition; @ObjectClassDefinition(// - name = "Ess Sungrow Virtual Grid Meter", // - description = "Implements virtual meter from Sungrow Hybrid ESS.") + name = "Ess Sungrow Grid Meter", // + description = "Implements grid meter from Sungrow Hybrid ESS.") @interface Config { @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") @@ -17,9 +17,9 @@ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; - @AttributeDefinition(name = "Core ID", description = "ID of the Sungrow Ess Component.") - String core_id() default "ess0"; + @AttributeDefinition(name = "ESS ID", description = "ID of the Sungrow Ess Component.") + String ess_id() default "ess0"; - String webconsole_configurationFactory_nameHint() default "Ess Sungrow Virtual Grid Meter [{id}]"; + String webconsole_configurationFactory_nameHint() default "Ess Sungrow Grid Meter [{id}]"; -} \ No newline at end of file +} diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowVirtualGridMeter.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeter.java similarity index 94% rename from io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowVirtualGridMeter.java rename to io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeter.java index 508b2258437..7d489553f02 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowVirtualGridMeter.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeter.java @@ -31,7 +31,7 @@ immediate = true, // configurationPolicy = ConfigurationPolicy.REQUIRE // ) -public class SungrowVirtualGridMeter extends AbstractOpenemsComponent implements ElectricityMeter, OpenemsComponent { +public class SungrowGridMeter extends AbstractOpenemsComponent implements ElectricityMeter, OpenemsComponent { protected Config config = null; @@ -41,7 +41,7 @@ public class SungrowVirtualGridMeter extends AbstractOpenemsComponent implements @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) private EssSungrow ess; - public SungrowVirtualGridMeter() { + public SungrowGridMeter() { super(// OpenemsComponent.ChannelId.values(), // ElectricityMeter.ChannelId.values() // @@ -62,11 +62,6 @@ public SungrowVirtualGridMeter() { private void activate(ComponentContext context, Config config) throws OpenemsException, OpenemsNamedException { this.config = config; super.activate(context, config.id(), config.alias(), config.enabled()); - - if (!this.config.enabled()) { - return; - } - this.mapChannelValues(); } diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/MyConfig.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/MyConfig.java new file mode 100644 index 00000000000..da51699be61 --- /dev/null +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/MyConfig.java @@ -0,0 +1,57 @@ +package io.openems.edge.sungrow.dccharger; + +import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.utils.ConfigUtils; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + private String id; + private String essId = null; + + private Builder() { + } + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setEssId(String essId) { + this.essId = essId; + 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 ess_id() { + return this.builder.essId; + } + + @Override + public String ess_target() { + return ConfigUtils.generateReferenceTargetFilter(this.id(), this.ess_id()); + } + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/SungrowVirtualDcChargerTest.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/SungrowVirtualDcChargerTest.java new file mode 100644 index 00000000000..160d7a5319d --- /dev/null +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/SungrowVirtualDcChargerTest.java @@ -0,0 +1,25 @@ +package io.openems.edge.sungrow.dccharger; + +import org.junit.Test; + +import io.openems.common.test.DummyConfigurationAdmin; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.sungrow.ess.EssSungrowImpl; + +public class SungrowVirtualDcChargerTest { + + private static final String CHARGER_ID = "charger0"; + private static final String ESS_ID = "ess0"; + + @Test + public void test() throws Exception { + new ComponentTest(new SungrowDcCharger()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("ess", new EssSungrowImpl()) // + .activate(MyConfig.create() // + .setId(CHARGER_ID) // + .setEssId(ESS_ID) // + .build()); + } + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/EssSungrowEssTest.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/EssSungrowEssTest.java new file mode 100644 index 00000000000..fd2c4c879a4 --- /dev/null +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/EssSungrowEssTest.java @@ -0,0 +1,29 @@ +package io.openems.edge.sungrow.ess; + +import org.junit.Test; + +import io.openems.edge.common.test.AbstractComponentTest.TestCase; +import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.test.ComponentTest; +import io.openems.common.test.DummyConfigurationAdmin; +import io.openems.edge.sungrow.ess.enums.ControlMode; + +public class EssSungrowEssTest { + + private static final String COMPONENT_ID = "ess0"; + private static final String MODBUS_ID = "modbus0"; + + @Test + public void test() throws Exception { + new ComponentTest(new EssSungrowImpl()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .activate(MyConfig.create() // + .setId(COMPONENT_ID) // + .setModbusId(MODBUS_ID) // + .setControlMode(ControlMode.SMART) // + .build()) + .next(new TestCase()); + } + +} diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/MyConfig.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/MyConfig.java new file mode 100644 index 00000000000..f21f2f2fb85 --- /dev/null +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/MyConfig.java @@ -0,0 +1,80 @@ +package io.openems.edge.sungrow.ess; + +import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.utils.ConfigUtils; +import io.openems.edge.sungrow.ess.enums.ControlMode; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + private String id; + private String modbusId = null; + private int modbusUnitId; + private ControlMode controlMode; + + 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 setControlMode(ControlMode cm) { + this.controlMode = cm; + 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 ControlMode controlMode() { + return this.builder.controlMode; + } + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java new file mode 100644 index 00000000000..8a3f7b75552 --- /dev/null +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java @@ -0,0 +1,52 @@ +package io.openems.edge.sungrow.meter; + +import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.utils.ConfigUtils; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + private String id; + private String essId = null; + + private Builder() { + } + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setEssId(String essId) { + this.essId = essId; + 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 ess_id() { + return this.builder.essId; + } + +} diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/SungrowVirtualGridMeterTest.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/SungrowVirtualGridMeterTest.java new file mode 100644 index 00000000000..1eb2b0c5311 --- /dev/null +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/SungrowVirtualGridMeterTest.java @@ -0,0 +1,25 @@ +package io.openems.edge.sungrow.meter; + +import org.junit.Test; + +import io.openems.common.test.DummyConfigurationAdmin; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.sungrow.ess.EssSungrowImpl; + +public class SungrowVirtualGridMeterTest { + + private static final String METER_ID = "meter0"; + private static final String ESS_ID = "ess0"; + + @Test + public void test() throws Exception { + new ComponentTest(new SungrowGridMeter()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("ess", new EssSungrowImpl()) // + .activate(MyConfig.create() // + .setId(METER_ID) // + .setEssId(ESS_ID) // + .build()); + } + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/MyConfig.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/MyConfig.java new file mode 100644 index 00000000000..7a92af0854a --- /dev/null +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/MyConfig.java @@ -0,0 +1,85 @@ +package io.openems.edge.sungrow.pvinverter; + +import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.utils.ConfigUtils; +import io.openems.common.types.MeterType; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + private String id; + private String modbusId = null; + private int modbusUnitId; + private boolean readOnly = true; + + 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 setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + 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 boolean readOnly() { + return this.builder.readOnly; + } + + @Override + public MeterType type() { + return MeterType.PRODUCTION; + } + +} diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImplTest.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImplTest.java new file mode 100644 index 00000000000..3e57548a737 --- /dev/null +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImplTest.java @@ -0,0 +1,27 @@ +package io.openems.edge.sungrow.pvinverter; + +import org.junit.Test; + +import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.test.ComponentTest; +import io.openems.common.test.DummyConfigurationAdmin; + +public class PvInverterSungrowImplTest { + + private static final String PV_INVERTER_ID = "pvInverter0"; + private static final String MODBUS_ID = "modbus0"; + + @Test + public void test() throws Exception { + new ComponentTest(new PvInverterSungrowImpl()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .activate(MyConfig.create() // + .setId(PV_INVERTER_ID) // + .setModbusId(MODBUS_ID) // + .setModbusUnitId(1) // + .build()) // + ; + } + +} From 3e1f01ff6795669e44f6363656dc2be717e07648 Mon Sep 17 00:00:00 2001 From: Thomas Sicking Date: Mon, 31 Aug 2026 12:43:45 +0200 Subject: [PATCH 04/15] Added interfaces for grid meter and DC charger --- .../sungrow/dccharger/SungrowDcCharger.java | 115 ++--------------- .../dccharger/SungrowDcChargerImpl.java | 119 ++++++++++++++++++ .../io/openems/edge/sungrow/meter/Config.java | 5 +- .../edge/sungrow/meter/SungrowGridMeter.java | 108 ++-------------- .../sungrow/meter/SungrowGridMeterImpl.java | 114 +++++++++++++++++ ...gerTest.java => SungrowDcChargerTest.java} | 4 +- .../openems/edge/sungrow/meter/MyConfig.java | 5 + ...terTest.java => SungrowGridMeterTest.java} | 4 +- 8 files changed, 268 insertions(+), 206 deletions(-) create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcChargerImpl.java create mode 100644 io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeterImpl.java rename io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/{SungrowVirtualDcChargerTest.java => SungrowDcChargerTest.java} (86%) rename io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/{SungrowVirtualGridMeterTest.java => SungrowGridMeterTest.java} (86%) diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcCharger.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcCharger.java index a36b0280b24..a3ac0ec5f83 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcCharger.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcCharger.java @@ -1,118 +1,23 @@ package io.openems.edge.sungrow.dccharger; -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.event.Event; -import org.osgi.service.event.EventHandler; -import org.osgi.service.event.propertytypes.EventTopics; -import org.osgi.service.metatype.annotations.Designate; - -import io.openems.common.exceptions.OpenemsException; -import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.channel.Doc; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.event.EdgeEventConstants; -import io.openems.edge.ess.dccharger.api.EssDcCharger; -import io.openems.edge.sungrow.ess.EssSungrow; -import io.openems.edge.timedata.api.Timedata; -import io.openems.edge.timedata.api.TimedataProvider; -import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; - -@Designate(ocd = Config.class, factory = true) -@Component(// - name = "Ess.Sungrow.DcCharger", // - immediate = true, // - configurationPolicy = ConfigurationPolicy.REQUIRE // -) -@EventTopics({ // - EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // -}) -public class SungrowDcCharger extends AbstractOpenemsComponent - implements EssDcCharger, TimedataProvider, OpenemsComponent, EventHandler { - - protected Config config = null; - - @Reference - private ConfigurationAdmin cm; - - @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) - private EssSungrow ess; - @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) - private volatile Timedata timedata = null; +public interface SungrowDcCharger extends OpenemsComponent { - private final CalculateEnergyFromPower calculateActualEnergy = new CalculateEnergyFromPower(this, - EssDcCharger.ChannelId.ACTUAL_ENERGY); + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + ; - public SungrowDcCharger() { - super(// - OpenemsComponent.ChannelId.values(), // - EssDcCharger.ChannelId.values() // - ); - } - - @Activate - private void activate(ComponentContext context, Config config) throws OpenemsException { - this.config = config; - super.activate(context, config.id(), config.alias(), config.enabled()); + private final Doc doc; - if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "ess", config.ess_id())) { - return; + private ChannelId(Doc doc) { + this.doc = doc; } - this.mapChannelValues(); - } - - @Deactivate - protected void deactivate() { - super.deactivate(); - } - - private void mapChannelValues() { - this.ess.getTotalDcPowerChannel().onSetNextValue(value -> { - if (value.isDefined()) { - this._setActualPower(value.get()); - } - }); - this.ess.getMppt1VoltageChannel().onSetNextValue(value -> { - if (value.isDefined()) { - this._setVoltage(value.get()); - } - }); - this.ess.getMppt1CurrentChannel().onSetNextValue(value -> { - if (value.isDefined()) { - this._setCurrent(value.get()); - } - }); - } - - @Override - public void handleEvent(Event event) { - if (!this.isEnabled()) { - return; - } - switch (event.getTopic()) { - case EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE -> { - this.calculateActualEnergy.update(this.getActualPower().get()); - } + @Override + public Doc doc() { + return this.doc; } } - @Override - public String debugLog() { - return "L:" + this.getActualPower().asString(); - } - - @Override - public Timedata getTimedata() { - return this.timedata; - } - } \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcChargerImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcChargerImpl.java new file mode 100644 index 00000000000..b7ef46cc33d --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcChargerImpl.java @@ -0,0 +1,119 @@ +package io.openems.edge.sungrow.dccharger; + +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.event.Event; +import org.osgi.service.event.EventHandler; +import org.osgi.service.event.propertytypes.EventTopics; +import org.osgi.service.metatype.annotations.Designate; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.ess.dccharger.api.EssDcCharger; +import io.openems.edge.sungrow.ess.EssSungrow; +import io.openems.edge.timedata.api.Timedata; +import io.openems.edge.timedata.api.TimedataProvider; +import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "Ess.Sungrow.DcCharger", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +@EventTopics({ // + EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // +}) +public class SungrowDcChargerImpl extends AbstractOpenemsComponent + implements SungrowDcCharger, EssDcCharger, TimedataProvider, OpenemsComponent, EventHandler { + + protected Config config = null; + + @Reference + private ConfigurationAdmin cm; + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + private EssSungrow ess; + + @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) + private volatile Timedata timedata = null; + + private final CalculateEnergyFromPower calculateActualEnergy = new CalculateEnergyFromPower(this, + EssDcCharger.ChannelId.ACTUAL_ENERGY); + + public SungrowDcChargerImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + SungrowDcCharger.ChannelId.values(), // + EssDcCharger.ChannelId.values() // + ); + } + + @Activate + private void activate(ComponentContext context, Config config) throws OpenemsException { + this.config = config; + super.activate(context, config.id(), config.alias(), config.enabled()); + + if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "ess", config.ess_id())) { + return; + } + + this.mapChannelValues(); + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + private void mapChannelValues() { + this.ess.getTotalDcPowerChannel().onSetNextValue(value -> { + if (value.isDefined()) { + this._setActualPower(value.get()); + } + }); + this.ess.getMppt1VoltageChannel().onSetNextValue(value -> { + if (value.isDefined()) { + this._setVoltage(value.get()); + } + }); + this.ess.getMppt1CurrentChannel().onSetNextValue(value -> { + if (value.isDefined()) { + this._setCurrent(value.get()); + } + }); + } + + @Override + public void handleEvent(Event event) { + if (!this.isEnabled()) { + return; + } + switch (event.getTopic()) { + case EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE -> { + this.calculateActualEnergy.update(this.getActualPower().get()); + } + } + } + + @Override + public String debugLog() { + return "L:" + this.getActualPower().asString(); + } + + @Override + public Timedata getTimedata() { + return this.timedata; + } + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java index e80f17157a4..e9232f33e25 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java @@ -17,8 +17,11 @@ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; - @AttributeDefinition(name = "ESS ID", description = "ID of the Sungrow Ess Component.") + @AttributeDefinition(name = "ESS-ID", description = "ID of the Sungrow Ess Component.") String ess_id() default "ess0"; + + @AttributeDefinition(name = "ESS target filter", description = "This is auto-generated by 'ESS-ID'.") + String ess_target() default "(enabled=true)"; String webconsole_configurationFactory_nameHint() default "Ess Sungrow Grid Meter [{id}]"; diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeter.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeter.java index 7d489553f02..beaa68bcecd 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeter.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeter.java @@ -1,107 +1,23 @@ package io.openems.edge.sungrow.meter; -import static io.openems.edge.meter.api.ElectricityMeter.calculatePhasesFromActivePower; -import static io.openems.edge.meter.api.ElectricityMeter.calculateSumCurrentFromPhases; -import static io.openems.edge.meter.api.ElectricityMeter.calculateAverageVoltageFromPhases; -import static io.openems.edge.meter.api.ElectricityMeter.calculateCurrentsFromActivePowerAndVoltage; - -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.OpenemsError.OpenemsNamedException; -import io.openems.common.exceptions.OpenemsException; -import io.openems.common.types.MeterType; -import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.channel.Doc; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.meter.api.ElectricityMeter; -import io.openems.edge.sungrow.ess.EssSungrow; - -@Designate(ocd = Config.class, factory = true) -@Component(// - name = "Ess.Sungrow.Gridmeter", // - immediate = true, // - configurationPolicy = ConfigurationPolicy.REQUIRE // -) -public class SungrowGridMeter extends AbstractOpenemsComponent implements ElectricityMeter, OpenemsComponent { - - protected Config config = null; - @Reference - private ConfigurationAdmin cm; +public interface SungrowGridMeter extends OpenemsComponent { - @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) - private EssSungrow ess; + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + ; - public SungrowGridMeter() { - super(// - OpenemsComponent.ChannelId.values(), // - ElectricityMeter.ChannelId.values() // - ); - - // Provide phase powers - calculatePhasesFromActivePower(this); - - // Provide missing voltages - calculateAverageVoltageFromPhases(this); - - // Provide Currents from Power and Voltage - calculateSumCurrentFromPhases(this); - calculateCurrentsFromActivePowerAndVoltage(this); - } + private final Doc doc; - @Activate - private void activate(ComponentContext context, Config config) throws OpenemsException, OpenemsNamedException { - this.config = config; - super.activate(context, config.id(), config.alias(), config.enabled()); - this.mapChannelValues(); - } - - @Deactivate - protected void deactivate() { - super.deactivate(); - } - - private void mapChannelValues() throws OpenemsException { - this.ess.getExportPowerChannel().onSetNextValue(newValue -> { - this._setActivePower(newValue.isDefined() ? -newValue.get() : null); - }); - this.ess.getTotalExportEnergyChannel().onSetNextValue(newValue -> { - this._setActiveConsumptionEnergy(newValue.get()); - }); - this.ess.getTotalImportEnergyChannel().onSetNextValue(newValue -> { - this._setActiveProductionEnergy(newValue.get()); - }); - this.ess.getGridFrequencyChannel().onSetNextValue(newValue -> { - this._setFrequency(newValue.get()); - }); - this.ess.getVoltageL1Channel().onSetNextValue(newValue -> { - this._setVoltageL1(newValue.get()); - }); - this.ess.getVoltageL2Channel().onSetNextValue(newValue -> { - this._setVoltageL2(newValue.get()); - }); - this.ess.getVoltageL3Channel().onSetNextValue(newValue -> { - this._setVoltageL3(newValue.get()); - }); - } - - @Override - public MeterType getMeterType() { - return MeterType.GRID; - } + private ChannelId(Doc doc) { + this.doc = doc; + } - @Override - public String debugLog() { - return "L:" + this.getActivePower().asString(); + @Override + public Doc doc() { + return this.doc; + } } } \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeterImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeterImpl.java new file mode 100644 index 00000000000..b4512b76169 --- /dev/null +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeterImpl.java @@ -0,0 +1,114 @@ +package io.openems.edge.sungrow.meter; + +import static io.openems.edge.meter.api.ElectricityMeter.calculatePhasesFromActivePower; +import static io.openems.edge.meter.api.ElectricityMeter.calculateSumCurrentFromPhases; +import static io.openems.edge.meter.api.ElectricityMeter.calculateAverageVoltageFromPhases; +import static io.openems.edge.meter.api.ElectricityMeter.calculateCurrentsFromActivePowerAndVoltage; + +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.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.MeterType; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.meter.api.ElectricityMeter; +import io.openems.edge.sungrow.ess.EssSungrow; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "Ess.Sungrow.Gridmeter", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class SungrowGridMeterImpl extends AbstractOpenemsComponent + implements SungrowGridMeter, ElectricityMeter, OpenemsComponent { + + protected Config config = null; + + @Reference + private ConfigurationAdmin cm; + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + private EssSungrow ess; + + public SungrowGridMeterImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + SungrowGridMeter.ChannelId.values(), // + ElectricityMeter.ChannelId.values() // + ); + + // Provide phase powers + calculatePhasesFromActivePower(this); + + // Provide missing voltages + calculateAverageVoltageFromPhases(this); + + // Provide Currents from Power and Voltage + calculateSumCurrentFromPhases(this); + calculateCurrentsFromActivePowerAndVoltage(this); + } + + @Activate + private void activate(ComponentContext context, Config config) throws OpenemsException, OpenemsNamedException { + this.config = config; + super.activate(context, config.id(), config.alias(), config.enabled()); + + if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "ess", config.ess_id())) { + return; + } + + this.mapChannelValues(); + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + private void mapChannelValues() throws OpenemsException { + this.ess.getExportPowerChannel().onSetNextValue(newValue -> { + this._setActivePower(newValue.isDefined() ? -newValue.get() : null); + }); + this.ess.getTotalExportEnergyChannel().onSetNextValue(newValue -> { + this._setActiveConsumptionEnergy(newValue.get()); + }); + this.ess.getTotalImportEnergyChannel().onSetNextValue(newValue -> { + this._setActiveProductionEnergy(newValue.get()); + }); + this.ess.getGridFrequencyChannel().onSetNextValue(newValue -> { + this._setFrequency(newValue.get()); + }); + this.ess.getVoltageL1Channel().onSetNextValue(newValue -> { + this._setVoltageL1(newValue.get()); + }); + this.ess.getVoltageL2Channel().onSetNextValue(newValue -> { + this._setVoltageL2(newValue.get()); + }); + this.ess.getVoltageL3Channel().onSetNextValue(newValue -> { + this._setVoltageL3(newValue.get()); + }); + } + + @Override + public MeterType getMeterType() { + return MeterType.GRID; + } + + @Override + public String debugLog() { + return "L:" + this.getActivePower().asString(); + } + +} \ No newline at end of file diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/SungrowVirtualDcChargerTest.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/SungrowDcChargerTest.java similarity index 86% rename from io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/SungrowVirtualDcChargerTest.java rename to io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/SungrowDcChargerTest.java index 160d7a5319d..84757aa0872 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/SungrowVirtualDcChargerTest.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/SungrowDcChargerTest.java @@ -6,14 +6,14 @@ import io.openems.edge.common.test.ComponentTest; import io.openems.edge.sungrow.ess.EssSungrowImpl; -public class SungrowVirtualDcChargerTest { +public class SungrowDcChargerTest { private static final String CHARGER_ID = "charger0"; private static final String ESS_ID = "ess0"; @Test public void test() throws Exception { - new ComponentTest(new SungrowDcCharger()) // + new ComponentTest(new SungrowDcChargerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", new EssSungrowImpl()) // .activate(MyConfig.create() // diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java index 8a3f7b75552..0cc45f4ce80 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java @@ -48,5 +48,10 @@ private MyConfig(Builder builder) { public String ess_id() { return this.builder.essId; } + + @Override + public String ess_target() { + return ConfigUtils.generateReferenceTargetFilter(this.id(), this.ess_id()); + } } diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/SungrowVirtualGridMeterTest.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/SungrowGridMeterTest.java similarity index 86% rename from io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/SungrowVirtualGridMeterTest.java rename to io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/SungrowGridMeterTest.java index 1eb2b0c5311..9108b4d5f9a 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/SungrowVirtualGridMeterTest.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/SungrowGridMeterTest.java @@ -6,14 +6,14 @@ import io.openems.edge.common.test.ComponentTest; import io.openems.edge.sungrow.ess.EssSungrowImpl; -public class SungrowVirtualGridMeterTest { +public class SungrowGridMeterTest { private static final String METER_ID = "meter0"; private static final String ESS_ID = "ess0"; @Test public void test() throws Exception { - new ComponentTest(new SungrowGridMeter()) // + new ComponentTest(new SungrowGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", new EssSungrowImpl()) // .activate(MyConfig.create() // From 8a987194000d0798aa31452d44bca1d232a75772 Mon Sep 17 00:00:00 2001 From: Thomas Sicking Date: Mon, 31 Aug 2026 12:47:07 +0200 Subject: [PATCH 05/15] Added Sungrow to EdgeApp --- io.openems.edge.application/EdgeApp.bndrun | 2 ++ 1 file changed, 2 insertions(+) diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index 0bd8146d2fd..972cf4b6838 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -209,6 +209,7 @@ bnd.identity;id='io.openems.edge.simulator',\ bnd.identity;id='io.openems.edge.sma',\ bnd.identity;id='io.openems.edge.solaredge',\ + bnd.identity;id='io.openems.edge.sungrow',\ bnd.identity;id='io.openems.edge.system.fenecon',\ bnd.identity;id='io.openems.edge.tesla.powerwall2',\ bnd.identity;id='io.openems.edge.timedata.influxdb',\ @@ -425,6 +426,7 @@ io.openems.edge.simulator;version=snapshot,\ io.openems.edge.sma;version=snapshot,\ io.openems.edge.solaredge;version=snapshot,\ + io.openems.edge.sungrow;version=snapshot,\ io.openems.edge.system.fenecon;version=snapshot,\ io.openems.edge.tesla.powerwall2;version=snapshot,\ io.openems.edge.thermometer.api;version=snapshot,\ From 3d8c8d195504438dce989c2620aac4f78e52b7ec Mon Sep 17 00:00:00 2001 From: Thomas Sicking Date: Mon, 31 Aug 2026 15:37:43 +0200 Subject: [PATCH 06/15] Prepare commit --- .../src/io/openems/edge/ess/api/SymmetricEss.java | 5 ++--- io.openems.edge.sungrow/bnd.bnd | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java index 12cba9a6cce..e664973d440 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java @@ -321,9 +321,8 @@ public default GridMode getGridMode() { return this.getGridModeChannel().value().asEnum(); } - /* - * Internal method to set the 'nextValue' on {@link ChannelId#GRID_MODE} - * Channel. + /** + * Internal method to set the 'nextValue' on {@link ChannelId#GRID_MODE} Channel. * * @param value the next value */ diff --git a/io.openems.edge.sungrow/bnd.bnd b/io.openems.edge.sungrow/bnd.bnd index 427415df54f..d1ad0ca26b6 100644 --- a/io.openems.edge.sungrow/bnd.bnd +++ b/io.openems.edge.sungrow/bnd.bnd @@ -5,7 +5,6 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ - io.openems.j2mod,\ io.openems.common,\ io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ @@ -14,6 +13,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.pvinverter.api,\ io.openems.edge.timedata.api,\ io.openems.j2mod,\ + io.openems.j2mod,\ -testpath: \ ${testpath} From 13fb2c6b7841f25eee58e349747d1940e37b317b Mon Sep 17 00:00:00 2001 From: Thomas Sicking Date: Mon, 31 Aug 2026 16:49:34 +0200 Subject: [PATCH 07/15] Removed duplicated entry --- io.openems.edge.sungrow/bnd.bnd | 1 - 1 file changed, 1 deletion(-) diff --git a/io.openems.edge.sungrow/bnd.bnd b/io.openems.edge.sungrow/bnd.bnd index d1ad0ca26b6..cc9ebc41cd5 100644 --- a/io.openems.edge.sungrow/bnd.bnd +++ b/io.openems.edge.sungrow/bnd.bnd @@ -13,7 +13,6 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.pvinverter.api,\ io.openems.edge.timedata.api,\ io.openems.j2mod,\ - io.openems.j2mod,\ -testpath: \ ${testpath} From b76f1547cae48fc23541eb4473b309265a104376 Mon Sep 17 00:00:00 2001 From: Thomas Sicking Date: Mon, 31 Aug 2026 17:11:47 +0200 Subject: [PATCH 08/15] Implemented getSurplusPower() --- .../io/openems/edge/sungrow/ess/EssSungrowImpl.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java index c321a26c2ac..75191281597 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java @@ -335,7 +335,16 @@ protected ModbusProtocol defineModbusProtocol() { @Override public Integer getSurplusPower() { - return null; + var productionPower = this.getTotalDcPower().orElse(0); + if (productionPower < 100) { + return null; + } + // "+" because the allowed charge power is negative + var surplusPower = productionPower + this.getAllowedChargePower().orElse(0); + if (surplusPower < 0) { + return null; + } + return surplusPower; } @Override From 0ed05dc4da354a1e022eb368dc1c383aaeaab5b1 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Wed, 2 Sep 2026 15:32:58 +0200 Subject: [PATCH 09/15] Apply tools/prepare-commit.sh --- io.openems.edge.sungrow/test/.gitignore | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 io.openems.edge.sungrow/test/.gitignore diff --git a/io.openems.edge.sungrow/test/.gitignore b/io.openems.edge.sungrow/test/.gitignore new file mode 100644 index 00000000000..e69de29bb2d From 836613c11e82af39daee57d13205c022d2ce5527 Mon Sep 17 00:00:00 2001 From: Thomas Sicking Date: Thu, 3 Sep 2026 14:25:30 +0200 Subject: [PATCH 10/15] Removed default settings --- .../openems/edge/sungrow/ess/EssSungrow.java | 133 ++++-------------- .../sungrow/pvinverter/PvInverterSungrow.java | 85 +++-------- 2 files changed, 46 insertions(+), 172 deletions(-) diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrow.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrow.java index 4da6a05fbaf..2986b6cbe11 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrow.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrow.java @@ -21,208 +21,127 @@ public interface EssSungrow extends OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { SERIAL_NUMBER(Doc.of(OpenemsType.STRING) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), // DAILY_OUTPUT_ENERGY(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), // + .unit(Unit.WATT_HOURS)), // INSIDE_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.DEZIDEGREE_CELSIUS) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), // + .unit(Unit.DEZIDEGREE_CELSIUS)), // MPPT1_VOLTAGE(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIVOLT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), + .unit(Unit.MILLIVOLT)), MPPT1_CURRENT(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), // + .unit(Unit.MILLIAMPERE)), // MPPT2_VOLTAGE(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIVOLT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), // + .unit(Unit.MILLIVOLT)), // MPPT2_CURRENT(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), // + .unit(Unit.MILLIAMPERE)), // TOTAL_DC_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // VOLTAGE_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIVOLT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), // + .unit(Unit.MILLIVOLT)), // VOLTAGE_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIVOLT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), + .unit(Unit.MILLIVOLT)), VOLTAGE_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIVOLT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), // + .unit(Unit.MILLIVOLT)), // POWER_FACTOR(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.THOUSANDTH) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), // + .unit(Unit.THOUSANDTH)), // GRID_FREQUENCY(Doc.of(OpenemsType.INTEGER) // .unit(Unit.MILLIHERTZ) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.MEDIUM)), // EXPORT_LIMIT_MIN(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), // EXPORT_LIMIT_MAX(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), // BDC_RATED_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), // CHARGE_MAX_CURRENT(Doc.of(OpenemsType.INTEGER) // .unit(Unit.AMPERE) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // DISCHARGE_MAX_CURRENT(Doc.of(OpenemsType.INTEGER) // .unit(Unit.AMPERE) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // SYSTEM_STATE(Doc.of(SystemState.values()) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), // POWER_GENERATED_FROM_PV(Doc.of(OpenemsType.BOOLEAN) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), // BATTERY_CHARGING(Doc.of(OpenemsType.BOOLEAN) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), // BATTERY_DISCHARGING(Doc.of(OpenemsType.BOOLEAN) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), // POSITIVE_LOAD_POWER(Doc.of(OpenemsType.BOOLEAN) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), // FEED_IN_POWER(Doc.of(OpenemsType.BOOLEAN) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), // IMPORT_POWER_FROM_GRID(Doc.of(OpenemsType.BOOLEAN) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), // NEGATIVE_LOAD_POWER(Doc.of(OpenemsType.BOOLEAN) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), // DAILY_PV_GENERATION(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), // + .unit(Unit.WATT_HOURS)), // TOTAL_PV_GENERATION(Doc.of(OpenemsType.LONG) // - .unit(Unit.WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), // + .unit(Unit.WATT_HOURS)), // DAILY_EXPORT_POWER_FROM_PV(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), // + .unit(Unit.WATT)), // TOTAL_EXPORT_ENERGY_FROM_PV(Doc.of(OpenemsType.LONG) // - .unit(Unit.WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), + .unit(Unit.WATT_HOURS)), LOAD_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.MEDIUM)), // EXPORT_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.MEDIUM)), DAILY_BATTERY_CHARGE_ENERGY_FROM_PV(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT_HOURS) - // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), + .unit(Unit.WATT_HOURS)), TOTAL_BATTERY_CHARGE_ENERGY_FROM_PV(Doc.of(OpenemsType.LONG) // - .unit(Unit.WATT_HOURS) - // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), + .unit(Unit.WATT_HOURS)), CO2_REDUCTION(Doc.of(OpenemsType.INTEGER) // .unit(Unit.NONE) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), DAILY_DIRECT_ENERGY_CONSUMPTION(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT_HOURS) - // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), + .unit(Unit.WATT_HOURS)), TOTAL_DIRECT_ENERGY_CONSUMPTION(Doc.of(OpenemsType.LONG) // - .unit(Unit.WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), + .unit(Unit.WATT_HOURS)), BATTERY_VOLTAGE(Doc.of(OpenemsType.INTEGER) // .unit(Unit.VOLT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), BATTERY_CURRENT(Doc.of(OpenemsType.INTEGER) // .unit(Unit.AMPERE) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), BATTERY_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), SOH(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.PERCENT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), + .unit(Unit.PERCENT)), BATTERY_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // .unit(Unit.DEZIDEGREE_CELSIUS) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.MEDIUM)), DAILY_BATTERY_DISCHARGE_ENERGY(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT_HOURS) - // - .accessMode(AccessMode.READ_ONLY) // + .unit(Unit.WATT_HOURS) // .persistencePriority(PersistencePriority.VERY_LOW)), SELF_CONSUMPTION_OF_TODAY(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.PERCENT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), + .unit(Unit.PERCENT)), CURRENT_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), + .unit(Unit.MILLIAMPERE)), CURRENT_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), + .unit(Unit.MILLIAMPERE)), CURRENT_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), + .unit(Unit.MILLIAMPERE)), DAILY_IMPORT_ENERGY(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), TOTAL_IMPORT_ENERGY(Doc.of(OpenemsType.LONG) // - .unit(Unit.WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), + .unit(Unit.WATT_HOURS)), DAILY_CHARGE_ENERGY(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), DAILY_EXPORT_ENERGY(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.VERY_LOW)), TOTAL_EXPORT_ENERGY(Doc.of(OpenemsType.LONG) // - .unit(Unit.WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW)), + .unit(Unit.WATT_HOURS)), EMS_MODE(Doc.of(EmsMode.values()) // .accessMode(AccessMode.READ_WRITE) // diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrow.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrow.java index 756d9a96a2c..b3ed66ea1b0 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrow.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrow.java @@ -10,111 +10,66 @@ public interface PvInverterSungrow extends OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - SERIAL_NUMBER(Doc.of(OpenemsType.STRING) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.VERY_LOW) // - ), // + SERIAL_NUMBER(Doc.of(OpenemsType.STRING)), // NOMINAL_OUTPUT_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.VERY_LOW) // - ), // + .persistencePriority(PersistencePriority.VERY_LOW)), // DAILY_ENERGY(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.MEDIUM) // - ), TOTAL_RUNNING_TIME(Doc.of(OpenemsType.INTEGER) // + .persistencePriority(PersistencePriority.MEDIUM)), TOTAL_RUNNING_TIME(Doc.of(OpenemsType.INTEGER) // .unit(Unit.HOUR) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.VERY_LOW) // - ), + .persistencePriority(PersistencePriority.VERY_LOW)), INTERNAL_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // .unit(Unit.DEZIDEGREE_CELSIUS) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.VERY_LOW) // - ), + .persistencePriority(PersistencePriority.VERY_LOW)), APPARENT_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.VOLT_AMPERE) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.MEDIUM) // - ), // + .persistencePriority(PersistencePriority.MEDIUM)), // DC_VOLTAGE_1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW) // - ), // + .unit(Unit.VOLT)), // DC_CURRENT_1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW) // - ), // + .unit(Unit.MILLIAMPERE)), // DC_VOLTAGE_2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW) // - ), // + .unit(Unit.VOLT)), // DC_CURRENT_2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW) // - ), // + .unit(Unit.MILLIAMPERE)), // DC_VOLTAGE_3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW) // - ), // + .unit(Unit.VOLT)), // DC_CURRENT_3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW) // - ), // + .unit(Unit.MILLIAMPERE)), // DC_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.HIGH) // - ), + .persistencePriority(PersistencePriority.HIGH)), POWER_FACTOR(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.THOUSANDTH) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW) // - ), + .unit(Unit.THOUSANDTH)), - WORK_STATE(Doc.of(WorkState.values()) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW) // - ), + WORK_STATE(Doc.of(WorkState.values())), NEGATIVE_VOLTAGE_TO_THE_GROUND(Doc.of(OpenemsType.INTEGER) // .unit(Unit.VOLT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.VERY_LOW) // - ), + .persistencePriority(PersistencePriority.VERY_LOW)), BUS_VOLTAGE(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.LOW) // - ), + .unit(Unit.VOLT)), POWER_LIMITATION_SWITCH(Doc.of(OpenemsType.BOOLEAN) // .unit(Unit.ON_OFF) // - .accessMode(AccessMode.READ_WRITE) // - ), POWER_LIMITATION_SETTING(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), + POWER_LIMITATION_SETTING(Doc.of(OpenemsType.INTEGER) // .unit(Unit.PERCENT) // - .accessMode(AccessMode.READ_WRITE) // - ); + .accessMode(AccessMode.READ_WRITE)); private final Doc doc; From 7aa909832091079dc4583219ac8d0c5b46c58990 Mon Sep 17 00:00:00 2001 From: Thomas Sicking Date: Thu, 3 Sep 2026 17:56:10 +0200 Subject: [PATCH 11/15] Use ReferenceConfigurationPlugin --- .../src/io/openems/edge/sungrow/dccharger/Config.java | 3 --- .../openems/edge/sungrow/dccharger/SungrowDcChargerImpl.java | 2 ++ .../src/io/openems/edge/sungrow/ess/Config.java | 3 --- .../src/io/openems/edge/sungrow/ess/EssSungrowImpl.java | 2 ++ .../src/io/openems/edge/sungrow/meter/Config.java | 3 --- .../io/openems/edge/sungrow/meter/SungrowGridMeterImpl.java | 2 ++ .../src/io/openems/edge/sungrow/pvinverter/Config.java | 3 --- .../edge/sungrow/pvinverter/PvInverterSungrowImpl.java | 2 ++ .../test/io/openems/edge/sungrow/dccharger/MyConfig.java | 5 ----- .../test/io/openems/edge/sungrow/ess/MyConfig.java | 5 ----- .../test/io/openems/edge/sungrow/meter/MyConfig.java | 5 ----- .../test/io/openems/edge/sungrow/pvinverter/MyConfig.java | 5 ----- 12 files changed, 8 insertions(+), 32 deletions(-) diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/Config.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/Config.java index dc45e71e099..8ec9ae2c4b4 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/Config.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/Config.java @@ -19,9 +19,6 @@ @AttributeDefinition(name = "ESS-ID", description = "ID of the Sungrow Ess Component.") String ess_id() default "ess0"; - - @AttributeDefinition(name = "ESS target filter", description = "This is auto-generated by 'ESS-ID'.") - String ess_target() default "(enabled=true)"; String webconsole_configurationFactory_nameHint() default "ESS Sungrow DC charger [{id}]"; diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcChargerImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcChargerImpl.java index b7ef46cc33d..f3d33c0bfef 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcChargerImpl.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcChargerImpl.java @@ -16,6 +16,7 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.referencetarget.GenerateTargetsFromReferences; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; @@ -34,6 +35,7 @@ @EventTopics({ // EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // }) +@GenerateTargetsFromReferences("ess") public class SungrowDcChargerImpl extends AbstractOpenemsComponent implements SungrowDcCharger, EssDcCharger, TimedataProvider, OpenemsComponent, EventHandler { diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/Config.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/Config.java index 687527d7342..acf1afff35c 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/Config.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/Config.java @@ -28,9 +28,6 @@ @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)"; - String webconsole_configurationFactory_nameHint() default "ESS Sungrow [{id}]"; } \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java index 75191281597..a34ecf55fc2 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java @@ -20,6 +20,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.referencetarget.GenerateTargetsFromReferences; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -56,6 +57,7 @@ immediate = true, // configurationPolicy = ConfigurationPolicy.REQUIRE // ) +@GenerateTargetsFromReferences("Modbus") public class EssSungrowImpl extends AbstractOpenemsModbusComponent implements EssSungrow, SymmetricEss, ManagedSymmetricEss, HybridEss, ModbusComponent, TimedataProvider, OpenemsComponent { diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java index e9232f33e25..6eba0f57305 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java @@ -19,9 +19,6 @@ @AttributeDefinition(name = "ESS-ID", description = "ID of the Sungrow Ess Component.") String ess_id() default "ess0"; - - @AttributeDefinition(name = "ESS target filter", description = "This is auto-generated by 'ESS-ID'.") - String ess_target() default "(enabled=true)"; String webconsole_configurationFactory_nameHint() default "Ess Sungrow Grid Meter [{id}]"; diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeterImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeterImpl.java index b4512b76169..111aeaa24f9 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeterImpl.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeterImpl.java @@ -19,6 +19,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.referencetarget.GenerateTargetsFromReferences; import io.openems.common.types.MeterType; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; @@ -31,6 +32,7 @@ immediate = true, // configurationPolicy = ConfigurationPolicy.REQUIRE // ) +@GenerateTargetsFromReferences("ess") public class SungrowGridMeterImpl extends AbstractOpenemsComponent implements SungrowGridMeter, ElectricityMeter, OpenemsComponent { diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/Config.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/Config.java index fb1986ff953..3a35c976095 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/Config.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/Config.java @@ -27,9 +27,6 @@ @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 = "Always Production") MeterType type() default MeterType.PRODUCTION; diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java index 0711bd60936..f75fbdb8e54 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java @@ -21,6 +21,7 @@ import org.osgi.service.metatype.annotations.Designate; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.referencetarget.GenerateTargetsFromReferences; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; @@ -53,6 +54,7 @@ @EventTopics({ // TOPIC_CYCLE_AFTER_PROCESS_IMAGE // }) +@GenerateTargetsFromReferences({"Modbus"}) public class PvInverterSungrowImpl extends AbstractOpenemsModbusComponent implements ManagedSymmetricPvInverter, ElectricityMeter, PvInverterSungrow, ModbusComponent, TimedataProvider, OpenemsComponent, EventHandler { diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/MyConfig.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/MyConfig.java index da51699be61..ab644345e04 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/MyConfig.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/MyConfig.java @@ -49,9 +49,4 @@ public String ess_id() { return this.builder.essId; } - @Override - public String ess_target() { - return ConfigUtils.generateReferenceTargetFilter(this.id(), this.ess_id()); - } - } \ No newline at end of file diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/MyConfig.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/MyConfig.java index f21f2f2fb85..64d96389eec 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/MyConfig.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/MyConfig.java @@ -62,11 +62,6 @@ 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; diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java index 0cc45f4ce80..15b6e116eb3 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java @@ -49,9 +49,4 @@ public String ess_id() { return this.builder.essId; } - @Override - public String ess_target() { - return ConfigUtils.generateReferenceTargetFilter(this.id(), this.ess_id()); - } - } diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/MyConfig.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/MyConfig.java index 7a92af0854a..dfb6a860ee0 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/MyConfig.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/MyConfig.java @@ -62,11 +62,6 @@ 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; From 18fa9894badd72dea3ff7811b793ed89cda9e794 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Sat, 19 Sep 2026 20:29:43 +0200 Subject: [PATCH 12/15] Cleanup --- .../backend/metadata/odoo/MetadataOdoo.java | 24 ------------------- .../common/test/AbstractComponentTest.java | 1 - .../edge/ess/api/ManagedSymmetricEss.java | 22 +---------------- .../io/openems/edge/ess/api/SymmetricEss.java | 9 ------- .../edge/sungrow/ess/EssSungrowImpl.java | 7 +++--- io.openems.wrapper/auth0-auth0.bnd | 2 ++ 6 files changed, 7 insertions(+), 58 deletions(-) diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/MetadataOdoo.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/MetadataOdoo.java index eba73ef7085..5ef10a9859f 100644 --- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/MetadataOdoo.java +++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/MetadataOdoo.java @@ -70,7 +70,6 @@ import io.openems.backend.metadata.odoo.odoo.OdooHandler; import io.openems.backend.metadata.odoo.odoo.OdooUserRole; import io.openems.backend.metadata.odoo.odoo.OdooUtils; -import io.openems.backend.metadata.odoo.odoo.http.OdooDeviceData; import io.openems.backend.metadata.odoo.postgres.PostgresHandler; import io.openems.backend.metrics.prometheus.DebugExecutor; import io.openems.common.channel.Level; @@ -78,7 +77,6 @@ import io.openems.common.event.EventReader; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.exceptions.OpenemsRuntimeException; import io.openems.common.jsonrpc.request.GetEdgesRequest.PaginationOptions; import io.openems.common.jsonrpc.response.GetEdgesResponse.EdgeMetadata; import io.openems.common.oem.OpenemsBackendOem; @@ -752,28 +750,6 @@ public CompletableFuture> getPageDevice(// }); } - private EdgeMetadata deviceDataToEdgeMetadata(User user, OdooDeviceData deviceData) { - - // TODO remove cached edge - final var cachedEdge = this.getEdge(deviceData.name()).orElse(null); - if (cachedEdge == null) { - throw new OpenemsRuntimeException("Unable to find edge with id [" + deviceData.name() + "]"); - } - - return new EdgeMetadata(// - deviceData.name(), // - deviceData.comment(), // - deviceData.producttype(), // - cachedEdge.getVersion(), // - deviceData.role(), // - cachedEdge.isOnline(), // - deviceData.lastmessage(), // - deviceData.firstSetupProtocol(), // - deviceData.sumState(), // - deviceData.settings() // - ); - } - @Override public Role getUserRole(User user, String edgeId) { final var userRoles = this.userRoles.computeIfAbsent(user.getId(), (userId) -> new ConcurrentHashMap<>()); diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java b/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java index 85a37b8db2b..cbf2b382cc1 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java +++ b/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java @@ -691,7 +691,6 @@ protected void validateOutputsReadValue(AbstractComponentTest act) throws } } - @SuppressWarnings("unchecked") private Object resolveEnum(Channel channel, Object got) { if (channel.channelDoc() instanceof EnumDoc enumDoc) { var intGot = TypeUtils.getAsType(OpenemsType.INTEGER, got); diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java index 49a346c268f..b0e600eb9c3 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java @@ -284,27 +284,7 @@ public default IntegerReadChannel getAllowedChargePowerChannel() { public default Value getAllowedChargePower() { return this.getAllowedChargePowerChannel().value(); } - - /** - * Internal method to set the 'nextValue' on - * {@link ChannelId#ALLOWED_CHARGE_POWER} Channel. - * - * @param value the next value - */ - public default void _setAllowedChargePower(int value) { - this.getAllowedChargePowerChannel().setNextValue(value); - } - - /** - * Internal method to set the 'nextValue' on - * {@link ChannelId#ALLOWED_CHARGE_POWER} Channel. - * - * @param value the next value - */ - public default void _setAllowedChargePower(Integer value) { - this.getAllowedChargePowerChannel().setNextValue(value); - } - + /** * Gets the Channel for {@link ChannelId#ALLOWED_DISCHARGE_POWER}. * diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java index e664973d440..418c4bff280 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java @@ -320,15 +320,6 @@ public default Channel getGridModeChannel() { public default GridMode getGridMode() { return this.getGridModeChannel().value().asEnum(); } - - /** - * Internal method to set the 'nextValue' on {@link ChannelId#GRID_MODE} Channel. - * - * @param value the next value - */ - public default void _setGridMode(GridMode value) { - this.getGridModeChannel().setNextValue(value); - } /** * Returns true if {@link GridMode} is {@link GridMode#ON_GRID} or diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java index a34ecf55fc2..9e695545c1e 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java @@ -3,6 +3,7 @@ import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_1; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1; +import static io.openems.edge.common.channel.ChannelUtils.setValue; import java.util.function.Consumer; @@ -113,7 +114,7 @@ private void activate(ComponentContext context, Config config) throws OpenemsExc } // NOTE: This should normally be read from the device - this._setGridMode(GridMode.ON_GRID); + setValue(this, SymmetricEss.ChannelId.GRID_MODE, GridMode.ON_GRID); this.installPowerListeners(); } @@ -151,7 +152,7 @@ private void installDcDischargePowerListener() { */ private void installAllowedChargePowerListener() { final Consumer> allowedCharge = ignore -> { - this._setAllowedChargePower(// + setValue(this, ManagedSymmetricEss.ChannelId.ALLOWED_CHARGE_POWER, // // set to 0 if either value is undefined -this.getBatteryVoltage().orElse(0) * this.getChargeMaxCurrent().orElse(0)); }; @@ -165,7 +166,7 @@ private void installAllowedChargePowerListener() { */ private void installAllowedDischargePowerListener() { final Consumer> allowedDischarge = ignore -> { - this._setAllowedDischargePower(// + setValue(this, ManagedSymmetricEss.ChannelId.ALLOWED_DISCHARGE_POWER, // // set to 0 if either value is undefined this.getBatteryVoltage().orElse(0) * this.getDischargeMaxCurrent().orElse(0)); }; diff --git a/io.openems.wrapper/auth0-auth0.bnd b/io.openems.wrapper/auth0-auth0.bnd index 642282d4fb8..2139bcad93d 100644 --- a/io.openems.wrapper/auth0-auth0.bnd +++ b/io.openems.wrapper/auth0-auth0.bnd @@ -38,6 +38,8 @@ Export-Package: \ com.auth0.client.mgmt.emails.types,\ com.auth0.client.mgmt.eventstreams,\ com.auth0.client.mgmt.eventstreams.types,\ + com.auth0.client.mgmt.experimentation,\ + com.auth0.client.mgmt.experimentation.types,\ com.auth0.client.mgmt.filter,\ com.auth0.client.mgmt.flows,\ com.auth0.client.mgmt.flows.types,\ From 705355938a58d604f2b70bd4fc7e12bb69deabcf Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Sat, 19 Sep 2026 20:51:40 +0200 Subject: [PATCH 13/15] Review --- .../dccharger/SungrowDcChargerImpl.java | 27 ++- .../edge/sungrow/ess/ApplyPowerHandler.java | 3 +- .../openems/edge/sungrow/ess/EssSungrow.java | 220 +++++++++--------- .../edge/sungrow/ess/EssSungrowImpl.java | 21 +- .../edge/sungrow/ess/enums/ControlMode.java | 15 +- .../io/openems/edge/sungrow/meter/Config.java | 4 +- .../sungrow/meter/SungrowGridMeterImpl.java | 23 +- .../edge/sungrow/pvinverter/Config.java | 39 ++-- .../sungrow/pvinverter/PvInverterSungrow.java | 107 ++++----- .../pvinverter/PvInverterSungrowImpl.java | 59 +++-- .../edge/sungrow/pvinverter/WorkState.java | 74 +++--- .../edge/sungrow/dccharger/MyConfig.java | 1 - .../dccharger/SungrowDcChargerTest.java | 16 +- .../edge/sungrow/ess/EssSungrowEssTest.java | 18 +- .../io/openems/edge/sungrow/ess/MyConfig.java | 1 - .../openems/edge/sungrow/meter/MyConfig.java | 3 +- .../sungrow/meter/SungrowGridMeterTest.java | 9 +- .../edge/sungrow/pvinverter/MyConfig.java | 1 - .../pvinverter/PvInverterSungrowImplTest.java | 17 +- 19 files changed, 317 insertions(+), 341 deletions(-) diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcChargerImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcChargerImpl.java index f3d33c0bfef..c3b8f809d25 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcChargerImpl.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/dccharger/SungrowDcChargerImpl.java @@ -1,5 +1,12 @@ package io.openems.edge.sungrow.dccharger; +import static io.openems.edge.common.event.EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE; +import static org.osgi.service.component.annotations.ReferenceCardinality.MANDATORY; +import static org.osgi.service.component.annotations.ReferenceCardinality.OPTIONAL; +import static org.osgi.service.component.annotations.ReferencePolicy.DYNAMIC; +import static org.osgi.service.component.annotations.ReferencePolicy.STATIC; +import static org.osgi.service.component.annotations.ReferencePolicyOption.GREEDY; + import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; @@ -7,9 +14,6 @@ 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.event.Event; import org.osgi.service.event.EventHandler; import org.osgi.service.event.propertytypes.EventTopics; @@ -19,7 +23,6 @@ import io.openems.common.referencetarget.GenerateTargetsFromReferences; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.ess.dccharger.api.EssDcCharger; import io.openems.edge.sungrow.ess.EssSungrow; import io.openems.edge.timedata.api.Timedata; @@ -33,8 +36,7 @@ configurationPolicy = ConfigurationPolicy.REQUIRE // ) @EventTopics({ // - EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // -}) + TOPIC_CYCLE_AFTER_PROCESS_IMAGE }) @GenerateTargetsFromReferences("ess") public class SungrowDcChargerImpl extends AbstractOpenemsComponent implements SungrowDcCharger, EssDcCharger, TimedataProvider, OpenemsComponent, EventHandler { @@ -44,10 +46,12 @@ public class SungrowDcChargerImpl extends AbstractOpenemsComponent @Reference private ConfigurationAdmin cm; - @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + @Reference(// + policy = STATIC, policyOption = GREEDY, cardinality = MANDATORY, // + target = "(&(id=${config.ess_id})(enabled=true))") private EssSungrow ess; - @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) + @Reference(policy = DYNAMIC, policyOption = GREEDY, cardinality = OPTIONAL) private volatile Timedata timedata = null; private final CalculateEnergyFromPower calculateActualEnergy = new CalculateEnergyFromPower(this, @@ -65,11 +69,6 @@ public SungrowDcChargerImpl() { private void activate(ComponentContext context, Config config) throws OpenemsException { this.config = config; super.activate(context, config.id(), config.alias(), config.enabled()); - - if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "ess", config.ess_id())) { - return; - } - this.mapChannelValues(); } @@ -102,7 +101,7 @@ public void handleEvent(Event event) { return; } switch (event.getTopic()) { - case EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE -> { + case TOPIC_CYCLE_AFTER_PROCESS_IMAGE -> { this.calculateActualEnergy.update(this.getActualPower().get()); } } diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/ApplyPowerHandler.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/ApplyPowerHandler.java index 047fc94e472..d7f4434bbf9 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/ApplyPowerHandler.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/ApplyPowerHandler.java @@ -7,7 +7,8 @@ import io.openems.edge.sungrow.ess.enums.EmsMode; /** - * Handler to apply the set active power to the Sungrow ESS depending on the configured {@link ControlMode}. + * Handler to apply the set active power to the Sungrow ESS depending on the + * configured {@link ControlMode}. */ public class ApplyPowerHandler { diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrow.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrow.java index 2986b6cbe11..dc9f2c0c1e2 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrow.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrow.java @@ -20,171 +20,170 @@ public interface EssSungrow extends OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - SERIAL_NUMBER(Doc.of(OpenemsType.STRING) // + SERIAL_NUMBER(Doc.of(OpenemsType.STRING)// .persistencePriority(PersistencePriority.VERY_LOW)), // - DAILY_OUTPUT_ENERGY(Doc.of(OpenemsType.INTEGER) // + DAILY_OUTPUT_ENERGY(Doc.of(OpenemsType.INTEGER)// .unit(Unit.WATT_HOURS)), // - INSIDE_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // + INSIDE_TEMPERATURE(Doc.of(OpenemsType.INTEGER)// .unit(Unit.DEZIDEGREE_CELSIUS)), // - MPPT1_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + MPPT1_VOLTAGE(Doc.of(OpenemsType.INTEGER)// .unit(Unit.MILLIVOLT)), - MPPT1_CURRENT(Doc.of(OpenemsType.INTEGER) // + MPPT1_CURRENT(Doc.of(OpenemsType.INTEGER)// .unit(Unit.MILLIAMPERE)), // - MPPT2_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + MPPT2_VOLTAGE(Doc.of(OpenemsType.INTEGER)// .unit(Unit.MILLIVOLT)), // - MPPT2_CURRENT(Doc.of(OpenemsType.INTEGER) // + MPPT2_CURRENT(Doc.of(OpenemsType.INTEGER)// .unit(Unit.MILLIAMPERE)), // - TOTAL_DC_POWER(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // + TOTAL_DC_POWER(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT)// .persistencePriority(PersistencePriority.HIGH)), // - VOLTAGE_L1(Doc.of(OpenemsType.INTEGER) // + VOLTAGE_L1(Doc.of(OpenemsType.INTEGER)// .unit(Unit.MILLIVOLT)), // - VOLTAGE_L2(Doc.of(OpenemsType.INTEGER) // + VOLTAGE_L2(Doc.of(OpenemsType.INTEGER)// .unit(Unit.MILLIVOLT)), - VOLTAGE_L3(Doc.of(OpenemsType.INTEGER) // + VOLTAGE_L3(Doc.of(OpenemsType.INTEGER)// .unit(Unit.MILLIVOLT)), // - POWER_FACTOR(Doc.of(OpenemsType.INTEGER) // + POWER_FACTOR(Doc.of(OpenemsType.INTEGER)// .unit(Unit.THOUSANDTH)), // - GRID_FREQUENCY(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIHERTZ) // + GRID_FREQUENCY(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.MILLIHERTZ)// .persistencePriority(PersistencePriority.MEDIUM)), // - EXPORT_LIMIT_MIN(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // + EXPORT_LIMIT_MIN(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT)// .persistencePriority(PersistencePriority.VERY_LOW)), // - EXPORT_LIMIT_MAX(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // + EXPORT_LIMIT_MAX(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT)// .persistencePriority(PersistencePriority.VERY_LOW)), // - BDC_RATED_POWER(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // + BDC_RATED_POWER(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT)// .persistencePriority(PersistencePriority.VERY_LOW)), // - CHARGE_MAX_CURRENT(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.AMPERE) // + CHARGE_MAX_CURRENT(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.AMPERE)// .persistencePriority(PersistencePriority.HIGH)), // - DISCHARGE_MAX_CURRENT(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.AMPERE) // + DISCHARGE_MAX_CURRENT(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.AMPERE)// .persistencePriority(PersistencePriority.HIGH)), // - SYSTEM_STATE(Doc.of(SystemState.values()) // + SYSTEM_STATE(Doc.of(SystemState.values())// .persistencePriority(PersistencePriority.VERY_LOW)), // - POWER_GENERATED_FROM_PV(Doc.of(OpenemsType.BOOLEAN) // + POWER_GENERATED_FROM_PV(Doc.of(OpenemsType.BOOLEAN)// .persistencePriority(PersistencePriority.VERY_LOW)), // - BATTERY_CHARGING(Doc.of(OpenemsType.BOOLEAN) // + BATTERY_CHARGING(Doc.of(OpenemsType.BOOLEAN)// .persistencePriority(PersistencePriority.VERY_LOW)), // - BATTERY_DISCHARGING(Doc.of(OpenemsType.BOOLEAN) // + BATTERY_DISCHARGING(Doc.of(OpenemsType.BOOLEAN)// .persistencePriority(PersistencePriority.VERY_LOW)), // - POSITIVE_LOAD_POWER(Doc.of(OpenemsType.BOOLEAN) // + POSITIVE_LOAD_POWER(Doc.of(OpenemsType.BOOLEAN)// .persistencePriority(PersistencePriority.VERY_LOW)), // - FEED_IN_POWER(Doc.of(OpenemsType.BOOLEAN) // + FEED_IN_POWER(Doc.of(OpenemsType.BOOLEAN)// .persistencePriority(PersistencePriority.VERY_LOW)), // - IMPORT_POWER_FROM_GRID(Doc.of(OpenemsType.BOOLEAN) // + IMPORT_POWER_FROM_GRID(Doc.of(OpenemsType.BOOLEAN)// .persistencePriority(PersistencePriority.VERY_LOW)), // - NEGATIVE_LOAD_POWER(Doc.of(OpenemsType.BOOLEAN) // + NEGATIVE_LOAD_POWER(Doc.of(OpenemsType.BOOLEAN)// .persistencePriority(PersistencePriority.VERY_LOW)), // - DAILY_PV_GENERATION(Doc.of(OpenemsType.INTEGER) // + DAILY_PV_GENERATION(Doc.of(OpenemsType.INTEGER)// .unit(Unit.WATT_HOURS)), // - TOTAL_PV_GENERATION(Doc.of(OpenemsType.LONG) // + TOTAL_PV_GENERATION(Doc.of(OpenemsType.LONG)// .unit(Unit.WATT_HOURS)), // - DAILY_EXPORT_POWER_FROM_PV(Doc.of(OpenemsType.INTEGER) // + DAILY_EXPORT_POWER_FROM_PV(Doc.of(OpenemsType.INTEGER)// .unit(Unit.WATT)), // - TOTAL_EXPORT_ENERGY_FROM_PV(Doc.of(OpenemsType.LONG) // + TOTAL_EXPORT_ENERGY_FROM_PV(Doc.of(OpenemsType.LONG)// .unit(Unit.WATT_HOURS)), - LOAD_POWER(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // + LOAD_POWER(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT)// .persistencePriority(PersistencePriority.MEDIUM)), // - EXPORT_POWER(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // + EXPORT_POWER(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT)// .persistencePriority(PersistencePriority.MEDIUM)), - DAILY_BATTERY_CHARGE_ENERGY_FROM_PV(Doc.of(OpenemsType.INTEGER) // + DAILY_BATTERY_CHARGE_ENERGY_FROM_PV(Doc.of(OpenemsType.INTEGER)// .unit(Unit.WATT_HOURS)), - TOTAL_BATTERY_CHARGE_ENERGY_FROM_PV(Doc.of(OpenemsType.LONG) // + TOTAL_BATTERY_CHARGE_ENERGY_FROM_PV(Doc.of(OpenemsType.LONG)// .unit(Unit.WATT_HOURS)), - CO2_REDUCTION(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.NONE) // + CO2_REDUCTION(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.NONE)// .persistencePriority(PersistencePriority.VERY_LOW)), - DAILY_DIRECT_ENERGY_CONSUMPTION(Doc.of(OpenemsType.INTEGER) // + DAILY_DIRECT_ENERGY_CONSUMPTION(Doc.of(OpenemsType.INTEGER)// .unit(Unit.WATT_HOURS)), - TOTAL_DIRECT_ENERGY_CONSUMPTION(Doc.of(OpenemsType.LONG) // + TOTAL_DIRECT_ENERGY_CONSUMPTION(Doc.of(OpenemsType.LONG)// .unit(Unit.WATT_HOURS)), - BATTERY_VOLTAGE(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // + BATTERY_VOLTAGE(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.VOLT)// .persistencePriority(PersistencePriority.HIGH)), - BATTERY_CURRENT(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.AMPERE) // + BATTERY_CURRENT(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.AMPERE)// .persistencePriority(PersistencePriority.HIGH)), - BATTERY_POWER(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // + BATTERY_POWER(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT)// .persistencePriority(PersistencePriority.HIGH)), - SOH(Doc.of(OpenemsType.INTEGER) // + SOH(Doc.of(OpenemsType.INTEGER)// .unit(Unit.PERCENT)), - BATTERY_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.DEZIDEGREE_CELSIUS) // + BATTERY_TEMPERATURE(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.DEZIDEGREE_CELSIUS)// .persistencePriority(PersistencePriority.MEDIUM)), - DAILY_BATTERY_DISCHARGE_ENERGY(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT_HOURS) // + DAILY_BATTERY_DISCHARGE_ENERGY(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT_HOURS)// .persistencePriority(PersistencePriority.VERY_LOW)), - SELF_CONSUMPTION_OF_TODAY(Doc.of(OpenemsType.INTEGER) // + SELF_CONSUMPTION_OF_TODAY(Doc.of(OpenemsType.INTEGER)// .unit(Unit.PERCENT)), - CURRENT_L1(Doc.of(OpenemsType.INTEGER) // + CURRENT_L1(Doc.of(OpenemsType.INTEGER)// .unit(Unit.MILLIAMPERE)), - CURRENT_L2(Doc.of(OpenemsType.INTEGER) // + CURRENT_L2(Doc.of(OpenemsType.INTEGER)// .unit(Unit.MILLIAMPERE)), - CURRENT_L3(Doc.of(OpenemsType.INTEGER) // + CURRENT_L3(Doc.of(OpenemsType.INTEGER)// .unit(Unit.MILLIAMPERE)), - DAILY_IMPORT_ENERGY(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT_HOURS) // + DAILY_IMPORT_ENERGY(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT_HOURS)// .persistencePriority(PersistencePriority.VERY_LOW)), - TOTAL_IMPORT_ENERGY(Doc.of(OpenemsType.LONG) // + TOTAL_IMPORT_ENERGY(Doc.of(OpenemsType.LONG)// .unit(Unit.WATT_HOURS)), - DAILY_CHARGE_ENERGY(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT_HOURS) // + DAILY_CHARGE_ENERGY(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT_HOURS)// .persistencePriority(PersistencePriority.VERY_LOW)), - DAILY_EXPORT_ENERGY(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT_HOURS) // + DAILY_EXPORT_ENERGY(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT_HOURS)// .persistencePriority(PersistencePriority.VERY_LOW)), - TOTAL_EXPORT_ENERGY(Doc.of(OpenemsType.LONG) // + TOTAL_EXPORT_ENERGY(Doc.of(OpenemsType.LONG)// .unit(Unit.WATT_HOURS)), - EMS_MODE(Doc.of(EmsMode.values()) // - .accessMode(AccessMode.READ_WRITE) // + EMS_MODE(Doc.of(EmsMode.values())// + .accessMode(AccessMode.READ_WRITE)// .persistencePriority(PersistencePriority.HIGH)), - CHARGE_DISCHARGE_COMMAND(Doc.of(ChargeDischargeCommand.values()) // - .accessMode(AccessMode.READ_WRITE) // + CHARGE_DISCHARGE_COMMAND(Doc.of(ChargeDischargeCommand.values())// + .accessMode(AccessMode.READ_WRITE)// .persistencePriority(PersistencePriority.HIGH)), - CHARGE_DISCHARGE_POWER(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // - .accessMode(AccessMode.READ_WRITE) // + CHARGE_DISCHARGE_POWER(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT)// + .accessMode(AccessMode.READ_WRITE)// .persistencePriority(PersistencePriority.HIGH)), - MAX_SOC(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.PERCENT) // - .accessMode(AccessMode.READ_WRITE) // + MAX_SOC(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.PERCENT)// + .accessMode(AccessMode.READ_WRITE)// .persistencePriority(PersistencePriority.VERY_LOW)), - MIN_SOC(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.PERCENT) // - .accessMode(AccessMode.READ_WRITE) // + MIN_SOC(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.PERCENT)// + .accessMode(AccessMode.READ_WRITE)// .persistencePriority(PersistencePriority.VERY_LOW)), - EXPORT_POWER_LIMIT(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // - .accessMode(AccessMode.READ_WRITE) // + EXPORT_POWER_LIMIT(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT)// + .accessMode(AccessMode.READ_WRITE)// .persistencePriority(PersistencePriority.VERY_LOW)), - OFF_GRID_OPTION(Doc.of(EnableDisable.values()) // - .accessMode(AccessMode.READ_WRITE) // + OFF_GRID_OPTION(Doc.of(EnableDisable.values())// + .accessMode(AccessMode.READ_WRITE)// .persistencePriority(PersistencePriority.VERY_LOW)), - HEARTBEAT(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.NONE) // - .accessMode(AccessMode.READ_WRITE) // + HEARTBEAT(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.NONE)// + .accessMode(AccessMode.READ_WRITE)// .persistencePriority(PersistencePriority.HIGH)), // - DEBUG_HEARTBEAT( - Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_ONLY) // + DEBUG_HEARTBEAT(Doc.of(OpenemsType.INTEGER)// + .accessMode(AccessMode.READ_ONLY)// .persistencePriority(PersistencePriority.HIGH)), // - METER_COMM_DETECTION(Doc.of(EnableDisable.values()) // - .accessMode(AccessMode.READ_WRITE) // + METER_COMM_DETECTION(Doc.of(EnableDisable.values())// + .accessMode(AccessMode.READ_WRITE)// .persistencePriority(PersistencePriority.VERY_LOW)), - EXPORT_POWER_LIMITATION(Doc.of(EnableDisable.values()) // - .accessMode(AccessMode.READ_WRITE) // + EXPORT_POWER_LIMITATION(Doc.of(EnableDisable.values())// + .accessMode(AccessMode.READ_WRITE)// .persistencePriority(PersistencePriority.VERY_LOW)), - RESERVED_SOC_FOR_BACKUP(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.PERCENT) // - .accessMode(AccessMode.READ_WRITE) // + RESERVED_SOC_FOR_BACKUP(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.PERCENT)// + .accessMode(AccessMode.READ_WRITE)// .persistencePriority(PersistencePriority.VERY_LOW)), // SMART_MODE_NOT_WORKING_WITH_PID_FILTER(Doc.of(Level.WARNING) // .text("SMART mode does not work correctly with active PID filter")) // @@ -202,7 +201,6 @@ public Doc doc() { } } - /** * Gets the Channel for {@link ChannelId#MPPT1_VOLTAGE}. * @@ -220,7 +218,7 @@ public default IntegerReadChannel getMppt1VoltageChannel() { public default IntegerReadChannel getMppt1CurrentChannel() { return this.channel(ChannelId.MPPT1_CURRENT); } - + /** * Gets the Channel for {@link ChannelId#MPPT2_VOLTAGE}. * @@ -238,7 +236,7 @@ public default IntegerReadChannel getMppt2VoltageChannel() { public default IntegerReadChannel getMppt2CurrentChannel() { return this.channel(ChannelId.MPPT2_CURRENT); } - + /** * Gets the Channel for {@link ChannelId#TOTAL_DC_POWER}. * @@ -256,7 +254,7 @@ public default IntegerReadChannel getTotalDcPowerChannel() { public default Value getTotalDcPower() { return this.getTotalDcPowerChannel().value(); } - + /** * Gets the Channel for {@link ChannelId#VOLTAGE_L1}. * @@ -265,7 +263,7 @@ public default Value getTotalDcPower() { public default IntegerReadChannel getVoltageL1Channel() { return this.channel(ChannelId.VOLTAGE_L1); } - + /** * Gets the Channel for {@link ChannelId#VOLTAGE_L2}. * @@ -274,7 +272,7 @@ public default IntegerReadChannel getVoltageL1Channel() { public default IntegerReadChannel getVoltageL2Channel() { return this.channel(ChannelId.VOLTAGE_L2); } - + /** * Gets the Channel for {@link ChannelId#VOLTAGE_L3}. * @@ -283,7 +281,7 @@ public default IntegerReadChannel getVoltageL2Channel() { public default IntegerReadChannel getVoltageL3Channel() { return this.channel(ChannelId.VOLTAGE_L3); } - + /** * Gets the Channel for {@link ChannelId#GRID_FREQUENCY}. * @@ -319,7 +317,7 @@ public default Value getChargeMaxCurrent() { public default IntegerReadChannel getDischargeMaxCurrentChannel() { return this.channel(ChannelId.DISCHARGE_MAX_CURRENT); } - + /** * Gets the {@link ChannelId#DISCHARGE_MAX_CURRENT}. * @@ -328,7 +326,7 @@ public default IntegerReadChannel getDischargeMaxCurrentChannel() { public default Value getDischargeMaxCurrent() { return this.getDischargeMaxCurrentChannel().value(); } - + /** * Gets the Channel for {@link ChannelId#EXPORT_POWER}. * @@ -346,7 +344,7 @@ public default IntegerReadChannel getExportPowerChannel() { public default IntegerReadChannel getBatteryVoltageChannel() { return this.channel(ChannelId.BATTERY_VOLTAGE); } - + /** * Gets the {@link ChannelId#BATTERY_VOLTAGE}. * diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java index 9e695545c1e..4e4721b126e 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrowImpl.java @@ -4,10 +4,12 @@ import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1; import static io.openems.edge.common.channel.ChannelUtils.setValue; +import static org.osgi.service.component.annotations.ReferenceCardinality.MANDATORY; +import static org.osgi.service.component.annotations.ReferencePolicy.STATIC; +import static org.osgi.service.component.annotations.ReferencePolicyOption.GREEDY; import java.util.function.Consumer; -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; @@ -97,10 +99,9 @@ public EssSungrowImpl() { ); } - @Reference - protected ConfigurationAdmin cm; - - @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + @Reference(// + policy = STATIC, policyOption = GREEDY, cardinality = MANDATORY, // + target = "(&(id=${config.modbus_id})(enabled=true))") protected void setModbus(BridgeModbus modbus) { super.setModbus(modbus); } @@ -108,10 +109,7 @@ protected void setModbus(BridgeModbus modbus) { @Activate private void activate(ComponentContext context, Config config) throws OpenemsException { this.config = config; - if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, - "Modbus", config.modbus_id())) { - return; - } + super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId()); // NOTE: This should normally be read from the device setValue(this, SymmetricEss.ChannelId.GRID_MODE, GridMode.ON_GRID); @@ -139,8 +137,8 @@ private void installPowerListeners() { */ private void installDcDischargePowerListener() { final Consumer> dcDischarge = ignore -> { - this._setDcDischargePower(TypeUtils.subtract(// - this.getActivePower().get(), this.getTotalDcPower().get())); + setValue(this, HybridEss.ChannelId.DC_DISCHARGE_POWER, // + TypeUtils.subtract(this.getActivePower().get(), this.getTotalDcPower().get())); }; this.getActivePowerChannel().onSetNextValue(dcDischarge); this.getTotalDcPowerChannel().onSetNextValue(dcDischarge); @@ -176,7 +174,6 @@ private void installAllowedDischargePowerListener() { @Override protected ModbusProtocol defineModbusProtocol() { - return new ModbusProtocol(this, // new FC4ReadInputRegistersTask(4989, Priority.HIGH, // m(EssSungrow.ChannelId.SERIAL_NUMBER, new StringWordElement(4989, 10)), // diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/ControlMode.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/ControlMode.java index cd74fdd8f29..4dc119e570f 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/ControlMode.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/enums/ControlMode.java @@ -3,20 +3,21 @@ public enum ControlMode { /** - * Uses the internal 'SELF_CONSUMPTION' mode of the Sungrow inverter. Allows no remote - * control of Set-Points. Requires a Sungrow Meter at the grid junction + * Uses the internal 'SELF_CONSUMPTION' mode of the Sungrow inverter. Allows no + * remote control of Set-Points. Requires a Sungrow Meter at the grid junction * point. */ INTERNAL, /** - * Uses the internal 'SELF_CONSUMPTION' mode of the Sungrow inverter but smartly switches to - * other modes if required. Requires a Sungrow Meter at the grid junction - * point. + * Uses the internal 'SELF_CONSUMPTION' mode of the Sungrow inverter but smartly + * switches to other modes if required. Requires a Sungrow Meter at the grid + * junction point. */ SMART, /** - * Full control of the Sungrow inverter by OpenEMS. Slower than internal 'SELF_CONSUMPTION' - * mode, but does not require a Sungrow Meter at the grid junction point. + * Full control of the Sungrow inverter by OpenEMS. Slower than internal + * 'SELF_CONSUMPTION' mode, but does not require a Sungrow Meter at the grid + * junction point. */ REMOTE; diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java index 6eba0f57305..0cd93575ead 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/Config.java @@ -4,7 +4,7 @@ import org.osgi.service.metatype.annotations.ObjectClassDefinition; @ObjectClassDefinition(// - name = "Ess Sungrow Grid Meter", // + name = "ESS Sungrow Grid Meter", // description = "Implements grid meter from Sungrow Hybrid ESS.") @interface Config { @@ -20,6 +20,6 @@ @AttributeDefinition(name = "ESS-ID", description = "ID of the Sungrow Ess Component.") String ess_id() default "ess0"; - String webconsole_configurationFactory_nameHint() default "Ess Sungrow Grid Meter [{id}]"; + String webconsole_configurationFactory_nameHint() default "ESS Sungrow Grid Meter [{id}]"; } diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeterImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeterImpl.java index 111aeaa24f9..8df3e516716 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeterImpl.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/meter/SungrowGridMeterImpl.java @@ -1,20 +1,19 @@ package io.openems.edge.sungrow.meter; -import static io.openems.edge.meter.api.ElectricityMeter.calculatePhasesFromActivePower; -import static io.openems.edge.meter.api.ElectricityMeter.calculateSumCurrentFromPhases; import static io.openems.edge.meter.api.ElectricityMeter.calculateAverageVoltageFromPhases; import static io.openems.edge.meter.api.ElectricityMeter.calculateCurrentsFromActivePowerAndVoltage; +import static io.openems.edge.meter.api.ElectricityMeter.calculatePhasesFromActivePower; +import static io.openems.edge.meter.api.ElectricityMeter.calculateSumCurrentFromPhases; +import static org.osgi.service.component.annotations.ReferenceCardinality.MANDATORY; +import static org.osgi.service.component.annotations.ReferencePolicy.STATIC; +import static org.osgi.service.component.annotations.ReferencePolicyOption.GREEDY; -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.OpenemsError.OpenemsNamedException; @@ -38,10 +37,9 @@ public class SungrowGridMeterImpl extends AbstractOpenemsComponent protected Config config = null; - @Reference - private ConfigurationAdmin cm; - - @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + @Reference(// + policy = STATIC, policyOption = GREEDY, cardinality = MANDATORY, // + target = "(&(id=${config.ess_id})(enabled=true))") private EssSungrow ess; public SungrowGridMeterImpl() { @@ -66,11 +64,6 @@ public SungrowGridMeterImpl() { private void activate(ComponentContext context, Config config) throws OpenemsException, OpenemsNamedException { this.config = config; super.activate(context, config.id(), config.alias(), config.enabled()); - - if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "ess", config.ess_id())) { - return; - } - this.mapChannelValues(); } diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/Config.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/Config.java index 3a35c976095..9f222f75f5d 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/Config.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/Config.java @@ -1,35 +1,36 @@ package io.openems.edge.sungrow.pvinverter; -import io.openems.common.types.MeterType; import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import io.openems.common.types.MeterType; + @ObjectClassDefinition(// - name = "PV-Inverter Sungrow", // - description = "Implements the Sungrow PV inverter") + name = "PV-Inverter Sungrow", // + description = "Implements the Sungrow PV inverter") @interface Config { - @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") - String id() default "pvInverter0"; + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "pvInverter0"; + + @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") + String alias() default ""; - @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 = "Is enabled?", description = "Is this Component enabled?") - boolean enabled() default true; - - @AttributeDefinition(name = "Read-Only mode", description = "Set to true, if PV Inverter should only be read.") - boolean readOnly() default true; + @AttributeDefinition(name = "Read-Only mode", description = "Set to true, if PV Inverter should only be read.") + boolean readOnly() default true; - @AttributeDefinition(name = "Modbus-ID", description = "ID of Modbus bridge.") - String modbus_id() default "modbus0"; + @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 Unit-ID", description = "The Unit-ID of the Modbus device.") + int modbusUnitId() default 1; - @AttributeDefinition(name = "Meter-Type", description = "Always Production") - MeterType type() default MeterType.PRODUCTION; + @AttributeDefinition(name = "Meter-Type", description = "Always Production") + MeterType type() default MeterType.PRODUCTION; - String webconsole_configurationFactory_nameHint() default "PV-Inverter Sungrow [{id}]"; + String webconsole_configurationFactory_nameHint() default "PV-Inverter Sungrow [{id}]"; } \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrow.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrow.java index b3ed66ea1b0..fc98fc1151a 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrow.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrow.java @@ -9,78 +9,79 @@ public interface PvInverterSungrow extends OpenemsComponent { - public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - SERIAL_NUMBER(Doc.of(OpenemsType.STRING)), // + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + SERIAL_NUMBER(Doc.of(OpenemsType.STRING)), // - NOMINAL_OUTPUT_POWER(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // - .persistencePriority(PersistencePriority.VERY_LOW)), // + NOMINAL_OUTPUT_POWER(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT)// + .persistencePriority(PersistencePriority.VERY_LOW)), // - DAILY_ENERGY(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT_HOURS) // - .persistencePriority(PersistencePriority.MEDIUM)), TOTAL_RUNNING_TIME(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.HOUR) // - .persistencePriority(PersistencePriority.VERY_LOW)), + DAILY_ENERGY(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT_HOURS)// + .persistencePriority(PersistencePriority.MEDIUM)), + TOTAL_RUNNING_TIME(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.HOUR)// + .persistencePriority(PersistencePriority.VERY_LOW)), - INTERNAL_TEMPERATURE(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.DEZIDEGREE_CELSIUS) // - .persistencePriority(PersistencePriority.VERY_LOW)), + INTERNAL_TEMPERATURE(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.DEZIDEGREE_CELSIUS)// + .persistencePriority(PersistencePriority.VERY_LOW)), - APPARENT_POWER(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT_AMPERE) // - .persistencePriority(PersistencePriority.MEDIUM)), // + APPARENT_POWER(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.VOLT_AMPERE)// + .persistencePriority(PersistencePriority.MEDIUM)), // - DC_VOLTAGE_1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT)), // + DC_VOLTAGE_1(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.VOLT)), // - DC_CURRENT_1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE)), // + DC_CURRENT_1(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.MILLIAMPERE)), // - DC_VOLTAGE_2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT)), // + DC_VOLTAGE_2(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.VOLT)), // - DC_CURRENT_2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE)), // + DC_CURRENT_2(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.MILLIAMPERE)), // - DC_VOLTAGE_3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT)), // + DC_VOLTAGE_3(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.VOLT)), // - DC_CURRENT_3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE)), // + DC_CURRENT_3(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.MILLIAMPERE)), // - DC_POWER(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // - .persistencePriority(PersistencePriority.HIGH)), + DC_POWER(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT)// + .persistencePriority(PersistencePriority.HIGH)), - POWER_FACTOR(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.THOUSANDTH)), + POWER_FACTOR(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.THOUSANDTH)), - WORK_STATE(Doc.of(WorkState.values())), + WORK_STATE(Doc.of(WorkState.values())), - NEGATIVE_VOLTAGE_TO_THE_GROUND(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .persistencePriority(PersistencePriority.VERY_LOW)), + NEGATIVE_VOLTAGE_TO_THE_GROUND(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.VOLT)// + .persistencePriority(PersistencePriority.VERY_LOW)), - BUS_VOLTAGE(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT)), + BUS_VOLTAGE(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.VOLT)), - POWER_LIMITATION_SWITCH(Doc.of(OpenemsType.BOOLEAN) // - .unit(Unit.ON_OFF) // - .accessMode(AccessMode.READ_WRITE)), - POWER_LIMITATION_SETTING(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.PERCENT) // - .accessMode(AccessMode.READ_WRITE)); + POWER_LIMITATION_SWITCH(Doc.of(OpenemsType.BOOLEAN)// + .unit(Unit.ON_OFF)// + .accessMode(AccessMode.READ_WRITE)), + POWER_LIMITATION_SETTING(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.PERCENT)// + .accessMode(AccessMode.READ_WRITE)); - private final Doc doc; + private final Doc doc; - private ChannelId(Doc doc) { - this.doc = doc; - } + private ChannelId(Doc doc) { + this.doc = doc; + } - @Override - public Doc doc() { - return this.doc; + @Override + public Doc doc() { + return this.doc; + } } - } } \ No newline at end of file diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java index f75fbdb8e54..851180ef36b 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImpl.java @@ -4,17 +4,18 @@ import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2; import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1; import static io.openems.edge.common.event.EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE; +import static org.osgi.service.component.annotations.ReferenceCardinality.MANDATORY; +import static org.osgi.service.component.annotations.ReferenceCardinality.OPTIONAL; +import static org.osgi.service.component.annotations.ReferencePolicy.DYNAMIC; +import static org.osgi.service.component.annotations.ReferencePolicy.STATIC; +import static org.osgi.service.component.annotations.ReferencePolicyOption.GREEDY; -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.event.Event; import org.osgi.service.event.EventHandler; import org.osgi.service.event.propertytypes.EventTopics; @@ -54,7 +55,7 @@ @EventTopics({ // TOPIC_CYCLE_AFTER_PROCESS_IMAGE // }) -@GenerateTargetsFromReferences({"Modbus"}) +@GenerateTargetsFromReferences({ "Modbus" }) public class PvInverterSungrowImpl extends AbstractOpenemsModbusComponent implements ManagedSymmetricPvInverter, ElectricityMeter, PvInverterSungrow, ModbusComponent, TimedataProvider, OpenemsComponent, EventHandler { @@ -70,12 +71,22 @@ public class PvInverterSungrowImpl extends AbstractOpenemsModbusComponent implem return 0xEE; }); - @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) - private volatile Timedata timedata = null; - private final CalculateEnergyFromPower calculateEnergy = new CalculateEnergyFromPower(this, ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY); + @Reference(policy = DYNAMIC, policyOption = GREEDY, cardinality = OPTIONAL) + private volatile Timedata timedata = null; + + @Override + @Reference(// + policy = STATIC, policyOption = GREEDY, cardinality = MANDATORY, // + target = "(&(id=${config.modbus_id})(enabled=true))") + protected void setModbus(BridgeModbus modbus) { + super.setModbus(modbus); + } + + private boolean readOnly = true; // oEMS add + public PvInverterSungrowImpl() { super(// OpenemsComponent.ChannelId.values(), // @@ -86,29 +97,12 @@ public PvInverterSungrowImpl() { ); } - @Reference - protected ConfigurationAdmin cm; - - @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) - protected void setModbus(BridgeModbus modbus) { - super.setModbus(modbus); - } - - private boolean readOnly = true; // oEMS add - @Activate void activate(ComponentContext context, Config config) throws OpenemsException { this.readOnly = config.readOnly(); // oEMS add - if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, - "Modbus", config.modbus_id())) { - return; - } - this.getActivePowerChannel().onSetNextValue(value -> { // - var powerPerPhase = value.orElse(0) / 3; - this._setActivePowerL1(powerPerPhase); - this._setActivePowerL2(powerPerPhase); - this._setActivePowerL3(powerPerPhase); - }); + super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId()); + + ElectricityMeter.calculatePhasesFromActivePower(this); } @Deactivate @@ -122,7 +116,8 @@ public void handleEvent(Event event) { return; } switch (event.getTopic()) { - case TOPIC_CYCLE_AFTER_PROCESS_IMAGE -> this.calculateEnergy.update(this.getActivePower().get()); + case TOPIC_CYCLE_AFTER_PROCESS_IMAGE // + -> this.calculateEnergy.update(this.getActivePower().get()); } } @@ -200,8 +195,10 @@ protected ModbusProtocol defineModbusProtocol() { m(ManagedSymmetricPvInverter.ChannelId.ACTIVE_POWER_LIMIT, new UnsignedWordElement(5038), // SCALE_FACTOR_2))); // if (!this.readOnly) { // oEMS add - protocol.addTask(new FC6WriteRegisterTask(5038, m(ManagedSymmetricPvInverter.ChannelId.ACTIVE_POWER_LIMIT, // - new UnsignedWordElement(5038), SCALE_FACTOR_2))); + protocol.addTask(// + new FC6WriteRegisterTask(5038, // + m(ManagedSymmetricPvInverter.ChannelId.ACTIVE_POWER_LIMIT, // + new UnsignedWordElement(5038), SCALE_FACTOR_2))); } return protocol; } diff --git a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/WorkState.java b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/WorkState.java index 7a1acfe27fb..56ce00cde68 100644 --- a/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/WorkState.java +++ b/io.openems.edge.sungrow/src/io/openems/edge/sungrow/pvinverter/WorkState.java @@ -3,42 +3,42 @@ import io.openems.common.types.OptionsEnum; public enum WorkState implements OptionsEnum { - UNDEFINED(-1, "Undefined"), // - RUN(0x0, "Run"), // - STOP(0x8000, "Stop"), // - KEY_STOP(0x1300, "Key stop"), // - EMERGENCY_STOP(0x1500, "Emergency stop"), // - STANDBY(0x1400, "Standby"), // - INITIAL_STANDBY(0x1200, "Initial standby"), // - STARTING(0x1600, "Starting"), // - ALARM_RUN(0x9100, "Alarm run"), // - DERATING_RUN(0x8100, "Derating run"), // - DISPATCH_RUN(0x8200, "Dispatch run"), // - FAULT(0x5500, "Fault"), // - COMMUNICATE_FAULT(0x2500, "Communicate fault") // - ; - - private int value; - private String name; - - private WorkState(int value, String name) { - this.value = value; - this.name = name; - } - - @Override - public int getValue() { - return this.value; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public OptionsEnum getUndefined() { - return UNDEFINED; - } + UNDEFINED(-1, "Undefined"), // + RUN(0x0, "Run"), // + STOP(0x8000, "Stop"), // + KEY_STOP(0x1300, "Key stop"), // + EMERGENCY_STOP(0x1500, "Emergency stop"), // + STANDBY(0x1400, "Standby"), // + INITIAL_STANDBY(0x1200, "Initial standby"), // + STARTING(0x1600, "Starting"), // + ALARM_RUN(0x9100, "Alarm run"), // + DERATING_RUN(0x8100, "Derating run"), // + DISPATCH_RUN(0x8200, "Dispatch run"), // + FAULT(0x5500, "Fault"), // + COMMUNICATE_FAULT(0x2500, "Communicate fault") // + ; + + private int value; + private String name; + + private WorkState(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } } \ No newline at end of file diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/MyConfig.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/MyConfig.java index ab644345e04..6bf7a11700c 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/MyConfig.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/MyConfig.java @@ -1,7 +1,6 @@ package io.openems.edge.sungrow.dccharger; import io.openems.common.test.AbstractComponentConfig; -import io.openems.common.utils.ConfigUtils; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/SungrowDcChargerTest.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/SungrowDcChargerTest.java index 84757aa0872..dc7fad7c3bb 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/SungrowDcChargerTest.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/dccharger/SungrowDcChargerTest.java @@ -1,25 +1,23 @@ package io.openems.edge.sungrow.dccharger; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import io.openems.common.test.DummyConfigurationAdmin; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.sungrow.ess.EssSungrowImpl; public class SungrowDcChargerTest { - private static final String CHARGER_ID = "charger0"; - private static final String ESS_ID = "ess0"; - @Test public void test() throws Exception { new ComponentTest(new SungrowDcChargerImpl()) // - .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", new EssSungrowImpl()) // .activate(MyConfig.create() // - .setId(CHARGER_ID) // - .setEssId(ESS_ID) // - .build()); + .setId("charger0") // + .setEssId("ess0") // + .build()) // + .next(new TestCase()) // + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/EssSungrowEssTest.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/EssSungrowEssTest.java index fd2c4c879a4..9b24e653558 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/EssSungrowEssTest.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/EssSungrowEssTest.java @@ -1,29 +1,25 @@ package io.openems.edge.sungrow.ess; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import io.openems.edge.common.test.AbstractComponentTest.TestCase; 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.common.test.DummyConfigurationAdmin; import io.openems.edge.sungrow.ess.enums.ControlMode; public class EssSungrowEssTest { - private static final String COMPONENT_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new EssSungrowImpl()) // - .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setControlMode(ControlMode.SMART) // .build()) - .next(new TestCase()); + .next(new TestCase()) // + .deactivate(); } } diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/MyConfig.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/MyConfig.java index 64d96389eec..c0d52f53971 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/MyConfig.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/ess/MyConfig.java @@ -1,7 +1,6 @@ package io.openems.edge.sungrow.ess; import io.openems.common.test.AbstractComponentConfig; -import io.openems.common.utils.ConfigUtils; import io.openems.edge.sungrow.ess.enums.ControlMode; @SuppressWarnings("all") diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java index 15b6e116eb3..b5c5606e1d7 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/MyConfig.java @@ -1,7 +1,6 @@ package io.openems.edge.sungrow.meter; import io.openems.common.test.AbstractComponentConfig; -import io.openems.common.utils.ConfigUtils; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { @@ -48,5 +47,5 @@ private MyConfig(Builder builder) { public String ess_id() { return this.builder.essId; } - + } diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/SungrowGridMeterTest.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/SungrowGridMeterTest.java index 9108b4d5f9a..015537849cb 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/SungrowGridMeterTest.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/meter/SungrowGridMeterTest.java @@ -1,8 +1,8 @@ package io.openems.edge.sungrow.meter; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import io.openems.common.test.DummyConfigurationAdmin; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.sungrow.ess.EssSungrowImpl; @@ -14,12 +14,13 @@ public class SungrowGridMeterTest { @Test public void test() throws Exception { new ComponentTest(new SungrowGridMeterImpl()) // - .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", new EssSungrowImpl()) // .activate(MyConfig.create() // .setId(METER_ID) // .setEssId(ESS_ID) // - .build()); + .build()) // + .next(new TestCase()) // + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/MyConfig.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/MyConfig.java index dfb6a860ee0..cd95b76fc95 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/MyConfig.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/MyConfig.java @@ -1,7 +1,6 @@ package io.openems.edge.sungrow.pvinverter; import io.openems.common.test.AbstractComponentConfig; -import io.openems.common.utils.ConfigUtils; import io.openems.common.types.MeterType; @SuppressWarnings("all") diff --git a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImplTest.java b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImplTest.java index 3e57548a737..7417d0acbf0 100644 --- a/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImplTest.java +++ b/io.openems.edge.sungrow/test/io/openems/edge/sungrow/pvinverter/PvInverterSungrowImplTest.java @@ -1,27 +1,24 @@ package io.openems.edge.sungrow.pvinverter; -import org.junit.Test; +import org.junit.jupiter.api.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.common.test.DummyConfigurationAdmin; public class PvInverterSungrowImplTest { - private static final String PV_INVERTER_ID = "pvInverter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new PvInverterSungrowImpl()) // - .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(PV_INVERTER_ID) // - .setModbusId(MODBUS_ID) // + .setId("pvInverter0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // .build()) // - ; + .next(new TestCase()) // + .deactivate(); } } From ccde13bc7760daa085ebf5a39caac1bf8251f8a4 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Sat, 19 Sep 2026 20:55:23 +0200 Subject: [PATCH 14/15] Add author and docs --- io.openems.edge.sungrow/bnd.bnd | 4 ++-- io.openems.edge.sungrow/readme.adoc | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/io.openems.edge.sungrow/bnd.bnd b/io.openems.edge.sungrow/bnd.bnd index cc9ebc41cd5..0b8595bc50f 100644 --- a/io.openems.edge.sungrow/bnd.bnd +++ b/io.openems.edge.sungrow/bnd.bnd @@ -1,5 +1,5 @@ -Bundle-Name: OpenEMS Edge io.openems.edge.sungrow -Bundle-Vendor: OpenEMS Association e.V. +Bundle-Name: OpenEMS Edge Sungrow +Bundle-Vendor: opernikus GmbH, OpenEMS Association e.V. Bundle-License: https://opensource.org/licenses/EPL-2.0 Bundle-Version: 1.0.0.${tstamp} diff --git a/io.openems.edge.sungrow/readme.adoc b/io.openems.edge.sungrow/readme.adoc index 6a9b74281ef..f8faf4cba09 100644 --- a/io.openems.edge.sungrow/readme.adoc +++ b/io.openems.edge.sungrow/readme.adoc @@ -1,3 +1,11 @@ -= Sungrow += Sungrow Energy Storage System and PV-Inverter + +Support for + +- Sungrow SH10RT Hybrid Ess (`Ess.Sungrow`); including + - DC-Chargers (`Ess.Sungrow.DcCharger`) + - Grid-Meter (`Ess.Sungrow.Gridmeter`) + +- Sungrow PV-Inverter https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.sungrow[Source Code icon:github[]] \ No newline at end of file From eae03b8a8544b0949b21b48bad2c93d2ee2051a0 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Sat, 19 Sep 2026 21:22:07 +0200 Subject: [PATCH 15/15] Add professional support link --- io.openems.edge.sungrow/readme.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/io.openems.edge.sungrow/readme.adoc b/io.openems.edge.sungrow/readme.adoc index f8faf4cba09..1642eea81db 100644 --- a/io.openems.edge.sungrow/readme.adoc +++ b/io.openems.edge.sungrow/readme.adoc @@ -8,4 +8,6 @@ Support for - Sungrow PV-Inverter +If you need professional support with this implementation, please get in touch with opernikus GmbH (https://opernikus.de) + https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.sungrow[Source Code icon:github[]] \ No newline at end of file