From b4d3b6c9de32ddd1d5224b0d7395e42f664d60a3 Mon Sep 17 00:00:00 2001 From: vision1727 Date: Wed, 3 Jun 2026 22:04:29 +0200 Subject: [PATCH 1/2] added Fronius Gen24 Added Fronius Gen24 --- io.openems.edge.fronius/bnd.bnd | 6 + .../edge/fronius/enums/BatteryState.java | 31 + ...OperatingModeForActivePowerLimitation.java | 27 + .../edge/fronius/enums/OperationHealth.java | 38 + .../edge/fronius/enums/PowerSupplyStatus.java | 26 + .../openems/edge/fronius/enums/PvString.java | 31 + .../edge/fronius/enums/SetControlMode.java | 34 + .../edge/fronius/enums/SystemState.java | 32 + .../edge/fronius/gen24/battery/Config.java | 39 + .../fronius/gen24/battery/FroniusGen24.java | 125 ++++ .../battery/FroniusGen24BatteryImpl.java | 443 ++++++++++++ .../batteryinverter/ApplyPowerHandler.java | 252 +++++++ .../BatteryInverterFroniusGen24.java | 253 +++++++ .../BatteryInverterFroniusGen24Impl.java | 679 ++++++++++++++++++ .../fronius/gen24/batteryinverter/Config.java | 31 + .../gen24/batteryinverter/ControlMode.java | 43 ++ .../batteryinverter/S160SunSpecModel.java | 677 +++++++++++++++++ .../edge/fronius/gen24/dccharger/Config.java | 27 + .../dccharger/FroniusGen24DcChargerImpl.java | 162 +++++ .../io/openems/edge/fronius/meter/Config.java | 2 +- .../edge/fronius/meter/MeterFroniusImpl.java | 1 + 21 files changed, 2958 insertions(+), 1 deletion(-) create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/enums/BatteryState.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/enums/OperatingModeForActivePowerLimitation.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/enums/OperationHealth.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/enums/PowerSupplyStatus.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/enums/PvString.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/enums/SetControlMode.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/enums/SystemState.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/battery/Config.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/battery/FroniusGen24.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/battery/FroniusGen24BatteryImpl.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/ApplyPowerHandler.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/BatteryInverterFroniusGen24.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/BatteryInverterFroniusGen24Impl.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/Config.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/ControlMode.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/S160SunSpecModel.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/dccharger/Config.java create mode 100644 io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/dccharger/FroniusGen24DcChargerImpl.java diff --git a/io.openems.edge.fronius/bnd.bnd b/io.openems.edge.fronius/bnd.bnd index 11288d026e4..aeb15d87ce2 100644 --- a/io.openems.edge.fronius/bnd.bnd +++ b/io.openems.edge.fronius/bnd.bnd @@ -11,6 +11,12 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.meter.api,\ io.openems.edge.pvinverter.api,\ io.openems.j2mod,\ + io.openems.edge.battery.api,\ + io.openems.edge.batteryinverter.api,\ + io.openems.edge.ess.api,\ + io.openems.edge.meter.api,\ + io.openems.edge.timedata.api,\ + -testpath: \ ${testpath},\ diff --git a/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/BatteryState.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/BatteryState.java new file mode 100644 index 00000000000..d987c40b15b --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/BatteryState.java @@ -0,0 +1,31 @@ +package io.openems.edge.fronius.enums; + +import io.openems.common.types.OptionsEnum; + +public enum BatteryState implements OptionsEnum { + UNDEFINED(-1, "Undefiniert"), + OFF(1, "Aus"), + EMPTY(2, "Leer"), + DISCHARGING(3, "Entladen"), + CHARGING(4, "Laden"), + FULL(5, "Voll"), + STANDBY(6, "Standby / Holding"), + TESTING(7, "Testmodus"); + + private final int value; + private final String name; + + private BatteryState(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.fronius/src/io/openems/edge/fronius/enums/OperatingModeForActivePowerLimitation.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/OperatingModeForActivePowerLimitation.java new file mode 100644 index 00000000000..7548b7fac37 --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/OperatingModeForActivePowerLimitation.java @@ -0,0 +1,27 @@ +package io.openems.edge.fronius.enums; + +import io.openems.common.types.OptionsEnum; + +public enum OperatingModeForActivePowerLimitation implements OptionsEnum { + UNDEFINED(-1, "Undefiniert"), + NO_CONTROL(0, "Keine Steuerung (Automatik)"), + SELF_CONSUMPTION(1, "Eigenverbrauch"), + MANUAL(4, "Manuelle Steuerung (OpenEMS)"); + + private final int value; + private final String name; + + private OperatingModeForActivePowerLimitation(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.fronius/src/io/openems/edge/fronius/enums/OperationHealth.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/OperationHealth.java new file mode 100644 index 00000000000..bcb38448c60 --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/OperationHealth.java @@ -0,0 +1,38 @@ +package io.openems.edge.fronius.enums; + +import io.openems.common.types.OptionsEnum; + +public enum OperationHealth implements OptionsEnum { + UNDEFINED(-1, "Undefiniert"), + OFF(1, "Aus"), + SLEEPING(2, "Standby / Nacht"), + STARTING(3, "Startvorgang"), + OK(4, "Alles okay (Normalbetrieb)"), + THROTTLED(5, "Gedrosselt (Warnung)"), + SHUTTING_DOWN(6, "Herunterfahren"), + ERROR(7, "Fehler (Fault)"), + STANDBY(8, "Standby"); + + private final int value; + private final String name; + + private OperationHealth(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.fronius/src/io/openems/edge/fronius/enums/PowerSupplyStatus.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/PowerSupplyStatus.java new file mode 100644 index 00000000000..df8e6a946d0 --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/PowerSupplyStatus.java @@ -0,0 +1,26 @@ +package io.openems.edge.fronius.enums; + +import io.openems.common.types.OptionsEnum; + +public enum PowerSupplyStatus implements OptionsEnum { + UNDEFINED(-1, "Undefiniert"), + GRID_CONNECTED(0, "Netzbetrieb"), + EMERGENCY_POWER(1, "Notstrombetrieb / Backup"); + + private final int value; + private final String name; + + private PowerSupplyStatus(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.fronius/src/io/openems/edge/fronius/enums/PvString.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/PvString.java new file mode 100644 index 00000000000..70bb65d0b55 --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/PvString.java @@ -0,0 +1,31 @@ +package io.openems.edge.fronius.enums; + +import io.openems.common.types.OptionsEnum; + +public enum PvString implements OptionsEnum { + ONE(1, "String 1"), // + TWO(2, "String 2"); + + private final int value; + private final String name; + + PvString(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 ONE; + } +} diff --git a/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/SetControlMode.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/SetControlMode.java new file mode 100644 index 00000000000..d3d6c31e7dc --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/SetControlMode.java @@ -0,0 +1,34 @@ +package io.openems.edge.fronius.enums; + +import io.openems.common.types.OptionsEnum; + +public enum SetControlMode implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "Disabled"), // + CHARGE_LIMIT(1, "Charge limit"), // + DISCHARGE_LIMIT(2, "Discharge limit"), // + CHARGE_AND_DISCHARGE_LIMIT(3, "Charge and discharge limit"); + + private final int value; + private final String name; + + private SetControlMode(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; + } +} diff --git a/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/SystemState.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/SystemState.java new file mode 100644 index 00000000000..71dcfb08092 --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/enums/SystemState.java @@ -0,0 +1,32 @@ +package io.openems.edge.fronius.enums; + +import io.openems.common.types.OptionsEnum; + +public enum SystemState implements OptionsEnum { + UNDEFINED(-1, "Undefiniert"), + OFF(1, "Aus"), + SLEEPING(2, "Standby/Nacht"), + STARTING(3, "Startvorgang"), + OK(4, "Normalbetrieb"), + THROTTLED(5, "Leistungsbegrenzung"), + SHUTTING_DOWN(6, "Herunterfahren"), + FAULT(7, "Fehler"), + STANDBY(8, "Standby"); + + 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.fronius/src/io/openems/edge/fronius/gen24/battery/Config.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/battery/Config.java new file mode 100644 index 00000000000..0a61858951b --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/battery/Config.java @@ -0,0 +1,39 @@ +package io.openems.edge.fronius.gen24.battery; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(// + name = "ESS Fronius Gen24 Battery with int+SF ", // + description = "Collects Some more Sunspec unrelated data.") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "battery0"; + + @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") + String alias() default ""; + + @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") + boolean enabled() default true; + + @AttributeDefinition(name = "Modbus-ID", description = "ID of Modbus bridge.") + String modbus_id() default "modbus0"; + + @AttributeDefinition(name = "Modbus-Unit-ID", description = "Modbus Unit-ID.") + int modbusUnitId() default 3; + + @AttributeDefinition(name = "Charge Max Voltage", description = "Maximum voltage for charging in V" + + "from Datasheet of Battery") + int chargeMaxVoltage() default 480; + + @AttributeDefinition(name = "Discharge Min Voltage", description = "Minimum voltage for discharging in V" + + "from Datasheet of Battery") + int dischargeMinVoltage() default 320; + + @AttributeDefinition(name = "Number of Batterymodules", description = "Number of Batterymodules") + int numberOfModules() default 4; + + String webconsole_configurationFactory_nameHint() default "ESS Fronius Gen24 Battery [{id}]"; + +} diff --git a/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/battery/FroniusGen24.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/battery/FroniusGen24.java new file mode 100644 index 00000000000..35139d6fb6a --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/battery/FroniusGen24.java @@ -0,0 +1,125 @@ +package io.openems.edge.fronius.gen24.battery; + +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.Channel; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.StateChannel; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.fronius.enums.BatteryState; +import io.openems.edge.fronius.enums.SetControlMode; + +public interface FroniusGen24 extends OpenemsComponent { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + + BAT_STATUS(Doc.of(BatteryState.values())// + .persistencePriority(PersistencePriority.HIGH)), // + + ACT_BAT_CHRG(Doc.of(OpenemsType.LONG)// + .unit(Unit.CUMULATED_WATT_HOURS)), // + BAT_CHRG(Doc.of(OpenemsType.LONG)// + .unit(Unit.CUMULATED_WATT_HOURS)), // + CUR_BAT_CHA(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT)), // + ACT_BAT_DSCH(Doc.of(OpenemsType.LONG)// + .unit(Unit.CUMULATED_WATT_HOURS)), // + BAT_DSCH(Doc.of(OpenemsType.LONG)// + .unit(Unit.CUMULATED_WATT_HOURS)), // + CUR_BAT_DSCH(Doc.of(OpenemsType.INTEGER)// + .unit(Unit.WATT)), // + BATTERY_WARNING(Doc.of(Level.WARNING)// + .persistencePriority(PersistencePriority.HIGH)// + .text("Warning in the battery!")), // + BATTERY_ERROR(Doc.of(Level.FAULT)// + .persistencePriority(PersistencePriority.HIGH)// + .text("Error in the battery!")), // + + SET_STORAGE_CONTROL_MODE(Doc.of(SetControlMode.values())// + .accessMode(AccessMode.READ_WRITE)), // + + SET_OUT_W_RTE(Doc.of(OpenemsType.INTEGER)// + .accessMode(AccessMode.WRITE_ONLY)// + .unit(Unit.NONE)), // + SET_IN_W_RTE(Doc.of(OpenemsType.INTEGER)// + .accessMode(AccessMode.WRITE_ONLY)// + .unit(Unit.NONE)), // + + // SELF_CONSUMPTION_ACTIVATION(Doc.of(OpenemsType.INTEGER) // + // .accessMode(AccessMode.READ_WRITE) // + // .unit(Unit.NONE)), // + ; + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + + } + + /** + * Gets the Channel for {@link ChannelId#CUR_BAT_CHA}. + * + * @return the Channel + */ + public default Channel getCurBatChaChannel() { + return this.channel(ChannelId.CUR_BAT_CHA); + } + + /** + * Gets the Channel for {@link ChannelId#CUR_BAT_DSCH}. + * + * @return the Channel + */ + public default Channel getCurBatDschChannel() { + return this.channel(ChannelId.CUR_BAT_DSCH); + } + + /** + * Gets the Channel for {@link ChannelId#BATTERY_WARNING}. + * + * @return the Channel + */ + public default StateChannel getBatteryWarningChannel() { + return this.channel(ChannelId.BATTERY_WARNING); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#BATTERY_WARNING} + * Channel. + * + * @param value the next value + */ + public default void _setBatteryWarning(boolean value) { + this.getBatteryWarningChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#BATTERY_ERROR}. + * + * @return the Channel + */ + public default StateChannel getBatteryErrorChannel() { + return this.channel(ChannelId.BATTERY_ERROR); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#BATTERY_ERROR} + * Channel. + * + * @param value the next value + */ + public default void _setBatteryError(boolean value) { + this.getBatteryErrorChannel().setNextValue(value); + } + +} diff --git a/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/battery/FroniusGen24BatteryImpl.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/battery/FroniusGen24BatteryImpl.java new file mode 100644 index 00000000000..a3e4b69d038 --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/battery/FroniusGen24BatteryImpl.java @@ -0,0 +1,443 @@ +package io.openems.edge.fronius.gen24.battery; + +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.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +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.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.referencetarget.GenerateTargetsFromReferences; +import io.openems.edge.battery.api.Battery; +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.SignedWordElement; +import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; +import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask; +import io.openems.edge.common.channel.Channel; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.startstop.StartStop; +import io.openems.edge.common.startstop.StartStoppable; +import io.openems.edge.fronius.gen24.batteryinverter.BatteryInverterFroniusGen24; + +@Designate(ocd = Config.class, factory = true) +@Component( + name = "Ess.Fronius.Gen24.Battery", + immediate = true, + configurationPolicy = ConfigurationPolicy.REQUIRE +) +@GenerateTargetsFromReferences("Modbus") +public class FroniusGen24BatteryImpl + extends AbstractOpenemsModbusComponent + implements Battery, + FroniusGen24, + ModbusComponent, + OpenemsComponent { + + private static final long UPDATE_INTERVAL_SECONDS = 1; + + private final Logger log = + LoggerFactory.getLogger(FroniusGen24BatteryImpl.class); + + private Config config; + + private ScheduledExecutorService valueUpdater; + private ScheduledFuture valueUpdaterFuture; + + @Override + @Reference( + policy = STATIC, + policyOption = GREEDY, + cardinality = MANDATORY, + target = "(&(id=${config.modbus_id})(enabled=true))" + ) + protected void setModbus(BridgeModbus modbus) { + super.setModbus(modbus); + } + + @Reference( + policy = STATIC, + policyOption = GREEDY, + cardinality = MANDATORY + ) + private BatteryInverterFroniusGen24 inverter; + + public FroniusGen24BatteryImpl() { + super( + OpenemsComponent.ChannelId.values(), + ModbusComponent.ChannelId.values(), + Battery.ChannelId.values(), + StartStoppable.ChannelId.values(), + FroniusGen24.ChannelId.values() + ); + } + + @Activate + private void activate( + ComponentContext context, + Config config + ) throws OpenemsException { + + this.config = config; + + super.activate( + context, + config.id(), + config.alias(), + config.enabled(), + config.modbusUnitId() + ); + + this.setConfiguredVoltageLimits(); + this.startValueUpdater(config); + } + + @Deactivate + @Override + protected void deactivate() { + + if (this.valueUpdaterFuture != null) { + this.valueUpdaterFuture.cancel(false); + this.valueUpdaterFuture = null; + } + + if (this.valueUpdater != null) { + this.valueUpdater.shutdownNow(); + this.valueUpdater = null; + } + + super.deactivate(); + } + + @Override + protected ModbusProtocol defineModbusProtocol() { + + return new ModbusProtocol( + this, + + new FC16WriteRegistersTask( + 40348, + m( + FroniusGen24.ChannelId.SET_STORAGE_CONTROL_MODE, + new UnsignedWordElement(40348) + ) + ), + + new FC16WriteRegistersTask( + 40355, + m( + FroniusGen24.ChannelId.SET_OUT_W_RTE, + new SignedWordElement(40355) + ), + + m( + FroniusGen24.ChannelId.SET_IN_W_RTE, + new SignedWordElement(40356) + ) + ) + ); + } + + private void startValueUpdater(Config config) { + + this.valueUpdater = Executors.newSingleThreadScheduledExecutor(r -> { + Thread thread = new Thread( + r, + "openems-fronius-gen24-battery-values-" + config.id() + ); + thread.setDaemon(true); + return thread; + }); + + this.valueUpdaterFuture = this.valueUpdater.scheduleWithFixedDelay( + this::updateFromInverterSafely, + 0, + UPDATE_INTERVAL_SECONDS, + TimeUnit.SECONDS + ); + } + + private void updateFromInverterSafely() { + + try { + this.updateAllValues(); + + } catch (Exception e) { + this.log.warn( + "Failed to update Fronius Gen24 battery values", + e + ); + } + } + + private void updateAllValues() { + + this.updateSoc(); + this.updateCurrent(); + this.updateVoltage(); + this.updateChargePower(); + this.updateDischargePower(); + this.updateCapacity(); + this.updatePowerLimits(); + } + + private void updateSoc() { + + this.channel(Battery.ChannelId.SOC) + .setNextValue( + this.toInteger( + this.readFloat(this.inverter::getModuleSOC) + ) + ); + } + + private void updateCurrent() { + + Float module3 = this.readFloat( + this.inverter::getModule3DcaChannel + ); + + Float module4 = this.readFloat( + this.inverter::getModule4DcaChannel + ); + + this.channel(Battery.ChannelId.CURRENT) + .setNextValue( + this.maxAsInteger(module3, module4) + ); + } + + private void updateVoltage() { + + Float module3 = this.readFloat( + this.inverter::getModule3DcVChannel + ); + + Float module4 = this.readFloat( + this.inverter::getModule4DcVChannel + ); + + this.channel(Battery.ChannelId.VOLTAGE) + .setNextValue( + this.maxAsInteger(module3, module4) + ); + } + + private void updateChargePower() { + + this.channel(FroniusGen24.ChannelId.CUR_BAT_CHA) + .setNextValue( + this.toInteger( + this.readFloat( + this.inverter::getModule3DcWChannel + ) + ) + ); + } + + private void updateDischargePower() { + + this.channel(FroniusGen24.ChannelId.CUR_BAT_DSCH) + .setNextValue( + this.toInteger( + this.readFloat( + this.inverter::getModule4DcWChannel + ) + ) + ); + } + + private void updateCapacity() { + + this.channel(Battery.ChannelId.CAPACITY) + .setNextValue( + this.toInteger( + this.readFloat( + this.inverter::getModuleCapacity + ) + ) + ); + } + + private void updatePowerLimits() { + + Float wChaMax = this.readFloat( + this.inverter::getStorageWChaMaxChannel + ); + + Integer voltage = this.readBatteryVoltage(); + + Integer currentLimit = this.calculateCurrentLimit( + wChaMax, + voltage + ); + + this._setChargeMaxCurrent(currentLimit); + this._setDischargeMaxCurrent(currentLimit); + } + + private void setConfiguredVoltageLimits() { + + int chargeMaxVoltage = Math.max( + 1, + this.config.chargeMaxVoltage() + ); + + int dischargeMinVoltage = Math.max( + 1, + this.config.dischargeMinVoltage() + ); + + this._setChargeMaxVoltage(chargeMaxVoltage); + this._setDischargeMinVoltage(dischargeMinVoltage); + } + + private Integer readBatteryVoltage() { + + Value nextValue = + this.getVoltageChannel().getNextValue(); + + if (nextValue.isDefined()) { + return nextValue.get(); + } + + Value value = + this.getVoltageChannel().value(); + + if (value.isDefined()) { + return value.get(); + } + + return null; + } + + private Float readFloat(FloatChannelSupplier channelSupplier) { + + try { + + Channel channel = channelSupplier.get(); + + Value nextValue = channel.getNextValue(); + + if (nextValue.isDefined()) { + return nextValue.get(); + } + + Value value = channel.value(); + + if (value.isDefined()) { + return value.get(); + } + + return null; + + } catch (OpenemsException e) { + return null; + } + } + + private Integer toInteger(Float value) { + + if (value == null) { + return null; + } + + return value.intValue(); + } + + private Integer maxAsInteger( + Float first, + Float second + ) { + + Integer firstValue = this.toInteger(first); + Integer secondValue = this.toInteger(second); + + if (firstValue == null) { + return secondValue; + } + + if (secondValue == null) { + return firstValue; + } + + return Math.max(firstValue, secondValue); + } + + private Integer calculateCurrentLimit( + Float wChaMax, + Integer voltage + ) { + if (voltage == null || voltage <= 0) { + return null; + } + if (wChaMax == null) { + return null; + } + return Math.max(0, (int) Math.floor(Math.abs(wChaMax) / voltage)); + } + + private int getNumberOfModules() { + + return Math.max( + 1, + this.config.numberOfModules() + ); + } + + private float getChargeMaxVoltagePerModule() { + + return (float) this.config.chargeMaxVoltage() + / this.getNumberOfModules(); + } + + private float getDischargeMinVoltagePerModule() { + + return (float) this.config.dischargeMinVoltage() + / this.getNumberOfModules(); + } + + @Override + public void setStartStop(StartStop value) + throws OpenemsNamedException { + + this._setStartStop(value); + } + + @Override + public String debugLog() { + + return "Soc:" + this.getSoc().asString() + + "|V:" + this.getVoltage().asString() + + "|ChaMaxV:" + this.getChargeMaxVoltage().asString() + + "|DschMinV:" + this.getDischargeMinVoltage().asString() + + "|ChaMaxI:" + this.getChargeMaxCurrent().asString() + + "|DschMaxI:" + this.getDischargeMaxCurrent().asString() + + "|Modules:" + this.getNumberOfModules() + + "|ChaMaxV/Mod:" + this.getChargeMaxVoltagePerModule() + + "|DschMinV/Mod:" + this.getDischargeMinVoltagePerModule(); + } + + @FunctionalInterface + private interface FloatChannelSupplier { + + Channel get() throws OpenemsException; + } +} diff --git a/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/ApplyPowerHandler.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/ApplyPowerHandler.java new file mode 100644 index 00000000000..429b75f4be0 --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/ApplyPowerHandler.java @@ -0,0 +1,252 @@ +package io.openems.edge.fronius.gen24.batteryinverter; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.common.channel.EnumWriteChannel; +import io.openems.edge.common.channel.IntegerWriteChannel; +import io.openems.edge.fronius.enums.SetControlMode; +import io.openems.edge.fronius.gen24.battery.FroniusGen24; + +public class ApplyPowerHandler { + + private static final int RATE_100_PERCENT = 10000; + private static final int RATE_HYSTERESIS = 25; + private static final long MIN_WRITE_INTERVAL_MS = 2000; + private static final long KEEP_ALIVE_INTERVAL_MS = 30000; + + // Wechselrichter-Steuerung (Model 123) + // WMaxLimPct erwartet 0..100, wobei 100 = 100 % + private static final int W_MAX_LIM_100_PERCENT = 100; + private static final int W_MAX_LIM_HYSTERESIS = 1; + private static final long W_MAX_LIM_KEEP_ALIVE_MS = 30000; + + private final BatteryInverterFroniusGen24Impl parent; + + private SetControlMode lastControlMode = null; + private Integer lastOutWRte = null; + private Integer lastInWRte = null; + private long lastWriteMillis = 0L; + + // Wechselrichter-Zustand + private Integer lastWMaxLimPct = null; + private int lastWMaxLimEna = -1; // -1 = noch nie geschrieben + private long lastWMaxLimWriteMillis = 0L; + + public ApplyPowerHandler(BatteryInverterFroniusGen24Impl parent) { + this.parent = parent; + } + + public synchronized void apply( + FroniusGen24 battery, + int setActivePower, + int setReactivePower, + ControlMode controlmode + ) throws OpenemsNamedException { + + Result result = switch (controlmode) { + case INTERNAL -> this.handleInternalMode(); + case REMOTE -> this.handleRemoteMode(setActivePower); + }; + + this.parent._setDebugControlMode(result.controlMode()); + + // Wechselrichter-Leistungsbegrenzung – immer prüfen, unabhängig von shouldWrite + var limitOpt = this.parent.getActivePowerLimitChannel().getNextWriteValueAndReset(); + if (limitOpt.isPresent() || this.lastWMaxLimEna == 1) { + this.applyInverterPowerLimit(limitOpt.isPresent() ? limitOpt.get() : null); + } + + // Im INTERNAL-Modus nichts schreiben – Fronius arbeitet autonom + if (result.controlMode() == SetControlMode.DISABLED) { + // Zustand zurücksetzen damit beim nächsten REMOTE-Start alles neu geschrieben wird + this.lastControlMode = null; + this.lastOutWRte = null; + this.lastInWRte = null; + return; + } + + // StorCtl_Mod (40348) jeden Zyklus schreiben im REMOTE-Modus – + // damit der Fronius den Modus nach Neustart/Verbindungsunterbruch sofort kennt + EnumWriteChannel setControlMode = + battery.channel(FroniusGen24.ChannelId.SET_STORAGE_CONTROL_MODE); + setControlMode.setNextWriteValue(result.controlMode()); + + if (!this.shouldWrite(result)) { + return; + } + + IntegerWriteChannel setOutWRte = + battery.channel(FroniusGen24.ChannelId.SET_OUT_W_RTE); + + IntegerWriteChannel setInWRte = + battery.channel(FroniusGen24.ChannelId.SET_IN_W_RTE); + + setOutWRte.setNextWriteValue(result.outWRte()); + setInWRte.setNextWriteValue(result.inWRte()); + + this.rememberWrittenResult(result); + } + + private Result handleInternalMode() { + return new Result(SetControlMode.DISABLED, 0, 0); + } + + private Result handleRemoteMode(int setActivePower) { + + Integer wChaMax = this.readWChaMax(); + + if (wChaMax == null || wChaMax <= 0) { + return this.handleInternalMode(); + } + + int limitedActivePower = clamp(setActivePower, -wChaMax, wChaMax); + + int rate = (int) Math.round( + (double) limitedActivePower / (double) wChaMax * RATE_100_PERCENT + ); + rate = clamp(rate, -RATE_100_PERCENT, RATE_100_PERCENT); + + int outWRte = rate; + int inWRte = rate * (-1); + + return new Result(SetControlMode.CHARGE_AND_DISCHARGE_LIMIT, outWRte, inWRte); + } + + private Integer readWChaMax() { + try { + var channel = this.parent.getStorageWChaMaxChannel(); + var nextValue = channel.getNextValue(); + if (nextValue.isDefined()) { + return Math.max(0, Math.round(Math.abs(nextValue.get()))); + } + var value = channel.value(); + if (value.isDefined()) { + return Math.max(0, Math.round(Math.abs(value.get()))); + } + return null; + } catch (OpenemsException e) { + return null; + } + } + + private boolean shouldWrite(Result result) { + + long now = System.currentTimeMillis(); + + if (this.lastControlMode == null) { + return true; + } + + if (now - this.lastWriteMillis < MIN_WRITE_INTERVAL_MS) { + return false; + } + + if (result.controlMode() != this.lastControlMode) { + return true; + } + + if (this.lastOutWRte == null + || Math.abs(result.outWRte() - this.lastOutWRte) >= RATE_HYSTERESIS) { + return true; + } + + if (this.lastInWRte == null + || Math.abs(result.inWRte() - this.lastInWRte) >= RATE_HYSTERESIS) { + return true; + } + + return now - this.lastWriteMillis >= KEEP_ALIVE_INTERVAL_MS; + } + + private void rememberWrittenResult(Result result) { + this.lastControlMode = result.controlMode(); + this.lastOutWRte = result.outWRte(); + this.lastInWRte = result.inWRte(); + this.lastWriteMillis = System.currentTimeMillis(); + } + + private static int clamp(int value, int min, int max) { + return Math.max(min, Math.min(max, value)); + } + + private static record Result( + SetControlMode controlMode, + int outWRte, + int inWRte + ) { + } + + // ========================================================================= + // Wechselrichter-Leistungsbegrenzung (SunSpec Model 123) + // ========================================================================= + + private void applyInverterPowerLimit(Integer limitW) { + + if (limitW != null) { + Integer wMaxLimPct = this.convertWattsToPct(limitW); + if (wMaxLimPct != null) { + this.writeWMaxLim(wMaxLimPct, 1); + } + } else { + // Kein Setpoint → Begrenzung deaktivieren + this.writeWMaxLim(W_MAX_LIM_100_PERCENT, 0); + } + } + + private Integer convertWattsToPct(int limitW) { + try { + var wMaxChannel = this.parent.getWMaxChannel(); + Float wMax = wMaxChannel.getNextValue().isDefined() + ? (Float) wMaxChannel.getNextValue().get() + : wMaxChannel.value().isDefined() + ? (Float) wMaxChannel.value().get() + : null; + + if (wMax == null || wMax <= 0) { + return null; + } + + int pct = (int) Math.round( + (double) clamp(limitW, 0, Math.round(wMax)) + / wMax + * W_MAX_LIM_100_PERCENT + ); + + return clamp(pct, 0, W_MAX_LIM_100_PERCENT); + + } catch (OpenemsException e) { + return null; + } + } + + private void writeWMaxLim(int wMaxLimPct, int ena) { + + wMaxLimPct = clamp(wMaxLimPct, 0, W_MAX_LIM_100_PERCENT); + ena = ena == 0 ? 0 : 1; + + long now = System.currentTimeMillis(); + + boolean enaChanged = ena != this.lastWMaxLimEna; + boolean pctChanged = this.lastWMaxLimPct == null + || Math.abs(wMaxLimPct - this.lastWMaxLimPct) >= W_MAX_LIM_HYSTERESIS; + boolean keepAlive = now - this.lastWMaxLimWriteMillis >= W_MAX_LIM_KEEP_ALIVE_MS; + + if (!enaChanged && !pctChanged && !keepAlive) { + return; + } + + try { + this.parent.writeWMaxLimPct(wMaxLimPct); + this.parent.writeWMaxLimEna(ena); + this.parent._setDebugWMaxLimPct(wMaxLimPct); + this.parent._setDebugWMaxLimEna(ena); + + this.lastWMaxLimPct = wMaxLimPct; + this.lastWMaxLimEna = ena; + this.lastWMaxLimWriteMillis = now; + + } catch (OpenemsNamedException e) { + // S123 nicht verfügbar – kein fataler Fehler, Batteriesteuerung läuft weiter + } + } +} \ No newline at end of file diff --git a/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/BatteryInverterFroniusGen24.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/BatteryInverterFroniusGen24.java new file mode 100644 index 00000000000..e44b4628d07 --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/BatteryInverterFroniusGen24.java @@ -0,0 +1,253 @@ +package io.openems.edge.fronius.gen24.batteryinverter; + +import io.openems.common.channel.Level; +import io.openems.common.channel.PersistencePriority; +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.batteryinverter.api.HybridManagedSymmetricBatteryInverter; +import io.openems.edge.batteryinverter.api.ManagedSymmetricBatteryInverter; +import io.openems.edge.batteryinverter.api.SymmetricBatteryInverter; +import io.openems.edge.bridge.modbus.api.ModbusComponent; +import io.openems.edge.common.channel.Channel; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.StateChannel; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.startstop.StartStoppable; +import io.openems.edge.fronius.enums.SetControlMode; +import io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter; + +public interface BatteryInverterFroniusGen24 extends HybridManagedSymmetricBatteryInverter, ManagedSymmetricBatteryInverter, + SymmetricBatteryInverter, StartStoppable, ModbusComponent, ManagedSymmetricPvInverter, OpenemsComponent { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + + INITIALIZING(Doc.of(Level.WARNING)// + .text("Initializing Sunspec Protocol")), // + WRONG_BATTERY(Doc.of(Level.FAULT)// + .text("Failed to run inverter. Battery is not a Fronius Gen24 battery component.")), // + DEBUG_CONTROL_MODE(Doc.of(SetControlMode.values())// + .persistencePriority(PersistencePriority.HIGH)), // + CONFIGURED_CONTROL_MODE(Doc.of(ControlMode.values())// + .persistencePriority(PersistencePriority.HIGH)), // + + DEBUG_W_MAX_LIM_PCT(Doc.of(io.openems.common.types.OpenemsType.INTEGER)// + .text("Zuletzt geschriebener WMaxLimPct-Wert (SunSpec S123, Skala 0-10000)")), // + + DEBUG_W_MAX_LIM_ENA(Doc.of(io.openems.common.types.OpenemsType.INTEGER)// + .text("WMaxLim_Ena: 1=aktiv, 0=deaktiviert")), // + ; + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + + } + + /** + * Gets the Channel for {@link ChannelId#INITIALIZING}. + * + * @return the Channel + */ + public default StateChannel getInitializingChannel() { + return this.channel(ChannelId.INITIALIZING); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#INITIALIZING} + * Channel. + * + * @param value the next value + */ + public default void _setInitializing(boolean value) { + this.getInitializingChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#WRONG_BATTERY}. + * + * @return the Channel + */ + public default StateChannel getWrongBatteryChannel() { + return this.channel(ChannelId.WRONG_BATTERY); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#WRONG_BATTERY} + * Channel. + * + * @param value the next value + */ + public default void _setWrongBattery(boolean value) { + this.getWrongBatteryChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#DEBUG_CONTROL_MODE}. + * + * @return the Channel + */ + public default Channel getDebugControlModeChannel() { + return this.channel(ChannelId.DEBUG_CONTROL_MODE); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#DEBUG_CONTROL_MODE} Channel. + * + * @param value the next value + */ + public default void _setDebugControlMode(SetControlMode value) { + this.getDebugControlModeChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#CONFIGURED_CONTROL_MODE}. + * + * @return the Channel + */ + public default Channel getConfiguredControlModeChannel() { + return this.channel(ChannelId.CONFIGURED_CONTROL_MODE); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#CONFIGURED_CONTROL_MODE} Channel. + * + * @param value the next value + */ + public default void _setConfiguredControlMode(ControlMode value) { + this.getConfiguredControlModeChannel().setNextValue(value); + } + + /** + * Gets the SunSpec Channel S160Module1DCW. + * + * @return the Channel + * @throws OpenemsException if the Channel is not present + */ + public Channel getModule1DcwChannel() throws OpenemsException; + + /** + * Gets the SunSpec Channel S160Module1DCA. + * + * @return the Channel + * @throws OpenemsException if the Channel is not present + */ + public Channel getModule1DcaChannel() throws OpenemsException; + + /** + * Gets the SunSpec Channel S160Module1DCV. + * + * @return the Channel + * @throws OpenemsException if the Channel is not present + */ + public Channel getModule1DcvChannel() throws OpenemsException; + + /** + * Gets the SunSpec Channel S160Module2DCW. + * + * @return the Channel + * @throws OpenemsException if the Channel is not present + */ + public Channel getModule2DcwChannel() throws OpenemsException; + + /** + * Gets the SunSpec Channel S160Module2DCA. + * + * @return the Channel + * @throws OpenemsException if the Channel is not present + */ + public Channel getModule2DcaChannel() throws OpenemsException; + + /** + * Gets the SunSpec Channel S160Module2DCV. + * + * @return the Channel + * @throws OpenemsException if the Channel is not present + */ + public Channel getModule2DcvChannel() throws OpenemsException; + + /** + * Asks if the SunSpec initialization is completed. + * + * @return true, if the SunSpec initialization is completed + */ + public Channel getModuleSOC() throws OpenemsException; + public Channel getModule3DcaChannel() throws OpenemsException; + public Channel getModule4DcaChannel() throws OpenemsException; + public Channel getModule3DcVChannel() throws OpenemsException; + public Channel getModule4DcVChannel() throws OpenemsException; + public Channel getModule3DcWChannel() throws OpenemsException; + public Channel getModule4DcWChannel() throws OpenemsException; + public Channel getModule3DcWHChannel() throws OpenemsException; + public Channel getModule4DcWHChannel() throws OpenemsException; + public Channel getModuleCapacity() throws OpenemsException; + public Channel getStorageWChaMaxChannel() throws OpenemsException; + + //public Channel getStorageOutWRteChannel() throws OpenemsException; + // public Channel getStorageInWRteChannel() throws OpenemsException; +// public Channel getStorageBatteryVoltageChannel() throws OpenemsException; + + + + + public boolean isInitialized(); + + // ------------------------------------------------------------------------- + // Konfliktauflösung: ManagedSymmetricPvInverter vs SymmetricBatteryInverter + // Re-Deklaration als abstrakt zwingt die Impl zur eindeutigen Implementierung + // ------------------------------------------------------------------------- + + @Override + io.openems.edge.common.channel.IntegerReadChannel getActivePowerChannel(); + + @Override + io.openems.edge.common.channel.IntegerReadChannel getReactivePowerChannel(); + + @Override + io.openems.edge.common.channel.IntegerReadChannel getMaxApparentPowerChannel(); + + @Override + boolean isManaged(); + + @Override + void _setActivePower(Integer value); + + @Override + void _setActivePower(int value); + + @Override + void _setReactivePower(Integer value); + + @Override + void _setReactivePower(int value); + + @Override + void _setMaxApparentPower(Integer value); + + @Override + void _setMaxApparentPower(int value); + + @Override + io.openems.edge.common.channel.value.Value getActivePower(); + + @Override + io.openems.edge.common.channel.value.Value getReactivePower(); + + @Override + io.openems.edge.common.channel.value.Value getMaxApparentPower(); + + public default void _setDebugWMaxLimPct(int value) { + this.channel(ChannelId.DEBUG_W_MAX_LIM_PCT).setNextValue(value); + } + + public default void _setDebugWMaxLimEna(int value) { + this.channel(ChannelId.DEBUG_W_MAX_LIM_ENA).setNextValue(value); + } +} diff --git a/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/BatteryInverterFroniusGen24Impl.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/BatteryInverterFroniusGen24Impl.java new file mode 100644 index 00000000000..cb5b57cc2ea --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/BatteryInverterFroniusGen24Impl.java @@ -0,0 +1,679 @@ +package io.openems.edge.fronius.gen24.batteryinverter; + +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.Map; +import java.util.function.Consumer; + +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableMap; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.referencetarget.GenerateTargetsFromReferences; +import io.openems.edge.battery.api.Battery; +import io.openems.edge.batteryinverter.api.HybridManagedSymmetricBatteryInverter; +import io.openems.edge.batteryinverter.api.ManagedSymmetricBatteryInverter; +import io.openems.edge.batteryinverter.api.SymmetricBatteryInverter; +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.sunspec.DefaultSunSpecModel; +import io.openems.edge.bridge.modbus.sunspec.SunSpecModel; +import io.openems.edge.bridge.modbus.sunspec.batteryinverter.AbstractSunSpecBatteryInverter; +import io.openems.edge.common.channel.Channel; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.startstop.StartStop; +import io.openems.edge.common.startstop.StartStoppable; +import io.openems.edge.common.sum.GridMode; +import io.openems.edge.common.taskmanager.Priority; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.ess.power.api.Power; +import io.openems.edge.fronius.gen24.battery.FroniusGen24; +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 = "Ess.Fronius.Gen24.Inverter", + immediate = true, + configurationPolicy = ConfigurationPolicy.REQUIRE, + service = { + BatteryInverterFroniusGen24.class, + HybridManagedSymmetricBatteryInverter.class, + ManagedSymmetricBatteryInverter.class, + SymmetricBatteryInverter.class, + StartStoppable.class, + ModbusComponent.class, + ManagedSymmetricPvInverter.class, + OpenemsComponent.class + } +) +@GenerateTargetsFromReferences("Modbus") +public class BatteryInverterFroniusGen24Impl extends AbstractSunSpecBatteryInverter + implements BatteryInverterFroniusGen24, + HybridManagedSymmetricBatteryInverter, + ManagedSymmetricBatteryInverter, + SymmetricBatteryInverter, + StartStoppable, + ModbusComponent, + TimedataProvider, + OpenemsComponent { + + private final Logger log = + LoggerFactory.getLogger(BatteryInverterFroniusGen24Impl.class); + + private static final int READ_FROM_MODBUS_BLOCK = 1; + + private static final Map ACTIVE_MODELS = + ImmutableMap.builder() + .put(DefaultSunSpecModel.S_1, Priority.LOW) + .put(DefaultSunSpecModel.S_103, Priority.HIGH) + .put(DefaultSunSpecModel.S_120, Priority.LOW) + .put(DefaultSunSpecModel.S_121, Priority.LOW) + .put(DefaultSunSpecModel.S_122, Priority.LOW) + .put(DefaultSunSpecModel.S_123, Priority.LOW) + .put(DefaultSunSpecModel.S_124, Priority.LOW) + .put(S160SunSpecModel.S_160, Priority.HIGH) + .build(); + + @Reference + protected Power power; + + @Reference( + policy = ReferencePolicy.DYNAMIC, + policyOption = ReferencePolicyOption.GREEDY, + cardinality = ReferenceCardinality.OPTIONAL + ) + private volatile Timedata timedata = null; + + private final CalculateEnergyFromPower calculateActiveChargeEnergy = + new CalculateEnergyFromPower( + this, + SymmetricBatteryInverter.ChannelId.ACTIVE_CHARGE_ENERGY); + + private final CalculateEnergyFromPower calculateActiveDischargeEnergy = + new CalculateEnergyFromPower( + this, + SymmetricBatteryInverter.ChannelId.ACTIVE_DISCHARGE_ENERGY); + + private final CalculateEnergyFromPower calculateDcChargeEnergy = + new CalculateEnergyFromPower( + this, + HybridManagedSymmetricBatteryInverter.ChannelId.DC_CHARGE_ENERGY); + + private final CalculateEnergyFromPower calculateDcDischargeEnergy = + new CalculateEnergyFromPower( + this, + HybridManagedSymmetricBatteryInverter.ChannelId.DC_DISCHARGE_ENERGY); + + private final ApplyPowerHandler applyPowerHandler = + new ApplyPowerHandler(this); + + private Config config; + + public BatteryInverterFroniusGen24Impl() throws OpenemsException { + + super( + ACTIVE_MODELS, + OpenemsComponent.ChannelId.values(), + ModbusComponent.ChannelId.values(), + SymmetricBatteryInverter.ChannelId.values(), + ManagedSymmetricBatteryInverter.ChannelId.values(), + HybridManagedSymmetricBatteryInverter.ChannelId.values(), + StartStoppable.ChannelId.values(), + new io.openems.edge.common.channel.ChannelId[] { + ManagedSymmetricPvInverter.ChannelId.ACTIVE_POWER_LIMIT + }, + BatteryInverterFroniusGen24.ChannelId.values() + ); + } + + @Override + @Reference( + policy = STATIC, + policyOption = GREEDY, + cardinality = MANDATORY, + target = "(&(id=${config.modbus_id})(enabled=true))" + ) + protected void setModbus(BridgeModbus modbus) { + super.setModbus(modbus); + } + + @Activate + private void activate(ComponentContext context, Config config) + throws OpenemsException { + + this.config = config; + + super.activate( + context, + config.id(), + config.alias(), + config.enabled(), + config.modbusUnitId(), + READ_FROM_MODBUS_BLOCK + ); + + this._setGridMode(GridMode.ON_GRID); + this._setConfiguredControlMode(config.controlMode()); + this._setInitializing(true); + } + + @Override + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + @Override + protected void onSunSpecInitializationCompleted() { + + this.logInfo( + this.log, + "SunSpec initialization finished. " + + this.channels().size() + + " Channels available." + ); + + this.mapFirstPointToChannel( + SymmetricBatteryInverter.ChannelId.ACTIVE_POWER, + ElementToChannelConverter.DIRECT_1_TO_1, + DefaultSunSpecModel.S103.W + ); + + this.mapFirstPointToChannel( + SymmetricBatteryInverter.ChannelId.REACTIVE_POWER, + ElementToChannelConverter.DIRECT_1_TO_1, + DefaultSunSpecModel.S103.V_AR + ); + + this.mapFirstPointToChannel( + SymmetricBatteryInverter.ChannelId.MAX_APPARENT_POWER, + ElementToChannelConverter.DIRECT_1_TO_1, + DefaultSunSpecModel.S121.W_MAX + ); + + this.installListeners(); + + this._setInitializing(false); + } + + @Override + public void run( + Battery battery, + int setActivePower, + int setReactivePower + ) throws OpenemsNamedException { + + this.calculateEnergy(); + + if (!(battery instanceof FroniusGen24 froniusBattery)) { + + this.log.warn( + "Unsupported battery type: {}", + battery.getClass().getSimpleName() + ); + + return; + } + + this.applyPowerHandler.apply( + froniusBattery, + setActivePower, + setReactivePower, + this.config.controlMode() + ); + } + + private void recalculateDcDischargePower() { + + try { + + int chargePower = + this.getModule3DcwChannel() + .getNextValue() + .orElse(0F) + .intValue(); + + int dischargePower = + this.getModule4DcwChannel() + .getNextValue() + .orElse(0F) + .intValue(); + + int batteryPower = dischargePower - chargePower; + + this._setDcDischargePower(batteryPower); + + if (batteryPower > 0) { + + this.log.info( + "Battery DISCHARGING with {} W " + + "[charge={} W, discharge={} W]", + batteryPower, + chargePower, + dischargePower + ); + + } else if (batteryPower < 0) { + + this.log.info( + "Battery CHARGING with {} W " + + "[charge={} W, discharge={} W]", + Math.abs(batteryPower), + chargePower, + dischargePower + ); + + } else { + + this.log.info( + "Battery IDLE " + + "[charge={} W, discharge={} W]", + chargePower, + dischargePower + ); + } + + } catch (OpenemsException e) { + + this.log.warn( + "Failed to calculate DC battery power", + e + ); + } + } + + private void installListeners() { + + final Consumer> calculateFloat = ignore -> { + this.recalculateDcDischargePower(); + }; + + try { + + this.getModule1DcwChannel() + .onSetNextValue(calculateFloat); + + this.getModule2DcwChannel() + .onSetNextValue(calculateFloat); + + this.getModule3DcwChannel() + .onSetNextValue(calculateFloat); + + this.getModule4DcwChannel() + .onSetNextValue(calculateFloat); + + } catch (OpenemsException e) { + + this.log.warn( + "Failed to install listeners", + e + ); + } + } + + private void calculateEnergy() { + + var activePower = this.getActivePower().get(); + + if (activePower == null) { + + this.calculateActiveChargeEnergy.update(null); + this.calculateActiveDischargeEnergy.update(null); + + } else if (activePower > 0) { + + this.calculateActiveChargeEnergy.update(0); + this.calculateActiveDischargeEnergy.update(activePower); + + } else { + + this.calculateActiveChargeEnergy.update(activePower * -1); + this.calculateActiveDischargeEnergy.update(0); + } + + var dcPower = this.getDcDischargePower().get(); + + if (dcPower == null) { + + this.calculateDcChargeEnergy.update(null); + this.calculateDcDischargeEnergy.update(null); + + } else if (dcPower > 0) { + + this.calculateDcChargeEnergy.update(0); + this.calculateDcDischargeEnergy.update(dcPower); + + } else { + + this.calculateDcChargeEnergy.update(dcPower * -1); + this.calculateDcDischargeEnergy.update(0); + } + } + + @Override + public Integer getSurplusPower() { + return 0; + } + + @Override + public String debugLog() { + return "|L:" + this.getActivePower().asString(); + } + + @Override + public int getPowerPrecision() { + return 1; + } + + @Override + public void setStartStop(StartStop value) + throws OpenemsNamedException { + + this._setStartStop(value); + } + + @Override + public Integer getDcPvPower() { + + try { + + return this.getModule1DcwChannel() + .value() + .orElse(0F) + .intValue() + + this.getModule2DcwChannel() + .value() + .orElse(0F) + .intValue(); + + } catch (OpenemsException e) { + + return null; + } + } + + @Override + public Timedata getTimedata() { + return this.timedata; + } + + @Override + public Channel getModuleSOC() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + DefaultSunSpecModel.S124.CHA_STATE + ); + } + + @Override + public Channel getModule1DcwChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_1_D_C_W + ); + } + + @Override + public Channel getModule1DcaChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_1_D_C_A + ); + } + + @Override + public Channel getModule1DcvChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_1_D_C_V + ); + } + + @Override + public Channel getModule2DcwChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_2_D_C_W + ); + } + + @Override + public Channel getModule2DcaChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_2_D_C_A + ); + } + + @Override + public Channel getModule2DcvChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_2_D_C_V + ); + } + + public Channel getModule3DcwChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_3_D_C_W + ); + } + + public Channel getModule4DcwChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_4_D_C_W + ); + } + + public Channel getModule3DcaChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_3_D_C_A + ); + } + + public Channel getModule4DcaChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_4_D_C_A + ); + } + + public Channel getModule3DcVChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_3_D_C_V + ); + } + + public Channel getModule4DcVChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_4_D_C_V + ); + } + + public Channel getModule3DcWChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_3_D_C_W + ); + } + + public Channel getModule4DcWChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_4_D_C_W + ); + } + + public Channel getModule3DcWHChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_3_D_C_W_H + ); + } + + public Channel getModule4DcWHChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + S160SunSpecModel.S160.MODULE_4_D_C_W_H + ); + } + + public Channel getModuleCapacity() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + DefaultSunSpecModel.S120.W_H_RTG + ); + } + + public Channel getStorageWChaMaxChannel() + throws OpenemsException { + + return this.getSunSpecChannelOrError( + DefaultSunSpecModel.S124.W_CHA_MAX + ); + } + + @Override + public boolean isInitialized() { + return this.isSunSpecInitializationCompleted(); + } + + @Override + public io.openems.edge.common.channel.IntegerReadChannel getActivePowerChannel() { + return this.channel(SymmetricBatteryInverter.ChannelId.ACTIVE_POWER); + } + + @Override + public io.openems.edge.common.channel.IntegerReadChannel getReactivePowerChannel() { + return this.channel(SymmetricBatteryInverter.ChannelId.REACTIVE_POWER); + } + + @Override + public io.openems.edge.common.channel.IntegerReadChannel getMaxApparentPowerChannel() { + return this.channel(SymmetricBatteryInverter.ChannelId.MAX_APPARENT_POWER); + } + + @Override + public boolean isManaged() { + return true; + } + + @Override + public void _setActivePower(Integer value) { + this.channel(SymmetricBatteryInverter.ChannelId.ACTIVE_POWER).setNextValue(value); + } + + @Override + public void _setActivePower(int value) { + this.channel(SymmetricBatteryInverter.ChannelId.ACTIVE_POWER).setNextValue(value); + } + + @Override + public void _setReactivePower(Integer value) { + this.channel(SymmetricBatteryInverter.ChannelId.REACTIVE_POWER).setNextValue(value); + } + + @Override + public void _setReactivePower(int value) { + this.channel(SymmetricBatteryInverter.ChannelId.REACTIVE_POWER).setNextValue(value); + } + + @Override + public void _setMaxApparentPower(Integer value) { + this.channel(SymmetricBatteryInverter.ChannelId.MAX_APPARENT_POWER).setNextValue(value); + } + + @Override + public void _setMaxApparentPower(int value) { + this.channel(SymmetricBatteryInverter.ChannelId.MAX_APPARENT_POWER).setNextValue(value); + } + + @Override + public io.openems.edge.common.channel.value.Value getActivePower() { + return (io.openems.edge.common.channel.value.Value) + this.channel(SymmetricBatteryInverter.ChannelId.ACTIVE_POWER).value(); + } + + @Override + public io.openems.edge.common.channel.value.Value getReactivePower() { + return (io.openems.edge.common.channel.value.Value) + this.channel(SymmetricBatteryInverter.ChannelId.REACTIVE_POWER).value(); + } + + @Override + public io.openems.edge.common.channel.value.Value getMaxApparentPower() { + return (io.openems.edge.common.channel.value.Value) + this.channel(SymmetricBatteryInverter.ChannelId.MAX_APPARENT_POWER).value(); + } + + // ------------------------------------------------------------------------- + // Package-sichtbare Hilfsmethoden fuer ApplyPowerHandler (WMaxLim S123) + // ------------------------------------------------------------------------- + + /** S121.WMax - Nennleistung des Wechselrichters. */ + Channel getWMaxChannel() throws OpenemsException { + return this.getSunSpecChannelOrError(DefaultSunSpecModel.S121.W_MAX); + } + + /** S123.WMaxLimPct schreiben. Wert 0..100 %, wird als FloatWriteChannel geschrieben. */ + @SuppressWarnings("unchecked") + void writeWMaxLimPct(int value) throws OpenemsNamedException { + int pct = Math.max(0, Math.min(100, value)); + + ((io.openems.edge.common.channel.FloatWriteChannel) + this.getSunSpecChannelOrError(DefaultSunSpecModel.S123.W_MAX_LIM_PCT)) + .setNextWriteValue((float) pct); + } + + /** S123.WMaxLim_Ena schreiben. Erwartet enum 0/1. */ + @SuppressWarnings("unchecked") + void writeWMaxLimEna(int value) throws OpenemsNamedException { + int ena = value == 0 ? 0 : 1; + + ((io.openems.edge.common.channel.EnumWriteChannel) + this.getSunSpecChannelOrError(DefaultSunSpecModel.S123.W_MAX_LIM_ENA)) + .setNextWriteValue(ena); + } +} \ No newline at end of file diff --git a/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/Config.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/Config.java new file mode 100644 index 00000000000..41e520a099d --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/Config.java @@ -0,0 +1,31 @@ +package io.openems.edge.fronius.gen24.batteryinverter; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(// + name = "ESS Fronius Gen24 Hybrid", // + description = "Implements the Fronius Gen24 hybrid inverter." + + "ONLY WORKS with MODBUS Server ENABLED AND INT+SF Setting.") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "batteryInverter0"; + + @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") + String alias() default ""; + + @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") + boolean enabled() default true; + + @AttributeDefinition(name = "Modbus-ID", description = "ID of Modbus bridge.") + String modbus_id() default "modbus0"; + + @AttributeDefinition(name = "Modbus-Unit-ID", description = "Modbus Unit-ID (Unit-ID).") + int modbusUnitId() default 1; + + @AttributeDefinition(name = "Control mode", description = "Sets the Control mode. \"Internal\" allows no power setpoints, " + + "\"Remote\" allows full control by OpenEMS") + ControlMode controlMode() default ControlMode.INTERNAL; + String webconsole_configurationFactory_nameHint() default "ESS Hybrid Fronius Inverter [{id}]"; +} diff --git a/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/ControlMode.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/ControlMode.java new file mode 100644 index 00000000000..d5abd5eedf7 --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/ControlMode.java @@ -0,0 +1,43 @@ +package io.openems.edge.fronius.gen24.batteryinverter; + +import io.openems.common.types.OptionsEnum; + +public enum ControlMode implements OptionsEnum { + + /** + * Full control of the Fronius Gen24 storage control registers by OpenEMS. + */ + REMOTE(1, "Remote"), + /** + * Disables remote storage control setpoints. + */ + INTERNAL(2, "Internal"); + /** + * Uses internal mode while the requested setpoint would not change the current + * grid balancing, otherwise switches to remote control. + */ + + private final int value; + private final String name; + + private ControlMode(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 INTERNAL; + } + +} diff --git a/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/S160SunSpecModel.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/S160SunSpecModel.java new file mode 100644 index 00000000000..5a711f375e3 --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/batteryinverter/S160SunSpecModel.java @@ -0,0 +1,677 @@ +// CHECKSTYLE:OFF +package io.openems.edge.fronius.gen24.batteryinverter; + +import io.openems.common.channel.AccessMode; +import io.openems.common.channel.Unit; +import io.openems.common.types.OptionsEnum; +import io.openems.edge.bridge.modbus.sunspec.Point; +import io.openems.edge.bridge.modbus.sunspec.Point.BitFieldPoint; +import io.openems.edge.bridge.modbus.sunspec.Point.BitFieldPoint.SunSpecBitPoint; +import io.openems.edge.bridge.modbus.sunspec.Point.BitPoint; +import io.openems.edge.bridge.modbus.sunspec.Point.EnumPoint; +import io.openems.edge.bridge.modbus.sunspec.Point.ScaleFactorPoint; +import io.openems.edge.bridge.modbus.sunspec.Point.ScaledValuePoint; +import io.openems.edge.bridge.modbus.sunspec.Point.ValuePoint; +import io.openems.edge.bridge.modbus.sunspec.SunSpecModel; +import io.openems.edge.bridge.modbus.sunspec.SunSpecModelType; +import io.openems.edge.bridge.modbus.sunspec.SunSpecPoint; + +//TODO same as FroniusSunspecModel - merge together +public enum S160SunSpecModel implements SunSpecModel { + S_160(// + "Mutliple MPPT Inverter Extension Model", // + "Mutliple MPPT Inverter Extension Model", // + "", // + 88, // + S160SunSpecModel.S160.values(), // + SunSpecModelType.INVERTER// + ); // + + public static enum S160 implements SunSpecPoint { + DCA_SF(new ScaleFactorPoint(// + "S160_DCA_S_F", // + "Dca_SF", // + "")), // + DCV_SF(new ScaleFactorPoint(// + "S160_DCV_S_F", // + "Dcv_SF", // + "")), // + DCW_SF(new ScaleFactorPoint(// + "S160_DCW_S_F", // + "Dcw_SF", // + "")), // + DCWH_SF(new ScaleFactorPoint(// + "S160_DCWH_S_F", // + "Dcwh_SF", // + "")), // + EVT(new BitFieldPoint(// + "S160_EVT", // + "Evt", // + "Global Events", // + BitFieldPoint.Type.BITFIELD32, // + false, // + AccessMode.READ_ONLY, // + S160_Evt.values())), // + N(new ValuePoint(// + "S160_N", // + "N", // + "Number of Modules", // + ValuePoint.Type.COUNT, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE)), // + TMS_PER(new ValuePoint(// + "S160_TMS_PER", // + "TmsPer", // + "Timestamp Period", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE)), // + + MODULE_1_I_D(new ValuePoint(// + "S160_MODULE_1_I_D", // + "Module1Id", // + "Input ID", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE)), // + MODULE_1_I_D_STR(new ValuePoint(// + "S160_MODULE_1_I_D_STR", // + "Module1IdStr", // + "Input ID String", // + ValuePoint.Type.STRING8, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE)), // + MODULE_1_D_C_A(new ScaledValuePoint(// + "S160_MODULE_1_D_C_A", // + "Module1Dca", // + "DC Current", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.AMPERE, // + "DCA_SF")), // + MODULE_1_D_C_V(new ScaledValuePoint(// + "S160_MODULE_1_D_C_V", // + "Module1Dcv", // + "DC Voltage", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "DCV_SF")), // + MODULE_1_D_C_W(new ScaledValuePoint(// + "S160_MODULE_1_D_C_W", // + "Module1Dcw", // + "DC Power", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT, // + "DCW_SF")), // + MODULE_1_D_C_W_H(new ScaledValuePoint(// + "S160_MODULE_1_D_C_W_H", // + "Module1Dcwh", // + "Lifetime Energy", // + ValuePoint.Type.ACC32, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT_HOURS, // + "DCWH_SF")), // + MODULE_1_TMS(new ValuePoint(// + "S160_MODULE_1_TMS", // + "Module1Timestamp", // + "Timestamp", // + ValuePoint.Type.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.SECONDS)), // + MODULE_1_TMP(new ValuePoint(// + "S160_MODULE_1_TEMP", // + "Module1Temp", // + "Temperature", // + ValuePoint.Type.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.DEGREE_CELSIUS)), // + MODULE_1_D_C_ST(new EnumPoint(// + "S160_MODULE_1_OP_STATES", // + "Module1OpStates", // + "Operating State", // + EnumPoint.Type.ENUM16, // + false, // + AccessMode.READ_ONLY, // + S160_DCSt.values())), // + MODULE_1_EVT(new BitFieldPoint(// + "S160_MODULE_1_EVT", // + "Evt", // + "Module Events", // + BitFieldPoint.Type.BITFIELD32, // + false, // + AccessMode.READ_ONLY, // + S160_Module_1_Evt.values())), // + + MODULE_2_I_D(new ValuePoint(// + "S160_MODULE_2_I_D", // + "Module2Id", // + "Input ID", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE)), // + MODULE_2_I_D_STR(new ValuePoint(// + "S160_MODULE_2_I_D_STR", // + "Module2IdStr", // + "Input ID String", // + ValuePoint.Type.STRING8, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE)), // + MODULE_2_D_C_A(new ScaledValuePoint(// + "S160_MODULE_2_D_C_A", // + "Module2Dca", // + "DC Current", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.AMPERE, // + "DCA_SF")), // + MODULE_2_D_C_V(new ScaledValuePoint(// + "S160_MODULE_2_D_C_V", // + "Module2Dcv", // + "DC Voltage", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "DCV_SF")), // + MODULE_2_D_C_W(new ScaledValuePoint(// + "S160_MODULE_2_D_C_W", // + "Module2Dcw", // + "DC Power", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT, // + "DCW_SF")), // + MODULE_2_D_C_W_H(new ScaledValuePoint(// + "S160_MODULE_2_D_C_W_H", // + "Module2Dcwh", // + "Lifetime Energy", // + ValuePoint.Type.ACC32, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT_HOURS, // + "DCWH_SF")), // + MODULE_2_TMS(new ValuePoint(// + "S160_MODULE_2_TMS", // + "Module2Timestamp", // + "Timestamp", // + ValuePoint.Type.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.SECONDS)), // + MODULE_2_TMP(new ValuePoint(// + "S160_MODULE_2_TEMP", // + "Module2Temp", // + "Temperature", // + ValuePoint.Type.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.DEGREE_CELSIUS)), // + MODULE_2_D_C_ST(new EnumPoint(// + "S160_MODULE_2_OP_STATES", // + "Module2OpStates", // + "Operating State", // + EnumPoint.Type.ENUM16, // + false, // + AccessMode.READ_ONLY, // + S160_DCSt.values())), // + MODULE_2_EVT(new BitFieldPoint(// + "S160_MODULE_2_EVT", // + "Evt", // + "Module Events", // + BitFieldPoint.Type.BITFIELD32, // + false, // + AccessMode.READ_ONLY, // + S160_Module_2_Evt.values())), // + + MODULE_3_I_D(new ValuePoint(// + "S160_MODULE_3_I_D", // + "Module2Id", // + "Input ID", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE)), // + MODULE_3_I_D_STR(new ValuePoint(// + "S160_MODULE_3_I_D_STR", // + "Module3IdStr", // + "Input ID String", // + ValuePoint.Type.STRING8, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE)), // + MODULE_3_D_C_A(new ScaledValuePoint(// + "S160_MODULE_3_D_C_A", // + "Module3Dca", // + "DC Current", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.AMPERE, // + "DCA_SF")), // + MODULE_3_D_C_V(new ScaledValuePoint(// + "S160_MODULE_3_D_C_V", // + "Module3Dcv", // + "DC Voltage", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "DCV_SF")), // + MODULE_3_D_C_W(new ScaledValuePoint(// + "S160_MODULE_3_D_C_W", // + "Module3Dcw", // + "DC Power", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT, // + "DCW_SF")), // + MODULE_3_D_C_W_H(new ScaledValuePoint(// + "S160_MODULE_3_D_C_W_H", // + "Module3Dcwh", // + "Lifetime Energy", // + ValuePoint.Type.ACC32, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT_HOURS, // + "DCWH_SF")), // + MODULE_3_TMS(new ValuePoint(// + "S160_MODULE_3_TMS", // + "Module3Timestamp", // + "Timestamp", // + ValuePoint.Type.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.SECONDS)), // + MODULE_3_TMP(new ValuePoint(// + "S160_MODULE_3_TEMP", // + "Module3Temp", // + "Temperature", // + ValuePoint.Type.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.DEGREE_CELSIUS)), // + MODULE_3_D_C_ST(new EnumPoint(// + "S160_MODULE_3_OP_STATES", // + "Module3OpStates", // + "Operating State", // + EnumPoint.Type.ENUM16, // + false, // + AccessMode.READ_ONLY, // + S160_DCSt.values())), // + MODULE_3_EVT(new BitFieldPoint(// + "S160_MODULE_3_EVT", // + "Evt", // + "Module Events", // + BitFieldPoint.Type.BITFIELD32, // + false, // + AccessMode.READ_ONLY, // + S160_Module_3_Evt.values())), // + + MODULE_4_I_D(new ValuePoint(// + "S160_MODULE_4_I_D", // + "Module4Id", // + "Input ID", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE)), // + MODULE_4_I_D_STR(new ValuePoint(// + "S160_MODULE_4_I_D_STR", // + "Module4IdStr", // + "Input ID String", // + ValuePoint.Type.STRING8, // + false, // + AccessMode.READ_ONLY, // + Unit.NONE)), // + MODULE_4_D_C_A(new ScaledValuePoint(// + "S160_MODULE_4_D_C_A", // + "Module4Dca", // + "DC Current", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.AMPERE, // + "DCA_SF")), // + MODULE_4_D_C_V(new ScaledValuePoint(// + "S160_MODULE_4_D_C_V", // + "Module4Dcv", // + "DC Voltage", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.VOLT, // + "DCV_SF")), // + MODULE_4_D_C_W(new ScaledValuePoint(// + "S160_MODULE_4_D_C_W", // + "Module4Dcw", // + "DC Power", // + ValuePoint.Type.UINT16, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT, // + "DCW_SF")), // + MODULE_4_D_C_W_H(new ScaledValuePoint(// + "S160_MODULE_4_D_C_W_H", // + "Module4Dcwh", // + "Lifetime Energy", // + ValuePoint.Type.ACC32, // + false, // + AccessMode.READ_ONLY, // + Unit.WATT_HOURS, // + "DCWH_SF")), // + MODULE_4_TMS(new ValuePoint(// + "S160_MODULE_4_TMS", // + "Module4Timestamp", // + "Timestamp", // + ValuePoint.Type.UINT32, // + false, // + AccessMode.READ_ONLY, // + Unit.SECONDS)), // + MODULE_4_TMP(new ValuePoint(// + "S160_MODULE_4_TEMP", // + "Module4Temp", // + "Temperature", // + ValuePoint.Type.INT16, // + false, // + AccessMode.READ_ONLY, // + Unit.DEGREE_CELSIUS)), // + MODULE_4_D_C_ST(new EnumPoint(// + "S160_MODULE_4_OP_STATES", // + "Module4OpStates", // + "Operating State", // + EnumPoint.Type.ENUM16, // + false, // + AccessMode.READ_ONLY, // + S160_DCSt.values())), // + MODULE_4_EVT(new BitFieldPoint(// + "S160_MODULE_4_EVT", // + "Evt", // + "Module Events", // + BitFieldPoint.Type.BITFIELD32, // + false, // + AccessMode.READ_ONLY, // + S160_Module_4_Evt.values())), // + + ; // + + protected final Point point; + + private S160(Point point) { + this.point = point; + } + + @Override + public Point get() { + return this.point; + } + } + + public static enum S160_DCSt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + OFF(1, "Off"), // + SLEEPING(2, "Sleeping"), // + STARTING(3, "Starting"), // + MPPT(4, "MPPT"), // + THROTTLED(5, "Throttled"), // + SHUTTING_DOWN(6, "Shutting down"), // + FAULT(7, "Fault"), // + STANDBY(8, "Standby"), // + TEST(9, "Test"), // + RESERVED_10(10, "Reserved 10"); // + + private final int value; + private final String name; + + private S160_DCSt(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; + } + } + + public static enum S160_Evt implements SunSpecBitPoint { + GROUND_FAULT(new BitPoint(0, "S160_EVT_GROUND_FAULT", "Ground Fault")), // + INPUT_OVER_VOLTAGE(new BitPoint(1, "S160_EVT_INPUT_OVER_VOLTAGE", "Input over voltage")), // + RESERVED_2(new BitPoint(2, "S160_EVT_RESERVED_2", "Reserved 2")), // + DC_DISCONNECT(new BitPoint(3, "S160_EVT_D_C_DISCONNECT", "DC Disconnect")), // + RESERVED_4(new BitPoint(4, "S160_EVT_RESERVED_4", "Reserved 4")), // + CABINET_OPEN(new BitPoint(5, "S160_EVT_CABINET_OPEN", "Cabinet open")), // + MANUAL_SHUTDOWN(new BitPoint(6, "S160_EVT_MANUAL_SHUTDOWN", "Manual Shutdown")), // + OVER_TEMP(new BitPoint(7, "S160_EVT_OVER_TEMP", "Over Temp, ")), // + RESERVED_8(new BitPoint(8, "S160_EVT_RESERVED_8", "Reserved 8")), // + RESERVED_9(new BitPoint(9, "S160_EVT_RESERVED_9", "Reserved 9")), // + RESERVED_10(new BitPoint(10, "S160_EVT_RESERVED_10", "Reserved 10")), // + RESERVED_11(new BitPoint(11, "S160_EVT_RESERVED_11", "Reserved 11")), // + BLOWN_FUSE(new BitPoint(12, "S160_EVT_BLOWN_FUSE", "Blown Fuse")), // + UNDER_TEMP(new BitPoint(13, "S160_EVT_UNDER_TEMP", "Under Temp")), // + MEMORY_LOSS(new BitPoint(14, "S160_EVT_MEMORY_LOSS", "Memory Loss")), // + ARC_DETECTION(new BitPoint(15, "S160_EVT_ARC_DETECTION", "Arc Detection")), // + RESERVED_16(new BitPoint(16, "S160_EVT_RESERVED_16", "Reserved 16")), // + RESERVED_17(new BitPoint(17, "S160_EVT_RESERVED_17", "Reserved 17")), // + RESERVED_18(new BitPoint(18, "S160_EVT_RESERVED_18", "Reserved 18")), // + RESERVED_19(new BitPoint(19, "S160_EVT_RESERVED_19", "Reserved 19")), // + TEST_FAILED(new BitPoint(20, "S160_EVT_TEST_FAILED", "Test failed")), // + INPUT_UNDER_VOLTAGE(new BitPoint(21, "S160_EVT_INPUT_UNDER_VOLTAGE", "Input under voltage")), // + INPUT_OVER_CURRENT(new BitPoint(22, "S160_EVT_INPUT_OVER_CURRENT", "Input over current")); // + + private final BitPoint point; + + private S160_Evt(BitPoint point) { + this.point = point; + } + + @Override + public BitPoint get() { + return this.point; + } + } + + public static enum S160_Module_1_Evt implements SunSpecBitPoint { + GROUND_FAULT(new BitPoint(0, "S160_MODULE_1_EVT_GROUND_FAULT", "Ground Fault")), // + INPUT_OVER_VOLTAGE(new BitPoint(1, "S160_MODULE_1_EVT_INPUT_OVER_VOLTAGE", "Input over voltage")), // + RESERVED_2(new BitPoint(2, "S160_MODULE_1_EVT_RESERVED_2", "Reserved 2")), // + DC_DISCONNECT(new BitPoint(3, "S160_MODULE_1_EVT_D_C_DISCONNECT", "DC Disconnect")), // + RESERVED_4(new BitPoint(4, "S160_MODULE_1_EVT_RESERVED_4", "Reserved 4")), // + CABINET_OPEN(new BitPoint(5, "S160_MODULE_1_EVT_CABINET_OPEN", "Cabinet open")), // + MANUAL_SHUTDOWN(new BitPoint(6, "S160_MODULE_1_EVT_MANUAL_SHUTDOWN", "Manual Shutdown")), // + OVER_TEMP(new BitPoint(7, "S160_MODULE_1_EVT_OVER_TEMP", "Over Temp, ")), // + RESERVED_8(new BitPoint(8, "S160_MODULE_1_EVT_RESERVED_8", "Reserved 8")), // + RESERVED_9(new BitPoint(9, "S160_MODULE_1_EVT_RESERVED_9", "Reserved 9")), // + RESERVED_10(new BitPoint(10, "S160_MODULE_1_EVT_RESERVED_10", "Reserved 10")), // + RESERVED_11(new BitPoint(11, "S160_MODULE_1_EVT_RESERVED_11", "Reserved 11")), // + BLOWN_FUSE(new BitPoint(12, "S160_MODULE_1_EVT_BLOWN_FUSE", "Blown Fuse")), // + UNDER_TEMP(new BitPoint(13, "S160_MODULE_1_EVT_UNDER_TEMP", "Under Temp")), // + MEMORY_LOSS(new BitPoint(14, "S160_MODULE_1_EVT_MEMORY_LOSS", "Memory Loss")), // + ARC_DETECTION(new BitPoint(15, "S160_MODULE_1_EVT_ARC_DETECTION", "Arc Detection")), // + RESERVED_16(new BitPoint(16, "S160_MODULE_1_EVT_RESERVED_16", "Reserved 16")), // + RESERVED_17(new BitPoint(17, "S160_MODULE_1_EVT_RESERVED_17", "Reserved 17")), // + RESERVED_18(new BitPoint(18, "S160_MODULE_1_EVT_RESERVED_18", "Reserved 18")), // + RESERVED_19(new BitPoint(19, "S160_MODULE_1_EVT_RESERVED_19", "Reserved 19")), // + TEST_FAILED(new BitPoint(20, "S160_MODULE_1_EVT_TEST_FAILED", "Test failed")), // + INPUT_UNDER_VOLTAGE(new BitPoint(21, "S160_MODULE_1_EVT_INPUT_UNDER_VOLTAGE", "Input under voltage")), // + INPUT_OVER_CURRENT(new BitPoint(22, "S160_MODULE_1_EVT_INPUT_OVER_CURRENT", "Input over current")); // + + private final BitPoint point; + + private S160_Module_1_Evt(BitPoint point) { + this.point = point; + } + + @Override + public BitPoint get() { + return this.point; + } + } + + public static enum S160_Module_2_Evt implements SunSpecBitPoint { + GROUND_FAULT(new BitPoint(0, "S160_MODULE_2_EVT_GROUND_FAULT", "Ground Fault")), // + INPUT_OVER_VOLTAGE(new BitPoint(1, "S160_MODULE_2_EVT_INPUT_OVER_VOLTAGE", "Input over voltage")), // + RESERVED_2(new BitPoint(2, "S160_MODULE_2_EVT_RESERVED_2", "Reserved 2")), // + DC_DISCONNECT(new BitPoint(3, "S160_MODULE_2_EVT_D_C_DISCONNECT", "DC Disconnect")), // + RESERVED_4(new BitPoint(4, "S160_MODULE_2_EVT_RESERVED_4", "Reserved 4")), // + CABINET_OPEN(new BitPoint(5, "S160_MODULE_2_EVT_CABINET_OPEN", "Cabinet open")), // + MANUAL_SHUTDOWN(new BitPoint(6, "S160_MODULE_2_EVT_MANUAL_SHUTDOWN", "Manual Shutdown")), // + OVER_TEMP(new BitPoint(7, "S160_MODULE_2_EVT_OVER_TEMP", "Over Temp, ")), // + RESERVED_8(new BitPoint(8, "S160_MODULE_2_EVT_RESERVED_8", "Reserved 8")), // + RESERVED_9(new BitPoint(9, "S160_MODULE_2_EVT_RESERVED_9", "Reserved 9")), // + RESERVED_10(new BitPoint(10, "S160_MODULE_2_EVT_RESERVED_10", "Reserved 10")), // + RESERVED_11(new BitPoint(11, "S160_MODULE_2_EVT_RESERVED_11", "Reserved 11")), // + BLOWN_FUSE(new BitPoint(12, "S160_MODULE_2_EVT_BLOWN_FUSE", "Blown Fuse")), // + UNDER_TEMP(new BitPoint(13, "S160_MODULE_2_EVT_UNDER_TEMP", "Under Temp")), // + MEMORY_LOSS(new BitPoint(14, "S160_MODULE_2_EVT_MEMORY_LOSS", "Memory Loss")), // + ARC_DETECTION(new BitPoint(15, "S160_MODULE_2_EVT_ARC_DETECTION", "Arc Detection")), // + RESERVED_16(new BitPoint(16, "S160_MODULE_2_EVT_RESERVED_16", "Reserved 16")), // + RESERVED_17(new BitPoint(17, "S160_MODULE_2_EVT_RESERVED_17", "Reserved 17")), // + RESERVED_18(new BitPoint(18, "S160_MODULE_2_EVT_RESERVED_18", "Reserved 18")), // + RESERVED_19(new BitPoint(19, "S160_MODULE_2_EVT_RESERVED_19", "Reserved 19")), // + TEST_FAILED(new BitPoint(20, "S160_MODULE_2_EVT_TEST_FAILED", "Test failed")), // + INPUT_UNDER_VOLTAGE(new BitPoint(21, "S160_MODULE_2_EVT_INPUT_UNDER_VOLTAGE", "Input under voltage")), // + INPUT_OVER_CURRENT(new BitPoint(22, "S160_MODULE_2_EVT_INPUT_OVER_CURRENT", "Input over current")); // + + private final BitPoint point; + + private S160_Module_2_Evt(BitPoint point) { + this.point = point; + } + + @Override + public BitPoint get() { + return this.point; + } + } + + public static enum S160_Module_3_Evt implements SunSpecBitPoint { + GROUND_FAULT(new BitPoint(0, "S160_MODULE_3_EVT_GROUND_FAULT", "Ground Fault")), // + INPUT_OVER_VOLTAGE(new BitPoint(1, "S160_MODULE_3_EVT_INPUT_OVER_VOLTAGE", "Input over voltage")), // + RESERVED_2(new BitPoint(2, "S160_MODULE_3_EVT_RESERVED_2", "Reserved 2")), // + DC_DISCONNECT(new BitPoint(3, "S160_MODULE_3_EVT_D_C_DISCONNECT", "DC Disconnect")), // + RESERVED_4(new BitPoint(4, "S160_MODULE_3_EVT_RESERVED_4", "Reserved 4")), // + CABINET_OPEN(new BitPoint(5, "S160_MODULE_3_EVT_CABINET_OPEN", "Cabinet open")), // + MANUAL_SHUTDOWN(new BitPoint(6, "S160_MODULE_3_EVT_MANUAL_SHUTDOWN", "Manual Shutdown")), // + OVER_TEMP(new BitPoint(7, "S160_MODULE_3_EVT_OVER_TEMP", "Over Temp, ")), // + RESERVED_8(new BitPoint(8, "S160_MODULE_3_EVT_RESERVED_8", "Reserved 8")), // + RESERVED_9(new BitPoint(9, "S160_MODULE_3_EVT_RESERVED_9", "Reserved 9")), // + RESERVED_10(new BitPoint(10, "S160_MODULE_3_EVT_RESERVED_10", "Reserved 10")), // + RESERVED_11(new BitPoint(11, "S160_MODULE_3_EVT_RESERVED_11", "Reserved 11")), // + BLOWN_FUSE(new BitPoint(12, "S160_MODULE_3_EVT_BLOWN_FUSE", "Blown Fuse")), // + UNDER_TEMP(new BitPoint(13, "S160_MODULE_3_EVT_UNDER_TEMP", "Under Temp")), // + MEMORY_LOSS(new BitPoint(14, "S160_MODULE_3_EVT_MEMORY_LOSS", "Memory Loss")), // + ARC_DETECTION(new BitPoint(15, "S160_MODULE_3_EVT_ARC_DETECTION", "Arc Detection")), // + RESERVED_16(new BitPoint(16, "S160_MODULE_3_EVT_RESERVED_16", "Reserved 16")), // + RESERVED_17(new BitPoint(17, "S160_MODULE_3_EVT_RESERVED_17", "Reserved 17")), // + RESERVED_18(new BitPoint(18, "S160_MODULE_3_EVT_RESERVED_18", "Reserved 18")), // + RESERVED_19(new BitPoint(19, "S160_MODULE_3_EVT_RESERVED_19", "Reserved 19")), // + TEST_FAILED(new BitPoint(20, "S160_MODULE_3_EVT_TEST_FAILED", "Test failed")), // + INPUT_UNDER_VOLTAGE(new BitPoint(21, "S160_MODULE_3_EVT_INPUT_UNDER_VOLTAGE", "Input under voltage")), // + INPUT_OVER_CURRENT(new BitPoint(22, "S160_MODULE_3_EVT_INPUT_OVER_CURRENT", "Input over current")); // + + private final BitPoint point; + + private S160_Module_3_Evt(BitPoint point) { + this.point = point; + } + + @Override + public BitPoint get() { + return this.point; + } + } + + public static enum S160_Module_4_Evt implements SunSpecBitPoint { + GROUND_FAULT(new BitPoint(0, "S160_MODULE_4_EVT_GROUND_FAULT", "Ground Fault")), // + INPUT_OVER_VOLTAGE(new BitPoint(1, "S160_MODULE_4_EVT_INPUT_OVER_VOLTAGE", "Input over voltage")), // + RESERVED_2(new BitPoint(2, "S160_MODULE_4_EVT_RESERVED_2", "Reserved 2")), // + DC_DISCONNECT(new BitPoint(3, "S160_MODULE_4_EVT_D_C_DISCONNECT", "DC Disconnect")), // + RESERVED_4(new BitPoint(4, "S160_MODULE_4_EVT_RESERVED_4", "Reserved 4")), // + CABINET_OPEN(new BitPoint(5, "S160_MODULE_4_EVT_CABINET_OPEN", "Cabinet open")), // + MANUAL_SHUTDOWN(new BitPoint(6, "S160_MODULE_4_EVT_MANUAL_SHUTDOWN", "Manual Shutdown")), // + OVER_TEMP(new BitPoint(7, "S160_MODULE_4_EVT_OVER_TEMP", "Over Temp, ")), // + RESERVED_8(new BitPoint(8, "S160_MODULE_4_EVT_RESERVED_8", "Reserved 8")), // + RESERVED_9(new BitPoint(9, "S160_MODULE_4_EVT_RESERVED_9", "Reserved 9")), // + RESERVED_10(new BitPoint(10, "S160_MODULE_4_EVT_RESERVED_10", "Reserved 10")), // + RESERVED_11(new BitPoint(11, "S160_MODULE_4_EVT_RESERVED_11", "Reserved 11")), // + BLOWN_FUSE(new BitPoint(12, "S160_MODULE_4_EVT_BLOWN_FUSE", "Blown Fuse")), // + UNDER_TEMP(new BitPoint(13, "S160_MODULE_4_EVT_UNDER_TEMP", "Under Temp")), // + MEMORY_LOSS(new BitPoint(14, "S160_MODULE_4_EVT_MEMORY_LOSS", "Memory Loss")), // + ARC_DETECTION(new BitPoint(15, "S160_MODULE_4_EVT_ARC_DETECTION", "Arc Detection")), // + RESERVED_16(new BitPoint(16, "S160_MODULE_4_EVT_RESERVED_16", "Reserved 16")), // + RESERVED_17(new BitPoint(17, "S160_MODULE_4_EVT_RESERVED_17", "Reserved 17")), // + RESERVED_18(new BitPoint(18, "S160_MODULE_4_EVT_RESERVED_18", "Reserved 18")), // + RESERVED_19(new BitPoint(19, "S160_MODULE_4_EVT_RESERVED_19", "Reserved 19")), // + TEST_FAILED(new BitPoint(20, "S160_MODULE_4_EVT_TEST_FAILED", "Test failed")), // + INPUT_UNDER_VOLTAGE(new BitPoint(21, "S160_MODULE_4_EVT_INPUT_UNDER_VOLTAGE", "Input under voltage")), // + INPUT_OVER_CURRENT(new BitPoint(22, "S160_MODULE_4_EVT_INPUT_OVER_CURRENT", "Input over current")); // + + private final BitPoint point; + + private S160_Module_4_Evt(BitPoint point) { + this.point = point; + } + + @Override + public BitPoint get() { + return this.point; + } + } + + public final String label; + public final String description; + public final String notes; + public final int length; + public final SunSpecPoint[] points; + public final SunSpecModelType modelType; + + private S160SunSpecModel(String label, String description, String notes, int length, SunSpecPoint[] points, + SunSpecModelType modelType) { + this.label = label; + this.description = description; + this.notes = notes; + this.length = length; + this.points = points; + this.modelType = modelType; + } + + @Override + public SunSpecPoint[] points() { + return this.points; + } + + @Override + public String label() { + return this.label; + } + +} + +//CHECKSTYLE:ON diff --git a/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/dccharger/Config.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/dccharger/Config.java new file mode 100644 index 00000000000..721d34bdcd5 --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/dccharger/Config.java @@ -0,0 +1,27 @@ +package io.openems.edge.fronius.gen24.dccharger; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +import io.openems.edge.fronius.enums.PvString; + +@ObjectClassDefinition(// + name = "ESS Fronius Gen24 DC Charger PV", // + description = "Implements the choosen Channel of the Gen24 as PV Inverter.") +@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 = "String Number", description = "Fronius Gen24 String to use.") + PvString pvString() default PvString.ONE; + + String webconsole_configurationFactory_nameHint() default "ESS Fronius Gen24 hybrid DC Charger PV [{id}]"; + +} diff --git a/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/dccharger/FroniusGen24DcChargerImpl.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/dccharger/FroniusGen24DcChargerImpl.java new file mode 100644 index 00000000000..bc7e3b0e994 --- /dev/null +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/gen24/dccharger/FroniusGen24DcChargerImpl.java @@ -0,0 +1,162 @@ +package io.openems.edge.fronius.gen24.dccharger; + +import static io.openems.edge.common.event.EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE; +import static io.openems.edge.common.event.EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE; +import static org.osgi.service.component.annotations.ConfigurationPolicy.REQUIRE; +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 java.util.function.Consumer; + +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +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.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.OpenemsType; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.type.TypeUtils; +import io.openems.edge.ess.dccharger.api.EssDcCharger; +import io.openems.edge.fronius.gen24.batteryinverter.BatteryInverterFroniusGen24; +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.Fronius.Gen24.DcCharger", // + immediate = true, // + configurationPolicy = REQUIRE) +@EventTopics({ // + TOPIC_CYCLE_BEFORE_PROCESS_IMAGE, // + TOPIC_CYCLE_AFTER_PROCESS_IMAGE, // +}) +public class FroniusGen24DcChargerImpl extends AbstractOpenemsComponent + implements EssDcCharger, TimedataProvider, EventHandler, OpenemsComponent { + + protected Config config = null; + + @Reference(policy = STATIC, policyOption = GREEDY, cardinality = MANDATORY) + private BatteryInverterFroniusGen24 inverter; + + @Reference(policy = DYNAMIC, policyOption = GREEDY, cardinality = OPTIONAL) + private volatile Timedata timedata = null; + + private final CalculateEnergyFromPower calculateProductionEnergy = new CalculateEnergyFromPower(this, // + EssDcCharger.ChannelId.ACTUAL_ENERGY); + + private boolean mapOnce = true; + + public FroniusGen24DcChargerImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + EssDcCharger.ChannelId.values() // + ); + } + + @Activate + 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; + } + + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + @Override + public void handleEvent(Event event) { + if (!this.isEnabled()) { + return; + } + switch (event.getTopic()) { + case TOPIC_CYCLE_BEFORE_PROCESS_IMAGE -> { + if (this.inverter.isInitialized()) { + this.mapChannelValuesOnce(); + } + } + case TOPIC_CYCLE_AFTER_PROCESS_IMAGE -> { + this.calculateProductionEnergy.update(this.getActualPower().get()); + } + } + } + + private void mapChannelValuesOnce() { + if (!this.mapOnce) { + return; + } + + final Consumer> powerConsumer = value -> { + if (value.isDefined()) { + this._setActualPower(TypeUtils.getAsType(OpenemsType.INTEGER, value)); + } else { + this._setActualPower(null); + } + }; + + final Consumer> voltageConsumer = value -> { + if (value.isDefined()) { + this._setVoltage((int) (1000.0f * (float) TypeUtils.getAsType(OpenemsType.FLOAT, value))); + } else { + this._setVoltage(null); + } + }; + final Consumer> ampereConsumer = value -> { + if (value.isDefined()) { + this._setCurrent((int) (1000.0f * (float) TypeUtils.getAsType(OpenemsType.FLOAT, value))); + } else { + this._setCurrent(null); + } + + }; + + try { + switch (this.config.pvString()) { + case ONE -> { + this.inverter.getModule1DcwChannel().onSetNextValue(powerConsumer); + this.inverter.getModule1DcvChannel().onSetNextValue(voltageConsumer); + this.inverter.getModule1DcaChannel().onSetNextValue(ampereConsumer); + } + case TWO -> { + this.inverter.getModule2DcwChannel().onSetNextValue(powerConsumer); + this.inverter.getModule2DcvChannel().onSetNextValue(voltageConsumer); + this.inverter.getModule2DcaChannel().onSetNextValue(ampereConsumer); + } + } + this.mapOnce = false; + } catch (OpenemsException e) { + this.mapOnce = true; + e.printStackTrace(); + } + + } + + @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.fronius/src/io/openems/edge/fronius/meter/Config.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/meter/Config.java index bcad6a95aaa..327bfb1f807 100644 --- a/io.openems.edge.fronius/src/io/openems/edge/fronius/meter/Config.java +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/meter/Config.java @@ -25,7 +25,7 @@ @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.") + @AttributeDefinition(name = "Modbus Unit-ID", description = "The Unit-ID of the Modbus device 240 for Symo, and 200 for Gen24.") int modbusUnitId() default 240; @AttributeDefinition(name = "Invert Power", description = "Inverts all Power values, inverts current values, swaps production and consumptioon energy, i.e. Power is multiplied with -1.") diff --git a/io.openems.edge.fronius/src/io/openems/edge/fronius/meter/MeterFroniusImpl.java b/io.openems.edge.fronius/src/io/openems/edge/fronius/meter/MeterFroniusImpl.java index 6959edd1d62..413f0cc8417 100644 --- a/io.openems.edge.fronius/src/io/openems/edge/fronius/meter/MeterFroniusImpl.java +++ b/io.openems.edge.fronius/src/io/openems/edge/fronius/meter/MeterFroniusImpl.java @@ -43,6 +43,7 @@ public class MeterFroniusImpl extends AbstractSunSpecMeter private static final Map ACTIVE_MODELS = ImmutableMap.builder() .put(DefaultSunSpecModel.S_1, Priority.LOW) // .put(DefaultSunSpecModel.S_213, Priority.HIGH) // + .put(DefaultSunSpecModel.S_203, Priority.HIGH)//added S203 to support int+sf in Gen24 .build(); private static final int READ_FROM_MODBUS_BLOCK = 1; From 7e681b89a3d03f8e0a354868ae403bab63c33037 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Mon, 13 Jul 2026 14:45:42 +0200 Subject: [PATCH 2/2] Apply tools/prepare-commit.sh --- io.openems.edge.fronius/bnd.bnd | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/io.openems.edge.fronius/bnd.bnd b/io.openems.edge.fronius/bnd.bnd index aeb15d87ce2..9361c68c927 100644 --- a/io.openems.edge.fronius/bnd.bnd +++ b/io.openems.edge.fronius/bnd.bnd @@ -5,18 +5,18 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ + ,\ io.openems.common,\ - io.openems.edge.bridge.modbus,\ - io.openems.edge.common,\ - io.openems.edge.meter.api,\ - io.openems.edge.pvinverter.api,\ - io.openems.j2mod,\ io.openems.edge.battery.api,\ io.openems.edge.batteryinverter.api,\ + io.openems.edge.bridge.modbus,\ + io.openems.edge.common,\ io.openems.edge.ess.api,\ io.openems.edge.meter.api,\ + io.openems.edge.meter.api,\ + io.openems.edge.pvinverter.api,\ io.openems.edge.timedata.api,\ - + io.openems.j2mod,\ -testpath: \ ${testpath},\