diff --git a/.gitignore b/.gitignore index 4f8419af1d6..c76e0b903ef 100644 --- a/.gitignore +++ b/.gitignore @@ -225,3 +225,7 @@ gradle-app.setting /tools/docker/openems-edge/build /tools/docker/openems-ui/build .atom-build.yml + +# OpenEMS temp files +io.openems.edge.controller.api.mqtt/edge0 +io.openems.edge.application/c:/ \ No newline at end of file diff --git a/cnf/build.bnd b/cnf/build.bnd index 792a8a8ee92..225a9a3406c 100644 --- a/cnf/build.bnd +++ b/cnf/build.bnd @@ -93,6 +93,7 @@ testpath: \ Edge_Scheduler;member=${filter;${p};io\.openems\.edge\.scheduler\..*},\ Edge_Thermometer;member=${filter;${p};io\.openems\.edge\.onewire\.thermometer|io\.openems\.edge\.thermometer\..*},\ Edge_Timedata;member=${filter;${p};io\.openems\.edge\.timedata\..*},\ + Edge_TimeOfUseTariff;member=${filter;${p};io\.openems\.edge\.timeofusetariff\..*},\ javac.source: 1.8 javac.target: 1.8 diff --git a/cnf/checkstyle.xml b/cnf/checkstyle.xml index 1ed435b2f14..cc887fb5119 100644 --- a/cnf/checkstyle.xml +++ b/cnf/checkstyle.xml @@ -202,8 +202,8 @@ - - + + diff --git a/gradle.properties b/gradle.properties index 80eb926a84a..9c1314792c4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,2 @@ -bnd_version=5.0.0 \ No newline at end of file +bnd_version=6.0.0 +bnd_snapshots=https://bndtools.jfrog.io/bndtools/libs-snapshot-local \ No newline at end of file diff --git a/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/B2bWebsocket.java b/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/B2bWebsocket.java index 1116ce8dcd8..1adc16df348 100644 --- a/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/B2bWebsocket.java +++ b/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/B2bWebsocket.java @@ -42,7 +42,7 @@ public class B2bWebsocket extends AbstractOpenemsBackendComponent { @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC) protected volatile Timedata timeData; - protected final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, + protected final ScheduledExecutorService executor = Executors.newScheduledThreadPool(10, new ThreadFactoryBuilder().setNameFormat("B2bWebsocket-%d").build()); public B2bWebsocket() { diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/Field.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/Field.java index 4531271f58b..b3fc898f9b5 100644 --- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/Field.java +++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/Field.java @@ -532,6 +532,7 @@ public boolean isQuery() { public enum StockProductionLot implements Field { SERIAL_NUMBER("name", true), // PRODUCT("product_id", true); + public static final String ODOO_MODEL = "stock.production.lot"; public static final String ODOO_TABLE = ODOO_MODEL.replace(".", "_"); diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooHandler.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooHandler.java index 39d3c24bd10..2ba3a905269 100644 --- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooHandler.java +++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooHandler.java @@ -743,7 +743,7 @@ private void sendRegistrationMail(int odooUserId, String password) throws Openem /** * Update language for the given user. * - * @param user {@link MyUser} the current user + * @param user {@link MyUser} the current user * @param language to set * @throws OpenemsException on error */ diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/postgres/PeriodicWriteWorker.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/postgres/PeriodicWriteWorker.java index a3524004f63..5b144eada1b 100644 --- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/postgres/PeriodicWriteWorker.java +++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/postgres/PeriodicWriteWorker.java @@ -49,7 +49,7 @@ public class PeriodicWriteWorker { /** * Executor for subscriptions task. */ - private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, + private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(10, new ThreadFactoryBuilder().setNameFormat("Metadata.Odoo.PGPeriodic-%d").build()); public PeriodicWriteWorker(PostgresHandler parent, HikariDataSource dataSource) { diff --git a/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/Influx.java b/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/Influx.java index a00b25b0572..281f49a789c 100644 --- a/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/Influx.java +++ b/io.openems.backend.timedata.influx/src/io/openems/backend/timedata/influx/Influx.java @@ -35,6 +35,7 @@ import io.openems.backend.common.metadata.Metadata; import io.openems.backend.common.timedata.EdgeCache; import io.openems.backend.common.timedata.Timedata; +import io.openems.common.OpenemsOEM; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.types.ChannelAddress; @@ -42,7 +43,6 @@ import io.openems.common.types.SemanticVersion; import io.openems.common.utils.StringUtils; import io.openems.shared.influxdb.InfluxConnector; -import io.openems.shared.influxdb.InfluxConstants; @Designate(ocd = Config.class, factory = false) @Component(// @@ -148,7 +148,7 @@ private void writeData(int influxEdgeId, TreeBasedTable channelEntry : channelEntries) { this.addValue(builder, channelEntry.getKey().toString(), channelEntry.getValue()); diff --git a/io.openems.common/src/io/openems/common/OpenemsConstants.java b/io.openems.common/src/io/openems/common/OpenemsConstants.java index fff7ba7582c..2dff0de629d 100644 --- a/io.openems.common/src/io/openems/common/OpenemsConstants.java +++ b/io.openems.common/src/io/openems/common/OpenemsConstants.java @@ -47,7 +47,7 @@ public class OpenemsConstants { * * Note: this should be max. 32 ASCII characters long */ - public final static String MANUFACTURER = "OpenEMS Association e.V."; + public final static String MANUFACTURER = OpenemsOEM.MANUFACTURER; /** * The model identifier of the device diff --git a/io.openems.common/src/io/openems/common/OpenemsOEM.java b/io.openems.common/src/io/openems/common/OpenemsOEM.java new file mode 100644 index 00000000000..c7c820498b5 --- /dev/null +++ b/io.openems.common/src/io/openems/common/OpenemsOEM.java @@ -0,0 +1,35 @@ +package io.openems.common; + +/** + * Adjustments for OpenEMS OEM distributions. + */ +// CHECKSTYLE:OFF +public class OpenemsOEM { + // CHECKSTYLE:ON + + /* + * General. + */ + public final static String MANUFACTURER = "OpenEMS Association e.V."; + + /* + * Backend-Api Controller + */ + public final static String BACKEND_API_URI = "ws://localhost:8081"; + + /* + * System-Update. + */ + /** + * Name of the Debian package. + */ + public static final String SYSTEM_UPDATE_PACKAGE = "none"; + public static final String SYSTEM_UPDATE_LATEST_VERSION_URL = "none"; + public static final String SYSTEM_UPDATE_SCRIPT_URL = "none"; + + /* + * Backend InfluxDB. + */ + public static final String INFLUXDB_TAG = "edge"; + +} \ No newline at end of file diff --git a/io.openems.common/src/io/openems/common/channel/Unit.java b/io.openems.common/src/io/openems/common/channel/Unit.java index dc2884c3a29..29874156f2d 100644 --- a/io.openems.common/src/io/openems/common/channel/Unit.java +++ b/io.openems.common/src/io/openems/common/channel/Unit.java @@ -149,6 +149,14 @@ public enum Unit { */ VOLT_AMPERE_HOURS("VAh"), + // ########## + // Energy Tariff + // ########## + /** + * Unit of Energy Price [€/MWh]. + */ + EUROS_PER_MEGAWATT_HOUR("€/MWh"), + // ########## // Frequency // ########## @@ -267,6 +275,7 @@ public String format(Object value, OpenemsType type) { case AMPERE: case DEGREE_CELSIUS: case DEZIDEGREE_CELSIUS: + case EUROS_PER_MEGAWATT_HOUR: case HERTZ: case MILLIAMPERE: case MILLIHERTZ: diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index 4d4854575ed..a9edab2d0bf 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -125,7 +125,7 @@ bnd.identity;id='io.openems.edge.meter.discovergy',\ bnd.identity;id='io.openems.edge.meter.janitza',\ bnd.identity;id='io.openems.edge.meter.microcare.sdm630',\ - bnd.identity;id='io.openems.edge.meter.pqplus.umd97',\ + bnd.identity;id='io.openems.edge.meter.pqplus',\ bnd.identity;id='io.openems.edge.meter.schneider.acti9.smartlink',\ bnd.identity;id='io.openems.edge.meter.sma.shm20',\ bnd.identity;id='io.openems.edge.meter.socomec',\ @@ -148,6 +148,8 @@ bnd.identity;id='io.openems.edge.tesla.powerwall2',\ bnd.identity;id='io.openems.edge.timedata.influxdb',\ bnd.identity;id='io.openems.edge.timedata.rrd4j',\ + bnd.identity;id='io.openems.edge.timeofusetariff.awattar',\ + bnd.identity;id='io.openems.edge.timeofusetariff.corrently',\ -runbundles: \ Java-WebSocket;version='[1.5.2,1.5.3)',\ @@ -263,7 +265,7 @@ io.openems.edge.meter.discovergy;version=snapshot,\ io.openems.edge.meter.janitza;version=snapshot,\ io.openems.edge.meter.microcare.sdm630;version=snapshot,\ - io.openems.edge.meter.pqplus.umd97;version=snapshot,\ + io.openems.edge.meter.pqplus;version=snapshot,\ io.openems.edge.meter.schneider.acti9.smartlink;version=snapshot,\ io.openems.edge.meter.sma.shm20;version=snapshot,\ io.openems.edge.meter.socomec;version=snapshot,\ @@ -291,6 +293,9 @@ io.openems.edge.timedata.api;version=snapshot,\ io.openems.edge.timedata.influxdb;version=snapshot,\ io.openems.edge.timedata.rrd4j;version=snapshot,\ + io.openems.edge.timeofusetariff.api;version=snapshot,\ + io.openems.edge.timeofusetariff.awattar;version=snapshot,\ + io.openems.edge.timeofusetariff.corrently;version=snapshot,\ io.openems.shared.influxdb;version=snapshot,\ io.openems.wrapper.eu.chargetime.ocpp;version=snapshot,\ io.openems.wrapper.fastexcel;version=snapshot,\ diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/ClusterVersionBChannelId.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/ClusterVersionBChannelId.java index 206d0bb24e5..48ce3cff78a 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/ClusterVersionBChannelId.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/ClusterVersionBChannelId.java @@ -29,17 +29,17 @@ public enum ClusterVersionBChannelId implements io.openems.edge.common.channel.C .unit(Unit.OHM) // .accessMode(AccessMode.READ_WRITE)), // - RACK_1_POSITIVE_CONTACTOR(Doc.of(ContactorControl.values()) // - .accessMode(AccessMode.READ_WRITE)), // - RACK_2_POSITIVE_CONTACTOR(Doc.of(ContactorControl.values()) // - .accessMode(AccessMode.READ_WRITE)), // - RACK_3_POSITIVE_CONTACTOR(Doc.of(ContactorControl.values()) // - .accessMode(AccessMode.READ_WRITE)), // - RACK_4_POSITIVE_CONTACTOR(Doc.of(ContactorControl.values()) // - .accessMode(AccessMode.READ_WRITE)), // - RACK_5_POSITIVE_CONTACTOR(Doc.of(ContactorControl.values()) // - .accessMode(AccessMode.READ_WRITE)), // - + RACK_1_POSITIVE_CONTACTOR(Doc.of(ContactorControl.values()) // + .accessMode(AccessMode.READ_WRITE)), // + RACK_2_POSITIVE_CONTACTOR(Doc.of(ContactorControl.values()) // + .accessMode(AccessMode.READ_WRITE)), // + RACK_3_POSITIVE_CONTACTOR(Doc.of(ContactorControl.values()) // + .accessMode(AccessMode.READ_WRITE)), // + RACK_4_POSITIVE_CONTACTOR(Doc.of(ContactorControl.values()) // + .accessMode(AccessMode.READ_WRITE)), // + RACK_5_POSITIVE_CONTACTOR(Doc.of(ContactorControl.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // StateChannels MASTER_ALARM_COMMUNICATION_ERROR_WITH_SUBMASTER(Doc.of(Level.FAULT) // .text("Communication error with submaster")), @@ -115,9 +115,8 @@ public enum ClusterVersionBChannelId implements io.openems.edge.common.channel.C RACK_5_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) // .text("Rack 1 Cycle over current")), RACK_5_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // - .text("Rack 1 Voltage difference")), - ; - + .text("Rack 1 Voltage difference")),; + private final Doc doc; private ClusterVersionBChannelId(Doc doc) { diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/Config.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/Config.java index 3a396135318..2d9db729679 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/Config.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/Config.java @@ -12,7 +12,7 @@ @interface Config { @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") - String id() default "bms0"; + String id() default "battery0"; @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") String alias() default ""; @@ -31,7 +31,7 @@ @AttributeDefinition(name = "Number of slaves", description = "The number of slaves in this battery rack (max. 20)", min = "1", max = "20") int numberOfSlaves() default 20; - + @AttributeDefinition(name = "Module type", description = "The type of modules in the rack") ModuleType moduleType() default ModuleType.MODULE_3_KWH; @@ -46,7 +46,7 @@ @AttributeDefinition(name = "Max Start Time", description = "Max Time in seconds allowed for starting the system") int maxStartTime() default 20; - + @AttributeDefinition(name = "Pending Tolerance", description = "time in seconds, that is waited if system status cannot be determined e.g. in case of reading errors") int pendingTolerance() default 15; diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java index 69253675916..89c370037f5 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java @@ -34,7 +34,7 @@ * */ public class SingleRack { - private static final String KEY_VOLTAGE = "VOLTAGE"; + private static final String KEY_VOLTAGE = "VOLTAGE"; private static final String KEY_CURRENT = "CURRENT"; private static final String KEY_CHARGE_INDICATION = "CHARGE_INDICATION"; private static final String KEY_SOC = "SOC"; @@ -296,7 +296,7 @@ protected Collection getTasks() { return tasks; } - + private int getIntFromChannel(String key, int defaultValue) { @SuppressWarnings("unchecked") Optional opt = (Optional) this.channelMap.get(key).value().asOptional(); @@ -438,15 +438,19 @@ private Map createChannelIdMap() { this.addEntry(map, KEY_MIN_CELL_TEMPERATURE_ID, new IntegerDoc().unit(Unit.NONE)); this.addEntry(map, KEY_MIN_CELL_TEMPERATURE, new IntegerDoc().unit(Unit.DEZIDEGREE_CELSIUS)); this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_DISCHA_TEMP_LOW, - Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Cell Discharge Temperature Low Alarm Level 2")); // Bit 15 + Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Cell Discharge Temperature Low Alarm Level 2")); // Bit + // 15 this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_DISCHA_TEMP_HIGH, - Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Cell Discharge Temperature High Alarm Level 2")); // Bit 14 + Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Cell Discharge Temperature High Alarm Level 2")); // Bit + // 14 this.addEntry(map, KEY_ALARM_LEVEL_2_GR_TEMPERATURE_HIGH, Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " GR Temperature High Alarm Level 2")); // Bit 10 this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_CHA_TEMP_LOW, - Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Cell Charge Temperature Low Alarm Level 2")); // Bit 7 + Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Cell Charge Temperature Low Alarm Level 2")); // Bit + // 7 this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_CHA_TEMP_HIGH, - Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Cell Charge Temperature High Alarm Level 2")); // Bit 6 + Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Cell Charge Temperature High Alarm Level 2")); // Bit + // 6 this.addEntry(map, KEY_ALARM_LEVEL_2_DISCHA_CURRENT_HIGH, Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Discharge Current High Alarm Level 2")); // Bit 5 this.addEntry(map, KEY_ALARM_LEVEL_2_TOTAL_VOLTAGE_LOW, @@ -460,25 +464,32 @@ private Map createChannelIdMap() { this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_VOLTAGE_HIGH, Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Cell Voltage High Alarm Level 2")); // Bit 0 this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_DISCHA_TEMP_LOW, - Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell Discharge Temperature Low Alarm Level 1")); // Bit 15 + Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell Discharge Temperature Low Alarm Level 1")); // Bit + // 15 this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_DISCHA_TEMP_HIGH, Doc.of(Level.WARNING) .text("Rack" + this.rackNumber + " Cell Discharge Temperature High Alarm Level 1")); // Bit 14 this.addEntry(map, KEY_ALARM_LEVEL_1_TOTAL_VOLTAGE_DIFF_HIGH, - Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Total Voltage Diff High Alarm Level 1")); // Bit 13 + Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Total Voltage Diff High Alarm Level 1")); // Bit + // 13 this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_VOLTAGE_DIFF_HIGH, - Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell Voltage Diff High Alarm Level 1")); // Bit 11 + Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell Voltage Diff High Alarm Level 1")); // Bit + // 11 this.addEntry(map, KEY_ALARM_LEVEL_1_GR_TEMPERATURE_HIGH, Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " GR Temperature High Alarm Level 1")); // Bit 10 this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_TEMP_DIFF_HIGH, - Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell temperature Diff High Alarm Level 1")); // Bit 9 + Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell temperature Diff High Alarm Level 1")); // Bit + // 9 this.addEntry(map, KEY_ALARM_LEVEL_1_SOC_LOW, Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " SOC Low Alarm Level 1")); // Bit 8 this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_CHA_TEMP_LOW, - Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell Charge Temperature Low Alarm Level 1")); // Bit 7 + Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell Charge Temperature Low Alarm Level 1")); // Bit + // 7 this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_CHA_TEMP_HIGH, - Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell Charge Temperature High Alarm Level 1")); // Bit 6 + Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell Charge Temperature High Alarm Level 1")); // Bit + // 6 this.addEntry(map, KEY_ALARM_LEVEL_1_DISCHA_CURRENT_HIGH, - Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Discharge Current High Alarm Level 1")); // Bit 5 + Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Discharge Current High Alarm Level 1")); // Bit + // 5 this.addEntry(map, KEY_ALARM_LEVEL_1_TOTAL_VOLTAGE_LOW, Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Total Voltage Low Alarm Level 1")); // Bit 4 this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_VOLTAGE_LOW, diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionC.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionC.java index 52b39e8e000..53a8f6c638f 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionC.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionC.java @@ -213,6 +213,11 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .text("The maximum number of start attempts failed")), // MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) // .text("The maximum number of stop attempts failed")), // + NUMBER_OF_MODULES_PER_TOWER(Doc.of(OpenemsType.INTEGER) // + .text("Number Modules per Tower")), // + NUMBER_OF_TOWERS(Doc.of(OpenemsType.INTEGER) // + .text("Number of Towers")), // + ; private final Doc doc; diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImpl.java index 49421bcd347..4a166465255 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImpl.java @@ -1,8 +1,11 @@ package io.openems.edge.battery.soltaro.cluster.versionc; +import java.util.LinkedList; import java.util.Optional; +import java.util.Queue; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -32,7 +35,6 @@ import io.openems.edge.battery.soltaro.cluster.versionc.statemachine.Context; import io.openems.edge.battery.soltaro.cluster.versionc.statemachine.StateMachine; import io.openems.edge.battery.soltaro.cluster.versionc.statemachine.StateMachine.State; -import io.openems.edge.battery.soltaro.common.batteryprotection.BatteryProtectionDefinitionSoltaro3000Wh; import io.openems.edge.battery.soltaro.common.batteryprotection.BatteryProtectionDefinitionSoltaro3500Wh; import io.openems.edge.battery.soltaro.common.enums.ModuleType; import io.openems.edge.battery.soltaro.single.versionc.enums.PreChargeControl; @@ -44,6 +46,7 @@ import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; +import io.openems.edge.bridge.modbus.api.ModbusUtils; import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.BitsWordElement; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; @@ -78,6 +81,8 @@ public class ClusterVersionCImpl extends AbstractOpenemsModbusComponent implemen ClusterVersionC, SoltaroBatteryVersionC, SoltaroCluster, // Battery, ModbusComponent, OpenemsComponent, EventHandler, ModbusSlave { + private static final int WATCHDOG = 90; + private final Logger log = LoggerFactory.getLogger(ClusterVersionCImpl.class); @Reference @@ -117,21 +122,6 @@ protected void setModbus(BridgeModbus modbus) { @Activate void activate(ComponentContext context, Config config) throws OpenemsNamedException { // Initialize active racks - if (config.isRack1Used()) { - this.racks.add(Rack.RACK_1); - } - if (config.isRack2Used()) { - this.racks.add(Rack.RACK_2); - } - if (config.isRack3Used()) { - this.racks.add(Rack.RACK_3); - } - if (config.isRack4Used()) { - this.racks.add(Rack.RACK_4); - } - if (config.isRack5Used()) { - this.racks.add(Rack.RACK_5); - } this.config = config; if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, @@ -140,33 +130,615 @@ void activate(ComponentContext context, Config config) throws OpenemsNamedExcept } // Initialize Battery-Protection - if (config.moduleType() == ModuleType.MODULE_3_5_KWH) { - // Special settings for 3.5 kWh module - this.batteryProtection = BatteryProtection.create(this) // - .applyBatteryProtectionDefinition(new BatteryProtectionDefinitionSoltaro3500Wh(), - this.componentManager) // - .build(); - } else { - // Default - this.batteryProtection = BatteryProtection.create(this) // - .applyBatteryProtectionDefinition(new BatteryProtectionDefinitionSoltaro3000Wh(), - this.componentManager) // - .build(); - } + this.batteryProtection = BatteryProtection.create(this) // + .applyBatteryProtectionDefinition(new BatteryProtectionDefinitionSoltaro3500Wh(), this.componentManager) // + .build(); - // Calculate Capacity - int capacity = this.config.numberOfSlaves() * this.config.moduleType().getCapacity_Wh(); - this._setCapacity(capacity); + // Read Number of Towers and Modules + this.getNumberOfTowers().thenAccept(numberOfTower -> { + this.getNumberOfModules().thenAccept(numberOfModules -> { + this.calculateCapacity(numberOfTower, numberOfModules); + this.initializeBatteryLimits(numberOfModules); + this.channel(ClusterVersionC.ChannelId.NUMBER_OF_TOWERS).setNextValue(numberOfTower); + this.channel(ClusterVersionC.ChannelId.NUMBER_OF_MODULES_PER_TOWER).setNextValue(numberOfModules); + + if (numberOfTower > 0) { + this.racks.add(Rack.RACK_1); + } + if (numberOfTower > 1) { + this.racks.add(Rack.RACK_2); + } + if (numberOfTower > 2) { + this.racks.add(Rack.RACK_3); + } + if (numberOfTower > 3) { + this.racks.add(Rack.RACK_4); + } + if (numberOfTower > 4) { + this.racks.add(Rack.RACK_5); + } + + try { + this.updateRackChannels(numberOfTower); + } catch (OpenemsException e) { + this.logError(this.log, + "Error while updatingRackChannels(" + numberOfTower + "): " + e.getMessage()); + e.printStackTrace(); + } + }); + }); // Set Watchdog Timeout IntegerWriteChannel c = this.channel(SoltaroBatteryVersionC.ChannelId.EMS_COMMUNICATION_TIMEOUT); - c.setNextWriteValue(config.watchdog()); + c.setNextWriteValue(WATCHDOG); + + } + + private void updateRackChannels(Integer numberOfModules) throws OpenemsException { + for (Rack r : this.racks) { + try { + this.getModbusProtocol().addTasks(// + + new FC3ReadRegistersTask(r.offset + 0x000B, Priority.LOW, // + m(this.createChannelId(r, RackChannel.EMS_ADDRESS), + new UnsignedWordElement(r.offset + 0x000B)), // + m(this.createChannelId(r, RackChannel.EMS_BAUDRATE), + new UnsignedWordElement(r.offset + 0x000C)), // + new DummyRegisterElement(r.offset + 0x000D, r.offset + 0x000F), + m(this.createChannelId(r, RackChannel.PRE_CHARGE_CONTROL), + new UnsignedWordElement(r.offset + 0x0010)), // + new DummyRegisterElement(r.offset + 0x0011, r.offset + 0x0014), + m(this.createChannelId(r, RackChannel.SET_SUB_MASTER_ADDRESS), + new UnsignedWordElement(r.offset + 0x0015)) // + ), // + new FC3ReadRegistersTask(r.offset + 0x00F4, Priority.LOW, // + m(this.createChannelId(r, RackChannel.EMS_COMMUNICATION_TIMEOUT), + new UnsignedWordElement(r.offset + 0x00F4)) // + ), + + // Single Cluster Control Registers (running without Master BMS) + new FC6WriteRegisterTask(r.offset + 0x0010, // + m(this.createChannelId(r, RackChannel.PRE_CHARGE_CONTROL), + new UnsignedWordElement(r.offset + 0x0010)) // + ), // + new FC6WriteRegisterTask(r.offset + 0x00F4, // + m(this.createChannelId(r, RackChannel.EMS_COMMUNICATION_TIMEOUT), + new UnsignedWordElement(r.offset + 0x00F4)) // + ), // + new FC16WriteRegistersTask(r.offset + 0x000B, // + m(this.createChannelId(r, RackChannel.EMS_ADDRESS), + new UnsignedWordElement(r.offset + 0x000B)), // + m(this.createChannelId(r, RackChannel.EMS_BAUDRATE), + new UnsignedWordElement(r.offset + 0x000C)) // + ), // + + // Single Cluster Control Registers (General) + new FC6WriteRegisterTask(r.offset + 0x00CC, // + m(this.createChannelId(r, RackChannel.SYSTEM_TOTAL_CAPACITY), + new UnsignedWordElement(r.offset + 0x00CC)) // + ), // + new FC6WriteRegisterTask(r.offset + 0x0015, // + m(this.createChannelId(r, RackChannel.SET_SUB_MASTER_ADDRESS), + new UnsignedWordElement(r.offset + 0x0015)) // + ), // + new FC6WriteRegisterTask(r.offset + 0x00F3, // + m(this.createChannelId(r, RackChannel.VOLTAGE_LOW_PROTECTION), + new UnsignedWordElement(r.offset + 0x00F3)) // + ), // + new FC3ReadRegistersTask(r.offset + 0x00CC, Priority.LOW, // + m(this.createChannelId(r, RackChannel.SYSTEM_TOTAL_CAPACITY), + new UnsignedWordElement(r.offset + 0x00CC)) // + ), + + // Single Cluster Status Registers + new FC3ReadRegistersTask(r.offset + 0x100, Priority.HIGH, // + m(this.createChannelId(r, RackChannel.VOLTAGE), + new UnsignedWordElement(r.offset + 0x100), + ElementToChannelConverter.SCALE_FACTOR_2), + m(this.createChannelId(r, RackChannel.CURRENT), new SignedWordElement(r.offset + 0x101), + ElementToChannelConverter.SCALE_FACTOR_2), + m(this.createChannelId(r, RackChannel.CHARGE_INDICATION), + new UnsignedWordElement(r.offset + 0x102)), + m(this.createChannelId(r, RackChannel.SOC), new UnsignedWordElement(r.offset + 0x103)), + m(this.createChannelId(r, RackChannel.SOH), new UnsignedWordElement(r.offset + 0x104)), + m(this.createChannelId(r, RackChannel.MAX_CELL_VOLTAGE_ID), + new UnsignedWordElement(r.offset + 0x105)), + m(this.createChannelId(r, RackChannel.MAX_CELL_VOLTAGE), + new UnsignedWordElement(r.offset + 0x106)), + m(this.createChannelId(r, RackChannel.MIN_CELL_VOLTAGE_ID), + new UnsignedWordElement(r.offset + 0x107)), + m(this.createChannelId(r, RackChannel.MIN_CELL_VOLTAGE), + new UnsignedWordElement(r.offset + 0x108)), + m(this.createChannelId(r, RackChannel.MAX_CELL_TEMPERATURE_ID), + new UnsignedWordElement(r.offset + 0x109)), + m(this.createChannelId(r, RackChannel.MAX_CELL_TEMPERATURE), + new SignedWordElement(r.offset + 0x10A), + ElementToChannelConverter.SCALE_FACTOR_MINUS_1), + m(this.createChannelId(r, RackChannel.MIN_CELL_TEMPERATURE_ID), + new UnsignedWordElement(r.offset + 0x10B)), + m(this.createChannelId(r, RackChannel.MIN_CELL_TEMPERATURE), + new SignedWordElement(r.offset + 0x10C), + ElementToChannelConverter.SCALE_FACTOR_MINUS_1), + m(this.createChannelId(r, RackChannel.AVERAGE_VOLTAGE), + new UnsignedWordElement(r.offset + 0x10D)), + m(this.createChannelId(r, RackChannel.SYSTEM_INSULATION), + new UnsignedWordElement(r.offset + 0x10E)), + m(this.createChannelId(r, RackChannel.SYSTEM_MAX_CHARGE_CURRENT), + new UnsignedWordElement(r.offset + 0x10F), + ElementToChannelConverter.SCALE_FACTOR_2), + m(this.createChannelId(r, RackChannel.SYSTEM_MAX_DISCHARGE_CURRENT), + new UnsignedWordElement(r.offset + 0x110), + ElementToChannelConverter.SCALE_FACTOR_2), + m(this.createChannelId(r, RackChannel.POSITIVE_INSULATION), + new UnsignedWordElement(r.offset + 0x111)), + m(this.createChannelId(r, RackChannel.NEGATIVE_INSULATION), + new UnsignedWordElement(r.offset + 0x112)), + m(this.createChannelId(r, RackChannel.CLUSTER_RUN_STATE), + new UnsignedWordElement(r.offset + 0x113)), + m(this.createChannelId(r, RackChannel.AVG_TEMPERATURE), + new SignedWordElement(r.offset + 0x114))), + new FC3ReadRegistersTask(r.offset + 0x18b, Priority.LOW, + m(this.createChannelId(r, RackChannel.PROJECT_ID), + new UnsignedWordElement(r.offset + 0x18b)), + m(this.createChannelId(r, RackChannel.VERSION_MAJOR), + new UnsignedWordElement(r.offset + 0x18c)), + m(this.createChannelId(r, RackChannel.VERSION_SUB), + new UnsignedWordElement(r.offset + 0x18d)), + m(this.createChannelId(r, RackChannel.VERSION_MODIFY), + new UnsignedWordElement(r.offset + 0x18e))), + + // System Warning/Shut Down Status Registers + new FC3ReadRegistersTask(r.offset + 0x140, Priority.LOW, + // Level 2 Alarm: BMS Self-protect, main contactor shut down + m(new BitsWordElement(r.offset + 0x140, this) // + .bit(0, this.createChannelId(r, RackChannel.LEVEL2_CELL_VOLTAGE_HIGH)) // + .bit(1, this.createChannelId(r, RackChannel.LEVEL2_TOTAL_VOLTAGE_HIGH)) // + .bit(2, this.createChannelId(r, RackChannel.LEVEL2_CHARGE_CURRENT_HIGH)) // + .bit(3, this.createChannelId(r, RackChannel.LEVEL2_CELL_VOLTAGE_LOW)) // + .bit(4, this.createChannelId(r, RackChannel.LEVEL2_TOTAL_VOLTAGE_LOW)) // + .bit(5, this.createChannelId(r, RackChannel.LEVEL2_DISCHARGE_CURRENT_HIGH)) // + .bit(6, this.createChannelId(r, RackChannel.LEVEL2_CHARGE_TEMP_HIGH)) // + .bit(7, this.createChannelId(r, RackChannel.LEVEL2_CHARGE_TEMP_LOW)) // + // 8 -> Reserved + // 9 -> Reserved + .bit(10, this.createChannelId(r, RackChannel.LEVEL2_POWER_POLE_TEMP_HIGH)) // + // 11 -> Reserved + .bit(12, this.createChannelId(r, RackChannel.LEVEL2_INSULATION_VALUE)) // + // 13 -> Reserved + .bit(14, this.createChannelId(r, RackChannel.LEVEL2_DISCHARGE_TEMP_HIGH)) // + .bit(15, this.createChannelId(r, RackChannel.LEVEL2_DISCHARGE_TEMP_LOW)) // + ), + // Level 1 Alarm: EMS Control to stop charge, discharge, charge&discharge + m(new BitsWordElement(r.offset + 0x141, this) // + .bit(0, this.createChannelId(r, RackChannel.LEVEL1_CELL_VOLTAGE_HIGH)) // + .bit(1, this.createChannelId(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_HIGH)) // + .bit(2, this.createChannelId(r, RackChannel.LEVEL1_CHARGE_CURRENT_HIGH)) // + .bit(3, this.createChannelId(r, RackChannel.LEVEL1_CELL_VOLTAGE_LOW)) // + .bit(4, this.createChannelId(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_LOW)) // + .bit(5, this.createChannelId(r, RackChannel.LEVEL1_DISCHARGE_CURRENT_HIGH)) // + .bit(6, this.createChannelId(r, RackChannel.LEVEL1_CHARGE_TEMP_HIGH)) // + .bit(7, this.createChannelId(r, RackChannel.LEVEL1_CHARGE_TEMP_LOW)) // + .bit(8, this.createChannelId(r, RackChannel.LEVEL1_SOC_LOW)) // + .bit(9, this.createChannelId(r, RackChannel.LEVEL1_TEMP_DIFF_TOO_BIG)) // + .bit(10, this.createChannelId(r, RackChannel.LEVEL1_POWER_POLE_TEMP_HIGH)) // + .bit(11, this.createChannelId(r, RackChannel.LEVEL1_CELL_VOLTAGE_DIFF_TOO_BIG)) // + .bit(12, this.createChannelId(r, RackChannel.LEVEL1_INSULATION_VALUE)) // + .bit(13, this.createChannelId(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_DIFF_TOO_BIG)) // + .bit(14, this.createChannelId(r, RackChannel.LEVEL1_DISCHARGE_TEMP_HIGH)) // + .bit(15, this.createChannelId(r, RackChannel.LEVEL1_DISCHARGE_TEMP_LOW)) // + ), + // Pre-Alarm: Temperature Alarm will active current limication + m(new BitsWordElement(r.offset + 0x142, this) // + .bit(0, this.createChannelId(r, RackChannel.PRE_ALARM_CELL_VOLTAGE_HIGH)) // + .bit(1, this.createChannelId(r, RackChannel.PRE_ALARM_TOTAL_VOLTAGE_HIGH)) // + .bit(2, this.createChannelId(r, RackChannel.PRE_ALARM_CHARGE_CURRENT_HIGH)) // + .bit(3, this.createChannelId(r, RackChannel.PRE_ALARM_CELL_VOLTAGE_LOW)) // + .bit(4, this.createChannelId(r, RackChannel.PRE_ALARM_TOTAL_VOLTAGE_LOW)) // + .bit(5, this.createChannelId(r, RackChannel.PRE_ALARM_DISCHARGE_CURRENT_HIGH)) // + .bit(6, this.createChannelId(r, RackChannel.PRE_ALARM_CHARGE_TEMP_HIGH)) // + .bit(7, this.createChannelId(r, RackChannel.PRE_ALARM_CHARGE_TEMP_LOW)) // + .bit(8, this.createChannelId(r, RackChannel.PRE_ALARM_SOC_LOW)) // + .bit(9, this.createChannelId(r, RackChannel.PRE_ALARM_TEMP_DIFF_TOO_BIG)) // + .bit(10, this.createChannelId(r, RackChannel.PRE_ALARM_POWER_POLE_HIGH))// + .bit(11, this.createChannelId(r, + RackChannel.PRE_ALARM_CELL_VOLTAGE_DIFF_TOO_BIG)) // + .bit(12, this.createChannelId(r, RackChannel.PRE_ALARM_INSULATION_FAIL)) // + .bit(13, this.createChannelId(r, + RackChannel.PRE_ALARM_TOTAL_VOLTAGE_DIFF_TOO_BIG)) // + .bit(14, this.createChannelId(r, RackChannel.PRE_ALARM_DISCHARGE_TEMP_HIGH)) // + .bit(15, this.createChannelId(r, RackChannel.PRE_ALARM_DISCHARGE_TEMP_LOW)) // + ) // + ), + // Other Alarm Info + new FC3ReadRegistersTask(r.offset + 0x1A5, Priority.LOW, // + m(new BitsWordElement(r.offset + 0x1A5, this) // + .bit(0, this.createChannelId(r, RackChannel.ALARM_COMMUNICATION_TO_MASTER_BMS)) // + .bit(1, this.createChannelId(r, RackChannel.ALARM_COMMUNICATION_TO_SLAVE_BMS)) // + .bit(2, this.createChannelId(r, + RackChannel.ALARM_COMMUNICATION_SLAVE_BMS_TO_TEMP_SENSORS)) // + .bit(3, this.createChannelId(r, RackChannel.ALARM_SLAVE_BMS_HARDWARE)) // + )), + // Slave BMS Fault Message Registers + new FC3ReadRegistersTask(r.offset + 0x185, Priority.LOW, // + m(new BitsWordElement(r.offset + 0x185, this) // + .bit(0, this.createChannelId(r, RackChannel.SLAVE_BMS_VOLTAGE_SENSOR_CABLES)) // + .bit(1, this.createChannelId(r, RackChannel.SLAVE_BMS_POWER_CABLE)) // + .bit(2, this.createChannelId(r, RackChannel.SLAVE_BMS_LTC6803)) // + .bit(3, this.createChannelId(r, RackChannel.SLAVE_BMS_VOLTAGE_SENSORS)) // + .bit(4, this.createChannelId(r, RackChannel.SLAVE_BMS_TEMP_SENSOR_CABLES)) // + .bit(5, this.createChannelId(r, RackChannel.SLAVE_BMS_TEMP_SENSORS)) // + .bit(6, this.createChannelId(r, RackChannel.SLAVE_BMS_POWER_POLE_TEMP_SENSOR)) // + .bit(7, this.createChannelId(r, RackChannel.SLAVE_BMS_TEMP_BOARD_COM)) // + .bit(8, this.createChannelId(r, RackChannel.SLAVE_BMS_BALANCE_MODULE)) // + .bit(9, this.createChannelId(r, RackChannel.SLAVE_BMS_TEMP_SENSORS2)) // + .bit(10, this.createChannelId(r, RackChannel.SLAVE_BMS_INTERNAL_COM)) // + .bit(11, this.createChannelId(r, RackChannel.SLAVE_BMS_EEPROM)) // + .bit(12, this.createChannelId(r, RackChannel.SLAVE_BMS_INIT)) // + )) // + ); + } catch (OpenemsException e) { + this.logError(this.log, "Error while creating modbus tasks: " + e.getMessage()); + e.printStackTrace(); + } // + Consumer addCellChannels = (type) -> { + for (int i = 0; i < numberOfModules; i++) { + AbstractModbusElement[] elements = new AbstractModbusElement[type.getSensorsPerModule()]; + for (int j = 0; j < type.getSensorsPerModule(); j++) { + int sensorIndex = i * type.getSensorsPerModule() + j; + io.openems.edge.common.channel.ChannelId channelId = CellChannelFactory.create(r, type, + sensorIndex); + // Register the Channel at this Component + this.addChannel(channelId); + // Add the Modbus Element and map it to the Channel + elements[j] = m(channelId, new UnsignedWordElement(r.offset + type.getOffset() + sensorIndex)); + } + // Add a Modbus read task for this module + try { + this.getModbusProtocol().addTasks(// + new FC3ReadRegistersTask(r.offset + type.getOffset() + i * type.getSensorsPerModule(), + Priority.LOW, elements)); + } catch (OpenemsException e) { + this.logError(this.log, "Error while creating modbus tasks: " + e.getMessage()); + e.printStackTrace(); + } + } + }; + addCellChannels.accept(CellChannelFactory.Type.VOLTAGE_CLUSTER); + addCellChannels.accept(CellChannelFactory.Type.TEMPERATURE_CLUSTER); + // WARN_LEVEL_Pre Alarm (Pre Alarm configuration registers RW) + { + AbstractModbusElement[] elements = new AbstractModbusElement[] { + m(this.createChannelId(r, RackChannel.PRE_ALARM_CELL_OVER_VOLTAGE_ALARM), + new UnsignedWordElement(r.offset + 0x080)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_CELL_OVER_VOLTAGE_RECOVER), + new UnsignedWordElement(r.offset + 0x081)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_SYSTEM_OVER_VOLTAGE_ALARM), + new UnsignedWordElement(r.offset + 0x082), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_SYSTEM_OVER_VOLTAGE_RECOVER), + new UnsignedWordElement(r.offset + 0x083), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_SYSTEM_CHARGE_OVER_CURRENT_ALARM), + new UnsignedWordElement(r.offset + 0x084), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_SYSTEM_CHARGE_OVER_CURRENT_RECOVER), + new UnsignedWordElement(r.offset + 0x085), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_CELL_UNDER_VOLTAGE_ALARM), + new UnsignedWordElement(r.offset + 0x086)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_CELL_UNDER_VOLTAGE_RECOVER), + new UnsignedWordElement(r.offset + 0x087)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_SYSTEM_UNDER_VOLTAGE_ALARM), + new UnsignedWordElement(r.offset + 0x088), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_SYSTEM_UNDER_VOLTAGE_RECOVER), + new UnsignedWordElement(r.offset + 0x089), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_SYSTEM_DISCHARGE_OVER_CURRENT_ALARM), + new UnsignedWordElement(r.offset + 0x08A), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_SYSTEM_DISCHARGE_OVER_CURRENT_RECOVER), + new UnsignedWordElement(r.offset + 0x08B), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_CELL_OVER_TEMPERATURE_ALARM), + new SignedWordElement(r.offset + 0x08C)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_CELL_OVER_TEMPERATURE_RECOVER), + new SignedWordElement(r.offset + 0x08D)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_CELL_UNDER_TEMPERATURE_ALARM), + new SignedWordElement(r.offset + 0x08E)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_CELL_UNDER_TEMPERATURE_RECOVER), + new SignedWordElement(r.offset + 0x08F)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_SOC_LOW_ALARM), + new UnsignedWordElement(r.offset + 0x090)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_SOC_LOW_ALARM_RECOVER), + new UnsignedWordElement(r.offset + 0x091)), // + new DummyRegisterElement(r.offset + 0x092, r.offset + 0x093), + m(this.createChannelId(r, RackChannel.PRE_ALARM_CONNECTOR_TEMPERATURE_HIGH_ALARM), + new SignedWordElement(r.offset + 0x094)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_CONNECTOR_TEMPERATURE_HIGH_ALARM_RECOVER), + new SignedWordElement(r.offset + 0x095)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_INSULATION_ALARM), + new UnsignedWordElement(r.offset + 0x096)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_INSULATION_ALARM_RECOVER), + new UnsignedWordElement(r.offset + 0x097)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_CELL_VOLTAGE_DIFFERENCE_ALARM), + new UnsignedWordElement(r.offset + 0x098)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_CELL_VOLTAGE_DIFFERENCE_ALARM_RECOVER), + new UnsignedWordElement(r.offset + 0x099)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_TOTAL_VOLTAGE_DIFFERENCE_ALARM), + new UnsignedWordElement(r.offset + 0x09A), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_TOTAL_VOLTAGE_DIFFERENCE_ALARM_RECOVER), + new UnsignedWordElement(r.offset + 0x09B), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_DISCHARGE_TEMPERATURE_HIGH_ALARM), + new SignedWordElement(r.offset + 0x09C)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_DISCHARGE_TEMPERATURE_HIGH_ALARM_RECOVER), + new SignedWordElement(r.offset + 0x09D)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_DISCHARGE_TEMPERATURE_LOW_ALARM), + new SignedWordElement(r.offset + 0x09E)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_DISCHARGE_TEMPERATURE_LOW_ALARM_RECOVER), + new SignedWordElement(r.offset + 0x09F)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_TEMPERATURE_DIFFERENCE_ALARM), + new SignedWordElement(r.offset + 0x0A0)), // + m(this.createChannelId(r, RackChannel.PRE_ALARM_TEMPERATURE_DIFFERENCE_ALARM_RECOVER), + new SignedWordElement(r.offset + 0x0A1)) // + }; + this.getModbusProtocol().addTasks(// + new FC16WriteRegistersTask(r.offset + 0x080, elements)); + this.getModbusProtocol().addTasks(// + new FC3ReadRegistersTask(r.offset + 0x080, Priority.LOW, elements)); + } + + // WARN_LEVEL1 (Level1 warning registers RW) + { + AbstractModbusElement[] elements = new AbstractModbusElement[] { + m(this.createChannelId(r, RackChannel.LEVEL1_CELL_OVER_VOLTAGE_PROTECTION), + new UnsignedWordElement(r.offset + 0x040)), // + m(this.createChannelId(r, RackChannel.LEVEL1_CELL_OVER_VOLTAGE_RECOVER), + new UnsignedWordElement(r.offset + 0x041)), // + m(this.createChannelId(r, RackChannel.LEVEL1_SYSTEM_OVER_VOLTAGE_PROTECTION), + new UnsignedWordElement(r.offset + 0x042), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL1_SYSTEM_OVER_VOLTAGE_RECOVER), + new UnsignedWordElement(r.offset + 0x043), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL1_SYSTEM_CHARGE_OVER_CURRENT_PROTECTION), + new UnsignedWordElement(r.offset + 0x044), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL1_SYSTEM_CHARGE_OVER_CURRENT_RECOVER), + new UnsignedWordElement(r.offset + 0x045), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL1_CELL_UNDER_VOLTAGE_PROTECTION), + new UnsignedWordElement(r.offset + 0x046)), // + m(this.createChannelId(r, RackChannel.LEVEL1_CELL_UNDER_VOLTAGE_RECOVER), + new UnsignedWordElement(r.offset + 0x047)), // + m(this.createChannelId(r, RackChannel.LEVEL1_SYSTEM_UNDER_VOLTAGE_PROTECTION), + new UnsignedWordElement(r.offset + 0x048), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL1_SYSTEM_UNDER_VOLTAGE_RECOVER), + new UnsignedWordElement(r.offset + 0x049), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL1_SYSTEM_DISCHARGE_OVER_CURRENT_PROTECTION), + new UnsignedWordElement(r.offset + 0x04A), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL1_SYSTEM_DISCHARGE_OVER_CURRENT_RECOVER), + new UnsignedWordElement(r.offset + 0x04B), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL1_CELL_OVER_TEMPERATURE_PROTECTION), + new SignedWordElement(r.offset + 0x04C)), // + m(this.createChannelId(r, RackChannel.LEVEL1_CELL_OVER_TEMPERATURE_RECOVER), + new SignedWordElement(r.offset + 0x04D)), // + m(this.createChannelId(r, RackChannel.LEVEL1_CELL_UNDER_TEMPERATURE_PROTECTION), + new SignedWordElement(r.offset + 0x04E)), // + m(this.createChannelId(r, RackChannel.LEVEL1_CELL_UNDER_TEMPERATURE_RECOVER), + new SignedWordElement(r.offset + 0x04F)), // + m(this.createChannelId(r, RackChannel.LEVEL1_SOC_LOW_PROTECTION), + new UnsignedWordElement(r.offset + 0x050)), // + m(this.createChannelId(r, RackChannel.LEVEL1_SOC_LOW_PROTECTION_RECOVER), + new UnsignedWordElement(r.offset + 0x051)), // + new DummyRegisterElement(r.offset + 0x052, r.offset + 0x053), // + m(this.createChannelId(r, RackChannel.LEVEL1_CONNECTOR_TEMPERATURE_HIGH_PROTECTION), + new SignedWordElement(r.offset + 0x054)), // + m(this.createChannelId(r, RackChannel.LEVEL1_CONNECTOR_TEMPERATURE_HIGH_PROTECTION_RECOVER), + new SignedWordElement(r.offset + 0x055)), // + m(this.createChannelId(r, RackChannel.LEVEL1_INSULATION_PROTECTION), + new UnsignedWordElement(r.offset + 0x056)), // + m(this.createChannelId(r, RackChannel.LEVEL1_INSULATION_PROTECTION_RECOVER), + new UnsignedWordElement(r.offset + 0x057)), // + m(this.createChannelId(r, RackChannel.LEVEL1_CELL_VOLTAGE_DIFFERENCE_PROTECTION), + new UnsignedWordElement(r.offset + 0x058)), // + m(this.createChannelId(r, RackChannel.LEVEL1_CELL_VOLTAGE_DIFFERENCE_PROTECTION_RECOVER), + new UnsignedWordElement(r.offset + 0x059)), // + m(this.createChannelId(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_DIFFERENCE_PROTECTION), + new UnsignedWordElement(r.offset + 0x05A), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_DIFFERENCE_PROTECTION_RECOVER), + new UnsignedWordElement(r.offset + 0x05B), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL1_DISCHARGE_TEMPERATURE_HIGH_PROTECTION), + new SignedWordElement(r.offset + 0x05C)), // + m(this.createChannelId(r, RackChannel.LEVEL1_DISCHARGE_TEMPERATURE_HIGH_PROTECTION_RECOVER), + new SignedWordElement(r.offset + 0x05D)), // + m(this.createChannelId(r, RackChannel.LEVEL1_DISCHARGE_TEMPERATURE_LOW_PROTECTION), + new SignedWordElement(r.offset + 0x05E)), // + m(this.createChannelId(r, RackChannel.LEVEL1_DISCHARGE_TEMPERATURE_LOW_PROTECTION_RECOVER), + new SignedWordElement(r.offset + 0x05F)), // + m(this.createChannelId(r, RackChannel.LEVEL1_TEMPERATURE_DIFFERENCE_PROTECTION), + new SignedWordElement(r.offset + 0x060)), // + m(this.createChannelId(r, RackChannel.LEVEL1_TEMPERATURE_DIFFERENCE_PROTECTION_RECOVER), + new SignedWordElement(r.offset + 0x061)) // + }; + this.getModbusProtocol().addTasks(// + new FC16WriteRegistersTask(r.offset + 0x040, elements)); + this.getModbusProtocol().addTasks(// + new FC3ReadRegistersTask(r.offset + 0x040, Priority.LOW, elements)); + } + + // WARN_LEVEL2 (Level2 Protection registers RW) + { + AbstractModbusElement[] elements = new AbstractModbusElement[] { + m(this.createChannelId(r, RackChannel.LEVEL2_CELL_OVER_VOLTAGE_PROTECTION), + new UnsignedWordElement(r.offset + 0x400)), // + m(this.createChannelId(r, RackChannel.LEVEL2_CELL_OVER_VOLTAGE_RECOVER), + new UnsignedWordElement(r.offset + 0x401)), // + m(this.createChannelId(r, RackChannel.LEVEL2_SYSTEM_OVER_VOLTAGE_PROTECTION), + new UnsignedWordElement(r.offset + 0x402)), // + m(this.createChannelId(r, RackChannel.LEVEL2_SYSTEM_OVER_VOLTAGE_RECOVER), + new UnsignedWordElement(r.offset + 0x403), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL2_SYSTEM_CHARGE_OVER_CURRENT_PROTECTION), + new UnsignedWordElement(r.offset + 0x404), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL2_SYSTEM_CHARGE_OVER_CURRENT_RECOVER), + new UnsignedWordElement(r.offset + 0x405), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL2_CELL_UNDER_VOLTAGE_PROTECTION), + new UnsignedWordElement(r.offset + 0x406)), // + m(this.createChannelId(r, RackChannel.LEVEL2_CELL_UNDER_VOLTAGE_RECOVER), + new UnsignedWordElement(r.offset + 0x407)), // + m(this.createChannelId(r, RackChannel.LEVEL2_SYSTEM_UNDER_VOLTAGE_PROTECTION), + new UnsignedWordElement(r.offset + 0x408), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL2_SYSTEM_UNDER_VOLTAGE_RECOVER), + new UnsignedWordElement(r.offset + 0x409), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL2_SYSTEM_DISCHARGE_OVER_CURRENT_PROTECTION), + new UnsignedWordElement(r.offset + 0x40A), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL2_SYSTEM_DISCHARGE_OVER_CURRENT_RECOVER), + new UnsignedWordElement(r.offset + 0x40B), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL2_CELL_OVER_TEMPERATURE_PROTECTION), + new SignedWordElement(r.offset + 0x40C)), // + m(this.createChannelId(r, RackChannel.LEVEL2_CELL_OVER_TEMPERATURE_RECOVER), + new SignedWordElement(r.offset + 0x40D)), // + m(this.createChannelId(r, RackChannel.LEVEL2_CELL_UNDER_TEMPERATURE_PROTECTION), + new SignedWordElement(r.offset + 0x40E)), // + m(this.createChannelId(r, RackChannel.LEVEL2_CELL_UNDER_TEMPERATURE_RECOVER), + new SignedWordElement(r.offset + 0x40F)), // + m(this.createChannelId(r, RackChannel.LEVEL2_SOC_LOW_PROTECTION), + new UnsignedWordElement(r.offset + 0x410)), // + m(this.createChannelId(r, RackChannel.LEVEL2_SOC_LOW_PROTECTION_RECOVER), + new UnsignedWordElement(r.offset + 0x411)), // + new DummyRegisterElement(r.offset + 0x412, r.offset + 0x413), // + m(this.createChannelId(r, RackChannel.LEVEL2_CONNECTOR_TEMPERATURE_HIGH_PROTECTION), + new SignedWordElement(r.offset + 0x414)), // + m(this.createChannelId(r, RackChannel.LEVEL2_CONNECTOR_TEMPERATURE_HIGH_PROTECTION_RECOVER), + new SignedWordElement(r.offset + 0x415)), // + m(this.createChannelId(r, RackChannel.LEVEL2_INSULATION_PROTECTION), + new UnsignedWordElement(r.offset + 0x416)), // + m(this.createChannelId(r, RackChannel.LEVEL2_INSULATION_PROTECTION_RECOVER), + new UnsignedWordElement(r.offset + 0x417)), // + m(this.createChannelId(r, RackChannel.LEVEL2_CELL_VOLTAGE_DIFFERENCE_PROTECTION), + new UnsignedWordElement(r.offset + 0x418)), // + m(this.createChannelId(r, RackChannel.LEVEL2_CELL_VOLTAGE_DIFFERENCE_PROTECTION_RECOVER), + new UnsignedWordElement(r.offset + 0x419)), // + m(this.createChannelId(r, RackChannel.LEVEL2_TOTAL_VOLTAGE_DIFFERENCE_PROTECTION), + new UnsignedWordElement(r.offset + 0x41A), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL2_TOTAL_VOLTAGE_DIFFERENCE_PROTECTION_RECOVER), + new UnsignedWordElement(r.offset + 0x41B), ElementToChannelConverter.SCALE_FACTOR_2), // + m(this.createChannelId(r, RackChannel.LEVEL2_DISCHARGE_TEMPERATURE_HIGH_PROTECTION), + new SignedWordElement(r.offset + 0x41C)), // + m(this.createChannelId(r, RackChannel.LEVEL2_DISCHARGE_TEMPERATURE_HIGH_PROTECTION_RECOVER), + new SignedWordElement(r.offset + 0x41D)), // + m(this.createChannelId(r, RackChannel.LEVEL2_DISCHARGE_TEMPERATURE_LOW_PROTECTION), + new SignedWordElement(r.offset + 0x41E)), // + m(this.createChannelId(r, RackChannel.LEVEL2_DISCHARGE_TEMPERATURE_LOW_PROTECTION_RECOVER), + new SignedWordElement(r.offset + 0x41F)), // + m(this.createChannelId(r, RackChannel.LEVEL2_TEMPERATURE_DIFFERENCE_PROTECTION), + new SignedWordElement(r.offset + 0x420)), // + m(this.createChannelId(r, RackChannel.LEVEL2_TEMPERATURE_DIFFERENCE_PROTECTION_RECOVER), + new SignedWordElement(r.offset + 0x421)) // + }; + this.getModbusProtocol().addTasks(// + new FC16WriteRegistersTask(r.offset + 0x400, elements)); + this.getModbusProtocol().addTasks(// + new FC3ReadRegistersTask(r.offset + 0x400, Priority.LOW, elements)); + } + + } + } + + private void initializeBatteryLimits(int numberOfModules) { // Initialize Battery Limits this._setChargeMaxCurrent(0 /* default value 0 to avoid damages */); this._setDischargeMaxCurrent(0 /* default value 0 to avoid damages */); - this._setChargeMaxVoltage(this.config.numberOfSlaves() * Constants.MAX_VOLTAGE_MILLIVOLT_PER_MODULE / 1000); - this._setDischargeMinVoltage(this.config.numberOfSlaves() * Constants.MIN_VOLTAGE_MILLIVOLT_PER_MODULE / 1000); + this._setChargeMaxVoltage(numberOfModules * Constants.MAX_VOLTAGE_MILLIVOLT_PER_MODULE / 1000); + this._setDischargeMinVoltage(numberOfModules * Constants.MIN_VOLTAGE_MILLIVOLT_PER_MODULE / 1000); + } + + /** + * Calculates the Capacity as Capacity per module multiplied with number of + * modules and sets the CAPACITY channel. + * + * @param numberOfTowers the number of battery towers + * @param numberOfModules the number of battery modules + */ + private void calculateCapacity(int numberOfTowers, int numberOfModules) { + int capacity = numberOfTowers * numberOfModules * ModuleType.MODULE_3_5_KWH.getCapacity_Wh(); + this._setCapacity(capacity); + } + + /** + * Gets the Number of Modules. + * + * @return the Number of Modules as a {@link CompletableFuture}. + * @throws OpenemsException on error + */ + private CompletableFuture getNumberOfModules() { + final CompletableFuture result = new CompletableFuture(); + + try { + ModbusUtils + .readELementOnce(this.getModbusProtocol(), + new UnsignedWordElement(0x20C1 /* No of modules for 1st tower */), true) + .thenAccept(numberOfModules -> { + if (numberOfModules == null) { + return; + } + result.complete(numberOfModules); + }); + } catch (OpenemsException e) { + result.completeExceptionally(e); + } + + return result; + } + + /** + * Recursively reads the 'No of modules' register of each tower. Eventually + * completes the {@link CompletableFuture}. + * + * @param result the {@link CompletableFuture} + * @param totalNumberOfTowers the recursively incremented total number of towers + * @param addresses Queue with the remaining 'No of modules' registers + * @param tryAgainOnError if true, tries to read till it receives a value; + * if false, stops after first try and possibly + * return null + */ + private void checkNumberOfTowers(CompletableFuture result, int totalNumberOfTowers, + final Queue addresses, boolean tryAgainOnError) { + final Integer address = addresses.poll(); + + if (address == null) { + // Finished Queue + result.complete(totalNumberOfTowers); + return; + } + + try { + // Read next address in Queue + ModbusUtils.readELementOnce(this.getModbusProtocol(), new UnsignedWordElement(address), tryAgainOnError) + .thenAccept(numberOfModules -> { + if (numberOfModules == null) { + if (tryAgainOnError) { + // Try again + return; + } else { + // Read error -> this tower does not exist. Stop here. + result.complete(totalNumberOfTowers); + return; + } + } + + // Read successful -> try to read next tower + this.checkNumberOfTowers(result, totalNumberOfTowers + 1, addresses, false); + }); + } catch (OpenemsException e) { + e.printStackTrace(); + result.completeExceptionally(e); + return; + } + } + + private CompletableFuture getNumberOfTowers() throws OpenemsException { + final CompletableFuture result = new CompletableFuture(); + + Queue addresses = new LinkedList(); + addresses.add(0x20C1 /* No of modules for 1st tower */); + addresses.add(0x30C1 /* No of modules for 2nd tower */); + addresses.add(0x40C1 /* No of modules for 3rd tower */); + addresses.add(0x50C1 /* No of modules for 4th tower */); + addresses.add(0x60C1 /* No of modules for 5th tower */); + + this.checkNumberOfTowers(result, 0, addresses, true); + + return result; } @Override @@ -381,429 +953,6 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { ) // )); // - // Create racks dynamically, do this before super() call because super() uses - // getModbusProtocol, and it is using racks... - for (Rack r : this.racks) { - protocol.addTasks(// - - new FC3ReadRegistersTask(r.offset + 0x000B, Priority.LOW, // - m(this.rack(r, RackChannel.EMS_ADDRESS), new UnsignedWordElement(r.offset + 0x000B)), // - m(this.rack(r, RackChannel.EMS_BAUDRATE), new UnsignedWordElement(r.offset + 0x000C)), // - new DummyRegisterElement(r.offset + 0x000D, r.offset + 0x000F), - m(this.rack(r, RackChannel.PRE_CHARGE_CONTROL), new UnsignedWordElement(r.offset + 0x0010)), // - new DummyRegisterElement(r.offset + 0x0011, r.offset + 0x0014), - m(this.rack(r, RackChannel.SET_SUB_MASTER_ADDRESS), - new UnsignedWordElement(r.offset + 0x0015)) // - ), // - new FC3ReadRegistersTask(r.offset + 0x00F4, Priority.LOW, // - m(this.rack(r, RackChannel.EMS_COMMUNICATION_TIMEOUT), - new UnsignedWordElement(r.offset + 0x00F4)) // - ), - - // Single Cluster Control Registers (running without Master BMS) - new FC6WriteRegisterTask(r.offset + 0x0010, // - m(this.rack(r, RackChannel.PRE_CHARGE_CONTROL), new UnsignedWordElement(r.offset + 0x0010)) // - ), // - new FC6WriteRegisterTask(r.offset + 0x00F4, // - m(this.rack(r, RackChannel.EMS_COMMUNICATION_TIMEOUT), - new UnsignedWordElement(r.offset + 0x00F4)) // - ), // - new FC16WriteRegistersTask(r.offset + 0x000B, // - m(this.rack(r, RackChannel.EMS_ADDRESS), new UnsignedWordElement(r.offset + 0x000B)), // - m(this.rack(r, RackChannel.EMS_BAUDRATE), new UnsignedWordElement(r.offset + 0x000C)) // - ), // - - // Single Cluster Control Registers (General) - new FC6WriteRegisterTask(r.offset + 0x00CC, // - m(this.rack(r, RackChannel.SYSTEM_TOTAL_CAPACITY), - new UnsignedWordElement(r.offset + 0x00CC)) // - ), // - new FC6WriteRegisterTask(r.offset + 0x0015, // - m(this.rack(r, RackChannel.SET_SUB_MASTER_ADDRESS), - new UnsignedWordElement(r.offset + 0x0015)) // - ), // - new FC6WriteRegisterTask(r.offset + 0x00F3, // - m(this.rack(r, RackChannel.VOLTAGE_LOW_PROTECTION), - new UnsignedWordElement(r.offset + 0x00F3)) // - ), // - new FC3ReadRegistersTask(r.offset + 0x00CC, Priority.LOW, // - m(this.rack(r, RackChannel.SYSTEM_TOTAL_CAPACITY), - new UnsignedWordElement(r.offset + 0x00CC)) // - ), - - // Single Cluster Status Registers - new FC3ReadRegistersTask(r.offset + 0x100, Priority.HIGH, // - m(this.rack(r, RackChannel.VOLTAGE), new UnsignedWordElement(r.offset + 0x100), - ElementToChannelConverter.SCALE_FACTOR_2), - m(this.rack(r, RackChannel.CURRENT), new SignedWordElement(r.offset + 0x101), - ElementToChannelConverter.SCALE_FACTOR_2), - m(this.rack(r, RackChannel.CHARGE_INDICATION), new UnsignedWordElement(r.offset + 0x102)), - m(this.rack(r, RackChannel.SOC), new UnsignedWordElement(r.offset + 0x103)), - m(this.rack(r, RackChannel.SOH), new UnsignedWordElement(r.offset + 0x104)), - m(this.rack(r, RackChannel.MAX_CELL_VOLTAGE_ID), new UnsignedWordElement(r.offset + 0x105)), - m(this.rack(r, RackChannel.MAX_CELL_VOLTAGE), new UnsignedWordElement(r.offset + 0x106)), - m(this.rack(r, RackChannel.MIN_CELL_VOLTAGE_ID), new UnsignedWordElement(r.offset + 0x107)), - m(this.rack(r, RackChannel.MIN_CELL_VOLTAGE), new UnsignedWordElement(r.offset + 0x108)), - m(this.rack(r, RackChannel.MAX_CELL_TEMPERATURE_ID), - new UnsignedWordElement(r.offset + 0x109)), - m(this.rack(r, RackChannel.MAX_CELL_TEMPERATURE), new SignedWordElement(r.offset + 0x10A), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1), - m(this.rack(r, RackChannel.MIN_CELL_TEMPERATURE_ID), - new UnsignedWordElement(r.offset + 0x10B)), - m(this.rack(r, RackChannel.MIN_CELL_TEMPERATURE), new SignedWordElement(r.offset + 0x10C), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1), - m(this.rack(r, RackChannel.AVERAGE_VOLTAGE), new UnsignedWordElement(r.offset + 0x10D)), - m(this.rack(r, RackChannel.SYSTEM_INSULATION), new UnsignedWordElement(r.offset + 0x10E)), - m(this.rack(r, RackChannel.SYSTEM_MAX_CHARGE_CURRENT), - new UnsignedWordElement(r.offset + 0x10F), - ElementToChannelConverter.SCALE_FACTOR_2), - m(this.rack(r, RackChannel.SYSTEM_MAX_DISCHARGE_CURRENT), - new UnsignedWordElement(r.offset + 0x110), - ElementToChannelConverter.SCALE_FACTOR_2), - m(this.rack(r, RackChannel.POSITIVE_INSULATION), new UnsignedWordElement(r.offset + 0x111)), - m(this.rack(r, RackChannel.NEGATIVE_INSULATION), new UnsignedWordElement(r.offset + 0x112)), - m(this.rack(r, RackChannel.CLUSTER_RUN_STATE), new UnsignedWordElement(r.offset + 0x113)), - m(this.rack(r, RackChannel.AVG_TEMPERATURE), new SignedWordElement(r.offset + 0x114))), - new FC3ReadRegistersTask(r.offset + 0x18b, Priority.LOW, - m(this.rack(r, RackChannel.PROJECT_ID), new UnsignedWordElement(r.offset + 0x18b)), - m(this.rack(r, RackChannel.VERSION_MAJOR), new UnsignedWordElement(r.offset + 0x18c)), - m(this.rack(r, RackChannel.VERSION_SUB), new UnsignedWordElement(r.offset + 0x18d)), - m(this.rack(r, RackChannel.VERSION_MODIFY), new UnsignedWordElement(r.offset + 0x18e))), - - // System Warning/Shut Down Status Registers - new FC3ReadRegistersTask(r.offset + 0x140, Priority.LOW, - // Level 2 Alarm: BMS Self-protect, main contactor shut down - m(new BitsWordElement(r.offset + 0x140, this) // - .bit(0, this.rack(r, RackChannel.LEVEL2_CELL_VOLTAGE_HIGH)) // - .bit(1, this.rack(r, RackChannel.LEVEL2_TOTAL_VOLTAGE_HIGH)) // - .bit(2, this.rack(r, RackChannel.LEVEL2_CHARGE_CURRENT_HIGH)) // - .bit(3, this.rack(r, RackChannel.LEVEL2_CELL_VOLTAGE_LOW)) // - .bit(4, this.rack(r, RackChannel.LEVEL2_TOTAL_VOLTAGE_LOW)) // - .bit(5, this.rack(r, RackChannel.LEVEL2_DISCHARGE_CURRENT_HIGH)) // - .bit(6, this.rack(r, RackChannel.LEVEL2_CHARGE_TEMP_HIGH)) // - .bit(7, this.rack(r, RackChannel.LEVEL2_CHARGE_TEMP_LOW)) // - // 8 -> Reserved - // 9 -> Reserved - .bit(10, this.rack(r, RackChannel.LEVEL2_POWER_POLE_TEMP_HIGH)) // - // 11 -> Reserved - .bit(12, this.rack(r, RackChannel.LEVEL2_INSULATION_VALUE)) // - // 13 -> Reserved - .bit(14, this.rack(r, RackChannel.LEVEL2_DISCHARGE_TEMP_HIGH)) // - .bit(15, this.rack(r, RackChannel.LEVEL2_DISCHARGE_TEMP_LOW)) // - ), - // Level 1 Alarm: EMS Control to stop charge, discharge, charge&discharge - m(new BitsWordElement(r.offset + 0x141, this) // - .bit(0, this.rack(r, RackChannel.LEVEL1_CELL_VOLTAGE_HIGH)) // - .bit(1, this.rack(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_HIGH)) // - .bit(2, this.rack(r, RackChannel.LEVEL1_CHARGE_CURRENT_HIGH)) // - .bit(3, this.rack(r, RackChannel.LEVEL1_CELL_VOLTAGE_LOW)) // - .bit(4, this.rack(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_LOW)) // - .bit(5, this.rack(r, RackChannel.LEVEL1_DISCHARGE_CURRENT_HIGH)) // - .bit(6, this.rack(r, RackChannel.LEVEL1_CHARGE_TEMP_HIGH)) // - .bit(7, this.rack(r, RackChannel.LEVEL1_CHARGE_TEMP_LOW)) // - .bit(8, this.rack(r, RackChannel.LEVEL1_SOC_LOW)) // - .bit(9, this.rack(r, RackChannel.LEVEL1_TEMP_DIFF_TOO_BIG)) // - .bit(10, this.rack(r, RackChannel.LEVEL1_POWER_POLE_TEMP_HIGH)) // - .bit(11, this.rack(r, RackChannel.LEVEL1_CELL_VOLTAGE_DIFF_TOO_BIG)) // - .bit(12, this.rack(r, RackChannel.LEVEL1_INSULATION_VALUE)) // - .bit(13, this.rack(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_DIFF_TOO_BIG)) // - .bit(14, this.rack(r, RackChannel.LEVEL1_DISCHARGE_TEMP_HIGH)) // - .bit(15, this.rack(r, RackChannel.LEVEL1_DISCHARGE_TEMP_LOW)) // - ), - // Pre-Alarm: Temperature Alarm will active current limication - m(new BitsWordElement(r.offset + 0x142, this) // - .bit(0, this.rack(r, RackChannel.PRE_ALARM_CELL_VOLTAGE_HIGH)) // - .bit(1, this.rack(r, RackChannel.PRE_ALARM_TOTAL_VOLTAGE_HIGH)) // - .bit(2, this.rack(r, RackChannel.PRE_ALARM_CHARGE_CURRENT_HIGH)) // - .bit(3, this.rack(r, RackChannel.PRE_ALARM_CELL_VOLTAGE_LOW)) // - .bit(4, this.rack(r, RackChannel.PRE_ALARM_TOTAL_VOLTAGE_LOW)) // - .bit(5, this.rack(r, RackChannel.PRE_ALARM_DISCHARGE_CURRENT_HIGH)) // - .bit(6, this.rack(r, RackChannel.PRE_ALARM_CHARGE_TEMP_HIGH)) // - .bit(7, this.rack(r, RackChannel.PRE_ALARM_CHARGE_TEMP_LOW)) // - .bit(8, this.rack(r, RackChannel.PRE_ALARM_SOC_LOW)) // - .bit(9, this.rack(r, RackChannel.PRE_ALARM_TEMP_DIFF_TOO_BIG)) // - .bit(10, this.rack(r, RackChannel.PRE_ALARM_POWER_POLE_HIGH))// - .bit(11, this.rack(r, RackChannel.PRE_ALARM_CELL_VOLTAGE_DIFF_TOO_BIG)) // - .bit(12, this.rack(r, RackChannel.PRE_ALARM_INSULATION_FAIL)) // - .bit(13, this.rack(r, RackChannel.PRE_ALARM_TOTAL_VOLTAGE_DIFF_TOO_BIG)) // - .bit(14, this.rack(r, RackChannel.PRE_ALARM_DISCHARGE_TEMP_HIGH)) // - .bit(15, this.rack(r, RackChannel.PRE_ALARM_DISCHARGE_TEMP_LOW)) // - ) // - ), - // Other Alarm Info - new FC3ReadRegistersTask(r.offset + 0x1A5, Priority.LOW, // - m(new BitsWordElement(r.offset + 0x1A5, this) // - .bit(0, this.rack(r, RackChannel.ALARM_COMMUNICATION_TO_MASTER_BMS)) // - .bit(1, this.rack(r, RackChannel.ALARM_COMMUNICATION_TO_SLAVE_BMS)) // - .bit(2, this.rack(r, RackChannel.ALARM_COMMUNICATION_SLAVE_BMS_TO_TEMP_SENSORS)) // - .bit(3, this.rack(r, RackChannel.ALARM_SLAVE_BMS_HARDWARE)) // - )), - // Slave BMS Fault Message Registers - new FC3ReadRegistersTask(r.offset + 0x185, Priority.LOW, // - m(new BitsWordElement(r.offset + 0x185, this) // - .bit(0, this.rack(r, RackChannel.SLAVE_BMS_VOLTAGE_SENSOR_CABLES)) // - .bit(1, this.rack(r, RackChannel.SLAVE_BMS_POWER_CABLE)) // - .bit(2, this.rack(r, RackChannel.SLAVE_BMS_LTC6803)) // - .bit(3, this.rack(r, RackChannel.SLAVE_BMS_VOLTAGE_SENSORS)) // - .bit(4, this.rack(r, RackChannel.SLAVE_BMS_TEMP_SENSOR_CABLES)) // - .bit(5, this.rack(r, RackChannel.SLAVE_BMS_TEMP_SENSORS)) // - .bit(6, this.rack(r, RackChannel.SLAVE_BMS_POWER_POLE_TEMP_SENSOR)) // - .bit(7, this.rack(r, RackChannel.SLAVE_BMS_TEMP_BOARD_COM)) // - .bit(8, this.rack(r, RackChannel.SLAVE_BMS_BALANCE_MODULE)) // - .bit(9, this.rack(r, RackChannel.SLAVE_BMS_TEMP_SENSORS2)) // - .bit(10, this.rack(r, RackChannel.SLAVE_BMS_INTERNAL_COM)) // - .bit(11, this.rack(r, RackChannel.SLAVE_BMS_EEPROM)) // - .bit(12, this.rack(r, RackChannel.SLAVE_BMS_INIT)) // - )) // - ); // - // TODO - /* - * Possibly improve it, see @link RackChannel deepCopyDoc() // - */ - Consumer addCellChannels = (type) -> { - for (int i = 0; i < this.config.numberOfSlaves(); i++) { - AbstractModbusElement[] elements = new AbstractModbusElement[type.getSensorsPerModule()]; - for (int j = 0; j < type.getSensorsPerModule(); j++) { - int sensorIndex = i * type.getSensorsPerModule() + j; - io.openems.edge.common.channel.ChannelId channelId = CellChannelFactory.create(r, type, - sensorIndex); - // Register the Channel at this Component - this.addChannel(channelId); - // Add the Modbus Element and map it to the Channel - elements[j] = m(channelId, new UnsignedWordElement(r.offset + type.getOffset() + sensorIndex)); - } - // Add a Modbus read task for this module - try { - protocol.addTask(// - new FC3ReadRegistersTask(r.offset + type.getOffset() + i * type.getSensorsPerModule(), - Priority.LOW, elements)); - } catch (OpenemsException e) { - this.log.error("! ERROR ! occurred while creating modbus tasks" + e.getMessage()); - } - } - }; - addCellChannels.accept(CellChannelFactory.Type.VOLTAGE_CLUSTER); - addCellChannels.accept(CellChannelFactory.Type.TEMPERATURE_CLUSTER); - - // WARN_LEVEL_Pre Alarm (Pre Alarm configuration registers RW) - { - AbstractModbusElement[] elements = new AbstractModbusElement[] { - m(this.rack(r, RackChannel.PRE_ALARM_CELL_OVER_VOLTAGE_ALARM), - new UnsignedWordElement(r.offset + 0x080)), // - m(this.rack(r, RackChannel.PRE_ALARM_CELL_OVER_VOLTAGE_RECOVER), - new UnsignedWordElement(r.offset + 0x081)), // - m(this.rack(r, RackChannel.PRE_ALARM_SYSTEM_OVER_VOLTAGE_ALARM), - new UnsignedWordElement(r.offset + 0x082), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.PRE_ALARM_SYSTEM_OVER_VOLTAGE_RECOVER), - new UnsignedWordElement(r.offset + 0x083), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.PRE_ALARM_SYSTEM_CHARGE_OVER_CURRENT_ALARM), - new UnsignedWordElement(r.offset + 0x084), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.PRE_ALARM_SYSTEM_CHARGE_OVER_CURRENT_RECOVER), - new UnsignedWordElement(r.offset + 0x085), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.PRE_ALARM_CELL_UNDER_VOLTAGE_ALARM), - new UnsignedWordElement(r.offset + 0x086)), // - m(this.rack(r, RackChannel.PRE_ALARM_CELL_UNDER_VOLTAGE_RECOVER), - new UnsignedWordElement(r.offset + 0x087)), // - m(this.rack(r, RackChannel.PRE_ALARM_SYSTEM_UNDER_VOLTAGE_ALARM), - new UnsignedWordElement(r.offset + 0x088), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.PRE_ALARM_SYSTEM_UNDER_VOLTAGE_RECOVER), - new UnsignedWordElement(r.offset + 0x089), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.PRE_ALARM_SYSTEM_DISCHARGE_OVER_CURRENT_ALARM), - new UnsignedWordElement(r.offset + 0x08A), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.PRE_ALARM_SYSTEM_DISCHARGE_OVER_CURRENT_RECOVER), - new UnsignedWordElement(r.offset + 0x08B), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.PRE_ALARM_CELL_OVER_TEMPERATURE_ALARM), - new SignedWordElement(r.offset + 0x08C)), // - m(this.rack(r, RackChannel.PRE_ALARM_CELL_OVER_TEMPERATURE_RECOVER), - new SignedWordElement(r.offset + 0x08D)), // - m(this.rack(r, RackChannel.PRE_ALARM_CELL_UNDER_TEMPERATURE_ALARM), - new SignedWordElement(r.offset + 0x08E)), // - m(this.rack(r, RackChannel.PRE_ALARM_CELL_UNDER_TEMPERATURE_RECOVER), - new SignedWordElement(r.offset + 0x08F)), // - m(this.rack(r, RackChannel.PRE_ALARM_SOC_LOW_ALARM), new UnsignedWordElement(r.offset + 0x090)), // - m(this.rack(r, RackChannel.PRE_ALARM_SOC_LOW_ALARM_RECOVER), - new UnsignedWordElement(r.offset + 0x091)), // - new DummyRegisterElement(r.offset + 0x092, r.offset + 0x093), - m(this.rack(r, RackChannel.PRE_ALARM_CONNECTOR_TEMPERATURE_HIGH_ALARM), - new SignedWordElement(r.offset + 0x094)), // - m(this.rack(r, RackChannel.PRE_ALARM_CONNECTOR_TEMPERATURE_HIGH_ALARM_RECOVER), - new SignedWordElement(r.offset + 0x095)), // - m(this.rack(r, RackChannel.PRE_ALARM_INSULATION_ALARM), - new UnsignedWordElement(r.offset + 0x096)), // - m(this.rack(r, RackChannel.PRE_ALARM_INSULATION_ALARM_RECOVER), - new UnsignedWordElement(r.offset + 0x097)), // - m(this.rack(r, RackChannel.PRE_ALARM_CELL_VOLTAGE_DIFFERENCE_ALARM), - new UnsignedWordElement(r.offset + 0x098)), // - m(this.rack(r, RackChannel.PRE_ALARM_CELL_VOLTAGE_DIFFERENCE_ALARM_RECOVER), - new UnsignedWordElement(r.offset + 0x099)), // - m(this.rack(r, RackChannel.PRE_ALARM_TOTAL_VOLTAGE_DIFFERENCE_ALARM), - new UnsignedWordElement(r.offset + 0x09A), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.PRE_ALARM_TOTAL_VOLTAGE_DIFFERENCE_ALARM_RECOVER), - new UnsignedWordElement(r.offset + 0x09B), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.PRE_ALARM_DISCHARGE_TEMPERATURE_HIGH_ALARM), - new SignedWordElement(r.offset + 0x09C)), // - m(this.rack(r, RackChannel.PRE_ALARM_DISCHARGE_TEMPERATURE_HIGH_ALARM_RECOVER), - new SignedWordElement(r.offset + 0x09D)), // - m(this.rack(r, RackChannel.PRE_ALARM_DISCHARGE_TEMPERATURE_LOW_ALARM), - new SignedWordElement(r.offset + 0x09E)), // - m(this.rack(r, RackChannel.PRE_ALARM_DISCHARGE_TEMPERATURE_LOW_ALARM_RECOVER), - new SignedWordElement(r.offset + 0x09F)), // - m(this.rack(r, RackChannel.PRE_ALARM_TEMPERATURE_DIFFERENCE_ALARM), - new SignedWordElement(r.offset + 0x0A0)), // - m(this.rack(r, RackChannel.PRE_ALARM_TEMPERATURE_DIFFERENCE_ALARM_RECOVER), - new SignedWordElement(r.offset + 0x0A1)) // - }; - protocol.addTask(new FC16WriteRegistersTask(r.offset + 0x080, elements)); - protocol.addTask(new FC3ReadRegistersTask(r.offset + 0x080, Priority.LOW, elements)); - } - - // WARN_LEVEL1 (Level1 warning registers RW) - { - AbstractModbusElement[] elements = new AbstractModbusElement[] { - m(this.rack(r, RackChannel.LEVEL1_CELL_OVER_VOLTAGE_PROTECTION), - new UnsignedWordElement(r.offset + 0x040)), // - m(this.rack(r, RackChannel.LEVEL1_CELL_OVER_VOLTAGE_RECOVER), - new UnsignedWordElement(r.offset + 0x041)), // - m(this.rack(r, RackChannel.LEVEL1_SYSTEM_OVER_VOLTAGE_PROTECTION), - new UnsignedWordElement(r.offset + 0x042), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL1_SYSTEM_OVER_VOLTAGE_RECOVER), - new UnsignedWordElement(r.offset + 0x043), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL1_SYSTEM_CHARGE_OVER_CURRENT_PROTECTION), - new UnsignedWordElement(r.offset + 0x044), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL1_SYSTEM_CHARGE_OVER_CURRENT_RECOVER), - new UnsignedWordElement(r.offset + 0x045), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL1_CELL_UNDER_VOLTAGE_PROTECTION), - new UnsignedWordElement(r.offset + 0x046)), // - m(this.rack(r, RackChannel.LEVEL1_CELL_UNDER_VOLTAGE_RECOVER), - new UnsignedWordElement(r.offset + 0x047)), // - m(this.rack(r, RackChannel.LEVEL1_SYSTEM_UNDER_VOLTAGE_PROTECTION), - new UnsignedWordElement(r.offset + 0x048), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL1_SYSTEM_UNDER_VOLTAGE_RECOVER), - new UnsignedWordElement(r.offset + 0x049), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL1_SYSTEM_DISCHARGE_OVER_CURRENT_PROTECTION), - new UnsignedWordElement(r.offset + 0x04A), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL1_SYSTEM_DISCHARGE_OVER_CURRENT_RECOVER), - new UnsignedWordElement(r.offset + 0x04B), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL1_CELL_OVER_TEMPERATURE_PROTECTION), - new SignedWordElement(r.offset + 0x04C)), // - m(this.rack(r, RackChannel.LEVEL1_CELL_OVER_TEMPERATURE_RECOVER), - new SignedWordElement(r.offset + 0x04D)), // - m(this.rack(r, RackChannel.LEVEL1_CELL_UNDER_TEMPERATURE_PROTECTION), - new SignedWordElement(r.offset + 0x04E)), // - m(this.rack(r, RackChannel.LEVEL1_CELL_UNDER_TEMPERATURE_RECOVER), - new SignedWordElement(r.offset + 0x04F)), // - m(this.rack(r, RackChannel.LEVEL1_SOC_LOW_PROTECTION), - new UnsignedWordElement(r.offset + 0x050)), // - m(this.rack(r, RackChannel.LEVEL1_SOC_LOW_PROTECTION_RECOVER), - new UnsignedWordElement(r.offset + 0x051)), // - new DummyRegisterElement(r.offset + 0x052, r.offset + 0x053), // - m(this.rack(r, RackChannel.LEVEL1_CONNECTOR_TEMPERATURE_HIGH_PROTECTION), - new SignedWordElement(r.offset + 0x054)), // - m(this.rack(r, RackChannel.LEVEL1_CONNECTOR_TEMPERATURE_HIGH_PROTECTION_RECOVER), - new SignedWordElement(r.offset + 0x055)), // - m(this.rack(r, RackChannel.LEVEL1_INSULATION_PROTECTION), - new UnsignedWordElement(r.offset + 0x056)), // - m(this.rack(r, RackChannel.LEVEL1_INSULATION_PROTECTION_RECOVER), - new UnsignedWordElement(r.offset + 0x057)), // - m(this.rack(r, RackChannel.LEVEL1_CELL_VOLTAGE_DIFFERENCE_PROTECTION), - new UnsignedWordElement(r.offset + 0x058)), // - m(this.rack(r, RackChannel.LEVEL1_CELL_VOLTAGE_DIFFERENCE_PROTECTION_RECOVER), - new UnsignedWordElement(r.offset + 0x059)), // - m(this.rack(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_DIFFERENCE_PROTECTION), - new UnsignedWordElement(r.offset + 0x05A), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_DIFFERENCE_PROTECTION_RECOVER), - new UnsignedWordElement(r.offset + 0x05B), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL1_DISCHARGE_TEMPERATURE_HIGH_PROTECTION), - new SignedWordElement(r.offset + 0x05C)), // - m(this.rack(r, RackChannel.LEVEL1_DISCHARGE_TEMPERATURE_HIGH_PROTECTION_RECOVER), - new SignedWordElement(r.offset + 0x05D)), // - m(this.rack(r, RackChannel.LEVEL1_DISCHARGE_TEMPERATURE_LOW_PROTECTION), - new SignedWordElement(r.offset + 0x05E)), // - m(this.rack(r, RackChannel.LEVEL1_DISCHARGE_TEMPERATURE_LOW_PROTECTION_RECOVER), - new SignedWordElement(r.offset + 0x05F)), // - m(this.rack(r, RackChannel.LEVEL1_TEMPERATURE_DIFFERENCE_PROTECTION), - new SignedWordElement(r.offset + 0x060)), // - m(this.rack(r, RackChannel.LEVEL1_TEMPERATURE_DIFFERENCE_PROTECTION_RECOVER), - new SignedWordElement(r.offset + 0x061)) // - }; - protocol.addTask(new FC16WriteRegistersTask(r.offset + 0x040, elements)); - protocol.addTask(new FC3ReadRegistersTask(r.offset + 0x040, Priority.LOW, elements)); - } - - // WARN_LEVEL2 (Level2 Protection registers RW) - { - AbstractModbusElement[] elements = new AbstractModbusElement[] { - m(this.rack(r, RackChannel.LEVEL2_CELL_OVER_VOLTAGE_PROTECTION), - new UnsignedWordElement(r.offset + 0x400)), // - m(this.rack(r, RackChannel.LEVEL2_CELL_OVER_VOLTAGE_RECOVER), - new UnsignedWordElement(r.offset + 0x401)), // - m(this.rack(r, RackChannel.LEVEL2_SYSTEM_OVER_VOLTAGE_PROTECTION), - new UnsignedWordElement(r.offset + 0x402)), // - m(this.rack(r, RackChannel.LEVEL2_SYSTEM_OVER_VOLTAGE_RECOVER), - new UnsignedWordElement(r.offset + 0x403), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL2_SYSTEM_CHARGE_OVER_CURRENT_PROTECTION), - new UnsignedWordElement(r.offset + 0x404), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL2_SYSTEM_CHARGE_OVER_CURRENT_RECOVER), - new UnsignedWordElement(r.offset + 0x405), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL2_CELL_UNDER_VOLTAGE_PROTECTION), - new UnsignedWordElement(r.offset + 0x406)), // - m(this.rack(r, RackChannel.LEVEL2_CELL_UNDER_VOLTAGE_RECOVER), - new UnsignedWordElement(r.offset + 0x407)), // - m(this.rack(r, RackChannel.LEVEL2_SYSTEM_UNDER_VOLTAGE_PROTECTION), - new UnsignedWordElement(r.offset + 0x408), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL2_SYSTEM_UNDER_VOLTAGE_RECOVER), - new UnsignedWordElement(r.offset + 0x409), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL2_SYSTEM_DISCHARGE_OVER_CURRENT_PROTECTION), - new UnsignedWordElement(r.offset + 0x40A), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL2_SYSTEM_DISCHARGE_OVER_CURRENT_RECOVER), - new UnsignedWordElement(r.offset + 0x40B), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL2_CELL_OVER_TEMPERATURE_PROTECTION), - new SignedWordElement(r.offset + 0x40C)), // - m(this.rack(r, RackChannel.LEVEL2_CELL_OVER_TEMPERATURE_RECOVER), - new SignedWordElement(r.offset + 0x40D)), // - m(this.rack(r, RackChannel.LEVEL2_CELL_UNDER_TEMPERATURE_PROTECTION), - new SignedWordElement(r.offset + 0x40E)), // - m(this.rack(r, RackChannel.LEVEL2_CELL_UNDER_TEMPERATURE_RECOVER), - new SignedWordElement(r.offset + 0x40F)), // - m(this.rack(r, RackChannel.LEVEL2_SOC_LOW_PROTECTION), - new UnsignedWordElement(r.offset + 0x410)), // - m(this.rack(r, RackChannel.LEVEL2_SOC_LOW_PROTECTION_RECOVER), - new UnsignedWordElement(r.offset + 0x411)), // - new DummyRegisterElement(r.offset + 0x412, r.offset + 0x413), // - m(this.rack(r, RackChannel.LEVEL2_CONNECTOR_TEMPERATURE_HIGH_PROTECTION), - new SignedWordElement(r.offset + 0x414)), // - m(this.rack(r, RackChannel.LEVEL2_CONNECTOR_TEMPERATURE_HIGH_PROTECTION_RECOVER), - new SignedWordElement(r.offset + 0x415)), // - m(this.rack(r, RackChannel.LEVEL2_INSULATION_PROTECTION), - new UnsignedWordElement(r.offset + 0x416)), // - m(this.rack(r, RackChannel.LEVEL2_INSULATION_PROTECTION_RECOVER), - new UnsignedWordElement(r.offset + 0x417)), // - m(this.rack(r, RackChannel.LEVEL2_CELL_VOLTAGE_DIFFERENCE_PROTECTION), - new UnsignedWordElement(r.offset + 0x418)), // - m(this.rack(r, RackChannel.LEVEL2_CELL_VOLTAGE_DIFFERENCE_PROTECTION_RECOVER), - new UnsignedWordElement(r.offset + 0x419)), // - m(this.rack(r, RackChannel.LEVEL2_TOTAL_VOLTAGE_DIFFERENCE_PROTECTION), - new UnsignedWordElement(r.offset + 0x41A), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL2_TOTAL_VOLTAGE_DIFFERENCE_PROTECTION_RECOVER), - new UnsignedWordElement(r.offset + 0x41B), ElementToChannelConverter.SCALE_FACTOR_2), // - m(this.rack(r, RackChannel.LEVEL2_DISCHARGE_TEMPERATURE_HIGH_PROTECTION), - new SignedWordElement(r.offset + 0x41C)), // - m(this.rack(r, RackChannel.LEVEL2_DISCHARGE_TEMPERATURE_HIGH_PROTECTION_RECOVER), - new SignedWordElement(r.offset + 0x41D)), // - m(this.rack(r, RackChannel.LEVEL2_DISCHARGE_TEMPERATURE_LOW_PROTECTION), - new SignedWordElement(r.offset + 0x41E)), // - m(this.rack(r, RackChannel.LEVEL2_DISCHARGE_TEMPERATURE_LOW_PROTECTION_RECOVER), - new SignedWordElement(r.offset + 0x41F)), // - m(this.rack(r, RackChannel.LEVEL2_TEMPERATURE_DIFFERENCE_PROTECTION), - new SignedWordElement(r.offset + 0x420)), // - m(this.rack(r, RackChannel.LEVEL2_TEMPERATURE_DIFFERENCE_PROTECTION_RECOVER), - new SignedWordElement(r.offset + 0x421)) // - }; - protocol.addTask(new FC16WriteRegistersTask(r.offset + 0x400, elements)); - protocol.addTask(new FC3ReadRegistersTask(r.offset + 0x400, Priority.LOW, elements)); - } - - } return protocol; } @@ -815,7 +964,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { * @param rackChannel the {@link RackChannel} * @return the {@link io.openems.edge.common.channel.ChannelId} */ - private final io.openems.edge.common.channel.ChannelId rack(Rack rack, RackChannel rackChannel) { + private final io.openems.edge.common.channel.ChannelId createChannelId(Rack rack, RackChannel rackChannel) { @SuppressWarnings("deprecation") Channel existingChannel = this._channel(rackChannel.toChannelIdString(rack)); if (existingChannel != null) { @@ -976,4 +1125,5 @@ public StartStop getStartStopTarget() { assert false; return StartStop.UNDEFINED; // can never happen } + } diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/Config.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/Config.java index 955d6a4b758..82f2e3bf414 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/Config.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/Config.java @@ -3,7 +3,6 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.battery.soltaro.common.enums.ModuleType; import io.openems.edge.common.startstop.StartStopConfig; @ObjectClassDefinition(// @@ -11,114 +10,26 @@ description = "Implements the Soltaro multi rack battery system.") public @interface Config { - /** - * Gets the . - * @return String - */ @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") - String id() default "bms0"; + String id() default "battery0"; - /** - * Gets the alias. - * @return String - */ @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") String alias() default ""; - /** - * Gets the enabled. - * @return boolean - */ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; - /** - * Gets the startStop. - * @return StartStopConfig - */ @AttributeDefinition(name = "Start/stop behaviour?", description = "Should this Component be forced to start or stop?") StartStopConfig startStop() default StartStopConfig.AUTO; - /** - * Gets the modbus_id. - * @return String - */ @AttributeDefinition(name = "Modbus-ID", description = "ID of Modbus bridge; ! Soltaro Cluster needs baudrate of 57600 !") String modbus_id() default "modbus0"; - /** - * Gets the modbusUnitId. - * @return int - */ @AttributeDefinition(name = "Modbus Unit-ID", description = "The Unit-ID of the Modbus device.") int modbusUnitId() default 0; - /** - * Gets the numberOfSlaves. - * @return int - */ - @AttributeDefinition(name = "Number of slaves", description = "The number of slaves in this battery rack (max. 20)", min = "1", max = "20") - int numberOfSlaves() default 20; - - /** - * Gets the moduleType. - * @return ModuleType - */ - @AttributeDefinition(name = "Module type", description = "The type of modules in the rack") - ModuleType moduleType() default ModuleType.MODULE_3_KWH; - - /** - * Gets the isRack1Used. - * @return boolean - */ - @AttributeDefinition(name = "Use Rack #1", description = "Is Rack #1 used?") - boolean isRack1Used() default true; - - /** - * Gets the isRack2Used. - * @return boolean - */ - @AttributeDefinition(name = "Use Rack #2", description = "Is Rack #1 used?") - boolean isRack2Used() default true; - - /** - * Gets the isRack3Used. - * @return boolean - */ - @AttributeDefinition(name = "Use Rack #3", description = "Is Rack #1 used?") - boolean isRack3Used() default true; - - /** - * Gets the isRack4Used. - * @return boolean - */ - @AttributeDefinition(name = "Use Rack #4", description = "Is Rack #1 used?") - boolean isRack4Used() default true; - - /** - * Gets the isRack5Used. - * @return boolean - */ - @AttributeDefinition(name = "Use Rack #5", description = "Is Rack #1 used?") - boolean isRack5Used() default true; - - /** - * Gets the watchdog. - * @return int - */ - @AttributeDefinition(name = "Watchdog", description = "Watchdog timeout in seconds") - int watchdog() default 90; - - /** - * Gets the Modbus_target. - * @return String - */ @AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.") String Modbus_target() default "(enabled=true)"; - /** - * Gets the webconsole_configurationFactory_nameHint. - * @return String - */ String webconsole_configurationFactory_nameHint() default "BMS Soltaro Cluster Version C [{id}]"; } \ No newline at end of file diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java index 3aa180fe550..a24decd409e 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java @@ -565,5 +565,5 @@ protected ChannelId toChannelId(Rack rack) { private String generateChannelId(Rack rack) { return rack.getChannelIdPrefix() + this.name(); } - + } diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/BatteryState.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/BatteryState.java index ad3087004c5..211bd501915 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/BatteryState.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/BatteryState.java @@ -1,9 +1,9 @@ package io.openems.edge.battery.soltaro.common.enums; public enum BatteryState { - - DEFAULT, - ON, - OFF, - ; + + DEFAULT, // + ON, // + OFF; + } diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/ModuleType.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/ModuleType.java index a584277a274..6a0e3d53297 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/ModuleType.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/ModuleType.java @@ -16,6 +16,7 @@ private ModuleType(int capacity) { /** * Gets the capacity. + * * @return int */ public int getCapacity_Wh() { diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/ResetState.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/ResetState.java index 93fcddee788..47bdc98cec2 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/ResetState.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/ResetState.java @@ -1,8 +1,10 @@ package io.openems.edge.battery.soltaro.common.enums; public enum ResetState { - NONE, - SLEEP, - RESET, - FINISHED + + NONE, // + SLEEP, // + RESET, // + FINISHED; + } diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/State.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/State.java index b95127bdbd2..5e6879ee428 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/State.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/common/enums/State.java @@ -9,7 +9,7 @@ public enum State implements OptionsEnum { INIT("Initializing", 2), // RUNNING("Running", 3), // STOPPING("Stopping", 4), // - ERROR("Error", 5), // + ERROR("Error", 5), // ERRORDELAY("Errordelay", 6), // ERROR_HANDLING("Errordelay", 7), // ; diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/Config.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/Config.java index 3ebea8f973a..bbe887d74b6 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/Config.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/Config.java @@ -11,7 +11,7 @@ @interface Config { @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") - String id() default "bms0"; + String id() default "battery0"; @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") String alias() default ""; @@ -27,22 +27,22 @@ @AttributeDefinition(name = "Error Level 2 Delay", description = "Sets the delay time in seconds how long the system should be stopped after an error level 2 has occurred") int errorLevel2Delay() default 600; - + @AttributeDefinition(name = "Max Start Time", description = "Max Time in seconds allowed for starting the system") int maxStartTime() default 30; - + @AttributeDefinition(name = "Pending Tolerance", description = "time in seconds, that is waited if system status cannot be determined e.g. in case of reading errors") int pendingTolerance() default 15; - + @AttributeDefinition(name = "Max Start Attempts", description = "Sets the counter how many time the system should try to start") int maxStartAppempts() default 5; - + @AttributeDefinition(name = "Start Not Successful Delay Time", description = "Sets the delay time in seconds how long the system should be stopped if it was not able to start") int startUnsuccessfulDelay() default 3600; - + @AttributeDefinition(name = "Minimal Cell Voltage Millivolt", description = "Minimal cell voltage in milli volt when system does not allow further discharging") int minimalCellVoltage() default 2800; - + @AttributeDefinition(name = "Capacity [kWh]", description = "The capacity of the Battery Rack.") int capacity() default 50; diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/Config.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/Config.java index 10d9f3f499e..e016de494d6 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/Config.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/Config.java @@ -11,146 +11,56 @@ description = "Implements the Soltaro battery rack system.") public @interface Config { - /** - * Return the id. - * - * @return id - */ @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") - String id() default "bms0"; + String id() default "battery0"; - /** - * Return the alias. - * - * @return alias - */ @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") String alias() default ""; - /** - * Return the enabled. - * - * @return enabled - */ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; - /** - * Return the modbus_id. - * - * @return modbus_id - */ @AttributeDefinition(name = "Modbus-ID", description = "ID of Modbus bridge.") String modbus_id() default "modbus0"; - /** - * Return the modbusUnitId. - * - * @return modbusUnitId - */ @AttributeDefinition(name = "Modbus Unit-ID", description = "The Unit-ID of the Modbus device.") int modbusUnitId() default 11; - /** - * Gets the StartStopConfig. - * - * @return StartStopConfig - */ @AttributeDefinition(name = "Start/stop behaviour?", description = "Should this Component be forced to start or stop?") StartStopConfig startStop() default StartStopConfig.AUTO; - /** - * Return the moduleType. - * - * @return moduleType - */ @AttributeDefinition(name = "Module type", description = "The type of modules in the rack") ModuleType moduleType() default ModuleType.MODULE_3_5_KWH; - /** - * Return the errorLevel2Delay. - * - * @return errorLevel2Delay - */ @AttributeDefinition(name = "Error Level 2 Delay", description = "Sets the delay time in seconds how long the system should be stopped after an error level 2 has occurred") int errorLevel2Delay() default 600; - /** - * Return the maxStartAppempts. - * - * @return maxStartAppempts - */ @AttributeDefinition(name = "Max Start Attempts", description = "Sets the counter how many time the system should try to start") int maxStartAppempts() default 5; - /** - * Return the maxStartTime. - * - * @return maxStartTime - */ @AttributeDefinition(name = "Max Start Time", description = "Max Time in seconds allowed for starting the system") int maxStartTime() default 30; - /** - * Return the startUnsuccessfulDelay. - * - * @return startUnsuccessfulDelay - */ @AttributeDefinition(name = "Start Not Successful Delay Time", description = "Sets the delay time in seconds how long the system should be stopped if it was not able to start") int startUnsuccessfulDelay() default 3600; - /** - * Return the watchdog. - * - * @return watchdog - */ @AttributeDefinition(name = "Watchdog", description = "Watchdog timeout in seconds") int watchdog() default 60; - /** - * Return the pendingTolerance. - * - * @return pendingTolerance - */ @AttributeDefinition(name = "Pending Tolerance", description = "time in seconds, that is waited if system status cannot be determined e.g. in case of reading errors") int pendingTolerance() default 15; - /** - * Return the soCLowAlarm. - * - * @return soCLowAlarm - */ @AttributeDefinition(name = "SoC Low Alarm", description = "Sets the value for BMS SoC protection (0..100)", min = "0", max = "100") int SoCLowAlarm() default 0; - /** - * Return the minimalCellVoltage. - * - * @return minimalCellVoltage - */ @AttributeDefinition(name = "Minimal Cell Voltage Millivolt", description = "Minimal cell voltage in milli volt when system does not allow further discharging") int minimalCellVoltage() default 2800; - /** - * Return the reduceTasks. - * - * @return reduceTasks - */ @AttributeDefinition(name = "Reduce tasks", description = "Reduces read and write tasks to avoid errors") boolean ReduceTasks() default false; - /** - * Return the Modbus_target. - * - * @return Modbus_target - */ @AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.") String Modbus_target() default "(enabled=true)"; - /** - * Return the webconsole_configurationFactory_nameHint. - * - * @return webconsole_configurationFactory_nameHint - */ String webconsole_configurationFactory_nameHint() default "BMS Soltaro Single Rack Version B [{id}]"; } \ No newline at end of file diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/statemachine/StateMachine.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/statemachine/StateMachine.java index c8bb670e84a..f1c2da278c7 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/statemachine/StateMachine.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/statemachine/StateMachine.java @@ -64,7 +64,7 @@ public StateHandler getStateHandler(State state) { case STOPPED: return new StoppedHandler(); case ERROR: - return new ErrorHandler(); + return new ErrorHandler(); } throw new IllegalArgumentException("Unknown State [" + state + "]"); } diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/Config.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/Config.java index 2b34119e05d..8a4f37fc7e5 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/Config.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/Config.java @@ -3,7 +3,6 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.battery.soltaro.common.enums.ModuleType; import io.openems.edge.common.startstop.StartStopConfig; @ObjectClassDefinition(// @@ -11,114 +10,26 @@ description = "Implements the Soltaro battery rack system.") public @interface Config { - /** - * Return the id. - * @return id - */ @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") - String id() default "bms0"; + String id() default "battery0"; - /** - * Return the alias. - * @return alias - */ @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") String alias() default ""; - /** - * Return the enabled. - * @return enabled - */ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; - /** - * Gets the StartStopConfig. - * @return StartStopConfig - */ @AttributeDefinition(name = "Start/stop behaviour?", description = "Should this Component be forced to start or stop?") StartStopConfig startStop() default StartStopConfig.AUTO; - /** - * Return the modbus_id. - * @return modbus_id - */ @AttributeDefinition(name = "Modbus-ID", description = "ID of Modbus bridge.") String modbus_id() default "modbus0"; - /** - * Return the modbusUnitId. - * @return modbusUnitId - */ @AttributeDefinition(name = "Modbus Unit-ID", description = "The Unit-ID of the Modbus device.") int modbusUnitId() default 0; - /** - * Return the numberOfSlaves. - * @return numberOfSlaves - */ - @AttributeDefinition(name = "Number of slaves", description = "The number of slaves in this battery rack (max. 20)", min = "1", max = "20") - int numberOfSlaves() default 20; - - /** - * Return the moduleType. - * @return moduleType - */ - @AttributeDefinition(name = "Module type", description = "The type of modules in the rack") - ModuleType moduleType() default ModuleType.MODULE_3_KWH; - - /** - * Return the errorLevel2Delay. - * @return errorLevel2Delay - */ - @AttributeDefinition(name = "Error Level 2 Delay", description = "Sets the delay time in seconds how long the system should be stopped after an error level 2 has occurred") - int errorLevel2Delay() default 600; - - /** - * Return the startUnsuccessfulDelay. - * @return startUnsuccessfulDelay - */ - @AttributeDefinition(name = "Start Not Successful Delay Time", description = "Sets the delay time in seconds how long the system should be stopped if it was not able to start") - int startUnsuccessfulDelay() default 3600; - - /** - * Return the watchdog. - * @return watchdog - */ - @AttributeDefinition(name = "Watchdog", description = "Watchdog timeout in seconds") - int watchdog() default 60; - - /** - * Return the pendingTolerance. - * @return pendingTolerance - */ - @AttributeDefinition(name = "Pending Tolerance", description = "time in seconds, that is waited if system status cannot be determined e.g. in case of reading errors") - int pendingTolerance() default 15; - - /** - * Return the soCLowAlarm. - * @return soCLowAlarm - */ - @AttributeDefinition(name = "SoC Low Alarm", description = "Sets the value for BMS SoC protection (0..100)", min = "0", max = "100") - int SocLowAlarm() default 0; - - /** - * Return the minimalCellVoltage. - * @return minimalCellVoltage - */ - @AttributeDefinition(name = "Minimal Cell Voltage Millivolt", description = "Minimal cell voltage in milli volt when system does not allow further discharging") - int minimalCellVoltage() default 2800; - - /** - * Return the Modbus_target. - * @return Modbus_target - */ @AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.") String Modbus_target() default "(enabled=true)"; - /** - * Return the webconsole_configurationFactory_nameHint. - * @return webconsole_configurationFactory_nameHint - */ String webconsole_configurationFactory_nameHint() default "BMS Soltaro Single Rack Version C [{id}]"; } \ No newline at end of file diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionC.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionC.java index baea68d4b1e..5ccca43c021 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionC.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionC.java @@ -147,7 +147,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId EMS_COMMUNICATION_TIMEOUT(Doc.of(OpenemsType.INTEGER) // .unit(Unit.SECONDS) // .accessMode(AccessMode.READ_WRITE)), // - WORK_PARAMETER_PCS_COMMUNICATION_RATE(Doc.of(OpenemsType.INTEGER) // + NUMBER_OF_MODULES_PER_TOWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.NONE) // .accessMode(AccessMode.READ_WRITE)), // SYSTEM_TOTAL_CAPACITY(Doc.of(OpenemsType.INTEGER) // diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionCImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionCImpl.java index 9ae3004084e..e66b87fffa8 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionCImpl.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionCImpl.java @@ -1,5 +1,6 @@ package io.openems.edge.battery.soltaro.single.versionc; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -25,7 +26,6 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.protection.BatteryProtection; -import io.openems.edge.battery.soltaro.common.batteryprotection.BatteryProtectionDefinitionSoltaro3000Wh; import io.openems.edge.battery.soltaro.common.batteryprotection.BatteryProtectionDefinitionSoltaro3500Wh; import io.openems.edge.battery.soltaro.common.enums.ModuleType; import io.openems.edge.battery.soltaro.single.versionc.statemachine.Context; @@ -38,6 +38,7 @@ import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; +import io.openems.edge.bridge.modbus.api.ModbusUtils; import io.openems.edge.bridge.modbus.api.element.AbstractModbusElement; import io.openems.edge.bridge.modbus.api.element.BitsWordElement; import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; @@ -81,6 +82,7 @@ public class SingleRackVersionCImpl extends AbstractOpenemsModbusComponent imple * Manages the {@link State}s of the StateMachine. */ private final StateMachine stateMachine = new StateMachine(State.UNDEFINED); + private static final int WATCHDOG = 60; private Config config; private BatteryProtection batteryProtection = null; @@ -110,34 +112,53 @@ void activate(ComponentContext context, Config config) throws OpenemsNamedExcept } // Initialize Battery-Protection - if (config.moduleType() == ModuleType.MODULE_3_5_KWH) { - // Special settings for 3.5 kWh module - this.batteryProtection = BatteryProtection.create(this) // - .applyBatteryProtectionDefinition(new BatteryProtectionDefinitionSoltaro3500Wh(), - this.componentManager) // - .build(); - } else { - // Default - this.batteryProtection = BatteryProtection.create(this) // - .applyBatteryProtectionDefinition(new BatteryProtectionDefinitionSoltaro3000Wh(), - this.componentManager) // - .build(); - } + // Special settings for 3.5 kWh module + this.batteryProtection = BatteryProtection.create(this) // + .applyBatteryProtectionDefinition(new BatteryProtectionDefinitionSoltaro3500Wh(), this.componentManager) // + .build(); - // Calculate Capacity - int capacity = this.config.numberOfSlaves() * this.config.moduleType().getCapacity_Wh(); - this._setCapacity(capacity); + this.getNumberOfModules().thenAccept(numberOfModules -> { + this.calculateCapacity(numberOfModules); + this.createCellVoltageAndTemperatureChannels(numberOfModules); + }); // Set Watchdog Timeout IntegerWriteChannel c = this.channel(SingleRackVersionC.ChannelId.EMS_COMMUNICATION_TIMEOUT); - c.setNextWriteValue(config.watchdog()); - - // Set State-Of-Charge Low Alarrm - IntegerWriteChannel protectionChannel = this.channel(SingleRackVersionC.ChannelId.LEVEL1_SOC_LOW_PROTECTION); - protectionChannel.setNextWriteValue(config.SocLowAlarm()); - IntegerWriteChannel recoverChannel = this - .channel(SingleRackVersionC.ChannelId.LEVEL1_SOC_LOW_PROTECTION_RECOVER); - recoverChannel.setNextWriteValue(config.SocLowAlarm()); + c.setNextWriteValue(WATCHDOG); + + } + + /** + * Calculates the Capacity as Capacity per module multiplied with number of + * modules and sets the CAPACITY channel. + * + * @param numberOfModules the number of battery modules + */ + private void calculateCapacity(Integer numberOfModules) { + int capacity = numberOfModules * ModuleType.MODULE_3_5_KWH.getCapacity_Wh(); + this._setCapacity(capacity); + } + + /** + * Gets the Number of Modules. + * + * @return the Number of Modules as a {@link CompletableFuture}. + * @throws OpenemsException on error + */ + private CompletableFuture getNumberOfModules() { + final CompletableFuture result = new CompletableFuture(); + try { + ModbusUtils.readELementOnce(this.getModbusProtocol(), new UnsignedWordElement(0x20C1), true) + .thenAccept(numberOfModules -> { + if (numberOfModules == null) { + return; + } + result.complete(numberOfModules); + }); + } catch (OpenemsException e) { + result.completeExceptionally(e); + } + return result; } @Deactivate @@ -218,8 +239,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { m(SingleRackVersionC.ChannelId.EMS_BAUDRATE, new UnsignedWordElement(0x200C)) // ), // new FC6WriteRegisterTask(0x20C1, // - m(SingleRackVersionC.ChannelId.WORK_PARAMETER_PCS_COMMUNICATION_RATE, - new UnsignedWordElement(0x20C1)) // + m(SingleRackVersionC.ChannelId.NUMBER_OF_MODULES_PER_TOWER, new UnsignedWordElement(0x20C1)) // ), // new FC6WriteRegisterTask(0x20F4, m(SingleRackVersionC.ChannelId.EMS_COMMUNICATION_TIMEOUT, new UnsignedWordElement(0x20F4)) // @@ -248,8 +268,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { m(SingleRackVersionC.ChannelId.SLEEP, new UnsignedWordElement(0x201D)) // ), // new FC3ReadRegistersTask(0x20C1, Priority.LOW, // - m(SingleRackVersionC.ChannelId.WORK_PARAMETER_PCS_COMMUNICATION_RATE, - new UnsignedWordElement(0x20C1)), // + m(SingleRackVersionC.ChannelId.NUMBER_OF_MODULES_PER_TOWER, new UnsignedWordElement(0x20C1)), // new DummyRegisterElement(0x20C2, 0x20CB), m(SingleRackVersionC.ChannelId.SYSTEM_TOTAL_CAPACITY, new UnsignedWordElement(0x20CC)) // ), // @@ -314,7 +333,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { m(SingleRackVersionC.ChannelId.CLUSTER_RUN_STATE, new UnsignedWordElement(0x2113)), // m(SingleRackVersionC.ChannelId.CLUSTER_1_AVG_TEMPERATURE, new UnsignedWordElement(0x2114)) // ), // - new FC3ReadRegistersTask(0x218b, Priority.LOW, + new FC3ReadRegistersTask(0x218b, Priority.ONCE, m(SingleRackVersionC.ChannelId.CLUSTER_1_PROJECT_ID, new UnsignedWordElement(0x218b)), // m(SingleRackVersionC.ChannelId.CLUSTER_1_VERSION_MAJOR, new UnsignedWordElement(0x218c)), // m(SingleRackVersionC.ChannelId.CLUSTER_1_VERSION_SUB, new UnsignedWordElement(0x218d)), // @@ -643,12 +662,16 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { protocol.addTask(new FC3ReadRegistersTask(0x2400, Priority.LOW, elements)); } + return protocol; + } + + void createCellVoltageAndTemperatureChannels(int numberOfModules) { /* * Add tasks for cell voltages and temperatures according to the number of * slaves, one task per module is created Cell voltages */ Consumer addCellChannels = (type) -> { - for (int i = 0; i < this.config.numberOfSlaves(); i++) { + for (int i = 0; i < numberOfModules; i++) { AbstractModbusElement[] elements = new AbstractModbusElement[type.getSensorsPerModule()]; for (int j = 0; j < type.getSensorsPerModule(); j++) { int sensorIndex = i * type.getSensorsPerModule() + j; @@ -666,7 +689,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { // Add a Modbus read task for this module int startAddress = type.getOffset() + i * type.getSensorsPerModule(); try { - protocol.addTask(// + this.getModbusProtocol().addTask(// new FC3ReadRegistersTask(startAddress, Priority.LOW, elements)); } catch (OpenemsException e) { this.logWarn(this.log, "Error while adding Modbus task for slave [" + i + "] starting at [" @@ -678,7 +701,6 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { addCellChannels.accept(CellChannelFactory.Type.VOLTAGE_SINGLE); addCellChannels.accept(CellChannelFactory.Type.TEMPERATURE_SINGLE); - return protocol; } @Override diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/versionc/utils/CellChannelFactory.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/versionc/utils/CellChannelFactory.java index ff7c33f1a41..31a6a902395 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/versionc/utils/CellChannelFactory.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/versionc/utils/CellChannelFactory.java @@ -35,7 +35,7 @@ public static enum Type { Unit.MILLIVOLT, // Constants.VOLTAGE_ADDRESS_OFFSET, // Constants.VOLTAGE_SENSORS_PER_MODULE); // - + private final String key; private final Unit unit; private final int offset; @@ -44,12 +44,13 @@ public static enum Type { private Type(String key, Unit unit, int offset, int sensorsPerModule) { this.key = key; this.unit = unit; - this.offset = + offset; + this.offset = +offset; this.sensorsPerModule = sensorsPerModule; } /** * Gets the Offset. + * * @return int */ public int getOffset() { @@ -58,6 +59,7 @@ public int getOffset() { /** * Gets the SensorsPerModule. + * * @return int */ public int getSensorsPerModule() { diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImplTest.java index 75bcef0675a..3166652aa61 100644 --- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImplTest.java +++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImplTest.java @@ -2,7 +2,6 @@ import org.junit.Test; -import io.openems.edge.battery.soltaro.common.enums.ModuleType; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.startstop.StartStopConfig; import io.openems.edge.common.test.ComponentTest; @@ -21,14 +20,7 @@ public void test() throws Exception { .activate(MyConfig.create() // .setId(BATTERY_ID) // .setModbusId(MODBUS_ID) // - .setModuleType(ModuleType.MODULE_3_5_KWH) // .setStartStop(StartStopConfig.AUTO) // - .setNumberOfSlaves(0) // - .setRack1Used(true) // - .setRack2Used(true) // - .setRack3Used(true) // - .setRack4Used(false) // - .setRack5Used(false) // .build()) // ; } diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/MyConfig.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/MyConfig.java index 09949854435..7c146d1fb47 100644 --- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/MyConfig.java +++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/MyConfig.java @@ -13,14 +13,6 @@ protected static class Builder { private String modbusId = null; public int modbusUnitId; public StartStopConfig startStop; - public int numberOfSlaves; - public ModuleType moduleType; - public boolean isRack1Used; - public boolean isRack2Used; - public boolean isRack3Used; - public boolean isRack4Used; - public boolean isRack5Used; - public int watchdog; private Builder() { } @@ -40,41 +32,6 @@ public Builder setStartStop(StartStopConfig startStop) { return this; } - public Builder setNumberOfSlaves(int numberOfSlaves) { - this.numberOfSlaves = numberOfSlaves; - return this; - } - - public Builder setModuleType(ModuleType moduleType) { - this.moduleType = moduleType; - return this; - } - - public Builder setRack1Used(boolean isRack1Used) { - this.isRack1Used = isRack1Used; - return this; - } - - public Builder setRack2Used(boolean isRack2Used) { - this.isRack2Used = isRack2Used; - return this; - } - - public Builder setRack3Used(boolean isRack3Used) { - this.isRack3Used = isRack3Used; - return this; - } - - public Builder setRack4Used(boolean isRack4Used) { - this.isRack4Used = isRack4Used; - return this; - } - - public Builder setRack5Used(boolean isRack5Used) { - this.isRack5Used = isRack5Used; - return this; - } - public MyConfig build() { return new MyConfig(this); } @@ -116,44 +73,4 @@ public StartStopConfig startStop() { return this.builder.startStop; } - @Override - public int numberOfSlaves() { - return this.builder.numberOfSlaves; - } - - @Override - public ModuleType moduleType() { - return this.builder.moduleType; - } - - @Override - public boolean isRack1Used() { - return this.builder.isRack1Used; - } - - @Override - public boolean isRack2Used() { - return this.builder.isRack2Used; - } - - @Override - public boolean isRack3Used() { - return this.builder.isRack3Used; - } - - @Override - public boolean isRack4Used() { - return this.builder.isRack4Used; - } - - @Override - public boolean isRack5Used() { - return this.builder.isRack5Used; - } - - @Override - public int watchdog() { - return this.builder.watchdog; - } - } \ No newline at end of file diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/MyConfig.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/MyConfig.java index 2a5e9eb2eee..46767841f07 100644 --- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/MyConfig.java +++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/MyConfig.java @@ -43,61 +43,11 @@ public Builder setModbusUnitId(int modbusUnitId) { return this; } - public Builder setErrorLevel2Delay(int errorLevel2Delay) { - this.errorLevel2Delay = errorLevel2Delay; - return this; - } - - public Builder setMaxStartTime(int maxStartTime) { - this.maxStartTime = maxStartTime; - return this; - } - - public Builder setPendingTolerance(int pendingTolerance) { - this.pendingTolerance = pendingTolerance; - return this; - } - - public Builder setMaxStartAppempts(int maxStartAppempts) { - this.maxStartAppempts = maxStartAppempts; - return this; - } - - public Builder setStartUnsuccessfulDelay(int startUnsuccessfulDelay) { - this.startUnsuccessfulDelay = startUnsuccessfulDelay; - return this; - } - - public Builder setMinimalCellVoltage(int minimalCellVoltage) { - this.minimalCellVoltage = minimalCellVoltage; - return this; - } - public Builder setStartStop(StartStopConfig startStop) { this.startStop = startStop; return this; } - public Builder setNumberOfSlaves(int numberOfSlaves) { - this.numberOfSlaves = numberOfSlaves; - return this; - } - - public Builder setModuleType(ModuleType moduleType) { - this.moduleType = moduleType; - return this; - } - - public Builder setWatchdog(int watchdog) { - this.watchdog = watchdog; - return this; - } - - public Builder setSocLowAlarm(int socLowAlarm) { - this.socLowAlarm = socLowAlarm; - return this; - } - public MyConfig build() { return new MyConfig(this); } @@ -134,49 +84,8 @@ public int modbusUnitId() { return this.builder.modbusUnitId; } - @Override - public int errorLevel2Delay() { - return this.builder.errorLevel2Delay; - } - - @Override - public int pendingTolerance() { - return this.builder.pendingTolerance; - } - - @Override - public int startUnsuccessfulDelay() { - return this.builder.startUnsuccessfulDelay; - } - - @Override - public int minimalCellVoltage() { - return this.builder.minimalCellVoltage; - } - @Override public StartStopConfig startStop() { return this.builder.startStop; } - - @Override - public int numberOfSlaves() { - return this.builder.numberOfSlaves; - } - - @Override - public ModuleType moduleType() { - return this.builder.moduleType; - } - - @Override - public int watchdog() { - return this.builder.watchdog; - } - - @Override - public int SocLowAlarm() { - return this.builder.socLowAlarm; - } - } \ No newline at end of file diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionCImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionCImplTest.java index 3ab3252ded9..c355f9d9c89 100644 --- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionCImplTest.java +++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionCImplTest.java @@ -2,7 +2,6 @@ import org.junit.Test; -import io.openems.edge.battery.soltaro.common.enums.ModuleType; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.startstop.StartStopConfig; import io.openems.edge.common.test.ComponentTest; @@ -22,17 +21,7 @@ public void test() throws Exception { .setId(BATTERY_ID) // .setModbusId(MODBUS_ID) // .setModbusUnitId(0) // - .setErrorLevel2Delay(0) // - .setMaxStartTime(0) // - .setPendingTolerance(0) // - .setMaxStartAppempts(0) // - .setStartUnsuccessfulDelay(0) // - .setMinimalCellVoltage(0) // .setStartStop(StartStopConfig.AUTO) // - .setNumberOfSlaves(0) // - .setModuleType(ModuleType.MODULE_3_5_KWH) // - .setWatchdog(0) // - .setSocLowAlarm(0) // .build()) // ; } diff --git a/io.openems.edge.batteryinverter.sinexcel/doc/2021_pws2-30k-sunspec_v106_revised_20210416_E.xlsx b/io.openems.edge.batteryinverter.sinexcel/doc/2021_pws2-30k-sunspec_v106_revised_20210416_E.xlsx new file mode 100644 index 00000000000..e1bd6df8bc1 Binary files /dev/null and b/io.openems.edge.batteryinverter.sinexcel/doc/2021_pws2-30k-sunspec_v106_revised_20210416_E.xlsx differ diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/Sinexcel.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/Sinexcel.java index e82012a9e35..b18cf39796f 100644 --- a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/Sinexcel.java +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/Sinexcel.java @@ -10,13 +10,33 @@ import io.openems.edge.batteryinverter.api.ManagedSymmetricBatteryInverter; import io.openems.edge.batteryinverter.api.OffGridBatteryInverter; import io.openems.edge.batteryinverter.api.SymmetricBatteryInverter; -import io.openems.edge.batteryinverter.sinexcel.enums.SinexcelState; +import io.openems.edge.batteryinverter.sinexcel.enums.ActivePowerControlMode; +import io.openems.edge.batteryinverter.sinexcel.enums.Baudrate; +import io.openems.edge.batteryinverter.sinexcel.enums.BlackStartMode; +import io.openems.edge.batteryinverter.sinexcel.enums.CpuType; +import io.openems.edge.batteryinverter.sinexcel.enums.DcVoltageLevel; +import io.openems.edge.batteryinverter.sinexcel.enums.EnableDisable; +import io.openems.edge.batteryinverter.sinexcel.enums.Epo; +import io.openems.edge.batteryinverter.sinexcel.enums.FrequencyVariationRate; +import io.openems.edge.batteryinverter.sinexcel.enums.GridCodeSelection; +import io.openems.edge.batteryinverter.sinexcel.enums.InterfaceType; +import io.openems.edge.batteryinverter.sinexcel.enums.InverterWiringTopology; +import io.openems.edge.batteryinverter.sinexcel.enums.ModulePowerLevel; +import io.openems.edge.batteryinverter.sinexcel.enums.OutputFrequencyLevel; +import io.openems.edge.batteryinverter.sinexcel.enums.OutputVoltageLevel; +import io.openems.edge.batteryinverter.sinexcel.enums.PhaseAngleAbrupt; +import io.openems.edge.batteryinverter.sinexcel.enums.PowerRisingMode; +import io.openems.edge.batteryinverter.sinexcel.enums.ProtocolSelection; +import io.openems.edge.batteryinverter.sinexcel.enums.ReactivePowerControlMode; +import io.openems.edge.batteryinverter.sinexcel.enums.SinexcelGridMode; +import io.openems.edge.batteryinverter.sinexcel.enums.SinglePhaseMode; +import io.openems.edge.batteryinverter.sinexcel.enums.StartMode; +import io.openems.edge.batteryinverter.sinexcel.enums.Switch; import io.openems.edge.batteryinverter.sinexcel.statemachine.StateMachine.State; import io.openems.edge.common.channel.BooleanReadChannel; import io.openems.edge.common.channel.BooleanWriteChannel; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.IntegerDoc; -import io.openems.edge.common.channel.IntegerWriteChannel; import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.modbusslave.ModbusSlave; @@ -31,167 +51,60 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .text("Current State of State-Machine")), // RUN_FAILED(Doc.of(Level.FAULT) // .text("Running the Logic failed")), // - - CLEAR_FAILURE_CMD(Doc.of(OpenemsType.BOOLEAN) // - .accessMode(AccessMode.READ_WRITE)), // - SET_INTERN_DC_RELAY(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_WRITE) // - .unit(Unit.NONE)), - SET_ACTIVE_POWER(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_WRITE)), // + .accessMode(AccessMode.READ_WRITE)// + .unit(Unit.WATT)), // SET_REACTIVE_POWER(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_WRITE)), // - - DEBUG_DISCHARGE_MAX_A(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.AMPERE)), // - - DEBUG_CHARGE_MAX_A(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)// + .unit(Unit.VOLT_AMPERE_REACTIVE)), // + CHARGE_MAX_CURRENT(new IntegerDoc() // + .accessMode(AccessMode.READ_WRITE) // .unit(Unit.AMPERE)), // - - CHARGE_MAX_A(new IntegerDoc() // + DISCHARGE_MAX_CURRENT(new IntegerDoc() // .accessMode(AccessMode.READ_WRITE) // - .unit(Unit.AMPERE) // - .onInit(new IntegerWriteChannel.MirrorToDebugChannel(Sinexcel.ChannelId.DEBUG_CHARGE_MAX_A))), // - - DISCHARGE_MAX_A(new IntegerDoc() // + .unit(Unit.AMPERE)), // + CHARGE_MAX_CURRENT_READ(new IntegerDoc() // .accessMode(AccessMode.READ_WRITE) // - .unit(Unit.AMPERE) // - .onInit(new IntegerWriteChannel.MirrorToDebugChannel(Sinexcel.ChannelId.DEBUG_DISCHARGE_MAX_A))), // - - TOPPING_CHARGE_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.AMPERE)), // + DISCHARGE_MAX_CURRENT_READ(new IntegerDoc() // .accessMode(AccessMode.READ_WRITE) // - .unit(Unit.VOLT)), - FLOAT_CHARGE_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.AMPERE)), // + CHARGE_MAX_VOLTAGE(new IntegerDoc() // .accessMode(AccessMode.READ_WRITE) // - .unit(Unit.VOLT)), - - DEBUG_DIS_MIN_V(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT)), // - DEBUG_CHA_MAX_V(Doc.of(OpenemsType.INTEGER) // .unit(Unit.VOLT)), // - DISCHARGE_MIN_V(new IntegerDoc() // + DISCHARGE_MIN_VOLTAGE(new IntegerDoc() // .accessMode(AccessMode.READ_WRITE) // - .unit(Unit.VOLT) // - .onInit(new IntegerWriteChannel.MirrorToDebugChannel(Sinexcel.ChannelId.DEBUG_DIS_MIN_V))), // - CHARGE_MAX_V(new IntegerDoc() // - .accessMode(AccessMode.READ_WRITE) // - .unit(Unit.VOLT) // - .onInit(new IntegerWriteChannel.MirrorToDebugChannel(Sinexcel.ChannelId.DEBUG_CHA_MAX_V))), // - - SET_ON_GRID_MODE(Doc.of(OpenemsType.BOOLEAN) // - .accessMode(AccessMode.READ_WRITE)), // - SET_OFF_GRID_MODE(Doc.of(OpenemsType.BOOLEAN) // - .accessMode(AccessMode.READ_WRITE)), // - - INVOUTVOLT_L1(Doc.of(OpenemsType.INTEGER) // .unit(Unit.VOLT)), // - INVOUTVOLT_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT)), - INVOUTVOLT_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT)), // - INVOUTCURRENT_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.AMPERE)), // - INVOUTCURRENT_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.AMPERE)), // - INVOUTCURRENT_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.AMPERE)), // - TEMPERATURE(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.DEGREE_CELSIUS)), // - - DC_POWER(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT)), // - - ANALOG_DC_CHARGE_ENERGY(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.KILOVOLT_AMPERE)), - ANALOG_DC_DISCHARGE_ENERGY(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.KILOVOLT_AMPERE)), - - FREQUENCY(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.HERTZ)), // - - DC_CURRENT(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.AMPERE)), // - DC_VOLTAGE(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT)), // - - SINEXCEL_STATE(Doc.of(SinexcelState.values())), // - - SERIAL(Doc.of(OpenemsType.STRING) // - .unit(Unit.NONE)), // - MODEL(Doc.of(OpenemsType.STRING) // - .unit(Unit.NONE)), // - - VERSION(Doc.of(OpenemsType.STRING) // - .unit(Unit.NONE)), // - - ANTI_ISLANDING(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_WRITE) // - .unit(Unit.ON_OFF)), - - POWER_CHANGE_MODE(Doc.of(OpenemsType.INTEGER) // + ACTIVE_DISCHARGE_ENERGY_VALUE_1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), + ACTIVE_DISCHARGE_ENERGY_VALUE_2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), + ACTIVE_CHARGE_ENERGY_VALUE_1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), + ACTIVE_CHARGE_ENERGY_VALUE_2(Doc.of(OpenemsType.INTEGER) // .accessMode(AccessMode.READ_WRITE)), - GRID_EXISTENCE_DETECTION_ON(Doc.of(OpenemsType.INTEGER) // - .accessMode(AccessMode.READ_WRITE) // - .unit(Unit.ON_OFF)), - - EMS_TIMEOUT(Doc.of(OpenemsType.INTEGER) // + // when implementing Lithium-ion batteries, these two registers MUST be set to + // the same + TOPPING_CHARGE_VOLTAGE(Doc.of(OpenemsType.INTEGER) // .accessMode(AccessMode.READ_WRITE) // - .unit(Unit.NONE)), - BMS_TIMEOUT(Doc.of(OpenemsType.INTEGER) // + .unit(Unit.VOLT)), + // when implementing Lithium-ion batteries, these two registers MUST be set to + // the same + FLOAT_CHARGE_VOLTAGE(Doc.of(OpenemsType.INTEGER) // .accessMode(AccessMode.READ_WRITE) // - .unit(Unit.NONE)), - - SET_START_COMMAND(Doc.of(OpenemsType.BOOLEAN) // - .accessMode(AccessMode.READ_WRITE)), // - - SET_STOP_COMMAND(Doc.of(OpenemsType.BOOLEAN) // - .accessMode(AccessMode.READ_WRITE)), // - - // EVENT Bitfield 32 - STATE_0(Doc.of(Level.FAULT) // - .text("Ground fault")), // - STATE_1(Doc.of(Level.WARNING) // - .text("DC over Voltage")), // - STATE_2(Doc.of(OpenemsType.BOOLEAN) // - .text("AC disconnect open")), // - STATE_3(Doc.of(Level.WARNING) // - .text("DC disconnect open")), // - STATE_4(Doc.of(Level.WARNING) // - .text("Grid shutdown")), // - STATE_5(Doc.of(Level.WARNING) // - .text("Cabinet open")), // - // Automatic Standby-Mode is activated after giving a active-power setpoint of - // zero for a while. - AUTOMATIC_STANDBY_MODE(Doc.of(Level.INFO) // - .text("Automatic Standby-Mode")), // - STATE_7(Doc.of(Level.WARNING) // - .text("Over temperature")), // - STATE_8(Doc.of(Level.WARNING) // - .text("AC Frequency above limit")), // - STATE_9(Doc.of(Level.WARNING) // - .text("AC Frequnecy under limit")), // - STATE_10(Doc.of(Level.WARNING) // - .text("AC Voltage above limit")), // - STATE_11(Doc.of(Level.WARNING) // - .text("AC Voltage under limit")), // - STATE_12(Doc.of(Level.WARNING) // - .text("Blown String fuse on input")), // - STATE_13(Doc.of(Level.WARNING) // - .text("Under temperature")), // - STATE_14(Doc.of(Level.WARNING) // - .text("Generic Memory or Communication error (internal)")), // - STATE_15(Doc.of(Level.FAULT) // - .text("Hardware test failure")), // + .unit(Unit.VOLT)), // - // FAULT LIST - STATE_16(Doc.of(Level.FAULT) // - .text("Fault Status")), // - STATE_17(Doc.of(Level.WARNING) // - .text("Alert Status")), // - STATE_19(Doc.of(OpenemsType.BOOLEAN) // + MANUFACTURER_AND_MODEL_NUMBER(Doc.of(OpenemsType.STRING) // + .accessMode(AccessMode.READ_ONLY)), // + SERIAL_NUMBER(Doc.of(OpenemsType.STRING) // + .accessMode(AccessMode.READ_ONLY)), // + FAULT_STATUS(Doc.of(Level.FAULT) // + .accessMode(AccessMode.READ_ONLY)), // + ALERT_STATUS(Doc.of(Level.WARNING) // + .accessMode(AccessMode.READ_ONLY)), // + INVERTER_GRID_MODE(Doc.of(OpenemsType.BOOLEAN) // .text("On Grid") // .onInit(c -> { // BooleanReadChannel channel = (BooleanReadChannel) c; @@ -209,117 +122,695 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { } }); })), + ISLAND_MODE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DERATING_STATUS(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + ALLOW_GRID_CONNECTION(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + STANDBY_STATUS(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + OBTAIN_FAULT_RECORD_FLAG(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + WRITE_POWER_GENERATION_INTO_EEPROM(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + INITIALIZE_DSP_PARAMETERS(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + MASTER_SLAVE_MODE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + AC_OVER_VOLTAGE_PROTECTION(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + AC_UNDER_VOLTAGE_PROTECTION(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + AC_OVER_FREQUENCY_PROTECTION(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + AC_UNDER_FREQUENCY_PROTECTION(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + GRID_VOLTAGE_UNBALANCE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + GRID_PHASE_REVERSE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + INVERTER_ISLAND(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + ON_GRID_OFF_GRID_SWITCH_OVER_FAILURE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + OUTPUT_GROUND_FAULT(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + GRID_PHASE_LOCK_FAILED(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + INTERNAL_AIR_OVER_TEMPERATURE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + GRID_CONNECTED_CONDITION_TIME_OUT(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + MODULE_RENUMBER_FAILURE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + // Monitor parallel use + CANB_COMMUNICATION_FAILURE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + POWER_FREQUENCY_SYNCHRONIZATION_FAILURE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + CARRIER_SYNCHRONIZATION_FALURE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + EPO_ERROR(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + MONITOR_PARAMETER_MISMATCH(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DSP_VERSION_ABNORMAL(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + CPLD_VERSION_ERROR(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + HARDWARE_VERSION_ERROR(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + // Monitor to DSP + CANA_COMMUNICATION_FAILURE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + AUXILARY_POWER_FAULT(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + FAN_FAILURE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_OVER_VOLTAGE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_LOW_VOLTAGE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_VOLTAGE_UNBALANCED(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + AC_RELAY_SHORT_CIRCUIT(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + OUTPUT_VOLTAGE_ABNORMAL(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + OUTPUT_CURRENT_UNBALANCED(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + OVER_TEMPERATURE_OF_HEAT_SINK(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + OUTPUT_OVER_LOAD_TOT(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + GRID_CONTINUE_OVER_VOLTAGE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + AC_SOFT_START_FAILURE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + INVERTER_START_FAILURE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + AC_RELAY_IS_OPEN(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + U2_BOARD_COMMUNICATION_IS_ABNORMAL(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + AC_DC_COMPONENT_EXCESS(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + MASTER_SLAVE_SAMPLING_ABNORMALITY(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + PARAMETER_SETTING_ERROR(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + LOW_OFF_GRID_ENERGY(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + N_LINE_IS_NOT_CONNECTED(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + STANDBY_BUS_HEIGHT(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + SINGLE_PHASE_WIRING_ERROR(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + EXCESSIVE_GRID_FREQUENCY_CHANGE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + ABRUPT_PHASE_ANGLE_FAULT_OF_POWER_GRID(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + GRID_CONNECTION_PARAMETER_CONFLICT(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + EE_READING_ERROR_1(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + EE_READING_ERROR_2(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + FLASH_READING_ERROR(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + INVERTER_OVER_LOAD(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + BATTERY_PARAMETER_SETTING_ERROR(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + SLAVE_LOST_ALARM(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_CHARGING(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_DISCHARGING(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + BATTERY_FULLY_CHARGED(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + BATTERY_EMPTY(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_FAULT_STATUS(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_ALERT_STATUS(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_INPUT_OVER_VOLTAGE_PROTECTION(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_INPUT_UNDER_VOLTAGE_PROTECTION(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + BMS_ALERT(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + BMS_COMMUNICATION_TIMEOUT(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + EMS_COMMUNICATION_TIMEOUT(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_SOFT_START_FAILED(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_RELAY_SHORT_CIRCUIT(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_RELAY_SHORT_OPEN(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + BATTERY_POWEROVER_LOAD(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + BATTERY_POWER_OVER_LOAD(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_BUS_STARTING_FAILED(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_QUICK_CHECK_OVER_CURRENT(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + DC_OC(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_ONLY)), // + // AC L1-L2 RMS voltage + GRID_VOLTAGE_L1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.MILLIVOLT)), // + // AC L2-L3 RMS voltage + GRID_VOLTAGE_L2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.MILLIVOLT)), // + // AC L3-L1 RMS voltage + GRID_VOLTAGE_L3(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.MILLIVOLT)), // + // AC L1 RMS current + GRID_CURRENT_L1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.MILLIAMPERE)), // + // AC L2 RMS current + GRID_CURRENT_L2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.MILLIAMPERE)), // + // AC L3 RMS current + GRID_CURRENT_L3(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.MILLIAMPERE)), // + // AC frequency + FREQUENCY(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.MILLIHERTZ)), // + // AC L1 Active Power + ACTIVE_POWER_L1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.WATT)), // + // AC L2 Active Power + ACTIVE_POWER_L2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.WATT)), // + // AC L3 Active Power + ACTIVE_POWER_L3(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.WATT)), // + // AC L1 Reactive Power + REACTIVE_POWER_L1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.VOLT_AMPERE_REACTIVE)), // + // AC L2 Reactive Power + REACTIVE_POWER_L2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.VOLT_AMPERE_REACTIVE)), // + // AC L3 Reactive Power + REACTIVE_POWER_L3(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.VOLT_AMPERE_REACTIVE)), // + // AC L1 Apparent Power + APPERENT_POWER_L1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.VOLT_AMPERE)), // + // AC L2 Apparent Power + APPERENT_POWER_L2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.VOLT_AMPERE)), // + // AC L3 Apparent Power + APPERENT_POWER_L3(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.VOLT_AMPERE)), // + // AC L1 Power Factor + COS_PHI_L1(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_ONLY)), // + // AC L2 Power Factor + COS_PHI_L2(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_ONLY)), // + // AC L3 Power Factor + COS_PHI_L3(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_ONLY)), // + // AC Apperent Power + APPARENT_POWER(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.VOLT_AMPERE)), // + // AC Power Factor + COS_PHI(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_ONLY)), // + + // TODO delete later, just for info whats inside ;) + REACTIVE_ENERGY(Doc.of(OpenemsType.LONG) // + .accessMode(AccessMode.READ_ONLY)), // + + // Temperature of DC heat sink + TEMPERATURE_OF_AC_HEAT_SINK(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.DEGREE_CELSIUS)), // + // BUS+ side voltage + DC_VOLTAGE_POSITIVE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.MILLIVOLT)), // + // BUS- side voltage + DC_VOLTAGE_NEGATIVE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.MILLIVOLT)), // + // Target off-grid voltage bias + SET_OFF_GRID_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)// + .unit(Unit.MILLIVOLT)), // + // Target off-grid frequency bias + SET_OFF_GRID_FREQUENCY(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)// + .unit(Unit.MILLIHERTZ)), // + DC_POWER(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.WATT)), // + DC_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.MILLIVOLT)), // + DC_CURRENT(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.MILLIAMPERE)), // + DC_CHARGE_ENERGY(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)// + .unit(Unit.WATT_HOURS)), // + DC_DISCHARGE_ENERGY(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)// + .unit(Unit.WATT_HOURS)), // + TEMPERATURE_OF_DC_DC_HEAT_SINK(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.DEGREE_CELSIUS)), // + DC_RELAY_REAR_END_VOLTAGE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.VOLT)), // + CHARGE_MAX_CURRENT_SETTING(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.MILLIAMPERE)), // + DISCHARGE_MAX_CURRENT_SETTING(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_ONLY)// + .unit(Unit.MILLIAMPERE)), // + IP_ADDRESS_BLOCK_1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + IP_ADDRESS_BLOCK_2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + IP_ADDRESS_BLOCK_3(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + IP_ADDRESS_BLOCK_4(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + NETMASK_BLOCK_1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + NETMASK_BLOCK_2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + NETMASK_BLOCK_3(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + NETMASK_BLOCK_4(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + GATEWAY_IP_BLOCK_1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + GATEWAY_IP_BLOCK_2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + GATEWAY_IP_BLOCK_3(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + GATEWAY_IP_BLOCK_4(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + MAC(Doc.of(OpenemsType.STRING) // + .accessMode(AccessMode.READ_WRITE)), // + MODBUS_UNIT_ID(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + BAUDRATE(Doc.of(Baudrate.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // default 1 + INTERFACE_TYPE(Doc.of(InterfaceType.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // Takes effect after hard reset,0-modbus + COMMUNICATION_PROTOCOL_SELECTION(Doc.of(ProtocolSelection.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // Prevent unexpected results of communication failures.when EMS timeout + // enabled, watchdog will work and even reading will feed the watchdog + EMS_TIMEOUT(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + // Enable ONLY when remote Emergency Stop Button is needed + EPO_ENABLE(Doc.of(Epo.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // Enable ONLY when remote BMS-inverter connection is needed + BMS_TIMEOUT(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + // Takes effect after hard reset,0-modbus + BMS_PROTOCOL_SELECTION(Doc.of(ProtocolSelection.values()) // + .accessMode(AccessMode.READ_WRITE)), // + SET_GRID_MODE(Doc.of(SinexcelGridMode.values()) // + .accessMode(AccessMode.READ_WRITE)), // + BUZZER_ENABLE(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE)), // + RESTORE_FACTORY_SETTING(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // To Start operation, only 1 will be accepted. Reading back value makes no + // sense + START_INVERTER(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + // To Stop operation, only 1 will be accepted. Reading back value makes no sense + STOP_INVERTER(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + // clear failure flag,when fault occurs, the system will stop and indicates + // fault.starting is invalid until the fault source is actually removed and this + // register is written 1. Reading back value makes no sense + CLEAR_FAILURE_COMMAND(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + // set the module to on grid mode. Reading back value makes no sense + SET_ON_GRID_MODE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + // set the module to off grid mode. Reading back value makes no sense + SET_OFF_GRID_MODE(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + // set the module to standby mode,setpoint 0:IGBT switching ,setpoint 1:no IGBT + // switching,low consumption.let the inverter to halt the IGBT switching, to + // save the power consumption, but all relays are still closed. + // Reading back value makes no sense + SET_STANDBY_COMMAND(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + SET_SOFT_START(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + RESET_INSTRUCTION(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + GRID_STOP(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + OUTPUT_VOLTAGE_LEVEL(Doc.of(OutputVoltageLevel.values()) // + .accessMode(AccessMode.READ_ONLY)), // + OUTPUT_FREQUENCY_LEVEL(Doc.of(OutputFrequencyLevel.values()) // + .accessMode(AccessMode.READ_ONLY)), // + INVERTER_WIRING_TOPOLOGY(Doc.of(InverterWiringTopology.values()) // + .accessMode(AccessMode.READ_ONLY)), // + SWITCHING_DEVICE_ACCESS_SETTING(Doc.of(Switch.values()) // + .accessMode(AccessMode.READ_WRITE)), // + MODULE_POWER_LEVEL(Doc.of(ModulePowerLevel.values()) // + .accessMode(AccessMode.READ_ONLY)), // + DC_VOLTAGE_LEVEL(Doc.of(DcVoltageLevel.values()) // + .accessMode(AccessMode.READ_ONLY)), // + CPU_TYPE(Doc.of(CpuType.values()) // + .accessMode(AccessMode.READ_WRITE)), // + OFF_GRID_AND_PARALLEL_ENABLE(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE)), // + SET_DC_SOFT_START_EXTERNAL_CONTROL(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE)), // + GRID_OVER_VOLTAGE_PROTECTION_AMPLITUDE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_OVER_VOLTAGE_TRIP_TIME_1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_OVER_VOLTAGE_TRIP_LEVEL_2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_OVER_VOLTAGE_TRIP_TIME_2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_UNDER_VOLTAGE_TRIP_LEVEL_1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_UNDER_VOLTAGE_TRIP_TIME_1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_UNDER_VOLTAGE_TRIP_LEVEL_2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_UNDER_VOLTAGE_TRIP_TIME_2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_UNDER_VOLTAGE_TRIP_LEVEL_3(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_UNDER_VOLTAGE_TRIP_TIME_3(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_OVER_FREQUENCY_TRIP_LEVEL_1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_OVER_FREQUENCY_TRIP_TIME_1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_OVER_FREQUENCY_TRIP_LEVEL_2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_OVER_FREQUENCY_TRIP_TIME_2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_UNDER_FREQUENCY_TRIP_LEVEL_1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_UNDER_FREQUENCY_TRIP_TIME_1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_UNDER_FREQUENCY_TRIP_LEVEL_2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + AC_UNDER_FREQUENCY_TRIP_TIME_2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + RECONNECT_TIME(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + // Anti-islanding is a CPUC RULE 21/HECO RULE14H/IEEE1547-requested function to + // make sure the inverter disconnect from the grid in case of blackout. + // This is to prevent the formation of an unintended island. The inverter design + // shall comply with the requirements of IEEE Std 1547 and UL 1741 standards (or + // latest versions) and be certified to have anti-islanding protection such that + // the synchronous inverter will automatically disconnect upon a utility system + // interruption + ANTI_ISLANDING(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // Frequency and Voltage Ride-Through. + // The ability to withstand voltage or frequency excursions outside defined + // limits without tripping or malfunctioning + FREQUENCY_VOLTAGE_RIDE_THROUGH(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // Grid-tied mode only, voltage and frequency setpoint the only setpoints + REACTIVE_POWER_CONTROL_MODE(Doc.of(ReactivePowerControlMode.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // To define how the power changes + POWER_RISING_MODE(Doc.of(PowerRisingMode.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // Grid-tied mode only, Volt/Watt control & Freq/Watt control means active power + // will be regulated by grid voltage/frequency following a curve/ramp rate given + // by HECO or CPUC or other local utility authority codes + ACTIVE_POWER_CONTROL_MODE(Doc.of(ActivePowerControlMode.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // Grid-tied mode only, voltage and frequency setpoint the only setpoints + GRID_VOLTAGE_ASYMMETRIC_DETECTON(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // Grid-tied mode only + CONTINUOUS_OVERVOLTAGE_DETECTION(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // Grid-tied mode only,detect whether the grid is on-service at powered-up. + GRID_EXISTENCE_DETECTION_ON(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE)), + // Grid-tied mode only + NEUTRAL_FLOATING_DETECTION(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // if disabled, the inverter will start the AC voltage, then close the relay. In + // some off-grid cases, such as there are inductive loads or transformer, the + // in rush exciting current will trip the inverter. Enabling this register, the + // inverter will close the relay first try to limit the current and start the + // voltage slowly + OFF_GRID_BLACKSTART_MODE(Doc.of(BlackStartMode.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // Grid tied mode only. take effect after power off. + GRID_CODE_SELCETION(Doc.of(GridCodeSelection.values()) // + .accessMode(AccessMode.READ_WRITE)), // + GRID_CONNECTED_ACTIVE_CAPACITY_LIMITATION_FUNCTION(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE)), // + GRID_ACTIVE_POWER_CAPACITY_SETTING(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + // Single mode enable and select + SINGLE_PHASE_MODE_SELECTION(Doc.of(SinglePhaseMode.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // Overvoltage drop active enable (only for EN50549 certification) + OVER_VOLTAGE_DROP_ACTIVE(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // 0-Manual start,1-Auto start,default:0 + START_UP_MODE(Doc.of(StartMode.values()) // + .accessMode(AccessMode.READ_WRITE)), // + LOCAL_ID_SETTING(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + // when implementing Lithium-ion batteries,this register MUST be set to 0 + CURRENT_FROM_TOPPING_CHARGING_TO_FLOAT_CHARGING(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + BATTERY_VOLTAGE_PROTECTION_LIMIT(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE) // + .unit(Unit.VOLT)), // + LEAKAGE_CURRENT_DC_COMPONENT_DETECTOR(Doc.of(EnableDisable.values()) // + .accessMode(AccessMode.READ_WRITE)), // + RESUME_AND_LIMIT_FREQUENCY(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + RESTORE_LOWER_FREQUENCY_OF_GRID_CONNECTION(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + VOLTAGE_REACTIVE_REFERENCE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + + // ratio * rated voltage,Available only when reactive power regulation mode is + // set to Volt/Var(53626). + VOLTAGE_AND_REACTIVE_POWER_ADJUSTMENT_POINT_V1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + VOLTAGE_AND_REACTIVE_POWER_ADJUSTMENT_POINT_V2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + VOLTAGE_AND_REACTIVE_POWER_ADJUSTMENT_POINT_V3(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + VOLTAGE_AND_REACTIVE_POWER_ADJUSTMENT_POINT_V4(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // - STATE_20(Doc.of(Level.INFO) // - .text("Off Grid")), // - STATE_21(Doc.of(Level.WARNING) // - .text("AC OVP")), // - STATE_22(Doc.of(Level.WARNING) // - .text("AC UVP")), // - STATE_23(Doc.of(Level.WARNING) // - .text("AC OFP")), // - STATE_24(Doc.of(Level.WARNING) // - .text("AC UFP")), // - STATE_25(Doc.of(Level.WARNING) // - .text("Grid Voltage Unbalance")), // - STATE_26(Doc.of(Level.WARNING) // - .text("Grid Phase reserve")), // - STATE_27(Doc.of(Level.INFO) // - .text("Islanding")), // - STATE_28(Doc.of(Level.WARNING) // - .text("On/ Off Grid Switching Error")), // - STATE_29(Doc.of(Level.WARNING) // - .text("Output Grounding Error")), // - STATE_30(Doc.of(Level.WARNING) // - .text("Output Current Abnormal")), // - STATE_31(Doc.of(Level.WARNING) // - .text("Grid Phase Lock Fails")), // - STATE_32(Doc.of(Level.WARNING) // - .text("Internal Air Over-Temp")), // - STATE_33(Doc.of(Level.WARNING) // - .text("Zeitueberschreitung der Netzverbindung")), // - STATE_34(Doc.of(Level.INFO) // - .text("EPO")), // - STATE_35(Doc.of(Level.FAULT) // - .text("HMI Parameters Fault")), // - STATE_36(Doc.of(Level.WARNING) // - .text("DSP Version Error")), // - STATE_37(Doc.of(Level.WARNING) // - .text("CPLD Version Error")), // - STATE_38(Doc.of(Level.WARNING) // - .text("Hardware Version Error")), // - STATE_39(Doc.of(Level.WARNING) // - .text("Communication Error")), // - STATE_40(Doc.of(Level.WARNING) // - .text("AUX Power Error")), // - STATE_41(Doc.of(Level.FAULT) // - .text("Fan Failure")), // - STATE_42(Doc.of(Level.WARNING) // - .text("BUS Over Voltage")), // - STATE_43(Doc.of(Level.WARNING) // - .text("BUS Low Voltage")), // - STATE_44(Doc.of(Level.WARNING) // - .text("BUS Voltage Unbalanced")), // - STATE_45(Doc.of(Level.WARNING) // - .text("AC Soft Start Failure")), // - STATE_46(Doc.of(Level.WARNING) // - .text("Reserved")), // - STATE_47(Doc.of(Level.WARNING) // - .text("Output Voltage Abnormal")), // - STATE_48(Doc.of(Level.WARNING) // - .text("Output Current Unbalanced")), // - STATE_49(Doc.of(Level.WARNING) // - .text("Over Temperature of Heat Sink")), // - STATE_50(Doc.of(Level.WARNING) // - .text("Output Overload")), // - STATE_51(Doc.of(Level.WARNING) // - .text("Reserved")), // - STATE_52(Doc.of(Level.WARNING) // - .text("AC Breaker Short-Circuit")), // - STATE_53(Doc.of(Level.WARNING) // - .text("Inverter Start Failure")), // - STATE_54(Doc.of(Level.WARNING) // - .text("AC Breaker is open")), // - STATE_55(Doc.of(Level.WARNING) // - .text("EE Reading Error 1")), // - STATE_56(Doc.of(Level.WARNING) // - .text("EE Reading Error 2")), // - STATE_57(Doc.of(Level.FAULT) // - .text("SPD Failure ")), // - STATE_58(Doc.of(Level.WARNING) // - .text("Inverter over load")), // - STATE_59(Doc.of(OpenemsType.BOOLEAN) // - .text("DC Charging")), // - STATE_60(Doc.of(OpenemsType.BOOLEAN) // - .text("DC Discharging")), // - STATE_61(Doc.of(Level.INFO) // - .text("Battery fully charged")), // - STATE_62(Doc.of(Level.INFO) // - .text("Battery empty")), // - STATE_63(Doc.of(Level.FAULT) // - .text("Fault Status")), // - STATE_64(Doc.of(Level.WARNING) // - .text("Alert Status")), // - STATE_65(Doc.of(Level.WARNING) // - .text("DC input OVP")), // - STATE_66(Doc.of(Level.WARNING) // - .text("DC input UVP")), // - STATE_67(Doc.of(Level.WARNING) // - .text("DC Groundig Error")), // - STATE_68(Doc.of(Level.WARNING) // - .text("BMS alerts")), // - STATE_69(Doc.of(Level.FAULT) // - .text("DC Soft-Start failure")), // - STATE_70(Doc.of(Level.WARNING) // - .text("DC relay short-circuit")), // - STATE_71(Doc.of(Level.WARNING) // - .text("DC relay short open")), // - STATE_72(Doc.of(Level.WARNING) // - .text("Battery power over load")), // - STATE_73(Doc.of(Level.FAULT) // - .text("BUS start fails")), // - STATE_74(Doc.of(Level.WARNING) // - .text("DC OCP")); + // refer to HECO RULE 14, keep default value if no aware of it + MAX_CAPACITIVE_REACTIVE_REGULATION_Q1(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + INITIAL_CAPACITIVE_REACTIVE_REGULATION_Q2(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + INITIAL_INDUCTIVE_REACTIVE_REGULATION_Q3(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + MAX_INDUCTIVE_REACTIVE_REGULATION_Q4(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + VOLTAGE_AND_REACTIVE_RESPONSE_TIME(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + REACTIVE_FIRST_ORDER_RESPONSE_TIME(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + + // ratio * rated voltage, Available only when active power regulation + // mode(53636) is set to Volt/Watt and operating in discharge mode, follow the + // FVRT table given by HECO or CPUC or other local utility authority codes. + INITIAL_VOLTAGE_V_START(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + END_VOLTAGE_V_STOP(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + INITIAL_POWER_P_START(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + END_POWER_P_STOP(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + RETURN_TO_SERVICE_DELAY(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + VOLT_WATT_RESPONSE_TIME(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + + // Available only when active power regulation mode(53636) is set to Freq/Watt + // and operating in discharge mode. When the actual frequency is above the + // point, the active power will be regulated(lowered) with the ramp rate. In + // Australia, this register shall be fixed to 0.25Hz + START_OF_FREQUENY_DROP(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + // The ramp rate is defined as multiple of set active power per hertz that above + // the above the Freq/Watt regulation point. Available only when active power + // regulation mode(53636) is set to Freq/Watt and operating in discharge mode. + // Example: Rated frequency is 60Hz, the target active power is set to 10kW, + // Freq/Watt regulation point is set to 2Hz, ramp rate is set as 0.5, If the + // actual frequency reaches 63Hz, the output active power will be + // 10kW-(63Hz-62Hz)0.5*(10kW/Hz) = 5kW + SLOPE_OF_FREQUENCY_DROP(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + // AS4777 only, bias Bias from rated frequency + FREQUENCY_WATT_F_STOP_DISCHARGE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + FREQUENCY_WATT_F_STOP_CHARGE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + // %Vrated,AS4777 only, ratio + VOLT_WATT_V_START_CHARGE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + // SS takes effect when the inverter starts, or when the inverter is on "grid + // reconnection" after the trip caused by FVRT timeout + SOFT_START_RAMP_RATE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + // RR takes effect in Volt-Watt or Freq-Watt mode when the grid is back to + // normal state and the inverter trying to go back to normal output. + // In other words, as long as the inverter doesnt trip, and the inverter had + // derated the output power, RR takes effect when the inverter tries to go back + // to normal output. Available only when Power rising mode is set to ramp + // mode(53626).If the value is 2.000, which means within 0.5 seconds the system + // can runs to full power output. + POWER_RAMP_RATE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + // Available only when Constant PF is enabled,Negative inductive, positive + // capacitive + POWER_FACTOR_SETTING(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + // Available only when active power regulation mode(53636) is set to Freq/Watt + // and operating in discharge mode. When the actual frequency is above the + // point, the active power will be regulated(lowered) with the ramp rate + POWER_FACTOR_P1(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + POWER_FACTOR_P2(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + POWER_FACTOR_P3(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + POWER_FACTOR_P4(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + + // +(lagging), -(leading), + POWER_FACTOR_CURVE_MODE_P1(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + POWER_FACTOR_CURVE_MODE_P2(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + POWER_FACTOR_CURVE_MODE_P3(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + POWER_FACTOR_CURVE_MODE_P4(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + + // %Vrated + CONTINUOS_OVER_VOLTAGE_TRIP_THRESHOLD(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + FREQUENCY_VARIATION_RATE_TRIP_THRESHOLD(Doc.of(FrequencyVariationRate.values()) // + .accessMode(AccessMode.READ_WRITE)), // + PHASE_ANGLE_ABRUPT_TRIP_THRESHOLD(Doc.of(PhaseAngleAbrupt.values()) // + .accessMode(AccessMode.READ_WRITE)), // + // Available only when Power rising mode is set to ramp mode . once tripped + // after FVRT timeout, the inverter can reconnect to the grid when frequency or + // voltage is back to the thresholds defined as "grid is back to service". In + // HECO14 /CPUC 21, this register is unnecessary to + GRID_RECONNECTION_VOLTAGE_UPPER_LIMIT(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + GRID_RECONNECTION_VOLTAGE_LOWER_LIMIT(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + // Available only when Power rising mode is set to ramp mode once tripped after + // FVRT timeout, the inverter can reconnect to the grid when frequency or + // voltage is back to the thresholds defined as "grid is back to service" + GRID_RECONNECTION_FREQUENCY_UPPER_LIMIT(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + // Bias from rated frequency + GRID_RECONNECTION_FREQUENCY_LOWER_LIMIT(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + LOW_FREQUENCY_RAMP_RATE(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE)), // + // TODO Values check Meter options !!!!!!!!!!!!!!!!!! + METER_ACTIVE_POWER(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.READ_WRITE).unit(Unit.WATT)), // + + GRID_VOLTAGE_CALIBRATION_L1(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + GRID_VOLTAGE_CALIBRATION_L2(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + GRID_VOLTAGE_CALIBRATION_L3(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + INVERTER_VOLTAGE_CALIBRATION_L1(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + INVERTER_VOLTAGE_CALIBRATION_L2(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + INVERTER_VOLTAGE_CALIBRATION_L3(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + INDUCTOR_CURRENT_CALIBRATION_L1_PARAMETERS_1(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + INDUCTOR_CURRENT_CALIBRATION_L2_PARAMETERS_1(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + INDUCTOR_CURRENT_CALIBRATION_L3_PARAMETERS_1(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + INDUCTOR_CURRENT_CALIBRATION_L1_PARAMETERS_2(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + INDUCTOR_CURRENT_CALIBRATION_L2_PARAMETERS_2(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + INDUCTOR_CURRENT_CALIBRATION_L3_PARAMETERS_2(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + OUTPUT_CURRENT_CALIBRATION_L1(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + OUTPUT_CURRENT_CALIBRATION_L2(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + OUTPUT_CURRENT_CALIBRATION_L3(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + POSITIVE_BUS_VOLTAGE_CALIBRATION(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + NEGATIVE_BUS_VOLTAGE_CALIBRATION(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + DC_VOLTAGE_CALIBRATION(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + DC_CURRENT_CALIBRATION(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + DC_INDUCTOR_CURRENT_CALIBRATION(Doc.of(OpenemsType.FLOAT) // + .accessMode(AccessMode.READ_WRITE)), // + TIME_SETTING(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.WRITE_ONLY)), // + PASSWORD(Doc.of(OpenemsType.INTEGER) // + .accessMode(AccessMode.WRITE_ONLY)) // + ; private final Doc doc; @@ -390,151 +881,38 @@ public default void setOffGridMode(Boolean value) throws OpenemsNamedException { } /** - * Gets the Channel for {@link ChannelId#SET_START_COMMAND}. + * Gets the Channel for {@link ChannelId#START_INVERTER}. * * @return the Channel */ - public default BooleanWriteChannel getSetStartCommandChannel() { - return this.channel(ChannelId.SET_START_COMMAND); + public default BooleanWriteChannel getStartInverterChannel() { + return this.channel(ChannelId.START_INVERTER); } /** - * Sends a START command to the inverter. See - * {@link ChannelId#SET_START_COMMAND}. + * Sends a START command to the inverter. See {@link ChannelId#START_INVERTER}. * * @throws OpenemsNamedException on error */ - public default void setStartCommand() throws OpenemsNamedException { - this.getSetStartCommandChannel().setNextWriteValue(true); // true = START + public default void setStartInverter() throws OpenemsNamedException { + this.getStartInverterChannel().setNextWriteValue(true); // true = START } /** - * Gets the Channel for {@link ChannelId#SET_STOP_COMMAND}. + * Gets the Channel for {@link ChannelId#STOP_INVERTER}. * * @return the Channel */ - public default BooleanWriteChannel getSetStopCommandChannel() { - return this.channel(ChannelId.SET_STOP_COMMAND); + public default BooleanWriteChannel getStopInverterChannel() { + return this.channel(ChannelId.STOP_INVERTER); } /** - * Sends a STOP command to the inverter. See {@link ChannelId#SET_STOP_COMMAND}. + * Sends a STOP command to the inverter. See {@link ChannelId#STOP_INVERTER}. * * @throws OpenemsNamedException on error */ - public default void setStopCommand() throws OpenemsNamedException { - this.getSetStopCommandChannel().setNextWriteValue(true); // true = STOP - } - - /** - * Gets the Channel for {@link ChannelId#EMS_TIMEOUT}. - * - * @return the Channel - */ - public default IntegerWriteChannel getEmsTimeoutChannel() { - return this.channel(ChannelId.EMS_TIMEOUT); - } - - /** - * Gets the EMS Timeout. See {@link ChannelId#EMS_TIMEOUT}. - * - * @return the Channel {@link Value} - */ - public default Value getEmsTimeout() { - return this.getEmsTimeoutChannel().value(); - } - - /** - * Sets a the EMS Timeout. See {@link ChannelId#EMS_TIMEOUT}. - * - * @param value the next write value - * @throws OpenemsNamedException on error - */ - public default void setEmsTimeout(Integer value) throws OpenemsNamedException { - this.getEmsTimeoutChannel().setNextWriteValue(value); - } - - /** - * Gets the Channel for {@link ChannelId#BMS_TIMEOUT}. - * - * @return the Channel - */ - public default IntegerWriteChannel getBmsTimeoutChannel() { - return this.channel(ChannelId.BMS_TIMEOUT); - } - - /** - * Gets the EMS Timeout. See {@link ChannelId#BMS_TIMEOUT}. - * - * @return the Channel {@link Value} - */ - public default Value getBmsTimeout() { - return this.getBmsTimeoutChannel().value(); - } - - /** - * Sets a the EMS Timeout. See {@link ChannelId#BMS_TIMEOUT}. - * - * @param value the next write value - * @throws OpenemsNamedException on error - */ - public default void setBmsTimeout(Integer value) throws OpenemsNamedException { - this.getBmsTimeoutChannel().setNextWriteValue(value); - } - - /** - * Gets the Channel for {@link ChannelId#GRID_EXISTENCE_DETECTION_ON}. - * - * @return the Channel - */ - public default IntegerWriteChannel getGridExistenceDetectionOnChannel() { - return this.channel(ChannelId.GRID_EXISTENCE_DETECTION_ON); - } - - /** - * See {@link ChannelId#GRID_EXISTENCE_DETECTION_ON}. - * - * @return the Channel {@link Value} - */ - public default Value getGridExistenceDetectionOn() { - return this.getGridExistenceDetectionOnChannel().value(); - } - - /** - * See {@link ChannelId#GRID_EXISTENCE_DETECTION_ON}. - * - * @param value the next write value - * @throws OpenemsNamedException on error - */ - public default void setGridExistenceDetectionOn(Integer value) throws OpenemsNamedException { - this.getGridExistenceDetectionOnChannel().setNextWriteValue(value); - } - - /** - * Gets the Channel for {@link ChannelId#POWER_CHANGE_MODE}. - * - * @return the Channel - */ - public default IntegerWriteChannel getPowerChangeModeChannel() { - return this.channel(ChannelId.POWER_CHANGE_MODE); - } - - /** - * See {@link ChannelId#POWER_CHANGE_MODE}. - * - * @return the Channel {@link Value} - */ - public default Value getPowerChangeMode() { - return this.getPowerChangeModeChannel().value(); - } - - /** - * See {@link ChannelId#POWER_CHANGE_MODE}. - * - * @param value the next write value - * @throws OpenemsNamedException on error - */ - public default void setPowerChangeMode(Integer value) throws OpenemsNamedException { - this.getPowerChangeModeChannel().setNextWriteValue(value); + public default void setStopInverter() throws OpenemsNamedException { + this.getStopInverterChannel().setNextWriteValue(true); // true = STOP } } diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/SinexcelImpl.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/SinexcelImpl.java index 04ebbb92a89..9950e953a51 100644 --- a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/SinexcelImpl.java +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/SinexcelImpl.java @@ -1,5 +1,6 @@ package io.openems.edge.batteryinverter.sinexcel; +import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import org.osgi.service.cm.ConfigurationAdmin; @@ -17,16 +18,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Objects; - import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.OptionsEnum; import io.openems.edge.battery.api.Battery; import io.openems.edge.batteryinverter.api.BatteryInverterConstraint; import io.openems.edge.batteryinverter.api.ManagedSymmetricBatteryInverter; import io.openems.edge.batteryinverter.api.OffGridBatteryInverter; import io.openems.edge.batteryinverter.api.SymmetricBatteryInverter; +import io.openems.edge.batteryinverter.sinexcel.enums.EnableDisable; +import io.openems.edge.batteryinverter.sinexcel.enums.PowerRisingMode; import io.openems.edge.batteryinverter.sinexcel.statemachine.Context; import io.openems.edge.batteryinverter.sinexcel.statemachine.StateMachine; import io.openems.edge.batteryinverter.sinexcel.statemachine.StateMachine.State; @@ -42,10 +44,11 @@ import io.openems.edge.bridge.modbus.api.element.StringWordElement; import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement; import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement; -import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask; import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; import io.openems.edge.bridge.modbus.api.task.FC6WriteRegisterTask; -import io.openems.edge.common.channel.IntegerWriteChannel; +import io.openems.edge.common.channel.BooleanWriteChannel; +import io.openems.edge.common.channel.WriteChannel; +import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; @@ -54,7 +57,6 @@ import io.openems.edge.common.startstop.StartStoppable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.common.type.TypeUtils; -import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.power.api.Phase; import io.openems.edge.ess.power.api.Power; import io.openems.edge.ess.power.api.Pwr; @@ -78,10 +80,11 @@ public class SinexcelImpl extends AbstractOpenemsModbusComponent implements Sine public static final int DEFAULT_UNIT_ID = 1; private static final int MAX_CURRENT = 90; // [A] - private static final int DEFAULT_EMS_TIMEOUT = 60; - private static final int DEFAULT_BMS_TIMEOUT = 0; - private static final int DEFAULT_GRID_EXISTENCE_DETECTION_ON = 0; - private static final int DEFAULT_POWER_CHANGE_MODE = 0; // 0 = STEP_MODE; 1 = RAMP_MODE + public static final int DEFAULT_EMS_TIMEOUT = 60; + public static final int DEFAULT_BMS_TIMEOUT = 0; + public static final EnableDisable DEFAULT_GRID_EXISTENCE_DETECTION_ON = EnableDisable.DISABLE; + public static final PowerRisingMode DEFAULT_POWER_RISING_MODE = PowerRisingMode.STEP; + private static final int MAX_TOPPING_CHARGE_VOLTAGE = 750; /** * Manages the {@link State}s of the StateMachine. @@ -119,11 +122,11 @@ protected void setModbus(BridgeModbus modbus) { @Activate protected void activate(ComponentContext context, Config config) throws OpenemsNamedException { + this.config = config; if (super.activate(context, config.id(), config.alias(), config.enabled(), DEFAULT_UNIT_ID, this.cm, "Modbus", config.modbus_id())) { return; } - this.config = config; } @Deactivate @@ -161,23 +164,48 @@ public void run(Battery battery, int setActivePower, int setReactivePower) throw } /** - * Compares and sets some default settings on the inverter, like Timeout. + * Updates the Channel if its current value is not equal to the new value. + * + * @param channelId Sinexcel Channel-Id + * @param newValue {@link OptionsEnum} value. + * @throws IllegalArgumentException on error + */ + private void updateIfNotEqual(Sinexcel.ChannelId channelId, OptionsEnum value) + throws IllegalArgumentException, OpenemsNamedException { + this.updateIfNotEqual(channelId, value.getValue()); + } + + /** + * Updates the Channel if its current value is not equal to the new value. + * + * @param channelId Sinexcel Channel-Id + * @param newValue Integer value. + * @throws IllegalArgumentException on error + */ + private void updateIfNotEqual(Sinexcel.ChannelId channelId, Integer newValue) throws IllegalArgumentException { + WriteChannel channel = this.channel(channelId); + Value currentValue = channel.value(); + if (currentValue.isDefined() && !Objects.equals(currentValue.get(), newValue)) { + try { + channel.setNextWriteValue(newValue); + } catch (OpenemsNamedException e) { + this.logWarn(this.log, "Unable to update Channel [" + channel.address() + "] from [" + currentValue + + "] to [" + newValue + "]"); + e.printStackTrace(); + } + } + } + + /** + * Sets some default settings on the inverter, like Timeout. * * @throws OpenemsNamedException on error */ private void setDefaultSettings() throws OpenemsNamedException { - if (!Objects.equal(this.getEmsTimeout().get(), DEFAULT_EMS_TIMEOUT)) { - this.setEmsTimeout(DEFAULT_EMS_TIMEOUT); - } - if (!Objects.equal(this.getBmsTimeout().get(), DEFAULT_BMS_TIMEOUT)) { - this.setBmsTimeout(DEFAULT_BMS_TIMEOUT); - } - if (!Objects.equal(this.getGridExistenceDetectionOn().get(), DEFAULT_GRID_EXISTENCE_DETECTION_ON)) { - this.setGridExistenceDetectionOn(DEFAULT_GRID_EXISTENCE_DETECTION_ON); - } - if (!Objects.equal(this.getPowerChangeMode().get(), DEFAULT_POWER_CHANGE_MODE)) { - this.setPowerChangeMode(DEFAULT_POWER_CHANGE_MODE); - } + this.updateIfNotEqual(Sinexcel.ChannelId.EMS_TIMEOUT, DEFAULT_EMS_TIMEOUT); + this.updateIfNotEqual(Sinexcel.ChannelId.BMS_TIMEOUT, DEFAULT_BMS_TIMEOUT); + this.updateIfNotEqual(Sinexcel.ChannelId.GRID_EXISTENCE_DETECTION_ON, DEFAULT_GRID_EXISTENCE_DETECTION_ON); + this.updateIfNotEqual(Sinexcel.ChannelId.POWER_RISING_MODE, DEFAULT_POWER_RISING_MODE); } /** @@ -187,37 +215,31 @@ private void setDefaultSettings() throws OpenemsNamedException { * @throws OpenemsNamedException on error */ private void setBatteryLimits(Battery battery) throws OpenemsNamedException { - // Upper voltage limit of battery protection >= Topping charge voltage >= Float // charge voltage >= Lower voltage limit of battery protection (814 >= 809 >= // 808 >= 813). - // Discharge Min Voltage - IntegerWriteChannel dischargeMinVoltageChannel = this.channel(Sinexcel.ChannelId.DISCHARGE_MIN_V); - Integer dischargeMinVoltage = battery.getDischargeMinVoltage().get(); - dischargeMinVoltageChannel.setNextWriteValue(dischargeMinVoltage); + this.updateIfNotEqual(Sinexcel.ChannelId.DISCHARGE_MIN_VOLTAGE, battery.getDischargeMinVoltage().get()); // Charge Max Voltage - IntegerWriteChannel chargeMaxVoltageChannel = this.channel(Sinexcel.ChannelId.CHARGE_MAX_V); - Integer chargeMaxVoltage = battery.getChargeMaxVoltage().get(); - chargeMaxVoltageChannel.setNextWriteValue(chargeMaxVoltage); + this.updateIfNotEqual(Sinexcel.ChannelId.CHARGE_MAX_VOLTAGE, battery.getChargeMaxVoltage().get()); - IntegerWriteChannel setSlowChargeVoltage = this.channel(Sinexcel.ChannelId.TOPPING_CHARGE_VOLTAGE); - setSlowChargeVoltage.setNextWriteValue(chargeMaxVoltage); + // Topping Charge Voltage + this.updateIfNotEqual(Sinexcel.ChannelId.TOPPING_CHARGE_VOLTAGE, + TypeUtils.min(battery.getChargeMaxVoltage().get(), MAX_TOPPING_CHARGE_VOLTAGE)); - IntegerWriteChannel setFloatChargeVoltage = this.channel(Sinexcel.ChannelId.FLOAT_CHARGE_VOLTAGE); - setFloatChargeVoltage.setNextWriteValue(chargeMaxVoltage); + // Float Charge Voltage + this.updateIfNotEqual(Sinexcel.ChannelId.FLOAT_CHARGE_VOLTAGE, + TypeUtils.min(battery.getChargeMaxVoltage().get(), MAX_TOPPING_CHARGE_VOLTAGE)); // Discharge Max Current // negative value is corrected as zero - IntegerWriteChannel dischargeMaxCurrentChannel = this.channel(Sinexcel.ChannelId.DISCHARGE_MAX_A); - dischargeMaxCurrentChannel.setNextWriteValue(// + this.updateIfNotEqual(Sinexcel.ChannelId.DISCHARGE_MAX_CURRENT, TypeUtils.fitWithin(0 /* enforce positive */, MAX_CURRENT, battery.getDischargeMaxCurrent().orElse(0))); // Charge Max Current // negative value is corrected as zero - IntegerWriteChannel chargeMaxCurrentChannel = this.channel(Sinexcel.ChannelId.CHARGE_MAX_A); - chargeMaxCurrentChannel.setNextWriteValue(// + this.updateIfNotEqual(Sinexcel.ChannelId.CHARGE_MAX_CURRENT, TypeUtils.fitWithin(0 /* enforce positive */, MAX_CURRENT, battery.getChargeMaxCurrent().orElse(0))); } @@ -298,244 +320,768 @@ public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { protected ModbusProtocol defineModbusProtocol() throws OpenemsException { return new ModbusProtocol(this, // - new FC3ReadRegistersTask(0x001, Priority.ONCE, // - m(Sinexcel.ChannelId.MODEL, new StringWordElement(0x001, 16)), // - m(Sinexcel.ChannelId.SERIAL, new StringWordElement(0x011, 8))), // - - new FC3ReadRegistersTask(0x020, Priority.HIGH, // - m(new BitsWordElement(0x020, this) // - .bit(0, Sinexcel.ChannelId.STATE_16) // - .bit(1, Sinexcel.ChannelId.STATE_17) // + new FC3ReadRegistersTask(1, Priority.HIGH, // + m(Sinexcel.ChannelId.MANUFACTURER_AND_MODEL_NUMBER, new StringWordElement(1, 16)), // + m(Sinexcel.ChannelId.SERIAL_NUMBER, new StringWordElement(17, 8)), // + new DummyRegisterElement(25, 31), m(new BitsWordElement(32, this) // + .bit(0, Sinexcel.ChannelId.FAULT_STATUS) // + .bit(1, Sinexcel.ChannelId.ALERT_STATUS) // .bit(2, OffGridBatteryInverter.ChannelId.INVERTER_STATE) // - .bit(3, Sinexcel.ChannelId.STATE_19) // - .bit(4, Sinexcel.ChannelId.STATE_20))), - new FC3ReadRegistersTask(0x024, Priority.LOW, // - m(new BitsWordElement(0x024, this) // - .bit(0, Sinexcel.ChannelId.STATE_21) // - .bit(1, Sinexcel.ChannelId.STATE_22) // - .bit(2, Sinexcel.ChannelId.STATE_23) // - .bit(3, Sinexcel.ChannelId.STATE_24) // - .bit(4, Sinexcel.ChannelId.STATE_25) // - .bit(5, Sinexcel.ChannelId.STATE_26) // - .bit(6, Sinexcel.ChannelId.STATE_27) // - .bit(7, Sinexcel.ChannelId.STATE_28) // - .bit(8, Sinexcel.ChannelId.STATE_29) // - .bit(9, Sinexcel.ChannelId.STATE_30) // - .bit(10, Sinexcel.ChannelId.STATE_31) // - .bit(11, Sinexcel.ChannelId.STATE_32) // - .bit(12, Sinexcel.ChannelId.STATE_33)), - m(new BitsWordElement(0x025, this) // - .bit(0, Sinexcel.ChannelId.STATE_34) // - .bit(1, Sinexcel.ChannelId.STATE_35) // - .bit(2, Sinexcel.ChannelId.STATE_36) // - .bit(3, Sinexcel.ChannelId.STATE_37) // - .bit(4, Sinexcel.ChannelId.STATE_38) // - .bit(5, Sinexcel.ChannelId.STATE_39) // - .bit(6, Sinexcel.ChannelId.STATE_40) // - .bit(7, Sinexcel.ChannelId.STATE_41) // - .bit(8, Sinexcel.ChannelId.STATE_42) // - .bit(9, Sinexcel.ChannelId.STATE_43) // - .bit(10, Sinexcel.ChannelId.STATE_44) // - .bit(11, Sinexcel.ChannelId.STATE_45) // - .bit(13, Sinexcel.ChannelId.STATE_47) // - .bit(14, Sinexcel.ChannelId.STATE_48) // - .bit(15, Sinexcel.ChannelId.STATE_49)), - m(new BitsWordElement(0x026, this) // - .bit(0, Sinexcel.ChannelId.STATE_50) // - .bit(2, Sinexcel.ChannelId.STATE_52) // - .bit(3, Sinexcel.ChannelId.STATE_53) // - .bit(4, Sinexcel.ChannelId.STATE_54)), - m(new BitsWordElement(0x027, this) // - .bit(0, Sinexcel.ChannelId.STATE_55) // - .bit(1, Sinexcel.ChannelId.STATE_56) // - .bit(2, Sinexcel.ChannelId.STATE_57) // - .bit(3, Sinexcel.ChannelId.STATE_58)), - m(new BitsWordElement(0x028, this) // - .bit(0, Sinexcel.ChannelId.STATE_59) // - .bit(1, Sinexcel.ChannelId.STATE_60) // - .bit(2, Sinexcel.ChannelId.STATE_61) // - .bit(3, Sinexcel.ChannelId.STATE_62) // - .bit(4, Sinexcel.ChannelId.STATE_63) // - .bit(5, Sinexcel.ChannelId.STATE_64)), - new DummyRegisterElement(0x029, 0x02A), m(new BitsWordElement(0x02B, this) // - .bit(0, Sinexcel.ChannelId.STATE_65) // - .bit(1, Sinexcel.ChannelId.STATE_66) // - .bit(2, Sinexcel.ChannelId.STATE_67) // - .bit(3, Sinexcel.ChannelId.STATE_68)), - m(new BitsWordElement(0x02C, this) // - .bit(0, Sinexcel.ChannelId.STATE_69) // - .bit(1, Sinexcel.ChannelId.STATE_70) // - .bit(2, Sinexcel.ChannelId.STATE_71) // - .bit(3, Sinexcel.ChannelId.STATE_72) // - .bit(4, Sinexcel.ChannelId.STATE_73)), - new DummyRegisterElement(0x02D, 0x02E), m(new BitsWordElement(0x02F, this) // - .bit(0, Sinexcel.ChannelId.STATE_74))), - - new FC3ReadRegistersTask(0x065, Priority.LOW, // - m(Sinexcel.ChannelId.INVOUTVOLT_L1, new UnsignedWordElement(0x065), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1), - m(Sinexcel.ChannelId.INVOUTVOLT_L2, new UnsignedWordElement(0x066), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1), - m(Sinexcel.ChannelId.INVOUTVOLT_L3, new UnsignedWordElement(0x067), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1), - m(Sinexcel.ChannelId.INVOUTCURRENT_L1, new UnsignedWordElement(0x068), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1), - m(Sinexcel.ChannelId.INVOUTCURRENT_L2, new UnsignedWordElement(0x069), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1), - m(Sinexcel.ChannelId.INVOUTCURRENT_L3, new UnsignedWordElement(0x06A), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1), - new DummyRegisterElement(0x06B, 0x07D), // - m(SymmetricEss.ChannelId.ACTIVE_DISCHARGE_ENERGY, new UnsignedDoublewordElement(0x07E), + .bit(3, Sinexcel.ChannelId.INVERTER_GRID_MODE) // + .bit(4, Sinexcel.ChannelId.ISLAND_MODE) // + .bit(5, Sinexcel.ChannelId.DERATING_STATUS) // + .bit(6, Sinexcel.ChannelId.ALLOW_GRID_CONNECTION) // + .bit(7, Sinexcel.ChannelId.STANDBY_STATUS))), // + new FC3ReadRegistersTask(33, Priority.LOW, // + m(new BitsWordElement(33, this) // + .bit(0, Sinexcel.ChannelId.OBTAIN_FAULT_RECORD_FLAG) // + .bit(1, Sinexcel.ChannelId.WRITE_POWER_GENERATION_INTO_EEPROM) // + .bit(2, Sinexcel.ChannelId.INITIALIZE_DSP_PARAMETERS) // + .bit(3, Sinexcel.ChannelId.MASTER_SLAVE_MODE)), // + new DummyRegisterElement(34, 35), m(new BitsWordElement(36, this) // + .bit(0, Sinexcel.ChannelId.AC_OVER_VOLTAGE_PROTECTION) // + .bit(1, Sinexcel.ChannelId.AC_UNDER_VOLTAGE_PROTECTION) // + .bit(2, Sinexcel.ChannelId.AC_OVER_FREQUENCY_PROTECTION) // + .bit(3, Sinexcel.ChannelId.AC_UNDER_FREQUENCY_PROTECTION) // + .bit(4, Sinexcel.ChannelId.GRID_VOLTAGE_UNBALANCE) // + .bit(5, Sinexcel.ChannelId.GRID_PHASE_REVERSE) // + .bit(6, Sinexcel.ChannelId.INVERTER_ISLAND) // + .bit(7, Sinexcel.ChannelId.ON_GRID_OFF_GRID_SWITCH_OVER_FAILURE) // + .bit(8, Sinexcel.ChannelId.OUTPUT_GROUND_FAULT) // + .bit(9, Sinexcel.ChannelId.GRID_PHASE_LOCK_FAILED) // + .bit(10, Sinexcel.ChannelId.INTERNAL_AIR_OVER_TEMPERATURE) // + .bit(11, Sinexcel.ChannelId.GRID_CONNECTED_CONDITION_TIME_OUT) // + .bit(12, Sinexcel.ChannelId.MODULE_RENUMBER_FAILURE) // + .bit(13, Sinexcel.ChannelId.CANB_COMMUNICATION_FAILURE) // + .bit(14, Sinexcel.ChannelId.POWER_FREQUENCY_SYNCHRONIZATION_FAILURE) // + .bit(15, Sinexcel.ChannelId.CARRIER_SYNCHRONIZATION_FALURE)), // + m(new BitsWordElement(37, this) // + .bit(0, Sinexcel.ChannelId.EPO_ERROR) // + .bit(1, Sinexcel.ChannelId.MONITOR_PARAMETER_MISMATCH) // + .bit(2, Sinexcel.ChannelId.DSP_VERSION_ABNORMAL) // + .bit(3, Sinexcel.ChannelId.CPLD_VERSION_ERROR) // + .bit(4, Sinexcel.ChannelId.HARDWARE_VERSION_ERROR) // + .bit(5, Sinexcel.ChannelId.CANA_COMMUNICATION_FAILURE) // + .bit(6, Sinexcel.ChannelId.AUXILARY_POWER_FAULT) // + .bit(7, Sinexcel.ChannelId.FAN_FAILURE) // + .bit(8, Sinexcel.ChannelId.DC_OVER_VOLTAGE) // + .bit(9, Sinexcel.ChannelId.DC_LOW_VOLTAGE) // + .bit(10, Sinexcel.ChannelId.DC_VOLTAGE_UNBALANCED) // + .bit(12, Sinexcel.ChannelId.AC_RELAY_SHORT_CIRCUIT) // + .bit(13, Sinexcel.ChannelId.OUTPUT_VOLTAGE_ABNORMAL) // + .bit(14, Sinexcel.ChannelId.OUTPUT_CURRENT_UNBALANCED) // + .bit(15, Sinexcel.ChannelId.OVER_TEMPERATURE_OF_HEAT_SINK)), // + m(new BitsWordElement(38, this) // + .bit(0, Sinexcel.ChannelId.OUTPUT_OVER_LOAD_TOT) // + .bit(1, Sinexcel.ChannelId.GRID_CONTINUE_OVER_VOLTAGE) // + .bit(2, Sinexcel.ChannelId.AC_SOFT_START_FAILURE) // + .bit(3, Sinexcel.ChannelId.INVERTER_START_FAILURE) // + .bit(4, Sinexcel.ChannelId.AC_RELAY_IS_OPEN) // + .bit(5, Sinexcel.ChannelId.U2_BOARD_COMMUNICATION_IS_ABNORMAL) // + .bit(6, Sinexcel.ChannelId.AC_DC_COMPONENT_EXCESS) // + .bit(7, Sinexcel.ChannelId.MASTER_SLAVE_SAMPLING_ABNORMALITY) // + .bit(8, Sinexcel.ChannelId.PARAMETER_SETTING_ERROR) // + .bit(9, Sinexcel.ChannelId.LOW_OFF_GRID_ENERGY) // + .bit(10, Sinexcel.ChannelId.N_LINE_IS_NOT_CONNECTED) // + .bit(11, Sinexcel.ChannelId.STANDBY_BUS_HEIGHT) // + .bit(12, Sinexcel.ChannelId.SINGLE_PHASE_WIRING_ERROR) // + .bit(13, Sinexcel.ChannelId.EXCESSIVE_GRID_FREQUENCY_CHANGE) // + .bit(14, Sinexcel.ChannelId.ABRUPT_PHASE_ANGLE_FAULT_OF_POWER_GRID) // + .bit(15, Sinexcel.ChannelId.GRID_CONNECTION_PARAMETER_CONFLICT)), // + m(new BitsWordElement(39, this) // + .bit(0, Sinexcel.ChannelId.EE_READING_ERROR_1) // + .bit(1, Sinexcel.ChannelId.EE_READING_ERROR_2) // + .bit(2, Sinexcel.ChannelId.FLASH_READING_ERROR) // + .bit(3, Sinexcel.ChannelId.INVERTER_OVER_LOAD) // + .bit(4, Sinexcel.ChannelId.BATTERY_PARAMETER_SETTING_ERROR) // + .bit(5, Sinexcel.ChannelId.SLAVE_LOST_ALARM)), // + m(new BitsWordElement(40, this) // + .bit(0, Sinexcel.ChannelId.DC_CHARGING) // + .bit(1, Sinexcel.ChannelId.DC_DISCHARGING) // + .bit(2, Sinexcel.ChannelId.BATTERY_FULLY_CHARGED) // + .bit(3, Sinexcel.ChannelId.BATTERY_EMPTY) // + .bit(4, Sinexcel.ChannelId.DC_FAULT_STATUS) // + .bit(5, Sinexcel.ChannelId.DC_ALERT_STATUS)), // + new DummyRegisterElement(41, 43), m(new BitsWordElement(44, this) // + .bit(0, Sinexcel.ChannelId.DC_INPUT_OVER_VOLTAGE_PROTECTION) // + .bit(1, Sinexcel.ChannelId.DC_INPUT_UNDER_VOLTAGE_PROTECTION) // + .bit(3, Sinexcel.ChannelId.BMS_ALERT) // + .bit(4, Sinexcel.ChannelId.BMS_COMMUNICATION_TIMEOUT) // + .bit(5, Sinexcel.ChannelId.EMS_COMMUNICATION_TIMEOUT)), // + m(new BitsWordElement(45, this) // + .bit(0, Sinexcel.ChannelId.DC_SOFT_START_FAILED) // + .bit(1, Sinexcel.ChannelId.DC_RELAY_SHORT_CIRCUIT) // + .bit(2, Sinexcel.ChannelId.DC_RELAY_SHORT_OPEN) // + .bit(3, Sinexcel.ChannelId.BATTERY_POWER_OVER_LOAD) // + .bit(4, Sinexcel.ChannelId.DC_BUS_STARTING_FAILED) // + .bit(5, Sinexcel.ChannelId.DC_QUICK_CHECK_OVER_CURRENT)), // + new DummyRegisterElement(46), m(new BitsWordElement(47, this) // + .bit(0, Sinexcel.ChannelId.DC_OC)) // + + ), + + new FC3ReadRegistersTask(101, Priority.LOW, // + m(Sinexcel.ChannelId.GRID_VOLTAGE_L1, new SignedWordElement(101), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.GRID_VOLTAGE_L2, new SignedWordElement(102), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.GRID_VOLTAGE_L3, new SignedWordElement(103), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.GRID_CURRENT_L1, new SignedWordElement(104), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.GRID_CURRENT_L2, new SignedWordElement(105), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.GRID_CURRENT_L3, new SignedWordElement(106), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.FREQUENCY, new SignedWordElement(107), + ElementToChannelConverter.SCALE_FACTOR_1), // + new DummyRegisterElement(108, 109), + m(Sinexcel.ChannelId.ACTIVE_POWER_L1, new SignedWordElement(110), + ElementToChannelConverter.SCALE_FACTOR_1), // + m(Sinexcel.ChannelId.ACTIVE_POWER_L2, new SignedWordElement(111), + ElementToChannelConverter.SCALE_FACTOR_1), // + m(Sinexcel.ChannelId.ACTIVE_POWER_L3, new SignedWordElement(112), + ElementToChannelConverter.SCALE_FACTOR_1), // + m(Sinexcel.ChannelId.REACTIVE_POWER_L1, new SignedWordElement(113), + ElementToChannelConverter.SCALE_FACTOR_1), // + m(Sinexcel.ChannelId.REACTIVE_POWER_L2, new SignedWordElement(114), + ElementToChannelConverter.SCALE_FACTOR_1), // fems + m(Sinexcel.ChannelId.REACTIVE_POWER_L3, new SignedWordElement(115), + ElementToChannelConverter.SCALE_FACTOR_1), // + m(Sinexcel.ChannelId.APPERENT_POWER_L1, new SignedWordElement(116), + ElementToChannelConverter.SCALE_FACTOR_1), // + m(Sinexcel.ChannelId.APPERENT_POWER_L2, new SignedWordElement(117), + ElementToChannelConverter.SCALE_FACTOR_1), // + m(Sinexcel.ChannelId.APPERENT_POWER_L3, new SignedWordElement(118), + ElementToChannelConverter.SCALE_FACTOR_1), // + m(Sinexcel.ChannelId.COS_PHI_L1, new SignedWordElement(119), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + m(Sinexcel.ChannelId.COS_PHI_L2, new SignedWordElement(120), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + m(Sinexcel.ChannelId.COS_PHI_L3, new SignedWordElement(121), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + m(SymmetricBatteryInverter.ChannelId.ACTIVE_POWER, new SignedWordElement(122), + new ElementToChannelConverterChain(ElementToChannelConverter.SCALE_FACTOR_1, + IGNORE_LESS_THAN_100)), // + m(SymmetricBatteryInverter.ChannelId.REACTIVE_POWER, new SignedWordElement(123), + ElementToChannelConverter.SCALE_FACTOR_1), // + m(Sinexcel.ChannelId.APPARENT_POWER, new SignedWordElement(124), + ElementToChannelConverter.SCALE_FACTOR_1), // + m(Sinexcel.ChannelId.COS_PHI, new SignedWordElement(125), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + m(SymmetricBatteryInverter.ChannelId.ACTIVE_DISCHARGE_ENERGY, + new UnsignedDoublewordElement(126), ElementToChannelConverter.SCALE_FACTOR_2), // + m(SymmetricBatteryInverter.ChannelId.ACTIVE_CHARGE_ENERGY, new UnsignedDoublewordElement(128), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.REACTIVE_ENERGY, new UnsignedDoublewordElement(130), + ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // + m(Sinexcel.ChannelId.TEMPERATURE_OF_AC_HEAT_SINK, new SignedWordElement(132)), // + m(Sinexcel.ChannelId.DC_VOLTAGE_POSITIVE, new SignedWordElement(133), ElementToChannelConverter.SCALE_FACTOR_2), // - m(SymmetricEss.ChannelId.ACTIVE_CHARGE_ENERGY, new UnsignedDoublewordElement(0x080), + m(Sinexcel.ChannelId.DC_VOLTAGE_NEGATIVE, new SignedWordElement(134), ElementToChannelConverter.SCALE_FACTOR_2), // - new DummyRegisterElement(0x082, 0x083), - m(Sinexcel.ChannelId.TEMPERATURE, new SignedWordElement(0x084)), - new DummyRegisterElement(0x085, 0x08C), // - m(Sinexcel.ChannelId.DC_POWER, new SignedWordElement(0x08D), - ElementToChannelConverter.SCALE_FACTOR_1), - new DummyRegisterElement(0x08E, 0x08F), // - m(Sinexcel.ChannelId.ANALOG_DC_CHARGE_ENERGY, new UnsignedDoublewordElement(0x090)), // - m(Sinexcel.ChannelId.ANALOG_DC_DISCHARGE_ENERGY, new UnsignedDoublewordElement(0x092))), // - - new FC6WriteRegisterTask(0x087, // - m(Sinexcel.ChannelId.SET_ACTIVE_POWER, new SignedWordElement(0x087), - ElementToChannelConverter.SCALE_FACTOR_2)), // in 100 W - - new FC6WriteRegisterTask(0x088, // - m(Sinexcel.ChannelId.SET_REACTIVE_POWER, new SignedWordElement(0x088), - ElementToChannelConverter.SCALE_FACTOR_2)), // in 100 var - - new FC3ReadRegistersTask(0x08A, Priority.LOW, - m(OffGridBatteryInverter.ChannelId.OFF_GRID_FREQUENCY, new SignedWordElement(0x08A), // + m(Sinexcel.ChannelId.SET_ACTIVE_POWER, new SignedWordElement(135), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.SET_REACTIVE_POWER, new SignedWordElement(136), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.SET_OFF_GRID_VOLTAGE, new SignedWordElement(137), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.SET_OFF_GRID_FREQUENCY, new SignedWordElement(138), + ElementToChannelConverter.SCALE_FACTOR_1), // + new DummyRegisterElement(139, 140), + m(Sinexcel.ChannelId.DC_POWER, new SignedWordElement(141), + ElementToChannelConverter.SCALE_FACTOR_1), // + m(Sinexcel.ChannelId.DC_VOLTAGE, new SignedWordElement(142), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.DC_CURRENT, new SignedWordElement(143), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.DC_CHARGE_ENERGY, new UnsignedDoublewordElement(144), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.DC_DISCHARGE_ENERGY, new UnsignedDoublewordElement(146), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.TEMPERATURE_OF_DC_DC_HEAT_SINK, new SignedWordElement(148)) // + ), + + new FC3ReadRegistersTask(224, Priority.LOW, // + m(Sinexcel.ChannelId.DC_RELAY_REAR_END_VOLTAGE, new SignedWordElement(224), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.CHARGE_MAX_CURRENT_READ, new UnsignedWordElement(225), + ElementToChannelConverter.SCALE_FACTOR_2), // + m(Sinexcel.ChannelId.DISCHARGE_MAX_CURRENT_READ, new UnsignedWordElement(226), + ElementToChannelConverter.SCALE_FACTOR_2), // + new DummyRegisterElement(227, 299), + m(Sinexcel.ChannelId.IP_ADDRESS_BLOCK_1, new UnsignedWordElement(300)), // + m(Sinexcel.ChannelId.IP_ADDRESS_BLOCK_2, new UnsignedWordElement(301)), // + m(Sinexcel.ChannelId.IP_ADDRESS_BLOCK_3, new UnsignedWordElement(302)), // + m(Sinexcel.ChannelId.IP_ADDRESS_BLOCK_4, new UnsignedWordElement(303)), // + m(Sinexcel.ChannelId.NETMASK_BLOCK_1, new UnsignedWordElement(304)), // + m(Sinexcel.ChannelId.NETMASK_BLOCK_2, new UnsignedWordElement(305)), // + m(Sinexcel.ChannelId.NETMASK_BLOCK_3, new UnsignedWordElement(306)), // + m(Sinexcel.ChannelId.NETMASK_BLOCK_4, new UnsignedWordElement(307)), // + m(Sinexcel.ChannelId.GATEWAY_IP_BLOCK_1, new UnsignedWordElement(308)), // + m(Sinexcel.ChannelId.GATEWAY_IP_BLOCK_2, new UnsignedWordElement(309)), // + m(Sinexcel.ChannelId.GATEWAY_IP_BLOCK_3, new UnsignedWordElement(310)), // + m(Sinexcel.ChannelId.GATEWAY_IP_BLOCK_4, new UnsignedWordElement(311)), // + m(Sinexcel.ChannelId.MAC, new StringWordElement(312, 3)), // + new DummyRegisterElement(315), // + m(Sinexcel.ChannelId.MODBUS_UNIT_ID, new UnsignedWordElement(316)), // + new DummyRegisterElement(317, 319), + m(Sinexcel.ChannelId.BAUDRATE, new UnsignedWordElement(320)), // + new DummyRegisterElement(321, 324), + m(Sinexcel.ChannelId.INTERFACE_TYPE, new UnsignedWordElement(325)), // + m(Sinexcel.ChannelId.COMMUNICATION_PROTOCOL_SELECTION, new UnsignedWordElement(326)), // + m(Sinexcel.ChannelId.EMS_TIMEOUT, new UnsignedWordElement(327)), // + m(Sinexcel.ChannelId.EPO_ENABLE, new UnsignedWordElement(328)), // + m(Sinexcel.ChannelId.BMS_TIMEOUT, new UnsignedWordElement(329)), // + m(Sinexcel.ChannelId.BMS_PROTOCOL_SELECTION, new UnsignedWordElement(330)), // + m(Sinexcel.ChannelId.SET_GRID_MODE, new UnsignedWordElement(331)), // + m(Sinexcel.ChannelId.BUZZER_ENABLE, new UnsignedWordElement(332)), // + m(Sinexcel.ChannelId.RESTORE_FACTORY_SETTING, new UnsignedWordElement(333)) // + ), + + new FC3ReadRegistersTask(650, Priority.LOW, // + m(Sinexcel.ChannelId.START_INVERTER, new UnsignedWordElement(650)), // + m(Sinexcel.ChannelId.STOP_INVERTER, new UnsignedWordElement(651)), // + m(Sinexcel.ChannelId.CLEAR_FAILURE_COMMAND, new UnsignedWordElement(652)), // + m(Sinexcel.ChannelId.SET_ON_GRID_MODE, new UnsignedWordElement(653)), // + m(Sinexcel.ChannelId.SET_OFF_GRID_MODE, new UnsignedWordElement(654)), // + m(Sinexcel.ChannelId.SET_STANDBY_COMMAND, new UnsignedWordElement(655)), // + m(Sinexcel.ChannelId.SET_SOFT_START, new UnsignedWordElement(656)), // + m(Sinexcel.ChannelId.RESET_INSTRUCTION, new UnsignedWordElement(657)), // + m(Sinexcel.ChannelId.GRID_STOP, new UnsignedWordElement(658)) // + ), + + new FC3ReadRegistersTask(748, Priority.LOW, // + m(Sinexcel.ChannelId.OUTPUT_VOLTAGE_LEVEL, new UnsignedWordElement(748)), // + m(Sinexcel.ChannelId.OUTPUT_FREQUENCY_LEVEL, new UnsignedWordElement(749)), // + m(Sinexcel.ChannelId.INVERTER_WIRING_TOPOLOGY, new UnsignedWordElement(750)), // + new DummyRegisterElement(751), + m(Sinexcel.ChannelId.SWITCHING_DEVICE_ACCESS_SETTING, new UnsignedWordElement(752)), // + m(Sinexcel.ChannelId.MODULE_POWER_LEVEL, new UnsignedWordElement(753)), // + m(Sinexcel.ChannelId.DC_VOLTAGE_LEVEL, new UnsignedWordElement(754)), // + m(Sinexcel.ChannelId.CPU_TYPE, new UnsignedWordElement(755)), // + m(Sinexcel.ChannelId.OFF_GRID_AND_PARALLEL_ENABLE, new UnsignedWordElement(756)), // + m(Sinexcel.ChannelId.SET_DC_SOFT_START_EXTERNAL_CONTROL, new UnsignedWordElement(757)), // + new DummyRegisterElement(758, 767), + m(Sinexcel.ChannelId.GRID_OVER_VOLTAGE_PROTECTION_AMPLITUDE, new SignedWordElement(768)), // + m(Sinexcel.ChannelId.AC_OVER_VOLTAGE_TRIP_TIME_1, new SignedWordElement(769)), // + m(Sinexcel.ChannelId.AC_OVER_VOLTAGE_TRIP_LEVEL_2, new SignedWordElement(770)), // + m(Sinexcel.ChannelId.AC_OVER_VOLTAGE_TRIP_TIME_2, new SignedWordElement(771)), // + m(Sinexcel.ChannelId.AC_UNDER_VOLTAGE_TRIP_LEVEL_1, new SignedWordElement(772)), // + m(Sinexcel.ChannelId.AC_UNDER_VOLTAGE_TRIP_TIME_1, new SignedWordElement(773)), // + m(Sinexcel.ChannelId.AC_UNDER_VOLTAGE_TRIP_LEVEL_2, new SignedWordElement(774)), // + m(Sinexcel.ChannelId.AC_UNDER_VOLTAGE_TRIP_TIME_2, new SignedWordElement(775)), // + m(Sinexcel.ChannelId.AC_UNDER_VOLTAGE_TRIP_LEVEL_3, new SignedWordElement(776)), // + m(Sinexcel.ChannelId.AC_UNDER_VOLTAGE_TRIP_TIME_3, new SignedWordElement(777)), // + + m(Sinexcel.ChannelId.AC_OVER_FREQUENCY_TRIP_LEVEL_1, new SignedWordElement(778)), // + m(Sinexcel.ChannelId.AC_OVER_FREQUENCY_TRIP_TIME_1, new SignedWordElement(779)), // + m(Sinexcel.ChannelId.AC_OVER_FREQUENCY_TRIP_LEVEL_2, new SignedWordElement(780)), // + m(Sinexcel.ChannelId.AC_OVER_FREQUENCY_TRIP_TIME_2, new SignedWordElement(781)), // + + m(Sinexcel.ChannelId.AC_UNDER_FREQUENCY_TRIP_LEVEL_1, new SignedWordElement(782)), // + m(Sinexcel.ChannelId.AC_UNDER_FREQUENCY_TRIP_TIME_1, new SignedWordElement(783)), // + m(Sinexcel.ChannelId.AC_UNDER_FREQUENCY_TRIP_LEVEL_2, new SignedWordElement(784)), // + m(Sinexcel.ChannelId.AC_UNDER_FREQUENCY_TRIP_TIME_2, new SignedWordElement(785)), // + + m(Sinexcel.ChannelId.RECONNECT_TIME, new SignedWordElement(786)), // + new DummyRegisterElement(787, 789), + m(Sinexcel.ChannelId.ANTI_ISLANDING, new UnsignedWordElement(790)), // + + m(Sinexcel.ChannelId.FREQUENCY_VOLTAGE_RIDE_THROUGH, new UnsignedWordElement(791)), // + m(Sinexcel.ChannelId.REACTIVE_POWER_CONTROL_MODE, new UnsignedWordElement(792)), // + m(Sinexcel.ChannelId.POWER_RISING_MODE, new UnsignedWordElement(793)), // + m(Sinexcel.ChannelId.ACTIVE_POWER_CONTROL_MODE, new UnsignedWordElement(794)), // + m(Sinexcel.ChannelId.GRID_VOLTAGE_ASYMMETRIC_DETECTON, new UnsignedWordElement(795)), // + m(Sinexcel.ChannelId.CONTINUOUS_OVERVOLTAGE_DETECTION, new UnsignedWordElement(796)), // + m(Sinexcel.ChannelId.GRID_EXISTENCE_DETECTION_ON, new UnsignedWordElement(797)), // + m(Sinexcel.ChannelId.NEUTRAL_FLOATING_DETECTION, new UnsignedWordElement(798)), // + m(Sinexcel.ChannelId.OFF_GRID_BLACKSTART_MODE, new UnsignedWordElement(799)), // + m(Sinexcel.ChannelId.GRID_CODE_SELCETION, new UnsignedWordElement(800)), // + m(Sinexcel.ChannelId.GRID_CONNECTED_ACTIVE_CAPACITY_LIMITATION_FUNCTION, + new UnsignedWordElement(801)), // + m(Sinexcel.ChannelId.GRID_ACTIVE_POWER_CAPACITY_SETTING, new UnsignedWordElement(802)), // + m(Sinexcel.ChannelId.SINGLE_PHASE_MODE_SELECTION, new UnsignedWordElement(803)), // + m(Sinexcel.ChannelId.OVER_VOLTAGE_DROP_ACTIVE, new UnsignedWordElement(804)), // + m(Sinexcel.ChannelId.START_UP_MODE, new UnsignedWordElement(805)), // + new DummyRegisterElement(806), + m(Sinexcel.ChannelId.LOCAL_ID_SETTING, new SignedWordElement(807)), // + m(Sinexcel.ChannelId.FLOAT_CHARGE_VOLTAGE, new SignedWordElement(808), + ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // + m(Sinexcel.ChannelId.TOPPING_CHARGE_VOLTAGE, new SignedWordElement(809), + ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // + m(Sinexcel.ChannelId.CURRENT_FROM_TOPPING_CHARGING_TO_FLOAT_CHARGING, + new SignedWordElement(810), ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // + m(Sinexcel.ChannelId.CHARGE_MAX_CURRENT, new SignedWordElement(811), + ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // + m(Sinexcel.ChannelId.DISCHARGE_MAX_CURRENT, new SignedWordElement(812), + ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // + m(Sinexcel.ChannelId.DISCHARGE_MIN_VOLTAGE, new SignedWordElement(813), + ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // + m(Sinexcel.ChannelId.CHARGE_MAX_VOLTAGE, new SignedWordElement(814), + ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // + m(Sinexcel.ChannelId.BATTERY_VOLTAGE_PROTECTION_LIMIT, new UnsignedWordElement(815), + ElementToChannelConverter.SCALE_FACTOR_MINUS_1) // + + ), + + new FC3ReadRegistersTask(825, Priority.LOW, // + m(Sinexcel.ChannelId.LEAKAGE_CURRENT_DC_COMPONENT_DETECTOR, new UnsignedWordElement(825)), // + new DummyRegisterElement(826, 845), + m(Sinexcel.ChannelId.RESUME_AND_LIMIT_FREQUENCY, new SignedWordElement(846)), // + m(Sinexcel.ChannelId.RESTORE_LOWER_FREQUENCY_OF_GRID_CONNECTION, new SignedWordElement(847)), // + m(Sinexcel.ChannelId.VOLTAGE_REACTIVE_REFERENCE, new UnsignedWordElement(848)), // + m(Sinexcel.ChannelId.VOLTAGE_AND_REACTIVE_POWER_ADJUSTMENT_POINT_V1, + new UnsignedWordElement(849)), // + m(Sinexcel.ChannelId.VOLTAGE_AND_REACTIVE_POWER_ADJUSTMENT_POINT_V2, + new SignedWordElement(850)), // + m(Sinexcel.ChannelId.VOLTAGE_AND_REACTIVE_POWER_ADJUSTMENT_POINT_V3, + new SignedWordElement(851)), // + m(Sinexcel.ChannelId.VOLTAGE_AND_REACTIVE_POWER_ADJUSTMENT_POINT_V4, + new SignedWordElement(852)), // + + m(Sinexcel.ChannelId.MAX_CAPACITIVE_REACTIVE_REGULATION_Q1, new SignedWordElement(853)), // + m(Sinexcel.ChannelId.INITIAL_CAPACITIVE_REACTIVE_REGULATION_Q2, new SignedWordElement(854)), // + m(Sinexcel.ChannelId.INITIAL_INDUCTIVE_REACTIVE_REGULATION_Q3, new SignedWordElement(855)), // + m(Sinexcel.ChannelId.MAX_INDUCTIVE_REACTIVE_REGULATION_Q4, new SignedWordElement(856)), // + + m(Sinexcel.ChannelId.VOLTAGE_AND_REACTIVE_RESPONSE_TIME, new SignedWordElement(857)), // + m(Sinexcel.ChannelId.REACTIVE_FIRST_ORDER_RESPONSE_TIME, new SignedWordElement(858)), // + new DummyRegisterElement(859, 861), + m(Sinexcel.ChannelId.INITIAL_VOLTAGE_V_START, new SignedWordElement(862)), // + m(Sinexcel.ChannelId.END_VOLTAGE_V_STOP, new SignedWordElement(863)), // + m(Sinexcel.ChannelId.INITIAL_POWER_P_START, new SignedWordElement(864)), // + m(Sinexcel.ChannelId.END_POWER_P_STOP, new SignedWordElement(865)), // + m(Sinexcel.ChannelId.RETURN_TO_SERVICE_DELAY, new SignedWordElement(866)), // + m(Sinexcel.ChannelId.VOLT_WATT_RESPONSE_TIME, new SignedWordElement(867)), // + m(Sinexcel.ChannelId.START_OF_FREQUENY_DROP, new SignedWordElement(868)), // + m(Sinexcel.ChannelId.SLOPE_OF_FREQUENCY_DROP, new SignedWordElement(869)), // + m(Sinexcel.ChannelId.FREQUENCY_WATT_F_STOP_DISCHARGE, new SignedWordElement(870)), // + m(Sinexcel.ChannelId.FREQUENCY_WATT_F_STOP_CHARGE, new SignedWordElement(871)), // + m(Sinexcel.ChannelId.VOLT_WATT_V_START_CHARGE, new SignedWordElement(872)), // + new DummyRegisterElement(873, 875), + m(Sinexcel.ChannelId.SOFT_START_RAMP_RATE, new SignedWordElement(876)), // + m(Sinexcel.ChannelId.POWER_RAMP_RATE, new SignedWordElement(877)), // + m(Sinexcel.ChannelId.POWER_FACTOR_SETTING, new SignedWordElement(878), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + m(Sinexcel.ChannelId.POWER_FACTOR_P1, new SignedWordElement(879), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + m(Sinexcel.ChannelId.POWER_FACTOR_P2, new SignedWordElement(880), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + m(Sinexcel.ChannelId.POWER_FACTOR_P3, new SignedWordElement(881), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + m(Sinexcel.ChannelId.POWER_FACTOR_P4, new SignedWordElement(882), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + m(Sinexcel.ChannelId.POWER_FACTOR_CURVE_MODE_P1, new SignedWordElement(883), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + m(Sinexcel.ChannelId.POWER_FACTOR_CURVE_MODE_P2, new SignedWordElement(884), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + m(Sinexcel.ChannelId.POWER_FACTOR_CURVE_MODE_P3, new SignedWordElement(885), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + m(Sinexcel.ChannelId.POWER_FACTOR_CURVE_MODE_P4, new SignedWordElement(886), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + m(Sinexcel.ChannelId.CONTINUOS_OVER_VOLTAGE_TRIP_THRESHOLD, new SignedWordElement(887), + ElementToChannelConverter.SCALE_FACTOR_MINUS_2), // + m(Sinexcel.ChannelId.FREQUENCY_VARIATION_RATE_TRIP_THRESHOLD, new SignedWordElement(888)), // + m(Sinexcel.ChannelId.PHASE_ANGLE_ABRUPT_TRIP_THRESHOLD, new SignedWordElement(889)), // + m(Sinexcel.ChannelId.GRID_RECONNECTION_VOLTAGE_UPPER_LIMIT, new SignedWordElement(890)), // + m(Sinexcel.ChannelId.GRID_RECONNECTION_VOLTAGE_LOWER_LIMIT, new SignedWordElement(891)), // + m(Sinexcel.ChannelId.GRID_RECONNECTION_FREQUENCY_UPPER_LIMIT, new SignedWordElement(892)), // + m(Sinexcel.ChannelId.GRID_RECONNECTION_FREQUENCY_LOWER_LIMIT, new SignedWordElement(893)), // + m(Sinexcel.ChannelId.LOW_FREQUENCY_RAMP_RATE, new SignedWordElement(894)) // + ), + + new FC3ReadRegistersTask(934, Priority.LOW, // + m(Sinexcel.ChannelId.METER_ACTIVE_POWER, new SignedWordElement(934), + ElementToChannelConverter.SCALE_FACTOR_1), // + new DummyRegisterElement(935, 947), + m(Sinexcel.ChannelId.GRID_VOLTAGE_CALIBRATION_L1, new UnsignedWordElement(948), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.GRID_VOLTAGE_CALIBRATION_L2, new UnsignedWordElement(949), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.GRID_VOLTAGE_CALIBRATION_L3, new UnsignedWordElement(950), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.INVERTER_VOLTAGE_CALIBRATION_L1, new UnsignedWordElement(951), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.INVERTER_VOLTAGE_CALIBRATION_L2, new UnsignedWordElement(952), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.INVERTER_VOLTAGE_CALIBRATION_L3, new UnsignedWordElement(953), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.INDUCTOR_CURRENT_CALIBRATION_L1_PARAMETERS_1, new UnsignedWordElement(954), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.INDUCTOR_CURRENT_CALIBRATION_L2_PARAMETERS_1, new UnsignedWordElement(955), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.INDUCTOR_CURRENT_CALIBRATION_L3_PARAMETERS_1, new UnsignedWordElement(956), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.INDUCTOR_CURRENT_CALIBRATION_L1_PARAMETERS_2, new UnsignedWordElement(957), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.INDUCTOR_CURRENT_CALIBRATION_L2_PARAMETERS_2, new UnsignedWordElement(958), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.INDUCTOR_CURRENT_CALIBRATION_L3_PARAMETERS_2, new UnsignedWordElement(959), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.OUTPUT_CURRENT_CALIBRATION_L1, new UnsignedWordElement(960), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.OUTPUT_CURRENT_CALIBRATION_L2, new UnsignedWordElement(961), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.OUTPUT_CURRENT_CALIBRATION_L3, new UnsignedWordElement(962), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.POSITIVE_BUS_VOLTAGE_CALIBRATION, new UnsignedWordElement(963), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.NEGATIVE_BUS_VOLTAGE_CALIBRATION, new UnsignedWordElement(964), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.DC_VOLTAGE_CALIBRATION, new UnsignedWordElement(965), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.DC_CURRENT_CALIBRATION, new UnsignedWordElement(966), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3), // + m(Sinexcel.ChannelId.DC_INDUCTOR_CURRENT_CALIBRATION, new UnsignedWordElement(967), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3) // + ), + + new FC6WriteRegisterTask(135, // + m(Sinexcel.ChannelId.SET_ACTIVE_POWER, new SignedWordElement(135), + ElementToChannelConverter.SCALE_FACTOR_2)), + new FC6WriteRegisterTask(136, // + m(Sinexcel.ChannelId.SET_REACTIVE_POWER, new SignedWordElement(136), + ElementToChannelConverter.SCALE_FACTOR_2)), + new FC6WriteRegisterTask(137, // + m(Sinexcel.ChannelId.SET_OFF_GRID_VOLTAGE, new SignedWordElement(137), ElementToChannelConverter.SCALE_FACTOR_MINUS_1)), - new FC16WriteRegistersTask(0x08A, - m(OffGridBatteryInverter.ChannelId.OFF_GRID_FREQUENCY, new SignedWordElement(0x08A), // + new FC6WriteRegisterTask(138, // + m(Sinexcel.ChannelId.SET_OFF_GRID_FREQUENCY, new SignedWordElement(138), ElementToChannelConverter.SCALE_FACTOR_MINUS_1)), - - new FC6WriteRegisterTask(0x147, m(Sinexcel.ChannelId.EMS_TIMEOUT, new UnsignedWordElement(0x147))), - new FC6WriteRegisterTask(0x149, m(Sinexcel.ChannelId.BMS_TIMEOUT, new UnsignedWordElement(0x149))), - - new FC3ReadRegistersTask(0x147, Priority.ONCE, - m(Sinexcel.ChannelId.EMS_TIMEOUT, new UnsignedWordElement(0x147)), // - new DummyRegisterElement(0x148), - m(Sinexcel.ChannelId.BMS_TIMEOUT, new UnsignedWordElement(0x149))), - - new FC3ReadRegistersTask(0x220, Priority.ONCE, - m(Sinexcel.ChannelId.VERSION, new StringWordElement(0x220, 8))), // - - new FC3ReadRegistersTask(0x248, Priority.HIGH, // - m(SymmetricEss.ChannelId.ACTIVE_POWER, new SignedWordElement(0x248), // - new ElementToChannelConverterChain( - ElementToChannelConverter.SCALE_FACTOR_1, IGNORE_LESS_THAN_100)), - new DummyRegisterElement(0x249), - m(Sinexcel.ChannelId.FREQUENCY, new SignedWordElement(0x24A), - ElementToChannelConverter.SCALE_FACTOR_MINUS_2), - new DummyRegisterElement(0x24B, 0x24D), // - m(SymmetricEss.ChannelId.REACTIVE_POWER, new SignedWordElement(0x24E)), // - new DummyRegisterElement(0x24F, 0x254), // - m(Sinexcel.ChannelId.DC_CURRENT, new SignedWordElement(0x255), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1), - new DummyRegisterElement(0x256), // - m(Sinexcel.ChannelId.DC_VOLTAGE, new UnsignedWordElement(0x257), + new FC6WriteRegisterTask(300, // + m(Sinexcel.ChannelId.IP_ADDRESS_BLOCK_1, new UnsignedWordElement(300))), + new FC6WriteRegisterTask(301, // + m(Sinexcel.ChannelId.IP_ADDRESS_BLOCK_2, new UnsignedWordElement(301))), + new FC6WriteRegisterTask(302, // + m(Sinexcel.ChannelId.IP_ADDRESS_BLOCK_3, new UnsignedWordElement(302))), + new FC6WriteRegisterTask(303, // + m(Sinexcel.ChannelId.IP_ADDRESS_BLOCK_4, new UnsignedWordElement(303))), + new FC6WriteRegisterTask(304, // + m(Sinexcel.ChannelId.NETMASK_BLOCK_1, new UnsignedWordElement(304))), + new FC6WriteRegisterTask(305, // + m(Sinexcel.ChannelId.NETMASK_BLOCK_2, new UnsignedWordElement(305))), + new FC6WriteRegisterTask(306, // + m(Sinexcel.ChannelId.NETMASK_BLOCK_3, new UnsignedWordElement(306))), + new FC6WriteRegisterTask(307, // + m(Sinexcel.ChannelId.NETMASK_BLOCK_4, new UnsignedWordElement(307))), + new FC6WriteRegisterTask(308, // + m(Sinexcel.ChannelId.GATEWAY_IP_BLOCK_1, new UnsignedWordElement(308))), + new FC6WriteRegisterTask(309, // + m(Sinexcel.ChannelId.GATEWAY_IP_BLOCK_2, new UnsignedWordElement(309))), + new FC6WriteRegisterTask(310, // + m(Sinexcel.ChannelId.GATEWAY_IP_BLOCK_3, new UnsignedWordElement(310))), + new FC6WriteRegisterTask(311, // + m(Sinexcel.ChannelId.GATEWAY_IP_BLOCK_4, new UnsignedWordElement(311))), + new FC6WriteRegisterTask(312, // + m(Sinexcel.ChannelId.MAC, new UnsignedWordElement(312))), + new FC6WriteRegisterTask(316, // + m(Sinexcel.ChannelId.MODBUS_UNIT_ID, new UnsignedWordElement(316))), + new FC6WriteRegisterTask(320, // + m(Sinexcel.ChannelId.BAUDRATE, new UnsignedWordElement(320))), + new FC6WriteRegisterTask(325, // + m(Sinexcel.ChannelId.INTERFACE_TYPE, new UnsignedWordElement(325))), + new FC6WriteRegisterTask(326, // + m(Sinexcel.ChannelId.COMMUNICATION_PROTOCOL_SELECTION, new UnsignedWordElement(326))), + new FC6WriteRegisterTask(327, // + m(Sinexcel.ChannelId.EMS_TIMEOUT, new UnsignedWordElement(327))), + new FC6WriteRegisterTask(328, // + m(Sinexcel.ChannelId.EPO_ENABLE, new UnsignedWordElement(328))), + new FC6WriteRegisterTask(329, // + m(Sinexcel.ChannelId.BMS_TIMEOUT, new UnsignedWordElement(329))), + new FC6WriteRegisterTask(330, // + m(Sinexcel.ChannelId.BMS_PROTOCOL_SELECTION, new UnsignedWordElement(330))), + new FC6WriteRegisterTask(331, // + m(Sinexcel.ChannelId.SET_GRID_MODE, new UnsignedWordElement(331))), + new FC6WriteRegisterTask(332, // + m(Sinexcel.ChannelId.BUZZER_ENABLE, new UnsignedWordElement(332))), + new FC6WriteRegisterTask(333, // + m(Sinexcel.ChannelId.RESTORE_FACTORY_SETTING, new UnsignedWordElement(333))), + new FC6WriteRegisterTask(650, // + m(Sinexcel.ChannelId.START_INVERTER, new UnsignedWordElement(650))), + new FC6WriteRegisterTask(651, // + m(Sinexcel.ChannelId.STOP_INVERTER, new UnsignedWordElement(651))), + new FC6WriteRegisterTask(652, // + m(Sinexcel.ChannelId.CLEAR_FAILURE_COMMAND, new UnsignedWordElement(652))), + new FC6WriteRegisterTask(653, // + m(Sinexcel.ChannelId.SET_ON_GRID_MODE, new UnsignedWordElement(653))), + new FC6WriteRegisterTask(654, // + m(Sinexcel.ChannelId.SET_OFF_GRID_MODE, new UnsignedWordElement(654))), + new FC6WriteRegisterTask(655, // + m(Sinexcel.ChannelId.SET_STANDBY_COMMAND, new UnsignedWordElement(655))), + new FC6WriteRegisterTask(656, // + m(Sinexcel.ChannelId.SET_SOFT_START, new UnsignedWordElement(656))), + new FC6WriteRegisterTask(657, // + m(Sinexcel.ChannelId.RESET_INSTRUCTION, new UnsignedWordElement(657))), + new FC6WriteRegisterTask(658, // + m(Sinexcel.ChannelId.GRID_STOP, new UnsignedWordElement(658))), + new FC6WriteRegisterTask(752, // + m(Sinexcel.ChannelId.SWITCHING_DEVICE_ACCESS_SETTING, new UnsignedWordElement(752))), + new FC6WriteRegisterTask(755, // + m(Sinexcel.ChannelId.CPU_TYPE, new UnsignedWordElement(755))), + new FC6WriteRegisterTask(756, // + m(Sinexcel.ChannelId.OFF_GRID_AND_PARALLEL_ENABLE, new UnsignedWordElement(756))), + new FC6WriteRegisterTask(757, // + m(Sinexcel.ChannelId.SET_DC_SOFT_START_EXTERNAL_CONTROL, new UnsignedWordElement(757))), + new FC6WriteRegisterTask(768, // + m(Sinexcel.ChannelId.GRID_OVER_VOLTAGE_PROTECTION_AMPLITUDE, new SignedWordElement(768))), + new FC6WriteRegisterTask(769, // + m(Sinexcel.ChannelId.AC_OVER_VOLTAGE_TRIP_TIME_1, new SignedWordElement(769))), + new FC6WriteRegisterTask(770, // + m(Sinexcel.ChannelId.AC_OVER_VOLTAGE_TRIP_LEVEL_2, new SignedWordElement(770))), + new FC6WriteRegisterTask(771, // + m(Sinexcel.ChannelId.AC_OVER_VOLTAGE_TRIP_TIME_2, new SignedWordElement(771))), + new FC6WriteRegisterTask(772, // + m(Sinexcel.ChannelId.AC_UNDER_VOLTAGE_TRIP_LEVEL_1, new SignedWordElement(772))), + new FC6WriteRegisterTask(773, // + m(Sinexcel.ChannelId.AC_UNDER_VOLTAGE_TRIP_TIME_1, new SignedWordElement(773))), + new FC6WriteRegisterTask(774, // + m(Sinexcel.ChannelId.AC_UNDER_VOLTAGE_TRIP_LEVEL_2, new SignedWordElement(774))), + new FC6WriteRegisterTask(775, // + m(Sinexcel.ChannelId.AC_UNDER_VOLTAGE_TRIP_TIME_2, new SignedWordElement(775))), + new FC6WriteRegisterTask(776, // + m(Sinexcel.ChannelId.AC_UNDER_VOLTAGE_TRIP_LEVEL_3, new SignedWordElement(776))), + new FC6WriteRegisterTask(777, // + m(Sinexcel.ChannelId.AC_UNDER_VOLTAGE_TRIP_TIME_3, new SignedWordElement(777))), + new FC6WriteRegisterTask(778, // + m(Sinexcel.ChannelId.AC_OVER_FREQUENCY_TRIP_LEVEL_1, new SignedWordElement(778))), + new FC6WriteRegisterTask(779, // + m(Sinexcel.ChannelId.AC_OVER_FREQUENCY_TRIP_TIME_1, new SignedWordElement(779))), + new FC6WriteRegisterTask(780, // + m(Sinexcel.ChannelId.AC_OVER_FREQUENCY_TRIP_LEVEL_2, new SignedWordElement(780))), + new FC6WriteRegisterTask(781, // + m(Sinexcel.ChannelId.AC_OVER_FREQUENCY_TRIP_TIME_2, new SignedWordElement(781))), + new FC6WriteRegisterTask(782, // + m(Sinexcel.ChannelId.AC_UNDER_FREQUENCY_TRIP_LEVEL_1, new SignedWordElement(782))), + new FC6WriteRegisterTask(783, // + m(Sinexcel.ChannelId.AC_UNDER_FREQUENCY_TRIP_TIME_1, new SignedWordElement(783))), + new FC6WriteRegisterTask(784, // + m(Sinexcel.ChannelId.AC_UNDER_FREQUENCY_TRIP_LEVEL_2, new SignedWordElement(784))), + new FC6WriteRegisterTask(785, // + m(Sinexcel.ChannelId.AC_UNDER_FREQUENCY_TRIP_TIME_2, new SignedWordElement(785))), + new FC6WriteRegisterTask(786, // + m(Sinexcel.ChannelId.RECONNECT_TIME, new SignedWordElement(786))), + new FC6WriteRegisterTask(790, // + m(Sinexcel.ChannelId.ANTI_ISLANDING, new UnsignedWordElement(790))), + new FC6WriteRegisterTask(791, // + m(Sinexcel.ChannelId.FREQUENCY_VOLTAGE_RIDE_THROUGH, new UnsignedWordElement(791))), + new FC6WriteRegisterTask(792, // + m(Sinexcel.ChannelId.REACTIVE_POWER_CONTROL_MODE, new UnsignedWordElement(792))), + new FC6WriteRegisterTask(793, // + m(Sinexcel.ChannelId.POWER_RISING_MODE, new UnsignedWordElement(793))), + new FC6WriteRegisterTask(794, // + m(Sinexcel.ChannelId.ACTIVE_POWER_CONTROL_MODE, new UnsignedWordElement(794))), + new FC6WriteRegisterTask(795, // + m(Sinexcel.ChannelId.GRID_VOLTAGE_ASYMMETRIC_DETECTON, new UnsignedWordElement(795))), + new FC6WriteRegisterTask(796, // + m(Sinexcel.ChannelId.CONTINUOUS_OVERVOLTAGE_DETECTION, new UnsignedWordElement(796))), + new FC6WriteRegisterTask(797, // + m(Sinexcel.ChannelId.GRID_EXISTENCE_DETECTION_ON, new UnsignedWordElement(797))), + new FC6WriteRegisterTask(798, // + m(Sinexcel.ChannelId.NEUTRAL_FLOATING_DETECTION, new UnsignedWordElement(798))), + new FC6WriteRegisterTask(799, // + m(Sinexcel.ChannelId.OFF_GRID_BLACKSTART_MODE, new UnsignedWordElement(799))), + new FC6WriteRegisterTask(800, // + m(Sinexcel.ChannelId.GRID_CODE_SELCETION, new UnsignedWordElement(800))), + new FC6WriteRegisterTask(801, // + m(Sinexcel.ChannelId.GRID_CONNECTED_ACTIVE_CAPACITY_LIMITATION_FUNCTION, + new UnsignedWordElement(801))), + new FC6WriteRegisterTask(802, // + m(Sinexcel.ChannelId.GRID_ACTIVE_POWER_CAPACITY_SETTING, new UnsignedWordElement(802))), + new FC6WriteRegisterTask(803, // + m(Sinexcel.ChannelId.SINGLE_PHASE_MODE_SELECTION, new UnsignedWordElement(803))), + new FC6WriteRegisterTask(804, // + m(Sinexcel.ChannelId.OVER_VOLTAGE_DROP_ACTIVE, new UnsignedWordElement(804))), + new FC6WriteRegisterTask(805, // + m(Sinexcel.ChannelId.START_UP_MODE, new UnsignedWordElement(805))), + new FC6WriteRegisterTask(807, // + m(Sinexcel.ChannelId.LOCAL_ID_SETTING, new SignedWordElement(807))), + new FC6WriteRegisterTask(808, // + m(Sinexcel.ChannelId.FLOAT_CHARGE_VOLTAGE, new SignedWordElement(808), ElementToChannelConverter.SCALE_FACTOR_MINUS_1)), - // TODO merge tasks - new FC3ReadRegistersTask(0x260, Priority.HIGH, - m(Sinexcel.ChannelId.SINEXCEL_STATE, new UnsignedWordElement(0x260))), // - - new FC3ReadRegistersTask(0x262, Priority.LOW, // - m(new BitsWordElement(0x262, this) // - .bit(0, Sinexcel.ChannelId.STATE_0) // - .bit(1, Sinexcel.ChannelId.STATE_1) // - .bit(2, Sinexcel.ChannelId.STATE_2) // - .bit(3, Sinexcel.ChannelId.STATE_3) // - .bit(4, Sinexcel.ChannelId.STATE_4) // - .bit(5, Sinexcel.ChannelId.STATE_5) // - .bit(6, Sinexcel.ChannelId.AUTOMATIC_STANDBY_MODE) // - .bit(7, Sinexcel.ChannelId.STATE_7) // - .bit(8, Sinexcel.ChannelId.STATE_8) // - .bit(9, Sinexcel.ChannelId.STATE_9) // - .bit(10, Sinexcel.ChannelId.STATE_10) // - .bit(11, Sinexcel.ChannelId.STATE_11) // - .bit(12, Sinexcel.ChannelId.STATE_12) // - .bit(13, Sinexcel.ChannelId.STATE_13) // - .bit(14, Sinexcel.ChannelId.STATE_14) // - .bit(15, Sinexcel.ChannelId.STATE_15))), - - // Required in high priority during startup/stop phase or on change of target - // grid-mode - new FC3ReadRegistersTask(0x28A, Priority.HIGH, // - m(Sinexcel.ChannelId.SET_START_COMMAND, new UnsignedWordElement(0x28A)), - m(Sinexcel.ChannelId.SET_STOP_COMMAND, new UnsignedWordElement(0x28B)), - m(Sinexcel.ChannelId.CLEAR_FAILURE_CMD, new UnsignedWordElement(0x28C)), - m(Sinexcel.ChannelId.SET_ON_GRID_MODE, new UnsignedWordElement(0x28D)), - m(Sinexcel.ChannelId.SET_OFF_GRID_MODE, new UnsignedWordElement(0x28E)), - new DummyRegisterElement(0x28F), - m(Sinexcel.ChannelId.SET_INTERN_DC_RELAY, new UnsignedWordElement(0x290))), - - new FC6WriteRegisterTask(0x28A, // - m(Sinexcel.ChannelId.SET_START_COMMAND, new UnsignedWordElement(0x28A))), - new FC6WriteRegisterTask(0x28B, // - m(Sinexcel.ChannelId.SET_STOP_COMMAND, new UnsignedWordElement(0x28B))), - new FC6WriteRegisterTask(0x28C, // - m(Sinexcel.ChannelId.CLEAR_FAILURE_CMD, new UnsignedWordElement(0x28C))), - new FC6WriteRegisterTask(0x28D, // - m(Sinexcel.ChannelId.SET_ON_GRID_MODE, new UnsignedWordElement(0x28D))), - new FC6WriteRegisterTask(0x28E, // - m(Sinexcel.ChannelId.SET_OFF_GRID_MODE, new UnsignedWordElement(0x28E))), - new FC6WriteRegisterTask(0x290, // FIXME: not documented! - m(Sinexcel.ChannelId.SET_INTERN_DC_RELAY, new UnsignedWordElement(0x290))), - - new FC3ReadRegistersTask(0x316, Priority.LOW, // - m(Sinexcel.ChannelId.ANTI_ISLANDING, new UnsignedWordElement(0x316))), - new FC6WriteRegisterTask(0x316, m(Sinexcel.ChannelId.ANTI_ISLANDING, new UnsignedWordElement(0x316))), - - new FC3ReadRegistersTask(0x319, Priority.LOW, // - m(Sinexcel.ChannelId.POWER_CHANGE_MODE, new UnsignedWordElement(0x319))), - new FC6WriteRegisterTask(0x319, - m(Sinexcel.ChannelId.POWER_CHANGE_MODE, new UnsignedWordElement(0x319))), - - new FC3ReadRegistersTask(0x31D, Priority.LOW, // - m(Sinexcel.ChannelId.GRID_EXISTENCE_DETECTION_ON, new UnsignedWordElement(0x31D))), - new FC6WriteRegisterTask(0x31D, - m(Sinexcel.ChannelId.GRID_EXISTENCE_DETECTION_ON, new UnsignedWordElement(0x31D))), - - new FC3ReadRegistersTask(0x328, Priority.LOW, // - m(Sinexcel.ChannelId.FLOAT_CHARGE_VOLTAGE, new UnsignedWordElement(0x328), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1), - m(Sinexcel.ChannelId.TOPPING_CHARGE_VOLTAGE, new UnsignedWordElement(0x329), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1), - new DummyRegisterElement(0x32A), - m(Sinexcel.ChannelId.CHARGE_MAX_A, new UnsignedWordElement(0x32B), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // - m(Sinexcel.ChannelId.DISCHARGE_MAX_A, new UnsignedWordElement(0x32C), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1), - m(Sinexcel.ChannelId.DISCHARGE_MIN_V, new UnsignedWordElement(0x32D), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1), - m(Sinexcel.ChannelId.CHARGE_MAX_V, new UnsignedWordElement(0x32E), + new FC6WriteRegisterTask(809, // + m(Sinexcel.ChannelId.TOPPING_CHARGE_VOLTAGE, new SignedWordElement(809), ElementToChannelConverter.SCALE_FACTOR_MINUS_1)), - - new FC6WriteRegisterTask(0x328, - m(Sinexcel.ChannelId.FLOAT_CHARGE_VOLTAGE, new UnsignedWordElement(0x328), + new FC6WriteRegisterTask(810, // + m(Sinexcel.ChannelId.CURRENT_FROM_TOPPING_CHARGING_TO_FLOAT_CHARGING, + new SignedWordElement(810), ElementToChannelConverter.SCALE_FACTOR_MINUS_1)), + new FC6WriteRegisterTask(811, // + m(Sinexcel.ChannelId.CHARGE_MAX_CURRENT, new SignedWordElement(811), ElementToChannelConverter.SCALE_FACTOR_MINUS_1)), - - new FC6WriteRegisterTask(0x329, - m(Sinexcel.ChannelId.TOPPING_CHARGE_VOLTAGE, new UnsignedWordElement(0x329), + new FC6WriteRegisterTask(812, // + m(Sinexcel.ChannelId.DISCHARGE_MAX_CURRENT, new SignedWordElement(812), ElementToChannelConverter.SCALE_FACTOR_MINUS_1)), - - new FC6WriteRegisterTask(0x32B, // - m(Sinexcel.ChannelId.CHARGE_MAX_A, new UnsignedWordElement(0x32B), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1)), // - - new FC6WriteRegisterTask(0x32C, // - m(Sinexcel.ChannelId.DISCHARGE_MAX_A, new UnsignedWordElement(0x32C), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1)), // - - new FC6WriteRegisterTask(0x32D, - m(Sinexcel.ChannelId.DISCHARGE_MIN_V, new UnsignedWordElement(0x32D), + new FC6WriteRegisterTask(813, // + m(Sinexcel.ChannelId.DISCHARGE_MIN_VOLTAGE, new SignedWordElement(813), ElementToChannelConverter.SCALE_FACTOR_MINUS_1)), - new FC6WriteRegisterTask(0x32E, m(Sinexcel.ChannelId.CHARGE_MAX_V, new UnsignedWordElement(0x32E), - ElementToChannelConverter.SCALE_FACTOR_MINUS_1))); + new FC6WriteRegisterTask(814, // + m(Sinexcel.ChannelId.CHARGE_MAX_VOLTAGE, new SignedWordElement(814), + ElementToChannelConverter.SCALE_FACTOR_MINUS_1)), + new FC6WriteRegisterTask(815, // + m(Sinexcel.ChannelId.BATTERY_VOLTAGE_PROTECTION_LIMIT, new UnsignedWordElement(815), + ElementToChannelConverter.SCALE_FACTOR_MINUS_1)), + new FC6WriteRegisterTask(825, // + m(Sinexcel.ChannelId.LEAKAGE_CURRENT_DC_COMPONENT_DETECTOR, new UnsignedWordElement(825))), + // TODO Check scale factors, channel names and so on... + new FC6WriteRegisterTask(846, // + m(Sinexcel.ChannelId.RESUME_AND_LIMIT_FREQUENCY, new SignedWordElement(846))), + new FC6WriteRegisterTask(847, // + m(Sinexcel.ChannelId.RESTORE_LOWER_FREQUENCY_OF_GRID_CONNECTION, new SignedWordElement(847))), + new FC6WriteRegisterTask(848, // + m(Sinexcel.ChannelId.VOLTAGE_REACTIVE_REFERENCE, new SignedWordElement(848))), + new FC6WriteRegisterTask(849, // + m(Sinexcel.ChannelId.VOLTAGE_AND_REACTIVE_POWER_ADJUSTMENT_POINT_V1, + new SignedWordElement(849))), + new FC6WriteRegisterTask(850, // + m(Sinexcel.ChannelId.VOLTAGE_AND_REACTIVE_POWER_ADJUSTMENT_POINT_V2, + new SignedWordElement(850))), + new FC6WriteRegisterTask(851, // + m(Sinexcel.ChannelId.VOLTAGE_AND_REACTIVE_POWER_ADJUSTMENT_POINT_V3, + new SignedWordElement(851))), + new FC6WriteRegisterTask(852, // + m(Sinexcel.ChannelId.VOLTAGE_AND_REACTIVE_POWER_ADJUSTMENT_POINT_V4, + new SignedWordElement(852))), + new FC6WriteRegisterTask(853, // + m(Sinexcel.ChannelId.MAX_CAPACITIVE_REACTIVE_REGULATION_Q1, new SignedWordElement(853))), + new FC6WriteRegisterTask(854, // + m(Sinexcel.ChannelId.INITIAL_CAPACITIVE_REACTIVE_REGULATION_Q2, new SignedWordElement(854))), + new FC6WriteRegisterTask(855, // + m(Sinexcel.ChannelId.INITIAL_INDUCTIVE_REACTIVE_REGULATION_Q3, new SignedWordElement(855))), + new FC6WriteRegisterTask(856, // + m(Sinexcel.ChannelId.MAX_INDUCTIVE_REACTIVE_REGULATION_Q4, new SignedWordElement(856))), + new FC6WriteRegisterTask(857, // + m(Sinexcel.ChannelId.VOLTAGE_AND_REACTIVE_RESPONSE_TIME, new SignedWordElement(857))), + new FC6WriteRegisterTask(858, // + m(Sinexcel.ChannelId.REACTIVE_FIRST_ORDER_RESPONSE_TIME, new SignedWordElement(858))), + new FC6WriteRegisterTask(862, // + m(Sinexcel.ChannelId.INITIAL_VOLTAGE_V_START, new SignedWordElement(862))), + new FC6WriteRegisterTask(863, // + m(Sinexcel.ChannelId.END_VOLTAGE_V_STOP, new SignedWordElement(863))), + new FC6WriteRegisterTask(864, // + m(Sinexcel.ChannelId.INITIAL_POWER_P_START, new SignedWordElement(864))), + new FC6WriteRegisterTask(865, // + m(Sinexcel.ChannelId.END_POWER_P_STOP, new SignedWordElement(865))), + new FC6WriteRegisterTask(866, // + m(Sinexcel.ChannelId.RETURN_TO_SERVICE_DELAY, new SignedWordElement(866))), + new FC6WriteRegisterTask(867, // + m(Sinexcel.ChannelId.VOLT_WATT_RESPONSE_TIME, new SignedWordElement(867))), + new FC6WriteRegisterTask(868, // + m(Sinexcel.ChannelId.START_OF_FREQUENY_DROP, new SignedWordElement(868))), + new FC6WriteRegisterTask(869, // + m(Sinexcel.ChannelId.SLOPE_OF_FREQUENCY_DROP, new SignedWordElement(869))), + new FC6WriteRegisterTask(870, // + m(Sinexcel.ChannelId.FREQUENCY_WATT_F_STOP_DISCHARGE, new SignedWordElement(870))), + new FC6WriteRegisterTask(871, // + m(Sinexcel.ChannelId.FREQUENCY_WATT_F_STOP_CHARGE, new SignedWordElement(871))), + new FC6WriteRegisterTask(872, // + m(Sinexcel.ChannelId.VOLT_WATT_V_START_CHARGE, new SignedWordElement(872))), + new FC6WriteRegisterTask(876, // + m(Sinexcel.ChannelId.SOFT_START_RAMP_RATE, new SignedWordElement(876))), + new FC6WriteRegisterTask(877, // + m(Sinexcel.ChannelId.POWER_RAMP_RATE, new SignedWordElement(877))), + new FC6WriteRegisterTask(878, // + m(Sinexcel.ChannelId.POWER_FACTOR_SETTING, new SignedWordElement(878))), + new FC6WriteRegisterTask(879, // + m(Sinexcel.ChannelId.POWER_FACTOR_P1, new SignedWordElement(879))), + new FC6WriteRegisterTask(880, // + m(Sinexcel.ChannelId.POWER_FACTOR_P2, new SignedWordElement(880))), + new FC6WriteRegisterTask(881, // + m(Sinexcel.ChannelId.POWER_FACTOR_P3, new SignedWordElement(881))), + new FC6WriteRegisterTask(882, // + m(Sinexcel.ChannelId.POWER_FACTOR_P4, new SignedWordElement(882))), + new FC6WriteRegisterTask(883, // + m(Sinexcel.ChannelId.POWER_FACTOR_CURVE_MODE_P1, new SignedWordElement(883))), + new FC6WriteRegisterTask(884, // + m(Sinexcel.ChannelId.POWER_FACTOR_CURVE_MODE_P2, new SignedWordElement(884))), + new FC6WriteRegisterTask(885, // + m(Sinexcel.ChannelId.POWER_FACTOR_CURVE_MODE_P3, new SignedWordElement(885))), + new FC6WriteRegisterTask(886, // + m(Sinexcel.ChannelId.POWER_FACTOR_CURVE_MODE_P4, new SignedWordElement(886))), + new FC6WriteRegisterTask(887, // + m(Sinexcel.ChannelId.CONTINUOS_OVER_VOLTAGE_TRIP_THRESHOLD, new SignedWordElement(887))), + new FC6WriteRegisterTask(888, // + m(Sinexcel.ChannelId.FREQUENCY_VARIATION_RATE_TRIP_THRESHOLD, new SignedWordElement(888))), + new FC6WriteRegisterTask(889, // + m(Sinexcel.ChannelId.PHASE_ANGLE_ABRUPT_TRIP_THRESHOLD, new SignedWordElement(889))), + new FC6WriteRegisterTask(890, // + m(Sinexcel.ChannelId.GRID_RECONNECTION_VOLTAGE_UPPER_LIMIT, new SignedWordElement(890))), + new FC6WriteRegisterTask(891, // + m(Sinexcel.ChannelId.GRID_RECONNECTION_VOLTAGE_LOWER_LIMIT, new SignedWordElement(891))), + new FC6WriteRegisterTask(892, // + m(Sinexcel.ChannelId.GRID_RECONNECTION_FREQUENCY_UPPER_LIMIT, new SignedWordElement(892))), + new FC6WriteRegisterTask(893, // + m(Sinexcel.ChannelId.GRID_RECONNECTION_FREQUENCY_LOWER_LIMIT, new SignedWordElement(893))), + new FC6WriteRegisterTask(894, // + m(Sinexcel.ChannelId.LOW_FREQUENCY_RAMP_RATE, new SignedWordElement(894))), + new FC6WriteRegisterTask(934, // + m(Sinexcel.ChannelId.METER_ACTIVE_POWER, new SignedWordElement(934))), + new FC6WriteRegisterTask(948, // + m(Sinexcel.ChannelId.GRID_VOLTAGE_CALIBRATION_L1, new UnsignedWordElement(948), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(949, // + m(Sinexcel.ChannelId.GRID_VOLTAGE_CALIBRATION_L2, new UnsignedWordElement(949), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(950, // + m(Sinexcel.ChannelId.GRID_VOLTAGE_CALIBRATION_L3, new UnsignedWordElement(950), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(951, // + m(Sinexcel.ChannelId.INVERTER_VOLTAGE_CALIBRATION_L1, new UnsignedWordElement(951), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(952, // + m(Sinexcel.ChannelId.INVERTER_VOLTAGE_CALIBRATION_L2, new UnsignedWordElement(952), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(953, // + m(Sinexcel.ChannelId.INVERTER_VOLTAGE_CALIBRATION_L3, new UnsignedWordElement(953), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(954, // + m(Sinexcel.ChannelId.INDUCTOR_CURRENT_CALIBRATION_L1_PARAMETERS_1, new UnsignedWordElement(954), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(955, // + m(Sinexcel.ChannelId.INDUCTOR_CURRENT_CALIBRATION_L2_PARAMETERS_1, new UnsignedWordElement(955), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(956, // + m(Sinexcel.ChannelId.INDUCTOR_CURRENT_CALIBRATION_L3_PARAMETERS_1, new UnsignedWordElement(956), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(957, // + m(Sinexcel.ChannelId.INDUCTOR_CURRENT_CALIBRATION_L1_PARAMETERS_2, new UnsignedWordElement(957), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(958, // + m(Sinexcel.ChannelId.INDUCTOR_CURRENT_CALIBRATION_L2_PARAMETERS_2, new UnsignedWordElement(958), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(959, // + m(Sinexcel.ChannelId.INDUCTOR_CURRENT_CALIBRATION_L3_PARAMETERS_2, new UnsignedWordElement(959), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(960, // + m(Sinexcel.ChannelId.OUTPUT_CURRENT_CALIBRATION_L1, new UnsignedWordElement(960), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(961, // + m(Sinexcel.ChannelId.OUTPUT_CURRENT_CALIBRATION_L2, new UnsignedWordElement(961), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(962, // + m(Sinexcel.ChannelId.OUTPUT_CURRENT_CALIBRATION_L3, new UnsignedWordElement(962), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(963, // + m(Sinexcel.ChannelId.POSITIVE_BUS_VOLTAGE_CALIBRATION, new UnsignedWordElement(963), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(964, // + m(Sinexcel.ChannelId.NEGATIVE_BUS_VOLTAGE_CALIBRATION, new UnsignedWordElement(964), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(965, // + m(Sinexcel.ChannelId.DC_VOLTAGE_CALIBRATION, new UnsignedWordElement(965), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(966, // + m(Sinexcel.ChannelId.DC_CURRENT_CALIBRATION, new UnsignedWordElement(966), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(967, // + m(Sinexcel.ChannelId.DC_INDUCTOR_CURRENT_CALIBRATION, new UnsignedWordElement(967), + ElementToChannelConverter.SCALE_FACTOR_MINUS_3)), + new FC6WriteRegisterTask(4001, // + m(Sinexcel.ChannelId.TIME_SETTING, new SignedWordElement(4001))), + new FC6WriteRegisterTask(4007, // + m(Sinexcel.ChannelId.PASSWORD, new SignedWordElement(4007))) + + ); // + } /** @@ -569,7 +1115,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { * @throws OpenemsNamedException on error */ public void softStart(boolean switchOn) throws OpenemsNamedException { - IntegerWriteChannel setDcRelay = this.channel(Sinexcel.ChannelId.SET_INTERN_DC_RELAY); - setDcRelay.setNextWriteValue(switchOn ? 1 : 0); + BooleanWriteChannel setSoftStart = this.channel(Sinexcel.ChannelId.SET_SOFT_START); + setSoftStart.setNextWriteValue(switchOn ? true : false); } } diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/ActivePowerControlMode.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/ActivePowerControlMode.java new file mode 100644 index 00000000000..872d850f6d6 --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/ActivePowerControlMode.java @@ -0,0 +1,34 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum ActivePowerControlMode implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + CONTANT_ACTIVE_POWER(0, "Constant Active Power"), // + VOLT_WATT_ENABLED(1, "Volt watt enabled"), // + CONSTANT_PF(0, "Constant power factor"), // + WATT_PF_ENABLED(0, "Watt power factor enabled");// + + private final int value; + private final String name; + + private ActivePowerControlMode(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/FalseTrue.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/Baudrate.java similarity index 66% rename from io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/FalseTrue.java rename to io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/Baudrate.java index ac9a9f94d08..18393493d2d 100644 --- a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/FalseTrue.java +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/Baudrate.java @@ -2,18 +2,16 @@ import io.openems.common.types.OptionsEnum; -/** - * This enum holds the common Sinexcel definition for 0 = false and 1 = true. - */ -public enum FalseTrue implements OptionsEnum { +//default 0 +public enum Baudrate implements OptionsEnum { UNDEFINED(-1, "Undefined"), // - FALSE(0, "False"), // - TRUE(1, "True"); // + B_19200(0, "19200"), // + B_9600(1, "9600"); // private final int value; private final String name; - private FalseTrue(int value, String name) { + private Baudrate(int value, String name) { this.value = value; this.name = name; } diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/BlackStartMode.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/BlackStartMode.java new file mode 100644 index 00000000000..949222fc711 --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/BlackStartMode.java @@ -0,0 +1,32 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum BlackStartMode implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + NORMAL_STARTUP(0, "Normal Start Up"), // + BLACK_STARTUP(1, "Black Start Up");// + + private final int value; + private final String name; + + private BlackStartMode(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/CpuType.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/CpuType.java new file mode 100644 index 00000000000..68d4be0c5ec --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/CpuType.java @@ -0,0 +1,32 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum CpuType implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + SINGLE_CPU(0, "Single Cpu"), // + DOUBLE_CPU(1, "Double Cpu");// + + private final int value; + private final String name; + + private CpuType(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/DcVoltageLevel.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/DcVoltageLevel.java new file mode 100644 index 00000000000..0eca1f41c91 --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/DcVoltageLevel.java @@ -0,0 +1,32 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum DcVoltageLevel implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + V_750(0, "750 V"), // + V_830(1, "830 V");// + + private final int value; + private final String name; + + private DcVoltageLevel(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/EnableDisable.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/EnableDisable.java new file mode 100644 index 00000000000..3001bf7b9d8 --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/EnableDisable.java @@ -0,0 +1,32 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum EnableDisable implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLE(0, "Disable"), // + ENABLE(1, "Enable"); // + + private final int value; + private final String name; + + private EnableDisable(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } +} \ No newline at end of file diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/Epo.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/Epo.java new file mode 100644 index 00000000000..cd6fc0d4484 --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/Epo.java @@ -0,0 +1,33 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum Epo implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + INVALID(0, "Modbus"), // + EPO(1, "Sunspec"), // + DRMO(2, "Sunspec");// + + private final int value; + private final String name; + + private Epo(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/FrequencyVariationRate.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/FrequencyVariationRate.java new file mode 100644 index 00000000000..b2c2aa6ea71 --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/FrequencyVariationRate.java @@ -0,0 +1,33 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum FrequencyVariationRate implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "Disabled"), // + RATE_0125(0, "Rate limit 0.125 Hz/s"), // + RATE_02(0, "Rate limit 0.2 Hz/s");// + + private final int value; + private final String name; + + private FrequencyVariationRate(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/GridCodeSelection.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/GridCodeSelection.java new file mode 100644 index 00000000000..57d0d301955 --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/GridCodeSelection.java @@ -0,0 +1,37 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum GridCodeSelection implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + SA1741(0, "SA1741"), // + VDE(1, "VDE"), // + AUSTRALIAN(2, "Australian"), // + G99(3, "G99"), // + HAWAIIAN(4, "Hawaiian"), // + EN50549(5, "EN50549"), // + AUSTRIA_TYPEA(6, "Austria Type A");// + + private final int value; + private final String name; + + private GridCodeSelection(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/InterfaceType.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/InterfaceType.java new file mode 100644 index 00000000000..17c6477d07e --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/InterfaceType.java @@ -0,0 +1,32 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum InterfaceType implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + RS_485(0, "RS-485"), // + ETHERNET(1, "Ethernet"); // + + private final int value; + private final String name; + + private InterfaceType(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/InverterWiringTopology.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/InverterWiringTopology.java new file mode 100644 index 00000000000..eaf9aaa9097 --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/InverterWiringTopology.java @@ -0,0 +1,32 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum InverterWiringTopology implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + THREE_PHASE_FOUR_WIRE(0, "3P4W"), // + THREE_PHASE_THREE_WIRE(1, "3P3W or 3P3W+N"); // + + private final int value; + private final String name; + + private InverterWiringTopology(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/ModulePowerLevel.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/ModulePowerLevel.java new file mode 100644 index 00000000000..9d1ef82b8a2 --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/ModulePowerLevel.java @@ -0,0 +1,34 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum ModulePowerLevel implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + TEN_KW(0, " 10 kW"), // + TWENTY_KW(1, "20 kW"), // + THIRTY_KW(2, "30 kW"), // + TWENTY_NINE_KW(3, "29 kW"); // + + private final int value; + private final String name; + + private ModulePowerLevel(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/OutputFrequencyLevel.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/OutputFrequencyLevel.java new file mode 100644 index 00000000000..8a2b1827a8e --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/OutputFrequencyLevel.java @@ -0,0 +1,32 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum OutputFrequencyLevel implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + Hz_50(0, "50 Hz"), // + Hz_60(1, "60 Hz");// + + private final int value; + private final String name; + + private OutputFrequencyLevel(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/OutputVoltageLevel.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/OutputVoltageLevel.java new file mode 100644 index 00000000000..43f8889353c --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/OutputVoltageLevel.java @@ -0,0 +1,33 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum OutputVoltageLevel implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + V_380(0, " 380 V"), // + V_400(1, "400 V"), // + V_480(2, "480 V"); // + + private final int value; + private final String name; + + private OutputVoltageLevel(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/PhaseAngleAbrupt.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/PhaseAngleAbrupt.java new file mode 100644 index 00000000000..30470cba10c --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/PhaseAngleAbrupt.java @@ -0,0 +1,33 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum PhaseAngleAbrupt implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLED(0, "Disabled"), // + ANGLE_ABRUPT_LIMIT_12_DEGREE(1, "Angle abrupt limit 12"), // + ANGLE_ABRUPT_LIMIT_6_DEGREE(2, "Angle abrupt limit 6"); // + + private final int value; + private final String name; + + private PhaseAngleAbrupt(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/PowerRisingMode.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/PowerRisingMode.java new file mode 100644 index 00000000000..a04e49da6f0 --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/PowerRisingMode.java @@ -0,0 +1,32 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum PowerRisingMode implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + STEP(0, "Step Function"), // + RAMP(1, "Ramp Function");// + + private final int value; + private final String name; + + private PowerRisingMode(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/ProtocolSelection.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/ProtocolSelection.java new file mode 100644 index 00000000000..80fece3aedd --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/ProtocolSelection.java @@ -0,0 +1,32 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum ProtocolSelection implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + MODBUS(0, "Modbus"), // + SUNSPEC(1, "Sunspec"); // + + private final int value; + private final String name; + + private ProtocolSelection(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/ReactivePowerControlMode.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/ReactivePowerControlMode.java new file mode 100644 index 00000000000..1b5a15ea1d8 --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/ReactivePowerControlMode.java @@ -0,0 +1,35 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum ReactivePowerControlMode implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + CONSTANT_REACTIVE_POWER(0, "Constant Reactive Power"), // + VOLT_VAR_ENABLED(1, "Volt Var Enabled"),// + CONSTANT_PF(2, "Constanr Power Factor"), // + WATT_PF_ENABLED(3, "Watt Power Factor Enabled"); // + + + private final int value; + private final String name; + + private ReactivePowerControlMode(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/SinexcelGridMode.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/SinexcelGridMode.java new file mode 100644 index 00000000000..6c3f5643ace --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/SinexcelGridMode.java @@ -0,0 +1,32 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum SinexcelGridMode implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + ON_GRID(0, "On Grid"), // + OFF_GRID(1, "Off Grid"); // + + private final int value; + private final String name; + + private SinexcelGridMode(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/SinglePhaseMode.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/SinglePhaseMode.java new file mode 100644 index 00000000000..551d9c359b0 --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/SinglePhaseMode.java @@ -0,0 +1,33 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum SinglePhaseMode implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + DISABLE(0, "Disable"), // + SINGLE_PHASE_230V(1, "Single Phae 230V"), // + SINGLE_PHASE_480V(2, "Single Phase 480V");// + + private final int value; + private final String name; + + private SinglePhaseMode(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/StartMode.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/StartMode.java new file mode 100644 index 00000000000..ac12bfee5e2 --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/StartMode.java @@ -0,0 +1,32 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum StartMode implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + MANUAL(0, "Manual Start"), // + AUTO(1, "Auto Start"); // + + private final int value; + private final String name; + + private StartMode(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/Switch.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/Switch.java new file mode 100644 index 00000000000..d28f28a0d5b --- /dev/null +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/enums/Switch.java @@ -0,0 +1,32 @@ +package io.openems.edge.batteryinverter.sinexcel.enums; + +import io.openems.common.types.OptionsEnum; + +public enum Switch implements OptionsEnum { + UNDEFINED(-1, "Undefined"), // + NO_SWITCH(0, "No Switch"), // + SWITCH(1, "Switch");// + + private final int value; + private final String name; + + private Switch(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.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/ErrorHandler.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/ErrorHandler.java index 1dd40ea573c..9e244e83e85 100644 --- a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/ErrorHandler.java +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/ErrorHandler.java @@ -26,7 +26,7 @@ protected void onEntry(Context context) throws OpenemsNamedException { // Try to stop systems final SinexcelImpl inverter = context.getParent(); inverter.softStart(false); - inverter.setStopCommand(); + inverter.setStopInverter(); } @Override @@ -41,7 +41,7 @@ public State runAndGetNextState(Context context) throws OpenemsNamedException { } private void setClearFailureCommand(Context context) throws OpenemsNamedException { - BooleanWriteChannel setClearFailureCmd = context.getParent().channel(Sinexcel.ChannelId.CLEAR_FAILURE_CMD); + BooleanWriteChannel setClearFailureCmd = context.getParent().channel(Sinexcel.ChannelId.CLEAR_FAILURE_COMMAND); setClearFailureCmd.setNextWriteValue(true); // 1: true, other: illegal } } diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/GoRunningHandler.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/GoRunningHandler.java index 57ca3cafe44..63574e43c03 100644 --- a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/GoRunningHandler.java +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/GoRunningHandler.java @@ -34,7 +34,7 @@ public State runAndGetNextState(Context context) throws OpenemsNamedException { } inverter.softStart(true); - inverter.setStartCommand(); + inverter.setStartInverter(); if (inverter.getBatteryInverterState().get() == Boolean.TRUE) { // Inverter is ON diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/GoStoppedHandler.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/GoStoppedHandler.java index a2958ba5a16..7c51ce24738 100644 --- a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/GoStoppedHandler.java +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/GoStoppedHandler.java @@ -12,7 +12,7 @@ public State runAndGetNextState(Context context) throws OpenemsNamedException { final SinexcelImpl inverter = context.getParent(); inverter.softStart(false); - inverter.setStopCommand(); + inverter.setStopInverter(); if (inverter.getBatteryInverterState().get() == Boolean.FALSE) { // Inverter is OFF diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/RunningHandler.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/RunningHandler.java index 66273ba1837..2de4e315942 100644 --- a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/RunningHandler.java +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/statemachine/RunningHandler.java @@ -25,7 +25,7 @@ public State runAndGetNextState(Context context) throws OpenemsNamedException { this.applyPower(context); inverter.softStart(true); - inverter.setStartCommand(); + inverter.setStartInverter(); return State.RUNNING; } diff --git a/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/SinexcelImplTest.java b/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/SinexcelImplTest.java index 8d76671a84b..408d2582961 100644 --- a/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/SinexcelImplTest.java +++ b/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/SinexcelImplTest.java @@ -11,6 +11,7 @@ import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.test.DummyBattery; import io.openems.edge.batteryinverter.api.OffGridBatteryInverter; +import io.openems.edge.batteryinverter.api.OffGridBatteryInverter.TargetGridMode; import io.openems.edge.batteryinverter.sinexcel.statemachine.StateMachine.State; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.component.OpenemsComponent; @@ -28,12 +29,9 @@ public class SinexcelImplTest { private static final String BATTERY_INVERTER_ID = "batteryInverter0"; private static final String BATTERY_ID = "battery0"; private static final String MODBUS_ID = "modbus0"; - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(BATTERY_INVERTER_ID, "StateMachine"); -// private static final ChannelAddress CLEAR_FAILURE_CMD = new ChannelAddress(BATTERY_INVERTER_ID, "ClearFailureCmd"); -// private static final ChannelAddress SET_ON_GRID_MODE = new ChannelAddress(BATTERY_INVERTER_ID, "SetOnGridMode"); -// private static final ChannelAddress SET_OFF_GRID_MODE = new ChannelAddress(BATTERY_INVERTER_ID, "SetOffGridMode"); - + private static final ChannelAddress SET_ON_GRID_MODE = new ChannelAddress(BATTERY_INVERTER_ID, "SetOnGridMode"); + private static final ChannelAddress SET_OFF_GRID_MODE = new ChannelAddress(BATTERY_INVERTER_ID, "SetOffGridMode"); private static final ChannelAddress MAX_APPARENT_POWER = new ChannelAddress(BATTERY_INVERTER_ID, // "MaxApparentPower"); @@ -91,28 +89,48 @@ public void testStart() throws Exception { .next(new TestCase("Fifth") // .output(STATE_MACHINE, State.GO_RUNNING)); } -// -// @Test -// public void testOffGrid() throws Exception { -// final TimeLeapClock clock = new TimeLeapClock( -// Instant.ofEpochSecond(1577836800L) /* starts at 1. January 2020 00:00:00 */, ZoneOffset.UTC); -// new MyComponentTest(new SinexcelImpl()) // -// .addReference("cm", new DummyConfigurationAdmin()) // -// .addReference("componentManager", new DummyComponentManager(clock)) // -// .addReference("power", new DummyPower()) // -// .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // -// .activate(MyConfig.create() // -// .setId(BATTERY_INVERTER_ID) // -// .setStartStopConfig(StartStopConfig.START) // -// .setModbusId(MODBUS_ID) // -// .build()) // -// .next(new TestCase() // -// .output(STATE_MACHINE, State.UNDEFINED)) // -// .next(new TestCase() // -// .output(STATE_MACHINE, State.GO_RUNNING) // -// .output(CLEAR_FAILURE_CMD, true)) // -// -// ; -// } + + @Test + public void testOffGrid() throws Exception { + final TimeLeapClock clock = new TimeLeapClock( + Instant.ofEpochSecond(1577836800L) /* starts at 1. January 2020 00:00:00 */, ZoneOffset.UTC); + SinexcelImpl sut = new SinexcelImpl(); + new MyComponentTest(sut) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("power", new DummyPower()) // + .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .activate(MyConfig.create() // + .setId(BATTERY_INVERTER_ID) // + .setStartStopConfig(StartStopConfig.START) // + .setModbusId(MODBUS_ID) // + .build()) // + .next(new TestCase() // + .output(STATE_MACHINE, State.UNDEFINED)) // + .next(new TestCase() // + .output(STATE_MACHINE, State.GO_RUNNING)) // + .next(new TestCase()// + .input(SET_OFF_GRID_MODE, false) // + .input(SET_ON_GRID_MODE, true)) // + .next(new TestCase()// + .input(INVERTER_STATE, true)) + .next(new TestCase() // + .output(STATE_MACHINE, State.RUNNING)) // + .next(new TestCase() // + .onExecuteControllersCallbacks(() -> sut.setTargetGridMode(TargetGridMode.GO_OFF_GRID))) // + .next(new TestCase() // + .output(STATE_MACHINE, State.UNDEFINED)) // + .next(new TestCase() // + .output(STATE_MACHINE, State.GO_RUNNING)) // + .next(new TestCase()// + .input(SET_OFF_GRID_MODE, true) // + .input(SET_ON_GRID_MODE, false)) // + .next(new TestCase()// + .input(INVERTER_STATE, true)) + .next(new TestCase() // + .output(STATE_MACHINE, State.RUNNING)) // + + ; + } } diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java b/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java index 4b4eef0faab..292b5acf9c5 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java +++ b/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java @@ -17,6 +17,7 @@ import java.util.Objects; import java.util.Set; +import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationAdmin; @@ -552,6 +553,10 @@ private void callActivate(AbstractComponentConfig config) // ComponentContext arg = DummyComponentContext.from(config); + } else if (BundleContext.class.isAssignableFrom(parameter.getType())) { + // BundleContext + arg = null; + } else if (parameter.getType().isInstance(config)) { // Config arg = config; diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentContext.java b/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentContext.java index 3b7526e9a2b..6a6a231beb2 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentContext.java +++ b/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentContext.java @@ -25,7 +25,6 @@ public static DummyComponentContext from(AbstractComponentConfig configuration) public DummyComponentContext(Dictionary properties) { this.properties = properties; - // TODO create DummyBundleContext } public DummyComponentContext() { diff --git a/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java b/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java index 75157312d2a..b90c4b7194e 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java +++ b/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java @@ -13,10 +13,19 @@ import io.openems.edge.common.channel.value.Value; /** - * Handles implicit conversions between {@link OpenemsType}s + * Handles implicit conversions between {@link OpenemsType}s. */ public class TypeUtils { + /** + * Converts and casts a Object to a given type. + * + * @param the Type for implicit casting of the result + * @param type the type as {@link OpenemsType} + * @param value the value as {@link Object} + * @return the converted and casted value + * @throws IllegalArgumentException on error + */ @SuppressWarnings("unchecked") public static T getAsType(OpenemsType type, Object value) throws IllegalArgumentException { // Extract Value containers @@ -79,6 +88,7 @@ public static T getAsType(OpenemsType type, Object value) throws IllegalArgu throw new IllegalArgumentException("Cannot convert String [" + value + "] to Boolean."); } } + break; case SHORT: if (value == null) { @@ -203,7 +213,7 @@ public static T getAsType(OpenemsType type, Object value) throws IllegalArgu } else if (value instanceof Boolean) { boolean boolValue = (Boolean) value; - return (T) Long.valueOf((boolValue ? 1l : 0l)); + return (T) Long.valueOf((boolValue ? 1L : 0L)); } else if (value instanceof Short) { return (T) (Long) ((Short) value).longValue(); @@ -267,13 +277,9 @@ public static T getAsType(OpenemsType type, Object value) throws IllegalArgu return (T) (Float) value; } else if (value instanceof Double) { - double doubleValue = (Double) value; - if (doubleValue >= Float.MIN_VALUE && doubleValue <= Float.MAX_VALUE) { - return (T) (Float) Float.valueOf((float) doubleValue); - } else { - throw new IllegalArgumentException( - "Cannot convert. Double [" + value + "] is not fitting in Float range."); - } + // Returns the value of this Double as a float after a narrowing primitive + // conversion. + return (T) (Float) Float.valueOf(((Double) value).floatValue()); } else if (value instanceof String) { String stringValue = (String) value; @@ -294,7 +300,7 @@ public static T getAsType(OpenemsType type, Object value) throws IllegalArgu } else if (value instanceof Boolean) { boolean boolValue = (Boolean) value; - return (T) Double.valueOf((boolValue ? 1l : 0l)); + return (T) Double.valueOf((boolValue ? 1L : 0L)); } else if (value instanceof Short) { return (T) Double.valueOf((Short) value); @@ -362,6 +368,13 @@ public static T getAsType(OpenemsType type, Object value) throws IllegalArgu } + /** + * Gets the value of the given type as {@link JsonElement}. + * + * @param type the type as {@link OpenemsType} + * @param originalValue the value + * @return the converted value + */ public static JsonElement getAsJson(OpenemsType type, Object originalValue) { if (originalValue == null) { return JsonNull.INSTANCE; @@ -390,8 +403,8 @@ public static JsonElement getAsJson(OpenemsType type, Object originalValue) { * Safely add Integers. If one of them is null it is considered '0'. If all of * them are null, 'null' is returned. * - * @param values - * @return + * @param values the {@link Integer} values + * @return the sum */ public static Integer sum(Integer... values) { Integer result = null; @@ -412,8 +425,8 @@ public static Integer sum(Integer... values) { * Safely add Longs. If one of them is null it is considered '0'. If all of them * are null, 'null' is returned. * - * @param values - * @return + * @param values the {@link Long} values + * @return the sum */ public static Long sum(Long... values) { Long result = null; @@ -551,6 +564,7 @@ public static Long divide(Long dividend, long divisor) { /** * Safely finds the max value of all values. * + * @param values the {@link Integer} values * @return the max value; or null if all values are null */ public static Integer max(Integer... values) { @@ -570,6 +584,25 @@ public static Integer max(Integer... values) { /** * Safely finds the min value of all values. * + * @param values the {@link Integer} values + * @return the min value; or null if all values are null + */ + public static Integer min(Integer... values) { + Integer result = null; + for (Integer value : values) { + if (result != null && value != null) { + result = Math.min(result, value); + } else if (value != null) { + result = value; + } + } + return result; + } + + /** + * Safely finds the min value of all values. + * + * @param values the {@link Double} values * @return the min value; or null if all values are null */ public static Double min(Double... values) { @@ -589,6 +622,7 @@ public static Double min(Double... values) { /** * Safely finds the average value of all values. * + * @param values the {@link Integer} values * @return the average value; or null if all values are null */ public static Float average(Integer... values) { @@ -609,6 +643,7 @@ public static Float average(Integer... values) { /** * Safely finds the average value of all values. * + * @param values the double values * @return the average value; or Double.NaN if all values are invalid. */ public static double average(double... values) { @@ -632,6 +667,7 @@ public static double average(double... values) { * Safely finds the average value of all values and rounds the result to an * Integer using {@link Math#round(float)}. * + * @param values the {@link Integer} values * @return the rounded average value; or null if all values are null */ public static Integer averageRounded(Integer... values) { @@ -643,23 +679,6 @@ public static Integer averageRounded(Integer... values) { } } - /** - * Safely finds the min value of all values. - * - * @return the min value; or null if all values are null - */ - public static Integer min(Integer... values) { - Integer result = null; - for (Integer value : values) { - if (result != null && value != null) { - result = Math.min(result, value); - } else if (value != null) { - result = value; - } - } - return result; - } - /** * Throws an descriptive exception if the object is null. * @@ -676,7 +695,7 @@ public static void assertNull(String description, Object... objects) throws Ille } /** - * Safely convert from {@link Integer} to {@link Double} + * Safely convert from {@link Integer} to {@link Double}. * * @param value the Integer value, possibly null * @return the Double value, possibly null @@ -690,7 +709,7 @@ public static Double toDouble(Integer value) { } /** - * Safely convert from {@link Float} to {@link Double} + * Safely convert from {@link Float} to {@link Double}. * * @param value the Float value, possibly null * @return the Double value, possibly null @@ -706,6 +725,7 @@ public static Double toDouble(Float value) { /** * Returns the 'alternativeValue' if the 'nullableValue' is null. * + * @param the Type for implicit casting * @param nullableValue the value, can be null * @param alternativeValue the alternative value * @return either the value (not null), alternatively the 'orElse' value diff --git a/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java b/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java index 1173a573003..cd887687730 100644 --- a/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java +++ b/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java @@ -179,6 +179,7 @@ public void testGetAsType() { assertException(() -> getAsFloat("foo")); assertEquals(expected, getAsFloat("123")); assertException(() -> getAsFloat(new Object())); + assertEquals(Float.valueOf(0.0f), getAsFloat(Double.valueOf(0.0))); } /* diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/BackendApiImpl.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/BackendApiImpl.java index af7fbec5202..5432144b63a 100644 --- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/BackendApiImpl.java +++ b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/BackendApiImpl.java @@ -104,7 +104,8 @@ void activate(ComponentContext context, Config config) { // initialize Executor String name = COMPONENT_NAME + ":" + this.id(); - this.executor = Executors.newScheduledThreadPool(1, + this.executor = Executors + .newScheduledThreadPool(10, new ThreadFactoryBuilder().setNameFormat(name + "-%d").build()); // initialize ApiWorker diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/Config.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/Config.java index df7a64e63a9..6e7826fe89a 100644 --- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/Config.java +++ b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/Config.java @@ -6,6 +6,7 @@ import org.osgi.service.metatype.annotations.AttributeType; import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import io.openems.common.OpenemsOEM; import io.openems.common.channel.PersistencePriority; @ObjectClassDefinition(// @@ -26,7 +27,7 @@ String apikey(); @AttributeDefinition(name = "Uri", description = "The connection Uri to OpenEMS Backend.") - String uri() default "ws://localhost:8081"; + String uri() default OpenemsOEM.BACKEND_API_URI; @AttributeDefinition(name = "Proxy Address", description = "The IP address or hostname of the proxy server.") String proxyAddress() default ""; diff --git a/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WebsocketApi.java b/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WebsocketApi.java index 34b63d76836..edcbdfcb0d2 100644 --- a/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WebsocketApi.java +++ b/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WebsocketApi.java @@ -121,7 +121,7 @@ protected void activate(ComponentContext context, Config config) { // initialize Executor String name = "Controller.Api.Websocket" + ":" + this.id(); - this.executor = Executors.newScheduledThreadPool(1, + this.executor = Executors.newScheduledThreadPool(10, new ThreadFactoryBuilder().setNameFormat(name + "-%d").build()); this.apiWorker.setTimeoutSeconds(config.apiTimeout()); diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/EmergencyCapacityReserve.java b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/EmergencyCapacityReserve.java index 75caa4ac37b..14c719d60dc 100644 --- a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/EmergencyCapacityReserve.java +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/EmergencyCapacityReserve.java @@ -62,6 +62,8 @@ public Doc doc() { } } + public Config getConfig(); + /** * Gets the Channel for {@link ChannelId#STATE_MACHINE}. * diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/EmergencyCapacityReserveImpl.java b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/EmergencyCapacityReserveImpl.java index c7a83a38b69..c189597da72 100644 --- a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/EmergencyCapacityReserveImpl.java +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/EmergencyCapacityReserveImpl.java @@ -193,4 +193,9 @@ private OptionalInt getLastValidSoc(IntegerReadChannel channel) { .findFirst(); } + @Override + public Config getConfig() { + return this.config; + } + } diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/package-info.java b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/package-info.java new file mode 100644 index 00000000000..56ce3b052dc --- /dev/null +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.controller.ess.emergencycapacityreserve; diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/package-info.java b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/package-info.java new file mode 100644 index 00000000000..2a615cb43dc --- /dev/null +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/statemachine/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.controller.ess.emergencycapacityreserve.statemachine; diff --git a/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/LimitTotalDischargeController.java b/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/LimitTotalDischargeController.java index cd2638c842c..ed4170bf765 100644 --- a/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/LimitTotalDischargeController.java +++ b/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/LimitTotalDischargeController.java @@ -50,6 +50,7 @@ public class LimitTotalDischargeController extends AbstractOpenemsComponent impl private int forceChargeSoc = 0; private Optional forceChargePower = Optional.empty(); private State state = State.NORMAL; + private Config config; public enum ChannelId implements io.openems.edge.common.channel.ChannelId { STATE_MACHINE(Doc.of(State.values()) // @@ -81,6 +82,7 @@ public LimitTotalDischargeController() { void activate(ComponentContext context, Config config) { super.activate(context, config.id(), config.alias(), config.enabled()); + this.config = config; this.essId = config.ess_id(); this.minSoc = config.minSoc(); this.forceChargeSoc = config.forceChargeSoc(); @@ -227,4 +229,8 @@ private boolean changeState(State nextState) { return false; } } + + public int getMinSoc() { + return this.config.minSoc(); + } } diff --git a/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/package-info.java b/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/package-info.java new file mode 100644 index 00000000000..7cfdbd337a2 --- /dev/null +++ b/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.controller.ess.limittotaldischarge; diff --git a/io.openems.edge.controller.ess.timeofusetariff.discharge/bnd.bnd b/io.openems.edge.controller.ess.timeofusetariff.discharge/bnd.bnd index a4aee2b1167..59ec0393dbd 100644 --- a/io.openems.edge.controller.ess.timeofusetariff.discharge/bnd.bnd +++ b/io.openems.edge.controller.ess.timeofusetariff.discharge/bnd.bnd @@ -8,8 +8,12 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ + io.openems.edge.controller.ess.emergencycapacityreserve,\ + io.openems.edge.controller.ess.limittotaldischarge,\ io.openems.edge.ess.api,\ io.openems.edge.predictor.api,\ + io.openems.edge.timedata.api,\ + io.openems.edge.timeofusetariff.api,\ io.openems.wrapper.okhttp,\ -testpath: \ diff --git a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/BoundarySpace.java b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/BoundarySpace.java index 8e2fafd04fe..2bbc9049672 100644 --- a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/BoundarySpace.java +++ b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/BoundarySpace.java @@ -18,10 +18,11 @@ class BoundarySpace { * @param consumptionMap predictions for consumption * @param maxStartHour fallback Morning-Hour from {@link Config} * @param maxEndHour fallback Evening-Hour from {@link Config} + * @param bufferMinutes Number of minutes, sunrise to be adjusted. * @return the {@link BoundarySpace} */ public static BoundarySpace from(ZonedDateTime startQuarterHour, TreeMap productionMap, - TreeMap consumptionMap, int maxStartHour, int maxEndHour) { + TreeMap consumptionMap, int maxStartHour, int maxEndHour, int bufferMinutes) { ZonedDateTime proLessThanCon = null; ZonedDateTime proMoreThanCon = null; @@ -32,18 +33,27 @@ public static BoundarySpace from(ZonedDateTime startQuarterHour, TreeMap consumption) // - && (entry.getKey().getDayOfYear() == startQuarterHour.getDayOfYear())) { - proLessThanCon = entry.getKey(); + final ZonedDateTime start; + if (isBeforeMidnight(startQuarterHour.getHour())) { + // Last hour of the day when Production < Consumption. + if ((production > consumption) // + && (entry.getKey().getDayOfYear() == startQuarterHour.getDayOfYear()) + && (entry.getKey().getHour() >= 14)) { + proLessThanCon = entry.getKey(); // Sunset + } + + start = startQuarterHour.plusDays(1); + + } else { + start = startQuarterHour; } // First hour of the day when production > consumption if ((production > consumption) // - && (entry.getKey().getDayOfYear() == startQuarterHour.plusDays(1).getDayOfYear()) // + && (entry.getKey().getDayOfYear() == start.getDayOfYear()) // && (proMoreThanCon == null) // && (entry.getKey().getHour() <= 10)) { - proMoreThanCon = entry.getKey(); + proMoreThanCon = entry.getKey(); // Sunrise } } } @@ -51,16 +61,31 @@ public static BoundarySpace from(ZonedDateTime startQuarterHour, TreeMap= 10 && hour <= 23) { + return true; + } + return false; + } } \ No newline at end of file diff --git a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/Config.java b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/Config.java index 2275b8de811..84f32cc7233 100644 --- a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/Config.java +++ b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/Config.java @@ -20,6 +20,14 @@ @AttributeDefinition(name = "Ess-ID", description = "ID of Ess device.") String ess_id(); + @AttributeDefinition(name = "Mode", description = "Set the type of mode.") + Mode mode() default Mode.AUTOMATIC; + + @AttributeDefinition(name = "Risk level of the customer", description = "" // + + "Low Risk: Less dependence on predictions; Energy in battery should always be used during the night. " // + + "High Risk: High dependence on predictions; Battery is scheduled to discharge completely based on predictions.") + DelayDischargeRiskLevel delayDischargeRiskLevel() default DelayDischargeRiskLevel.MEDIUM; + @AttributeDefinition(name = "Fallback Morning-Hour", description = "Fallback for calculation to stop at this hour") int maxStartHour() default 8; diff --git a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/DelayDischargeRiskLevel.java b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/DelayDischargeRiskLevel.java new file mode 100644 index 00000000000..48615c98989 --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/DelayDischargeRiskLevel.java @@ -0,0 +1,46 @@ +package io.openems.edge.controller.ess.timeofusetariff.discharge; + +/** + * The Risk Level is describing the risk propensity and effects on the SoC curve + * during the night. + */ +public enum DelayDischargeRiskLevel { + + /** + * Less dependent on predictions. The state of charge will most likely be at + * minimum SoC level before there is more production than consumption, but might + * end up buying from grid during high price hour for consumption. + */ + LOW(60), // + + /** + * Moderately dependent on predictions. The state of charge will likely be at + * minimum SoC level before there is more production than consumption. It is + * still possible that the storage might be empty and end up buying from grid + * during the high price hour. + */ + MEDIUM(30), // + + /** + * Complete dependency on Predictions. The state of charge will likely be at + * minimum SoC level before there is more production than consumption, but very + * often certain percentage SoC will remain in the battery which goes unused for + * the night consumption. + */ + HIGH(0); + + public final int bufferMinutes; + + private DelayDischargeRiskLevel(int bufferMinutes) { + this.bufferMinutes = bufferMinutes; + } + + /** + * Get buffer minutes. + * + * @return buffer minutes + */ + public int getBufferMinutes() { + return this.bufferMinutes; + } +} diff --git a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/Mode.java b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/Mode.java new file mode 100644 index 00000000000..b68bd6e631e --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/Mode.java @@ -0,0 +1,5 @@ +package io.openems.edge.controller.ess.timeofusetariff.discharge; + +public enum Mode { + OFF, AUTOMATIC; +} diff --git a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/StateMachine.java b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/StateMachine.java new file mode 100644 index 00000000000..7e389d0d70e --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/StateMachine.java @@ -0,0 +1,34 @@ +package io.openems.edge.controller.ess.timeofusetariff.discharge; + +import io.openems.common.types.OptionsEnum; + +public enum StateMachine implements OptionsEnum { + + DELAYED(0, "Delayed"), // + ALLOWS_DISCHARGE(1, "No active limitation, discharge is allowed"), // + STANDBY(2, "Outside controller time limits"); // + + private final int value; + private final String name; + + private StateMachine(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 STANDBY; + } + +} diff --git a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/TimeOfUseTariffDischarge.java b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/TimeOfUseTariffDischarge.java index cb1c0035bd7..d7f2561f98e 100644 --- a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/TimeOfUseTariffDischarge.java +++ b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/TimeOfUseTariffDischarge.java @@ -1,15 +1,33 @@ package io.openems.edge.controller.ess.timeofusetariff.discharge; +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.value.Value; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.controller.api.Controller; public interface TimeOfUseTariffDischarge extends Controller, OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - BUYING_FROM_GRID(Doc.of(OpenemsType.BOOLEAN)// - .text("The controller selfoptimization ran succefully")), + + /** + * Current state of the Time of use tariff discharge controller. + */ + STATE_MACHINE(Doc.of(StateMachine.values()) // + .text("Current state of the Controller")), + + /** + * Aggregated seconds when storage is blocked for discharge. + */ + DELAYED_TIME(Doc.of(OpenemsType.LONG) // + .unit(Unit.CUMULATED_SECONDS) // + .persistencePriority(PersistencePriority.HIGH)), // + + DELAYED(Doc.of(OpenemsType.BOOLEAN)// + .text("The controller currently blocks discharge")), TARGET_HOURS_IS_EMPTY(Doc.of(OpenemsType.BOOLEAN)// .text("The list of target hours is empty")), QUATERLY_PRICES_TAKEN(Doc.of(OpenemsType.BOOLEAN)// @@ -19,21 +37,30 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { TOTAL_CONSUMPTION(Doc.of(OpenemsType.INTEGER) // .text("Total consmption for the night")), QUARTERLY_PRICES(Doc.of(OpenemsType.FLOAT) // - .text("Price of the electricity for the corresponding Hour.")), + .unit(Unit.EUROS_PER_MEGAWATT_HOUR) // + .text("Price of the electricity for the current Hour")), REMAINING_CONSUMPTION(Doc.of(OpenemsType.DOUBLE) // .text("remaining consmption to charge from grid")), - NUMBER_OF_TARGET_HOURS(Doc.of(OpenemsType.INTEGER) // - .text("Target Hours")), + TARGET_HOURS(Doc.of(OpenemsType.INTEGER) // + .text("Number of Target Hours")), AVAILABLE_CAPACITY(Doc.of(OpenemsType.INTEGER) // .text("Available capcity in the battery during evening")), // - PRO_MORE_THAN_CON(Doc.of(OpenemsType.INTEGER) // - .text("Hour of Production more than Consumption")), + USABLE_CAPACITY(Doc.of(OpenemsType.INTEGER) // + .text("Usable capcity in the battery during after taking limit soc into consideration")), // + PRO_MORE_THAN_CON_ACTUAL(Doc.of(OpenemsType.INTEGER) // + .text("Actual Hour of Production more than Consumption")), + PRO_MORE_THAN_CON_SET(Doc.of(OpenemsType.INTEGER) // + .text("Hour of Production more than Consumption set based on risk level")), PRO_LESS_THAN_CON(Doc.of(OpenemsType.INTEGER) // .text("Hour of Production less than Consumption")), - PRODUCTION(Doc.of(OpenemsType.INTEGER) // - .text("Production")), - CONSUMPTON(Doc.of(OpenemsType.INTEGER) // - .text("Consumption")); + PREDICTED_PRODUCTION(Doc.of(OpenemsType.INTEGER) // + .text("Predicted Production for the current quarterly hour")), + PREDICTED_CONSUMPTION(Doc.of(OpenemsType.INTEGER) // + .text("Predicted Consumption for the current quarterly hour")), + MIN_SOC(Doc.of(OpenemsType.INTEGER) // + .text("Minimum SoC to avoid complete discharge")), + PREDICTED_SOC_WITHOUT_LOGIC(Doc.of(OpenemsType.INTEGER) // + .text("SoC prediction curve without controller logic")),; private final Doc doc; @@ -46,4 +73,164 @@ public Doc doc() { return this.doc; } } + + /** + * Gets the Channel for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel + */ + public default Channel getStateMachineChannel() { + return this.channel(ChannelId.STATE_MACHINE); + } + + /** + * Gets the Status of the Controller. See {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel {@link Value} + */ + public default StateMachine getStateMachine() { + return this.getStateMachineChannel().value().asEnum(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#STATE_MACHINE} + * Channel. + * + * @param value the next value + */ + public default void _setStateMachine(StateMachine value) { + this.getStateMachineChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#DELAYED}. + * + * @return the Channel + */ + public default Channel getDelayedChannel() { + return this.channel(ChannelId.DELAYED); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#DELAYED} Channel. + * + * @param value the next value + */ + public default void _setDelayed(boolean value) { + this.getDelayedChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#QUARTERLY_PRICES}. + * + * @return the Channel + */ + public default Channel getQuarterlyPricesChannel() { + return this.channel(ChannelId.QUARTERLY_PRICES); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#QUARTERLY_PRICES} + * Channel. + * + * @param value the next value + */ + public default void _setQuarterlyPrices(Float value) { + this.getQuarterlyPricesChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#PREDICTED_PRODUCTION}. + * + * @return the Channel + */ + public default Channel getPredictedProductionChannel() { + return this.channel(ChannelId.PREDICTED_PRODUCTION); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#PREDICTED_PRODUCTION} Channel. + * + * @param value the next value + */ + public default void _setPredictedProduction(Integer value) { + this.getPredictedProductionChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#PREDICTED_CONSUMPTION}. + * + * @return the Channel + */ + public default Channel getPredictedConsumptionChannel() { + return this.channel(ChannelId.PREDICTED_CONSUMPTION); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#PREDICTED_CONSUMPTION} Channel. + * + * @param value the next value + */ + public default void _setPredictedConsumption(Integer value) { + this.getPredictedConsumptionChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#PREDICTED_SOC_WITHOUT_LOGIC}. + * + * @return the Channel + */ + public default Channel getPredictedSocWithoutLogicChannel() { + return this.channel(ChannelId.PREDICTED_SOC_WITHOUT_LOGIC); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#PREDICTED_SOC_WITHOUT_LOGIC} Channel. + * + * @param value the next value + */ + public default void _setPredictedSocWithoutLogic(Integer value) { + this.getPredictedSocWithoutLogicChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#TARGET_HOURS_CALCULATED}. + * + * @return the Channel + */ + public default Channel getTargetHoursCalculatedChannel() { + return this.channel(ChannelId.TARGET_HOURS_CALCULATED); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#TARGET_HOURS_CALCULATED} Channel. + * + * @param value the next value + */ + public default void _setTargetHoursCalculated(Boolean value) { + this.getTargetHoursCalculatedChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#TARGET_HOURS_IS_EMPTY}. + * + * @return the Channel + */ + public default Channel getTargetHoursIsEmptyChannel() { + return this.channel(ChannelId.TARGET_HOURS_IS_EMPTY); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#TARGET_HOURS_IS_EMPTY} Channel. + * + * @param value the next value + */ + public default void _setTargetHoursIsEmpty(Boolean value) { + this.getTargetHoursIsEmptyChannel().setNextValue(value); + } } diff --git a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/TimeOfUseTariffDischargeImpl.java b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/TimeOfUseTariffDischargeImpl.java index ebb9f3df7d8..f6d7e925752 100644 --- a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/TimeOfUseTariffDischargeImpl.java +++ b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/TimeOfUseTariffDischargeImpl.java @@ -2,12 +2,11 @@ import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.temporal.ChronoField; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.TreeMap; +import java.util.concurrent.CopyOnWriteArrayList; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -28,10 +27,17 @@ import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.controller.api.Controller; -import io.openems.edge.controller.ess.timeofusetariff.discharge.tariff.AwattarProvider; -import io.openems.edge.controller.ess.timeofusetariff.discharge.tariff.TimeOfUseTariff; +import io.openems.edge.controller.ess.emergencycapacityreserve.EmergencyCapacityReserve; +import io.openems.edge.controller.ess.limittotaldischarge.LimitTotalDischargeController; import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.predictor.api.manager.PredictorManager; +import io.openems.edge.predictor.api.oneday.Prediction24Hours; +import io.openems.edge.timedata.api.Timedata; +import io.openems.edge.timedata.api.TimedataProvider; +import io.openems.edge.timedata.api.utils.CalculateActiveTime; +import io.openems.edge.timeofusetariff.api.TimeOfUsePrices; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; +import io.openems.edge.timeofusetariff.api.utils.TimeOfUseTariffUtils; @Designate(ocd = Config.class, factory = true) @Component(// @@ -40,11 +46,17 @@ configurationPolicy = ConfigurationPolicy.REQUIRE // ) public class TimeOfUseTariffDischargeImpl extends AbstractOpenemsComponent - implements Controller, OpenemsComponent, TimeOfUseTariffDischarge { + implements Controller, OpenemsComponent, TimedataProvider, TimeOfUseTariffDischarge { private static final ChannelAddress SUM_PRODUCTION = new ChannelAddress("_sum", "ProductionActivePower"); private static final ChannelAddress SUM_CONSUMPTION = new ChannelAddress("_sum", "ConsumptionActivePower"); + /** + * Delayed Time is aggregated also after restart of OpenEMS. + */ + private final CalculateActiveTime calculateDelayedTime = new CalculateActiveTime(this, + TimeOfUseTariffDischarge.ChannelId.DELAYED_TIME); + @Reference private ConfigurationAdmin cm; @@ -54,31 +66,37 @@ public class TimeOfUseTariffDischargeImpl extends AbstractOpenemsComponent @Reference private PredictorManager predictorManager; + @Reference + private TimeOfUseTariff timeOfUseTariff; + + @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) + private volatile Timedata timedata = null; + + @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MULTIPLE) + private volatile List ctrlEmergencyCapacityReserves = new CopyOnWriteArrayList<>(); + + @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MULTIPLE) + private volatile List ctrlLimitTotalDischargeControllers = new CopyOnWriteArrayList<>(); + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) private ManagedSymmetricEss ess; - private final TimeOfUseTariff timeOfUseTariff; - private Config config = null; - private boolean isPredictionValuesTaken = false; private BoundarySpace boundarySpace = null; private TreeMap consumptionMap = new TreeMap<>(); private TreeMap productionMap = new TreeMap<>(); private List targetPeriods = new ArrayList(); - private TreeMap quarterlyPrices = new TreeMap<>(); + private TreeMap quarterlyPricesMap = new TreeMap<>(); + private TreeMap socWithoutLogic = new TreeMap<>(); private ZonedDateTime lastAccessedTime = ZonedDateTime.of(2021, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + private ZonedDateTime lastUpdatePriceTime = ZonedDateTime.of(2021, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - public TimeOfUseTariffDischargeImpl(TimeOfUseTariff timeOfUseTariff) { + public TimeOfUseTariffDischargeImpl() { super(// OpenemsComponent.ChannelId.values(), // Controller.ChannelId.values(), // TimeOfUseTariffDischarge.ChannelId.values() // ); - this.timeOfUseTariff = timeOfUseTariff; - } - - public TimeOfUseTariffDischargeImpl() { - this(new AwattarProvider()); } @Activate @@ -99,69 +117,56 @@ protected void deactivate() { @Override public void run() throws OpenemsNamedException { + // Current Date Time rounded off to 15 minutes. - ZonedDateTime now = roundZonedDateTimeDownToMinutes(ZonedDateTime.now(this.componentManager.getClock()), 15); + ZonedDateTime now = TimeOfUseTariffUtils.getNowRoundedDownToMinutes(this.componentManager.getClock(), 15); - this.calculateBoundarySpace(now); + // Prices contains the price values and the time it is retrieved. + TimeOfUsePrices prices = this.timeOfUseTariff.getPrices(); + this.calculateBoundarySpace(now, prices); - this.calculateTargetPeriodsWithinBoundarySpace(now); + // Mode given from the configuration. + switch (this.config.mode()) { - this.avoidDischargeDuringTargetPeriods(); + case AUTOMATIC: + this.modeAutomatic(now); + break; + case OFF: + this.modeOff(); + break; + } this.updateVisualizationChannels(now); } - /* - * Every Day at 14:00 the Hourly Prices are updated. we receive the predictions - * at 14:00 till next day 13:00. + /** + * calculates the boundary space for the activation of the controller. * - * 'isPredictionValuesTaken' to make sure the control logic executes only once - * during the hour. + * @param now Current Date Time rounded off to 15 minutes. + * @param prices TimeOfUsePrices object, containing prices and the time it + * retrieved. */ - private void calculateBoundarySpace(ZonedDateTime now) { - if (now.getHour() == 14 && !this.isPredictionValuesTaken) { - - // Predictions as Integer array in 15 minute intervals. - final Integer[] predictionProduction = this.predictorManager.get24HoursPrediction(SUM_PRODUCTION) // - .getValues(); - final Integer[] predictionConsumption = this.predictorManager.get24HoursPrediction(SUM_CONSUMPTION) // - .getValues(); - final ZonedDateTime predictionStartQuarterHour = roundZonedDateTimeDownToMinutes(now, 15); - - // resetting values - this.quarterlyPrices.clear(); - - // Converts the given 15 minute integer array to a TreeMap values. - this.convertDataStructure(predictionProduction, predictionConsumption, predictionStartQuarterHour); - - // calculates the boundary space, within which the controller needs to work. - this.boundarySpace = BoundarySpace.from(predictionStartQuarterHour, this.productionMap, this.consumptionMap, - this.config.maxStartHour(), this.config.maxEndHour()); + private void calculateBoundarySpace(ZonedDateTime now, TimeOfUsePrices prices) { - // Update Channels - this.channel(TimeOfUseTariffDischarge.ChannelId.PRO_MORE_THAN_CON) - .setNextValue(this.boundarySpace.proMoreThanCon.getHour()); - this.channel(TimeOfUseTariffDischarge.ChannelId.PRO_LESS_THAN_CON) - .setNextValue(this.boundarySpace.proLessThanCon.getHour()); + /* + * Every day, Prices are updated in API at a certain hour. we update the + * predictions and the prices during those hour. + */ + if (this.lastUpdatePriceTime.isBefore(prices.getUpdateTime())) { + // gets the prices, predictions and calculates the boundary space. + this.getBoundarySpace(now, prices); - // Hourly Prices from API - this.quarterlyPrices = this.timeOfUseTariff.getPrices(); - - // setting the channel id values - if (this.quarterlyPrices.isEmpty()) { - this.channel(TimeOfUseTariffDischarge.ChannelId.QUATERLY_PRICES_TAKEN).setNextValue(false); - return; - - } else { - this.channel(TimeOfUseTariffDischarge.ChannelId.QUATERLY_PRICES_TAKEN).setNextValue(true); - } - - this.isPredictionValuesTaken = true; // boolean used to take prediction values only once. + } else { + // update the channel + this.channel(TimeOfUseTariffDischarge.ChannelId.QUATERLY_PRICES_TAKEN).setNextValue(false); } - // resets the 'isPredictionValuesTaken' to be ready for next day. - if (now.getHour() == 15 && this.isPredictionValuesTaken) { - this.isPredictionValuesTaken = false; + // Calculates the prices and predictions when the controller is restarted or + // re-enabled in any time. + if (this.quarterlyPricesMap.isEmpty()) { + + // gets the prices, predictions and calculates the boundary space. + this.getBoundarySpace(now, prices); } } @@ -173,7 +178,7 @@ private void calculateBoundarySpace(ZonedDateTime now) { */ private void calculateTargetPeriodsWithinBoundarySpace(ZonedDateTime now) throws InvalidValueException { // Initializing with Default values. - this.channel(TimeOfUseTariffDischarge.ChannelId.TARGET_HOURS_CALCULATED).setNextValue(false); + this._setTargetHoursCalculated(false); // if the boundary space are calculated, start scheduling only during boundary // space. @@ -182,73 +187,226 @@ private void calculateTargetPeriodsWithinBoundarySpace(ZonedDateTime now) throws // Runs every 15 minutes. if (now.isAfter(this.lastAccessedTime)) { - int netCapacity = this.ess.getCapacity().getOrError(); - int soc = this.ess.getSoc().getOrError(); - int availableCapacity = Math.round((netCapacity * soc) / 100F); - - this.channel(TimeOfUseTariffDischarge.ChannelId.AVAILABLE_CAPACITY).setNextValue(availableCapacity); - - int remainingCapacity = this.getRemainingCapacity(availableCapacity, this.productionMap, + long availableEnergy = this.getAvailableEnergy(now); + long remainingEnergy = this.getRemainingCapacity(availableEnergy, this.productionMap, this.consumptionMap, now, this.boundarySpace); // Resetting this.targetPeriods.clear(); // list of periods calculation. - if (remainingCapacity > 0) { + if (remainingEnergy > 0) { // Initiating the calculation - this.targetPeriods = this.calculateTargetPeriods(this.consumptionMap, this.quarterlyPrices, - remainingCapacity, this.boundarySpace); - this.channel(TimeOfUseTariffDischarge.ChannelId.TARGET_HOURS_CALCULATED).setNextValue(true); + this.targetPeriods = this.calculateTargetPeriods(this.consumptionMap, this.quarterlyPricesMap, + remainingEnergy, this.boundarySpace); + this._setTargetHoursCalculated(true); } - this.channel(TimeOfUseTariffDischarge.ChannelId.NUMBER_OF_TARGET_HOURS) - .setNextValue(this.targetPeriods.size()); + this.channel(TimeOfUseTariffDischarge.ChannelId.TARGET_HOURS).setNextValue(this.targetPeriods.size()); this.lastAccessedTime = now; } + } else { + this._setStateMachine(StateMachine.STANDBY); + } + } + + /** + * Returns the available energy in the battery which is usable for consumption + * after adjusting the minimum SoC capacity. + * + * @param now Current Date Time rounded off to 15 minutes. + * @return available energy in Watt-milliseconds[Wmsec]. + * @throws InvalidValueException on error + */ + private long getAvailableEnergy(ZonedDateTime now) throws InvalidValueException { + + int netCapacity = this.ess.getCapacity().getOrError(); + int soc = this.ess.getSoc().getOrError(); + + // Usable capacity based on minimum SoC from Limit total discharge and emergency + // reserve controllers. + int limitSoc = 0; + for (LimitTotalDischargeController ctrl : this.ctrlLimitTotalDischargeControllers) { + limitSoc = Math.max(limitSoc, ctrl.getMinSoc()); + } + for (EmergencyCapacityReserve ctrl : this.ctrlEmergencyCapacityReserves) { + limitSoc = Math.max(limitSoc, ctrl.getConfig().reserveSoc()); + } + this.channel(TimeOfUseTariffDischarge.ChannelId.MIN_SOC).setNextValue(limitSoc); + + // Calculating available energy and usable energy [Wmsec] in the battery. + long availableEnergy = (long) (((double) netCapacity /* [Wh] */ * 3600 /* [Wsec] */ * 1000 /* [Wmsec] */ + / 100 /* [%] */) * soc /* [current SoC] */); + + // Value is divided by 3600 * 1000 to convert from [Wmsec] to [Wh]. + this.channel(TimeOfUseTariffDischarge.ChannelId.AVAILABLE_CAPACITY).setNextValue(availableEnergy / 3600000); + + long limitEnergy = (long) (((double) netCapacity /* [Wh] */ * 3600 /* [Wsec] */ * 1000 /* [Wmsec] */ + / 100 /* [%] */) * limitSoc /* [current SoC] */); + + availableEnergy = Math.max(0, (availableEnergy - limitEnergy)); + + // Value is divided by 3600 * 1000 to convert from [Wmsec] to [Wh]. + this.channel(TimeOfUseTariffDischarge.ChannelId.USABLE_CAPACITY).setNextValue(availableEnergy / 3600000); + + // To estimate the soc curve when controller logic is not applied + if (now.equals(this.boundarySpace.proLessThanCon)) { + this.socWithoutLogic = this.generateSocCurveWithoutLogic(netCapacity, availableEnergy, limitEnergy, + this.consumptionMap, soc, now, this.boundarySpace); + } + + return availableEnergy; + } + + /** + * This method calculates the boundary space within the prediction hours. + * + * @param now current time. + * @param prices TimeOfUsePrices object, containing prices and the time it + * retrieved. + */ + private void getBoundarySpace(ZonedDateTime now, TimeOfUsePrices prices) { + + // Predictions as Integer array in 15 minute intervals. + final Integer[] predictionProduction = this.predictorManager.get24HoursPrediction(SUM_PRODUCTION) // + .getValues(); + final Integer[] predictionConsumption = this.predictorManager.get24HoursPrediction(SUM_CONSUMPTION) // + .getValues(); + + // Prices as Float array in 15 minute intervals. + final Float[] quarterlyPrices = prices.getValues(); + this.channel(TimeOfUseTariffDischarge.ChannelId.QUATERLY_PRICES_TAKEN).setNextValue(true); + + // Converts the given 15 minute integer array to a TreeMap values. + this.convertDataStructure(predictionProduction, predictionConsumption, now, quarterlyPrices); + + if (this.quarterlyPricesMap.isEmpty()) { + this.channel(TimeOfUseTariffDischarge.ChannelId.QUATERLY_PRICES_TAKEN).setNextValue(false); + } + + // Buffer minutes to adjust sunrise based on the risk level. + int bufferMinutes = this.config.delayDischargeRiskLevel().bufferMinutes; + + // calculates the boundary space, within which the controller needs to work. + this.boundarySpace = BoundarySpace.from(now, this.productionMap, this.consumptionMap, + this.config.maxStartHour(), this.config.maxEndHour(), bufferMinutes); + + // Update Channels + this.channel(TimeOfUseTariffDischarge.ChannelId.PRO_MORE_THAN_CON_SET) + .setNextValue(this.boundarySpace.proMoreThanCon.getHour()); + this.channel(TimeOfUseTariffDischarge.ChannelId.PRO_MORE_THAN_CON_ACTUAL) + .setNextValue(this.boundarySpace.proMoreThanCon.plusMinutes(bufferMinutes).getHour()); + this.channel(TimeOfUseTariffDischarge.ChannelId.PRO_LESS_THAN_CON) + .setNextValue(this.boundarySpace.proLessThanCon.getHour()); + } + + /** + * This method returns the map of 15 minutes soc curve values when no controller + * logic is applied. + * + * @param netCapacity Net Capacity of the battery. + * @param availableEnergy available energy in the battery. + * @param limitEnergy energy restricted to used based on min soc. + * @param consumptionMap map of predicted consumption values. + * @param soc current SoC of the battery. + * @param now current time. + * @param boundarySpace the {@link BoundarySpace} + * + * @return {@link TreeMap} with {@link ZonedDateTime} as key and SoC as value. + */ + private TreeMap generateSocCurveWithoutLogic(int netCapacity, long availableEnergy, + long limitEnergy, TreeMap consumptionMap, int soc, ZonedDateTime now, + BoundarySpace boundarySpace) { + + TreeMap socWithoutLogic = new TreeMap<>(); + + // current values. + socWithoutLogic.put(now, soc); + + for (Entry entry : consumptionMap.subMap(now, boundarySpace.proMoreThanCon) + .entrySet()) { + + long duration = 15 * 60 * 1000; + long currentConsumptionEnergy = entry.getValue() * duration; + + if (availableEnergy > limitEnergy) { + availableEnergy -= currentConsumptionEnergy; + } + + double calculatedSoc = availableEnergy // + / (netCapacity * 3600. /* [Wsec] */ * 1000 /* [Wmsec] */) // + * 100 /* [SoC] */; + + if (calculatedSoc > 100) { + soc = 100; + } else if (calculatedSoc < 0) { + soc = 0; + } else { + soc = (int) Math.round(calculatedSoc); + } + + socWithoutLogic.put(entry.getKey().plusMinutes(15), soc); } + + return socWithoutLogic; } /** * Apply the actual logic of avoiding to discharge the battery during target * periods. * + * @param now Current Date Time rounded off to 15 minutes. * @throws OpenemsNamedException on error */ - private void avoidDischargeDuringTargetPeriods() throws OpenemsNamedException { - this.channel(TimeOfUseTariffDischarge.ChannelId.TARGET_HOURS_IS_EMPTY) - .setNextValue(this.targetPeriods.isEmpty()); + private void modeAutomatic(ZonedDateTime now) throws OpenemsNamedException { - ZonedDateTime currentQuarterHour = roundZonedDateTimeDownToMinutes( - ZonedDateTime.now(this.componentManager.getClock()), 15) // - .withZoneSameInstant(ZoneId.systemDefault()); + this.calculateTargetPeriodsWithinBoundarySpace(now); - if (this.targetPeriods.contains(currentQuarterHour)) { - // set result - this.ess.setActivePowerLessOrEquals(0); + this._setTargetHoursIsEmpty(this.targetPeriods.isEmpty()); - this.channel(TimeOfUseTariffDischarge.ChannelId.BUYING_FROM_GRID).setNextValue(true); + ZonedDateTime currentQuarterHour = TimeOfUseTariffUtils + .getNowRoundedDownToMinutes(this.componentManager.getClock(), 15) // + .withZoneSameInstant(ZoneId.systemDefault()); - } else { - this.channel(TimeOfUseTariffDischarge.ChannelId.BUYING_FROM_GRID).setNextValue(false); + if (this.boundarySpace != null && this.boundarySpace.isWithinBoundary(now)) { + if (this.targetPeriods.contains(currentQuarterHour)) { + // set result + this.ess.setActivePowerLessOrEquals(0); + this._setDelayed(true); + this._setStateMachine(StateMachine.DELAYED); + } else { + this._setDelayed(false); + this._setStateMachine(StateMachine.ALLOWS_DISCHARGE); + } } } + /** + * Apply the mode OFF logic. + */ + private void modeOff() { + // Do Nothing + this._setTargetHoursCalculated(false); + this._setTargetHoursIsEmpty(true); + this._setDelayed(false); + this._setStateMachine(StateMachine.STANDBY); + } + /** * This is only to visualize data for better debugging. * * @param now Current Date Time rounded off to 15 minutes. */ private void updateVisualizationChannels(ZonedDateTime now) { - // Storing quarterly prices in channel for visualization in Grafana. - if (!this.quarterlyPrices.isEmpty()) { - for (Entry entry : this.quarterlyPrices - .subMap(this.boundarySpace.proLessThanCon, this.boundarySpace.proMoreThanCon).entrySet()) { + // Update time counter with 'Delayed' of this run. + this.calculateDelayedTime.update(this.getDelayedChannel().getNextValue().orElse(false)); + // Storing quarterly prices in channel for visualization in Grafana and UI. + if (!this.quarterlyPricesMap.isEmpty()) { + for (Entry entry : this.quarterlyPricesMap.entrySet()) { if (now.isEqual(entry.getKey())) { - this.channel(TimeOfUseTariffDischarge.ChannelId.QUARTERLY_PRICES) // - .setNextValue(entry.getValue()); + this._setQuarterlyPrices(entry.getValue()); } } } @@ -256,114 +414,144 @@ private void updateVisualizationChannels(ZonedDateTime now) { // Storing Production and Consumption in channel for visualization in Grafana. if (!this.productionMap.isEmpty()) { for (Entry entry : this.productionMap.entrySet()) { - if (now.isEqual(entry.getKey())) { - this.channel(TimeOfUseTariffDischarge.ChannelId.PRODUCTION) // - .setNextValue(entry.getValue()); - this.channel(TimeOfUseTariffDischarge.ChannelId.CONSUMPTON) // - .setNextValue(this.consumptionMap.get(entry.getKey())); + this._setPredictedProduction(entry.getValue()); + this._setPredictedConsumption(this.consumptionMap.get(entry.getKey())); } } } + + Integer predictedSocWithoutLogic = null; + if (!this.socWithoutLogic.isEmpty()) { + if (this.boundarySpace.isWithinBoundary(now)) { + for (Entry entry : this.socWithoutLogic.entrySet()) { + if (now.isEqual(entry.getKey())) { + predictedSocWithoutLogic = entry.getValue(); + } + } + } + } else { + this.socWithoutLogic.clear(); + } + this._setPredictedSocWithoutLogic(predictedSocWithoutLogic); } /** - * This method converts the 15 minute integer array values to a one hour - * {@link TreeMap} format for ease in later calculations. + * This method converts the 15 minute integer array values to a {@link TreeMap} + * format for ease in later calculations. * * @param productionValues list of 96 production values predicted, comprising * for next 24 hours. * @param consumptionValues list of 96 consumption values predicted, comprising * for next 24 hours. * @param startHour start hour of the predictions. + * @param quarterlyPrices list of 96 quarterly electricity prices, comprising + * for next 24 hours. */ - private void convertDataStructure(Integer[] productionValues, Integer[] consumptionValues, - ZonedDateTime startHour) { + private void convertDataStructure(Integer[] productionValues, Integer[] consumptionValues, ZonedDateTime startHour, + Float[] quarterlyPrices) { this.productionMap.clear(); this.consumptionMap.clear(); + this.quarterlyPricesMap.clear(); - for (int i = 0; i < 96; i++) { + for (int i = 0; i < Prediction24Hours.NUMBER_OF_VALUES; i++) { Integer production = productionValues[i]; Integer consumption = consumptionValues[i]; + Float price = quarterlyPrices[i]; ZonedDateTime time = startHour.plusMinutes(i * 15); - if (production != null && consumption != null) { + if (production != null) { this.productionMap.put(time, production); + } + + if (consumption != null) { this.consumptionMap.put(time, consumption); } + + if (price != null) { + this.quarterlyPricesMap.put(time, price); + } } } /** - * This Method Returns the remaining Capacity that needs to be taken from Grid. + * This Method Returns the remaining Capacity that needs to be consumed from the + * Grid. * - * @param availableCapacity Amount of energy available in the ess based on SoC. - * @param productionMap predicted production data along with time in - * {@link TreeMap} format. - * @param consumptionMap predicted consumption data along with time in - * {@link TreeMap} format. - * @param now Current Date Time rounded off to 15 minutes. - * @param boundarySpace the {@link BoundarySpace} + * @param availableEnergy Amount of energy available in the ess based on SoC. + * @param productionMap predicted production data along with time in + * {@link TreeMap} format. + * @param consumptionMap predicted consumption data along with time in + * {@link TreeMap} format. + * @param now Current Date Time rounded off to 15 minutes. + * @param boundarySpace the {@link BoundarySpace} * @return remainingCapacity Amount of energy that should be covered from grid * for consumption in night. */ - private int getRemainingCapacity(int availableCapacity, TreeMap productionMap, + private long getRemainingCapacity(long availableEnergy, TreeMap productionMap, TreeMap consumptionMap, ZonedDateTime now, BoundarySpace boundarySpace) { - int consumptionTotal = 0; - int remainingCapacity = 0; + long consumptionEnergy = 0; + long remainingEnergy = 0; for (Entry entry : consumptionMap // .subMap(now, boundarySpace.proMoreThanCon) // .entrySet()) { - consumptionTotal = consumptionTotal + entry.getValue() - productionMap.get(entry.getKey()); + long duration = 15 * 60 * 1000; + long currentConsumptionEnergy = entry.getValue() * duration; + long currentProductionEnergy = productionMap.get(entry.getKey()) * duration; + + consumptionEnergy = consumptionEnergy + currentConsumptionEnergy - Math.max(0, currentProductionEnergy); } // remaining amount of energy that should be covered from grid. - remainingCapacity = consumptionTotal - availableCapacity; + remainingEnergy = Math.max(0, (consumptionEnergy - availableEnergy)); // Update Channels - this.channel(TimeOfUseTariffDischarge.ChannelId.TOTAL_CONSUMPTION).setNextValue(consumptionTotal); - this.channel(TimeOfUseTariffDischarge.ChannelId.REMAINING_CONSUMPTION).setNextValue(remainingCapacity); + // Values are divided by 3600 * 1000 to convert from [Wmsec] to [Wh]. + this.channel(TimeOfUseTariffDischarge.ChannelId.TOTAL_CONSUMPTION).setNextValue((consumptionEnergy / 3600000)); + this.channel(TimeOfUseTariffDischarge.ChannelId.REMAINING_CONSUMPTION) + .setNextValue((remainingEnergy / 3600000)); - return remainingCapacity; + return remainingEnergy; } /** * This method returns the list of periods, during which ESS is avoided for * consumption. * - * @param consumptionMap predicted consumption data along with time in - * {@link TreeMap} format. - * @param quarterlyPrices {@link TreeMap} consisting of hourly electricity - * prices along with time. - * @param remainingCapacity Amount of energy that should be covered from grid - * for consumption in night. - * @param boundarySpace the {@link BoundarySpace} + * @param consumptionMap predicted consumption data along with time in + * {@link TreeMap} format. + * @param quarterlyPrices {@link TreeMap} consisting of hourly electricity + * prices along with time. + * @param remainingEnergy Amount of energy that should be covered from grid for + * consumption in night. + * @param boundarySpace the {@link BoundarySpace} * @return {@link List} list of target periods to avoid charging/discharging of * the battery. */ private List calculateTargetPeriods(TreeMap consumptionMap, - TreeMap quarterlyPrices, Integer remainingCapacity, BoundarySpace boundarySpace) { + TreeMap quarterlyPrices, long remainingEnergy, BoundarySpace boundarySpace) { List targetHours = new ArrayList(); - ZonedDateTime currentQuarterHour = roundZonedDateTimeDownToMinutes( - ZonedDateTime.now(this.componentManager.getClock()), 15) // - .withZoneSameInstant(ZoneId.systemDefault()); + ZonedDateTime currentQuarterHour = TimeOfUseTariffUtils + .getNowRoundedDownToMinutes(this.componentManager.getClock(), 15) // + .withZoneSameInstant(ZoneId.systemDefault()); List> priceList = new ArrayList<>(quarterlyPrices // .subMap(currentQuarterHour, boundarySpace.proMoreThanCon) // .entrySet()); priceList.sort(Entry.comparingByValue()); + long duration = 15 * 60 * 1000; for (Entry entry : priceList) { targetHours.add(entry.getKey()); - remainingCapacity -= consumptionMap.get(entry.getKey()); + remainingEnergy = remainingEnergy - (consumptionMap.get(entry.getKey()) * duration); // checks if we have sufficient capacity. - if (remainingCapacity <= 0) { + if (remainingEnergy <= 0) { break; } } @@ -371,15 +559,8 @@ private List calculateTargetPeriods(TreeMap getPrices() { - try { - String jsonData = getJsonFromAwattar(); - TreeMap result = parsePrices(jsonData); - return result; - - } catch (Exception e) { - return new TreeMap<>(); - } - } - - /** - * Reads the JSON from aWATTar API. - * - * @return a JSON string - * @throws IOException on error - */ - protected static String getJsonFromAwattar() throws IOException { - OkHttpClient client = new OkHttpClient(); - Request request = new Request.Builder() // - .url(AWATTAR_API_URL) // - // aWATTar currently does not anymore require an Apikey. - // .header("Authorization", Credentials.basic(apikey, "")) // - .build(); - - Response response = client.newCall(request).execute(); - if (!response.isSuccessful()) { - throw new IOException("Unexpected code " + response); - } - return response.body().string(); - } - - /** - * Parse the aWATTar JSON to the Price Map. - * - * @param jsonData the aWATTar JSON - * @return the Price Map - * @throws OpenemsNamedException on error - */ - protected static TreeMap parsePrices(String jsonData) throws OpenemsNamedException { - TreeMap result = new TreeMap<>(); - - if (!jsonData.isEmpty()) { - - JsonObject line = JsonUtils.getAsJsonObject(JsonUtils.parse(jsonData)); - JsonArray data = JsonUtils.getAsJsonArray(line, "data"); - - for (JsonElement element : data) { - - float marketPrice = JsonUtils.getAsFloat(element, "marketprice"); - long startTimestampLong = JsonUtils.getAsLong(element, "start_timestamp"); - - // Converting Long time stamp to ZonedDateTime. - ZonedDateTime startTimeStamp = ZonedDateTime // - .ofInstant(Instant.ofEpochMilli(startTimestampLong), ZoneId.systemDefault()) - .truncatedTo(ChronoUnit.HOURS); - - // Adding the values in the Map. - result.put(startTimeStamp, marketPrice); - result.put(startTimeStamp.plusMinutes(15), marketPrice); - result.put(startTimeStamp.plusMinutes(30), marketPrice); - result.put(startTimeStamp.plusMinutes(45), marketPrice); - } - } - return result; - } -} \ No newline at end of file diff --git a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/tariff/TimeOfUseTariff.java b/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/tariff/TimeOfUseTariff.java deleted file mode 100644 index e03dccc8f8d..00000000000 --- a/io.openems.edge.controller.ess.timeofusetariff.discharge/src/io/openems/edge/controller/ess/timeofusetariff/discharge/tariff/TimeOfUseTariff.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.openems.edge.controller.ess.timeofusetariff.discharge.tariff; - -import java.time.ZonedDateTime; -import java.util.TreeMap; - -public interface TimeOfUseTariff { - - /** - * This method returns the Quarterly prices in a {@link TreeMap} format. - * - * @return prices {@link TreeMap} consisting of quarterly electricity prices - * along with time; or an empty Map on error - */ - public TreeMap getPrices(); - -} diff --git a/io.openems.edge.controller.ess.timeofusetariff.discharge/test/io/openems/edge/controller/ess/timeofusetariff/discharge/DummyTimeOfUseTariffProvider.java b/io.openems.edge.controller.ess.timeofusetariff.discharge/test/io/openems/edge/controller/ess/timeofusetariff/discharge/DummyTimeOfUseTariffProvider.java deleted file mode 100644 index 2416155c8b5..00000000000 --- a/io.openems.edge.controller.ess.timeofusetariff.discharge/test/io/openems/edge/controller/ess/timeofusetariff/discharge/DummyTimeOfUseTariffProvider.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.openems.edge.controller.ess.timeofusetariff.discharge; - -import java.time.ZonedDateTime; -import java.util.TreeMap; - -import io.openems.edge.controller.ess.timeofusetariff.discharge.tariff.TimeOfUseTariff; - -public class DummyTimeOfUseTariffProvider implements TimeOfUseTariff { - - private final ZonedDateTime now; - - public DummyTimeOfUseTariffProvider(ZonedDateTime now) { - this.now = now; - } - - @Override - public TreeMap getPrices() { - - TreeMap quarterlyPrices = new TreeMap<>(); - - Float[] prices = { 158.95f, 160.98f, 171.95f, 174.96f, // - 161.93f, 152f, 120.01f, 111.03f, // - 105.04f, 105f, 74.23f, 73.28f, // - 67.97f, 72.53f, 89.66f, 150.01f, // - 173.54f, 178.4f, 158.91f, 140.01f, // - 149.99f, 157.43f, 130.9f, 120.14f }; - - for (int i = 0; i < 24; i++) { - quarterlyPrices.put(now, prices[i]); - quarterlyPrices.put(now.plusMinutes(15), prices[i]); - quarterlyPrices.put(now.plusMinutes(30), prices[i]); - quarterlyPrices.put(now.plusMinutes(45), prices[i]); - } - - return quarterlyPrices; - } - -} diff --git a/io.openems.edge.controller.ess.timeofusetariff.discharge/test/io/openems/edge/controller/ess/timeofusetariff/discharge/MyConfig.java b/io.openems.edge.controller.ess.timeofusetariff.discharge/test/io/openems/edge/controller/ess/timeofusetariff/discharge/MyConfig.java index f52c0a9b78f..c2f64a79e0c 100644 --- a/io.openems.edge.controller.ess.timeofusetariff.discharge/test/io/openems/edge/controller/ess/timeofusetariff/discharge/MyConfig.java +++ b/io.openems.edge.controller.ess.timeofusetariff.discharge/test/io/openems/edge/controller/ess/timeofusetariff/discharge/MyConfig.java @@ -9,8 +9,10 @@ public class MyConfig extends AbstractComponentConfig implements Config { protected static class Builder { private String id; private String essId; + public Mode mode; private int maxStartHour; private int maxEndHour; + public DelayDischargeRiskLevel delayDischargeRiskLevel; private Builder() { } @@ -25,6 +27,11 @@ public Builder setEssId(String essId) { return this; } + public Builder setMode(Mode mode) { + this.mode = mode; + return this; + } + public Builder setMaxStartHour(int maxStartHour) { this.maxStartHour = maxStartHour; return this; @@ -38,6 +45,11 @@ public Builder setMaxEndHour(int maxEndHour) { public MyConfig build() { return new MyConfig(this); } + + public Builder setDelayDischargeRiskLevel(DelayDischargeRiskLevel delayDischargeRiskLevel) { + this.delayDischargeRiskLevel = delayDischargeRiskLevel; + return this; + } } /** @@ -61,6 +73,11 @@ public String ess_id() { return this.builder.essId; } + @Override + public Mode mode() { + return this.builder.mode; + } + @Override public int maxStartHour() { return this.builder.maxStartHour; @@ -75,4 +92,9 @@ public int maxEndHour() { public String ess_target() { return ConfigUtils.generateReferenceTargetFilter(this.id(), this.ess_id()); } + + @Override + public DelayDischargeRiskLevel delayDischargeRiskLevel() { + return this.builder.delayDischargeRiskLevel; + } } \ No newline at end of file diff --git a/io.openems.edge.controller.ess.timeofusetariff.discharge/test/io/openems/edge/controller/ess/timeofusetariff/discharge/TimeOfUseTariffDischargeTest.java b/io.openems.edge.controller.ess.timeofusetariff.discharge/test/io/openems/edge/controller/ess/timeofusetariff/discharge/TimeOfUseTariffDischargeTest.java index 7c826a7d712..dd0d32547ba 100644 --- a/io.openems.edge.controller.ess.timeofusetariff.discharge/test/io/openems/edge/controller/ess/timeofusetariff/discharge/TimeOfUseTariffDischargeTest.java +++ b/io.openems.edge.controller.ess.timeofusetariff.discharge/test/io/openems/edge/controller/ess/timeofusetariff/discharge/TimeOfUseTariffDischargeTest.java @@ -4,7 +4,6 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; -import java.util.Arrays; import org.junit.Test; @@ -18,6 +17,7 @@ import io.openems.edge.predictor.api.test.DummyPrediction48Hours; import io.openems.edge.predictor.api.test.DummyPredictor24Hours; import io.openems.edge.predictor.api.test.DummyPredictorManager; +import io.openems.edge.timeofusetariff.test.DummyTimeOfUseTariffProvider; public class TimeOfUseTariffDischargeTest { @@ -34,9 +34,12 @@ public class TimeOfUseTariffDischargeTest { private static final ChannelAddress TOTAL_CONSUMPTION = new ChannelAddress(CTRL_ID, "TotalConsumption"); private static final ChannelAddress REMAINING_CONSUMPTION = new ChannelAddress(CTRL_ID, "RemainingConsumption"); private static final ChannelAddress AVAILABLE_CAPACITY = new ChannelAddress(CTRL_ID, "AvailableCapacity"); + private static final ChannelAddress USABLE_CAPACITY = new ChannelAddress(CTRL_ID, "UsableCapacity"); private static final ChannelAddress QUATERLY_PRICES_TAKEN = new ChannelAddress(CTRL_ID, "QuaterlyPricesTaken"); private static final ChannelAddress TARGET_HOURS_CALCULATED = new ChannelAddress(CTRL_ID, "TargetHoursCalculated"); private static final ChannelAddress TARGET_HOURS_IS_EMPTY = new ChannelAddress(CTRL_ID, "TargetHoursIsEmpty"); + private static final ChannelAddress TARGET_HOURS = new ChannelAddress(CTRL_ID, "TargetHours"); + private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine"); /* * Default Prediction values @@ -96,8 +99,16 @@ public class TimeOfUseTariffDischargeTest { 3226, 2358, 1778, 1002, 455, 654, 534, 1587, 1638, 459, 330, 258, 368, 728, 1096, 878 // }; + private static final Float[] DEFAULT_HOURLY_PRICES = { 158.95f, 160.98f, 171.95f, 174.96f, // + 161.93f, 152f, 120.01f, 111.03f, // + 105.04f, 105f, 74.23f, 73.28f, // + 67.97f, 72.53f, 89.66f, 150.01f, // + 173.54f, 178.4f, 158.91f, 140.01f, // + 149.99f, 157.43f, 130.9f, 120.14f // + }; + @Test - public void ExecutesDuringMarketTimeTest() throws Exception { + public void executesBeforeMarketTimeTest() throws Exception { final TimeLeapClock clock = new TimeLeapClock(Instant.parse("2021-01-01T13:45:00.00Z"), ZoneOffset.UTC); final DummyComponentManager cm = new DummyComponentManager(clock); @@ -116,30 +127,85 @@ public void ExecutesDuringMarketTimeTest() throws Exception { final DummyPredictorManager predictorManager = new DummyPredictorManager(productionPredictor, consumptionPredictor); + // Price provider + final DummyTimeOfUseTariffProvider timeOfUseTariffProvider = DummyTimeOfUseTariffProvider + .fromHourlyPrices(ZonedDateTime.now(clock), DEFAULT_HOURLY_PRICES); + // Printing - System.out.println("Time: " + clock); - System.out.println(Arrays.toString(predictorManager - .get24HoursPrediction(ChannelAddress.fromString("_sum/ProductionActivePower")).getValues())); - System.out.println(Arrays.toString(predictorManager - .get24HoursPrediction(ChannelAddress.fromString("_sum/ConsumptionActivePower")).getValues())); + // System.out.println("Time: " + clock); + // System.out.println(Arrays.toString(predictorManager + // .get24HoursPrediction(ChannelAddress.fromString("_sum/ProductionActivePower")).getValues())); + // System.out.println(Arrays.toString(predictorManager + // .get24HoursPrediction(ChannelAddress.fromString("_sum/ConsumptionActivePower")).getValues())); - new ControllerTest(new TimeOfUseTariffDischargeImpl(new DummyTimeOfUseTariffProvider(ZonedDateTime.now(clock)))) // + new ControllerTest(new TimeOfUseTariffDischargeImpl()) // .addReference("predictorManager", predictorManager) // .addReference("componentManager", cm) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("timeOfUseTariff", timeOfUseTariffProvider) // .activate(MyConfig.create() // .setId(CTRL_ID) // .setEssId(ESS_ID) // .setMaxStartHour(8) // .setMaxEndHour(16) // + .setMode(Mode.AUTOMATIC) // + .setDelayDischargeRiskLevel(DelayDischargeRiskLevel.HIGH) // .build()) .next(new TestCase("Cycle - 1") // .output(AVAILABLE_CAPACITY, null) // - .output(QUATERLY_PRICES_TAKEN, null) // + .output(QUATERLY_PRICES_TAKEN, true) // .output(TARGET_HOURS_CALCULATED, false) // - .output(TARGET_HOURS_IS_EMPTY, true)) - .next(new TestCase("Cycle - 2") // + .output(TARGET_HOURS_IS_EMPTY, true) // + .output(STATE_MACHINE, StateMachine.STANDBY)); + } + + @Test + public void executesDuringMarketTimeTest() throws Exception { + + final TimeLeapClock clock = new TimeLeapClock(Instant.parse("2021-01-01T16:00:00.00Z"), ZoneOffset.UTC); + final DummyComponentManager cm = new DummyComponentManager(clock); + + // Predictions + final DummyPrediction48Hours productionPrediction = new DummyPrediction48Hours(DEFAULT_PRODUCTION_PREDICTION); + final DummyPrediction48Hours consumptionPrediction = new DummyPrediction48Hours(DEFAULT_CONSUMPTION_PREDICTION); + + // Predictors + final DummyPredictor24Hours productionPredictor = new DummyPredictor24Hours(PREDICTOR_ID, cm, + productionPrediction, "_sum/ProductionActivePower"); + final DummyPredictor24Hours consumptionPredictor = new DummyPredictor24Hours(PREDICTOR_ID, cm, + consumptionPrediction, "_sum/ConsumptionActivePower"); + + // PredictorManager + final DummyPredictorManager predictorManager = new DummyPredictorManager(productionPredictor, + consumptionPredictor); + + // Price provider + final DummyTimeOfUseTariffProvider timeOfUseTariffProvider = DummyTimeOfUseTariffProvider + .fromHourlyPrices(ZonedDateTime.now(clock), DEFAULT_HOURLY_PRICES); + + // Printing + // System.out.println("Time: " + clock); + // System.out.println(Arrays.toString(predictorManager + // .get24HoursPrediction(ChannelAddress.fromString("_sum/ProductionActivePower")).getValues())); + // System.out.println(Arrays.toString(predictorManager + // .get24HoursPrediction(ChannelAddress.fromString("_sum/ConsumptionActivePower")).getValues())); + + new ControllerTest(new TimeOfUseTariffDischargeImpl()) // + .addReference("predictorManager", predictorManager) // + .addReference("componentManager", cm) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("timeOfUseTariff", timeOfUseTariffProvider) // + .activate(MyConfig.create() // + .setId(CTRL_ID) // + .setEssId(ESS_ID) // + .setMaxStartHour(8) // + .setMaxEndHour(16) // + .setMode(Mode.AUTOMATIC) // + .setDelayDischargeRiskLevel(DelayDischargeRiskLevel.HIGH) // + .build()) + .next(new TestCase("Cycle - 1") // .timeleap(clock, 15, ChronoUnit.MINUTES)// .input(ESS_CAPACITY, 12000) // .input(ESS_SOC, 100) // @@ -147,19 +213,151 @@ public void ExecutesDuringMarketTimeTest() throws Exception { .output(QUATERLY_PRICES_TAKEN, true) // .output(TARGET_HOURS_CALCULATED, true)// .output(TARGET_HOURS_IS_EMPTY, false)// - .output(TOTAL_CONSUMPTION, 65791) // - .output(REMAINING_CONSUMPTION, 53791.0)) - .next(new TestCase("Cycle - 3") // + .output(TOTAL_CONSUMPTION, 15248) // + .output(REMAINING_CONSUMPTION, 3248.0) // + .output(STATE_MACHINE, StateMachine.ALLOWS_DISCHARGE)) + .next(new TestCase("Cycle - 2") // .timeleap(clock, 1, ChronoUnit.MINUTES)// .output(QUATERLY_PRICES_TAKEN, true) // .output(TARGET_HOURS_CALCULATED, false)// - .output(TARGET_HOURS_IS_EMPTY, false))// - .next(new TestCase("Cycle - 4") // + .output(TARGET_HOURS_IS_EMPTY, false) // + .output(STATE_MACHINE, StateMachine.ALLOWS_DISCHARGE)) + .next(new TestCase("Cycle - 3") // .timeleap(clock, 14, ChronoUnit.MINUTES)// .output(QUATERLY_PRICES_TAKEN, true) // .output(TARGET_HOURS_CALCULATED, true)// - .output(TARGET_HOURS_IS_EMPTY, false))// + .output(TARGET_HOURS_IS_EMPTY, false) // + .output(STATE_MACHINE, StateMachine.ALLOWS_DISCHARGE)) + .activate(MyConfig.create() // + .setId(CTRL_ID) // + .setEssId(ESS_ID) // + .setMaxStartHour(8) // + .setMaxEndHour(16) // + .setMode(Mode.OFF) // + .setDelayDischargeRiskLevel(DelayDischargeRiskLevel.HIGH) // + .build()) + .next(new TestCase("Cycle - 4") // + .output(QUATERLY_PRICES_TAKEN, true) // + .output(TARGET_HOURS_CALCULATED, false)// + .output(TARGET_HOURS_IS_EMPTY, true) // + .output(STATE_MACHINE, StateMachine.STANDBY)) + .next(new TestCase("Cycle - 5") // + .timeleap(clock, 15, ChronoUnit.MINUTES)// + .output(QUATERLY_PRICES_TAKEN, true) // + .output(TARGET_HOURS_CALCULATED, false)// + .output(TARGET_HOURS_IS_EMPTY, true) // + .output(STATE_MACHINE, StateMachine.STANDBY)); + } + + @Test + public void executesBeforeMidnight() throws Exception { + + final TimeLeapClock clock = new TimeLeapClock(Instant.parse("2021-01-01T21:00:00.00Z"), ZoneOffset.UTC); + final DummyComponentManager cm = new DummyComponentManager(clock); + + // Predictions + final DummyPrediction48Hours productionPrediction = new DummyPrediction48Hours(DEFAULT_PRODUCTION_PREDICTION); + final DummyPrediction48Hours consumptionPrediction = new DummyPrediction48Hours(DEFAULT_CONSUMPTION_PREDICTION); + + // Predictors + final DummyPredictor24Hours productionPredictor = new DummyPredictor24Hours(PREDICTOR_ID, cm, + productionPrediction, "_sum/ProductionActivePower"); + final DummyPredictor24Hours consumptionPredictor = new DummyPredictor24Hours(PREDICTOR_ID, cm, + consumptionPrediction, "_sum/ConsumptionActivePower"); + + // PredictorManager + final DummyPredictorManager predictorManager = new DummyPredictorManager(productionPredictor, + consumptionPredictor); - ; // + // Price provider + final DummyTimeOfUseTariffProvider timeOfUseTariffProvider = DummyTimeOfUseTariffProvider + .fromHourlyPrices(ZonedDateTime.now(clock), DEFAULT_HOURLY_PRICES); + + // Printing + // System.out.println("Time: " + clock); + // System.out.println(Arrays.toString(predictorManager + // .get24HoursPrediction(ChannelAddress.fromString("_sum/ProductionActivePower")).getValues())); + // System.out.println(Arrays.toString(predictorManager + // .get24HoursPrediction(ChannelAddress.fromString("_sum/ConsumptionActivePower")).getValues())); + + new ControllerTest(new TimeOfUseTariffDischargeImpl()) // + .addReference("predictorManager", predictorManager) // + .addReference("componentManager", cm) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("timeOfUseTariff", timeOfUseTariffProvider) // + .activate(MyConfig.create() // + .setId(CTRL_ID) // + .setEssId(ESS_ID) // + .setMaxStartHour(8) // + .setMaxEndHour(16) // + .setMode(Mode.AUTOMATIC) // + .setDelayDischargeRiskLevel(DelayDischargeRiskLevel.HIGH) // + .build()) + .next(new TestCase("Cycle - 1") // + .input(ESS_CAPACITY, 12000) // + .input(ESS_SOC, 100) // + .output(AVAILABLE_CAPACITY, 12000) // + .output(USABLE_CAPACITY, 12000) // + .output(REMAINING_CONSUMPTION, 0.0) // + .output(QUATERLY_PRICES_TAKEN, true) // + .output(TARGET_HOURS_CALCULATED, false) // + .output(TARGET_HOURS_IS_EMPTY, true) // + .output(STATE_MACHINE, StateMachine.ALLOWS_DISCHARGE)); + } + + @Test + public void executesAfterMidnight() throws Exception { + + final TimeLeapClock clock = new TimeLeapClock(Instant.parse("2021-01-01T11:00:00.00Z"), ZoneOffset.UTC); + final DummyComponentManager cm = new DummyComponentManager(clock); + + // Predictions + final DummyPrediction48Hours productionPrediction = new DummyPrediction48Hours(DEFAULT_PRODUCTION_PREDICTION); + final DummyPrediction48Hours consumptionPrediction = new DummyPrediction48Hours(DEFAULT_CONSUMPTION_PREDICTION); + + // Predictors + final DummyPredictor24Hours productionPredictor = new DummyPredictor24Hours(PREDICTOR_ID, cm, + productionPrediction, "_sum/ProductionActivePower"); + final DummyPredictor24Hours consumptionPredictor = new DummyPredictor24Hours(PREDICTOR_ID, cm, + consumptionPrediction, "_sum/ConsumptionActivePower"); + + // PredictorManager + final DummyPredictorManager predictorManager = new DummyPredictorManager(productionPredictor, + consumptionPredictor); + + // Price provider + final DummyTimeOfUseTariffProvider timeOfUseTariffProvider = DummyTimeOfUseTariffProvider + .fromHourlyPrices(ZonedDateTime.now(clock), DEFAULT_HOURLY_PRICES); + + // Printing + // System.out.println("Time: " + clock); + // System.out.println(Arrays.toString(predictorManager + // .get24HoursPrediction(ChannelAddress.fromString("_sum/ProductionActivePower")).getValues())); + // System.out.println(Arrays.toString(predictorManager + // .get24HoursPrediction(ChannelAddress.fromString("_sum/ConsumptionActivePower")).getValues())); + + new ControllerTest(new TimeOfUseTariffDischargeImpl()) // + .addReference("predictorManager", predictorManager) // + .addReference("componentManager", cm) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("timeOfUseTariff", timeOfUseTariffProvider) // + .activate(MyConfig.create() // + .setId(CTRL_ID) // + .setEssId(ESS_ID) // + .setMaxStartHour(8) // + .setMaxEndHour(16) // + .setMode(Mode.AUTOMATIC) // + .setDelayDischargeRiskLevel(DelayDischargeRiskLevel.HIGH) // + .build()) + .next(new TestCase("Cycle - 1") // + .output(AVAILABLE_CAPACITY, null) // + .output(USABLE_CAPACITY, null) // + .output(TARGET_HOURS, null) // + .output(QUATERLY_PRICES_TAKEN, true) // + .output(TARGET_HOURS_CALCULATED, false) // + .output(TARGET_HOURS_IS_EMPTY, true) // + .output(STATE_MACHINE, StateMachine.STANDBY)); } } diff --git a/io.openems.edge.controller.ess.timeofusetariff.discharge/test/io/openems/edge/controller/ess/timeofusetariff/discharge/tariff/AwattarProviderTest.java b/io.openems.edge.controller.ess.timeofusetariff.discharge/test/io/openems/edge/controller/ess/timeofusetariff/discharge/tariff/AwattarProviderTest.java deleted file mode 100644 index 94d943c4342..00000000000 --- a/io.openems.edge.controller.ess.timeofusetariff.discharge/test/io/openems/edge/controller/ess/timeofusetariff/discharge/tariff/AwattarProviderTest.java +++ /dev/null @@ -1,187 +0,0 @@ -package io.openems.edge.controller.ess.timeofusetariff.discharge.tariff; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.time.ZonedDateTime; -import java.util.TreeMap; - -import org.junit.Test; - -import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; - -public class AwattarProviderTest { - - @Test - public void nonEmptyStringTest() throws OpenemsNamedException { - // Parsing with custom data - TreeMap prices = AwattarProvider.parsePrices("{" - + " \"object\": \"list\"," - + " \"data\": [" - + " {" - + " \"start_timestamp\": 1632402000000," - + " \"end_timestamp\": 1632405600000," - + " \"marketprice\": 158.95," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632405600000," - + " \"end_timestamp\": 1632409200000," - + " \"marketprice\": 160.98," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632409200000," - + " \"end_timestamp\": 1632412800000," - + " \"marketprice\": 171.15," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632412800000," - + " \"end_timestamp\": 1632416400000," - + " \"marketprice\": 174.96," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632416400000," - + " \"end_timestamp\": 1632420000000," - + " \"marketprice\": 161.53," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632420000000," - + " \"end_timestamp\": 1632423600000," - + " \"marketprice\": 152," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632423600000," - + " \"end_timestamp\": 1632427200000," - + " \"marketprice\": 120.01," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632427200000," - + " \"end_timestamp\": 1632430800000," - + " \"marketprice\": 111.03," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632430800000," - + " \"end_timestamp\": 1632434400000," - + " \"marketprice\": 105.04," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632434400000," - + " \"end_timestamp\": 1632438000000," - + " \"marketprice\": 105," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632438000000," - + " \"end_timestamp\": 1632441600000," - + " \"marketprice\": 74.23," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632441600000," - + " \"end_timestamp\": 1632445200000," - + " \"marketprice\": 73.28," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632445200000," - + " \"end_timestamp\": 1632448800000," - + " \"marketprice\": 67.97," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632448800000," - + " \"end_timestamp\": 1632452400000," - + " \"marketprice\": 72.53," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632452400000," - + " \"end_timestamp\": 1632456000000," - + " \"marketprice\": 89.66," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632456000000," - + " \"end_timestamp\": 1632459600000," - + " \"marketprice\": 150.1," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632459600000," - + " \"end_timestamp\": 1632463200000," - + " \"marketprice\": 173.54," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632463200000," - + " \"end_timestamp\": 1632466800000," - + " \"marketprice\": 178.4," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632466800000," - + " \"end_timestamp\": 1632470400000," - + " \"marketprice\": 158.91," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632470400000," - + " \"end_timestamp\": 1632474000000," - + " \"marketprice\": 140.01," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632474000000," - + " \"end_timestamp\": 1632477600000," - + " \"marketprice\": 149.99," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632477600000," - + " \"end_timestamp\": 1632481200000," - + " \"marketprice\": 157.43," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632481200000," - + " \"end_timestamp\": 1632484800000," - + " \"marketprice\": 130.9," - + " \"unit\": \"Eur/MWh\"" - + " }," - + " {" - + " \"start_timestamp\": 1632484800000," - + " \"end_timestamp\": 1632488400000," - + " \"marketprice\": 120.14," - + " \"unit\": \"Eur/MWh\"" - + " }" - + " ]," - + " \"url\": \"/at/v1/marketdata\"" - + "}"); // - - // To check if the Map is not empty - assertFalse(prices.isEmpty()); - - // To check if the a value input from the string is present in map. - assertTrue(prices.containsValue(120.14f)); - - } - - @Test - public void emptyStringTest() throws OpenemsNamedException { - // Parsing with empty string - TreeMap prices = AwattarProvider.parsePrices(""); - - // To check if the map is empty. - assertTrue(prices.isEmpty()); - - } - -} diff --git a/io.openems.edge.core/bnd.bnd b/io.openems.edge.core/bnd.bnd index 554848e03bf..558f1f2f235 100644 --- a/io.openems.edge.core/bnd.bnd +++ b/io.openems.edge.core/bnd.bnd @@ -14,7 +14,8 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.scheduler.api,\ io.openems.edge.timedata.api,\ io.openems.wrapper.fastexcel,\ - io.openems.wrapper.sdnotify + io.openems.wrapper.okhttp,\ + io.openems.wrapper.sdnotify,\ -testpath: \ ${testpath},\ diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/HostImpl.java b/io.openems.edge.core/src/io/openems/edge/core/host/HostImpl.java index 0e81cb64b28..c31a7172815 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/host/HostImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/host/HostImpl.java @@ -29,8 +29,10 @@ import io.openems.edge.common.jsonapi.JsonApi; import io.openems.edge.common.user.User; import io.openems.edge.core.host.jsonrpc.ExecuteSystemCommandRequest; +import io.openems.edge.core.host.jsonrpc.ExecuteSystemUpdateRequest; import io.openems.edge.core.host.jsonrpc.GetNetworkConfigRequest; import io.openems.edge.core.host.jsonrpc.GetNetworkConfigResponse; +import io.openems.edge.core.host.jsonrpc.GetSystemUpdateStateRequest; import io.openems.edge.core.host.jsonrpc.SetNetworkConfigRequest; /** @@ -53,6 +55,7 @@ public class HostImpl extends AbstractOpenemsComponent implements Host, OpenemsC private final DiskSpaceWorker diskSpaceWorker; private final NetworkConfigurationWorker networkConfigurationWorker; private final UsbConfigurationWorker usbConfigurationWorker; + private final SystemUpdateHandler systemUpdateHandler; protected Config config; @@ -72,6 +75,7 @@ public HostImpl() { this.diskSpaceWorker = new DiskSpaceWorker(this); this.networkConfigurationWorker = new NetworkConfigurationWorker(this); this.usbConfigurationWorker = new UsbConfigurationWorker(this); + this.systemUpdateHandler = new SystemUpdateHandler(this); // Initialize 'Hostname' channel try { @@ -109,6 +113,7 @@ protected void deactivate() { this.diskSpaceWorker.deactivate(); this.networkConfigurationWorker.deactivate(); this.usbConfigurationWorker.deactivate(); + this.systemUpdateHandler.deactivate(); super.deactivate(); } @@ -116,6 +121,8 @@ protected void deactivate() { @Override public CompletableFuture handleJsonrpcRequest(User user, JsonrpcRequest request) throws OpenemsNamedException { + user.assertRoleIsAtLeast("handleJsonrpcRequest", Role.OWNER); + switch (request.getMethod()) { case GetNetworkConfigRequest.METHOD: @@ -124,6 +131,12 @@ public CompletableFuture handleJsonrpcRequest( case SetNetworkConfigRequest.METHOD: return this.handleSetNetworkConfigRequest(user, SetNetworkConfigRequest.from(request)); + case GetSystemUpdateStateRequest.METHOD: + return this.handleGetSystemUpdateStateRequest(user, GetSystemUpdateStateRequest.from(request)); + + case ExecuteSystemUpdateRequest.METHOD: + return this.handleExecuteSystemUpdateRequest(user, ExecuteSystemUpdateRequest.from(request)); + case ExecuteSystemCommandRequest.METHOD: return this.handleExecuteCommandRequest(user, ExecuteSystemCommandRequest.from(request)); @@ -143,7 +156,6 @@ public CompletableFuture handleJsonrpcRequest( private CompletableFuture handleGetNetworkConfigRequest(User user, GetNetworkConfigRequest request) throws OpenemsNamedException { user.assertRoleIsAtLeast("handleGetNetworkConfigRequest", Role.OWNER); - NetworkConfiguration config = this.operatingSystem.getNetworkConfiguration(); GetNetworkConfigResponse response = new GetNetworkConfigResponse(request.getId(), config); return CompletableFuture.completedFuture(response); @@ -160,7 +172,6 @@ private CompletableFuture handleGetNetworkConfigRequest( private CompletableFuture handleSetNetworkConfigRequest(User user, SetNetworkConfigRequest request) throws OpenemsNamedException { user.assertRoleIsAtLeast("handleSetNetworkConfigRequest", Role.OWNER); - NetworkConfiguration oldNetworkConfiguration = this.operatingSystem.getNetworkConfiguration(); this.operatingSystem.handleSetNetworkConfigRequest(oldNetworkConfiguration, request); @@ -170,6 +181,36 @@ private CompletableFuture handleSetNetworkConfigRequest( return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.getId())); } + /** + * Handles a {@link GetSystemUpdateStateRequest}. + * + * @param user the User + * @param request the {@link GetSystemUpdateStateRequest} + * @return the Future JSON-RPC Response + * @throws OpenemsNamedException on error + */ + private CompletableFuture handleGetSystemUpdateStateRequest(User user, + GetSystemUpdateStateRequest request) throws OpenemsNamedException { + user.assertRoleIsAtLeast("handleGetSystemUpdateStateRequest", Role.OWNER); + + return this.systemUpdateHandler.handleGetSystemUpdateStateRequest(request); + } + + /** + * Handles a {@link ExecuteSystemUpdateRequest}. + * + * @param user the User + * @param request the {@link ExecuteSystemUpdateRequest} + * @return the Future JSON-RPC Response + * @throws OpenemsNamedException on error + */ + private CompletableFuture handleExecuteSystemUpdateRequest(User user, + ExecuteSystemUpdateRequest request) throws OpenemsNamedException { + user.assertRoleIsAtLeast("handleSystemUpdateRequest", Role.OWNER); + + return this.systemUpdateHandler.handleExecuteSystemUpdateRequest(request); + } + /** * Handles a ExecuteCommandRequest. * @@ -181,7 +222,6 @@ private CompletableFuture handleSetNetworkConfigRequest( private CompletableFuture handleExecuteCommandRequest(User user, ExecuteSystemCommandRequest request) throws OpenemsNamedException { user.assertRoleIsAtLeast("handleExecuteCommandRequest", Role.ADMIN); - return this.operatingSystem.handleExecuteCommandRequest(request); } @@ -205,7 +245,7 @@ protected void logError(Logger log, String message) { * * @param execCommand the command * @return the parsed result - * @throws IOException + * @throws IOException on error */ private static String execReadToString(String execCommand) throws IOException { try (Scanner s = new Scanner(Runtime.getRuntime().exec(execCommand).getInputStream()).useDelimiter("\\A")) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemDebianSystemd.java b/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemDebianSystemd.java index 0a373bd458a..f0b5c6b34af 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemDebianSystemd.java +++ b/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemDebianSystemd.java @@ -226,12 +226,21 @@ public CompletableFuture handleExecuteCommandReque try { Process proc; if (request.getUsername().isPresent() && request.getPassword().isPresent()) { + // Authenticate with user and password proc = Runtime.getRuntime().exec(new String[] { // "/bin/bash", "-c", "--", // "echo " + request.getPassword().get() + " | " // - + " /usr/bin/sudo -Sk -p '' -u \"" + request.getUsername().get() + "\" " // - + "-- " + request.getCommand() }); + + " /usr/bin/sudo -Sk -p '' -u \"" + request.getUsername().get() + "\" -- " // + + request.getCommand() }); + } else if (request.getPassword().isPresent()) { + // Authenticate with password (user must have 'sudo' permissions) + proc = Runtime.getRuntime().exec(new String[] { // + "/bin/bash", "-c", "--", // + "echo " + request.getPassword().get() + " | " // + + " /usr/bin/sudo -Sk -p '' -- " // + + request.getCommand() }); } else { + // No authentication: run as current user proc = Runtime.getRuntime().exec(new String[] { // "/bin/bash", "-c", "--", request.getCommand() }); } @@ -249,7 +258,7 @@ public CompletableFuture handleExecuteCommandReque String[] stdout = new String[] { // "Command [" + request.getCommand() + "] executed in background...", // "Check system logs for more information." }; - result.complete(new ExecuteSystemCommandResponse(request.getId(), stdout, new String[0])); + result.complete(new ExecuteSystemCommandResponse(request.getId(), stdout, new String[0], 0)); } else { /* @@ -268,7 +277,8 @@ public CompletableFuture handleExecuteCommandReque stderr.addAll(stderrFuture.get(1, TimeUnit.SECONDS)); result.complete(new ExecuteSystemCommandResponse(request.getId(), // stdout.toArray(new String[stdout.size()]), // - stderr.toArray(new String[stderr.size()]) // + stderr.toArray(new String[stderr.size()]), // + proc.exitValue() // )); } catch (Throwable e) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/SystemUpdateHandler.java b/io.openems.edge.core/src/io/openems/edge/core/host/SystemUpdateHandler.java new file mode 100644 index 00000000000..c7bb3ba526c --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/host/SystemUpdateHandler.java @@ -0,0 +1,291 @@ +package io.openems.edge.core.host; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.common.OpenemsOEM; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; +import io.openems.common.utils.ThreadPoolUtils; +import io.openems.edge.core.host.jsonrpc.ExecuteSystemCommandRequest; +import io.openems.edge.core.host.jsonrpc.ExecuteSystemCommandResponse; +import io.openems.edge.core.host.jsonrpc.ExecuteSystemUpdateRequest; +import io.openems.edge.core.host.jsonrpc.GetSystemUpdateStateRequest; +import io.openems.edge.core.host.jsonrpc.GetSystemUpdateStateResponse; +import io.openems.edge.core.host.jsonrpc.GetSystemUpdateStateResponse.UpdateState; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +/** + * This Worker reads the actual network configuration and stores it in the Host + * configuration. + */ +public class SystemUpdateHandler { + + private static final int SHORT_TIMEOUT = 10; // [s] + + private static final String MARKER_BASH_TRACE = "+-+-+-+ "; + private static final String MARKER = "#-#-#-# "; + private static final String MARKER_FINISHED = MARKER + "FINISHED "; + private static final String MARKER_FINISHED_SUCCESSFULLY = MARKER_FINISHED + "SUCCESSFULLY"; + private static final String MARKER_FINISHED_WITH_ERROR = MARKER_FINISHED + "WITH ERROR"; + + private final Logger log = LoggerFactory.getLogger(SystemUpdateHandler.class); + private final HostImpl parent; + private final UpdateState updateState = new UpdateState(); + + private final ExecutorService executor = Executors.newCachedThreadPool(); + + public SystemUpdateHandler(HostImpl parent) { + this.parent = parent; + } + + /** + * Deactivates the {@link SystemUpdateHandler}. + */ + public void deactivate() { + ThreadPoolUtils.shutdownAndAwaitTermination(this.executor, 1); + } + + /** + * Handles a {@link GetSystemUpdateStateRequest}. + * + * @param request the {@link GetSystemUpdateStateRequest} + * @return the Future JSON-RPC Response + * @throws OpenemsNamedException on error + */ + protected CompletableFuture handleGetSystemUpdateStateRequest( + GetSystemUpdateStateRequest request) throws OpenemsNamedException { + final CompletableFuture result = new CompletableFuture(); + + if (this.updateState.isRunning()) { + result.complete(GetSystemUpdateStateResponse.isRunning(request.getId(), this.updateState)); + + } else { + // Read currently installed version + this.executeSystemCommand("dpkg-query --showformat='${Version}' --show fems", SHORT_TIMEOUT) + .whenComplete((response, ex) -> { + if (ex != null) { + result.completeExceptionally(ex); + return; + } + String[] stdout = response.getStdout(); + if (stdout.length < 1) { + result.completeExceptionally(ex /* todo */); + return; + } + String currentVersion = stdout[0]; + + // Read latest version + try { + String latestVersion = this.download(OpenemsOEM.SYSTEM_UPDATE_LATEST_VERSION_URL).trim(); + result.complete( + GetSystemUpdateStateResponse.from(request.getId(), currentVersion, latestVersion)); + return; + + } catch (IOException e) { + result.completeExceptionally(e); + return; + } + }); + } + return result; + } + + private String download(String url) throws IOException { + OkHttpClient client = new OkHttpClient(); + Request r = new Request.Builder() // + .url(url) // + .build(); + try (Response resp = client.newCall(r).execute()) { + if (!resp.isSuccessful()) { + throw new IOException(resp.message()); + } + + return resp.body().string().trim(); + } + } + + private CompletableFuture executeSystemCommand(String command, int timeoutSeconds) + throws OpenemsNamedException { + final boolean runInBackground = false; + final Optional username = Optional.empty(); + final Optional password = Optional.empty(); + return this.parent.operatingSystem.handleExecuteCommandRequest( + new ExecuteSystemCommandRequest(command, runInBackground, timeoutSeconds, username, password)); + } + + /** + * Handles a {@link ExecuteSystemUpdateRequest} and makes sure the update is + * executed only once. + * + * @param request the {@link ExecuteSystemUpdateRequest} + * @return the {@link JsonrpcResponseSuccess} + * @throws OpenemsNamedException on error + */ + protected CompletableFuture handleExecuteSystemUpdateRequest( + ExecuteSystemUpdateRequest request) throws OpenemsNamedException { + if (this.updateState.isRunning()) { + throw new OpenemsException("System Update is already running"); + + } else { + this.updateState.reset(); + this.updateState.setRunning(true); + this.updateState.setDebugMode(request.isDebug()); + + CompletableFuture result = new CompletableFuture(); + this.executor.execute(() -> { + GetSystemUpdateStateResponse response = GetSystemUpdateStateResponse.isRunning(request.getId(), + this.updateState); + try { + this.executeUpdate(result); + this.updateState.setPercentCompleted(100); + this.updateState.addLog("# Finished successfully"); + result.complete(response); + + } catch (Exception e) { + this.updateState.addLog("# Finished with error"); + this.parent.logError(this.log, "Error while executing System Update: " + e.getMessage()); + e.printStackTrace(); + result.completeExceptionally(new OpenemsException(e.getMessage() + "\n" + response.toString())); + } + this.updateState.setRunning(false); + }); + return result; + } + } + + private void executeUpdate(CompletableFuture result) throws Exception { + Path logFile = null; + Path scriptFile = null; + try { + logFile = Files.createTempFile("system-update-log-", null); + this.updateState.addLog("# Creating Logfile [" + logFile + "]"); + + // Download Update Script to temporary file + this.updateState.addLog("# Downloading update script [" + OpenemsOEM.SYSTEM_UPDATE_SCRIPT_URL + "]"); + scriptFile = Files.createTempFile("system-update-script-", null); + String script = // + "export PS4='" + MARKER_BASH_TRACE + "${LINENO} '; \n" // + + this.download(OpenemsOEM.SYSTEM_UPDATE_SCRIPT_URL); + Files.write(scriptFile, script.getBytes(StandardCharsets.US_ASCII)); + + final float totalNumberOfLines = script.split("\r\n|\r|\n").length; + + // Make sure 'at' command is available + if (this.executeSystemCommand("which at", SHORT_TIMEOUT).get().getStdout().length == 0) { + this.updateState.addLog("# Command 'at' is missing"); + + { + this.updateState.addLog("# Executing 'apt-get update'"); + ExecuteSystemCommandResponse response = this.executeSystemCommand("apt-get update", 3600).get(); + this.updateState.addLog("'apt-get update'", response); + if (response.getExitCode() != 0) { + throw new Exception("'apt-get update' failed"); + } + } + { + this.updateState.addLog("# Executing 'apt-get install at'"); + ExecuteSystemCommandResponse response = this.executeSystemCommand("apt-get -y install at", 3600) + .get(); + this.updateState.addLog("'apt-get install at'", response); + if (response.getExitCode() != 0) { + throw new Exception("'apt-get install at' failed"); + } + } + } + + // Execute Update Script + { + this.updateState.addLog("# Executing update script [" + scriptFile + "]"); + ExecuteSystemCommandResponse response = this.executeSystemCommand("echo '" // + + " {" // + + " bash -ex " + scriptFile.toString() + "; " // + + " if [ $? -eq 0 ]; then " // + + " echo \"" + MARKER_FINISHED_SUCCESSFULLY + "\"; " // + + " else " // + + " echo \"" + MARKER_FINISHED_WITH_ERROR + "\"; " // + + " fi; " // + + " } >" + logFile.toAbsolutePath() + " 2>&1' " // + + "| at now", SHORT_TIMEOUT).get(); + if (response.getExitCode() != 0) { + throw new Exception("Executing update script [" + scriptFile + "] failed"); + } + } + + // Read log output + boolean keepReading = true; + try (final BufferedReader reader = Files.newBufferedReader(logFile, StandardCharsets.US_ASCII)) { + while (keepReading) { + final String line = reader.readLine(); + if (line == null) { + // wait until there is more of the file for us to read + Thread.sleep(500); + continue; + } + + final String log; + if (line.startsWith(MARKER_BASH_TRACE)) { + /* + * Update percent completed + reformat commands + */ + String lineWithNumber = line.substring(MARKER_BASH_TRACE.length()); + int lengthOfNumber = lineWithNumber.indexOf(" "); + // Parse number of line and calculate percent completed + int numberOfLine = Integer.parseInt(lineWithNumber.substring(0, lengthOfNumber)); + this.updateState.setPercentCompleted(Math.round((numberOfLine * 100) / totalNumberOfLines)); + // Strip number of line and prefix with '#' + log = "# " + lineWithNumber.substring(lengthOfNumber); + + } else if (line.contains(MARKER_FINISHED)) { + /* + * Finished update script + */ + if (line.contains(MARKER_FINISHED_WITH_ERROR)) { + // Finished with error + throw new Exception("Error while executing update script"); + } + // Else: finished successfully + break; + + } else { + log = line; + } + + if (log != null) { + this.updateState.addLog(log); + } + } + } + + } finally { + // Cleanup + if (logFile != null) { + try { + Files.delete(logFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (scriptFile != null) { + try { + Files.delete(scriptFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemCommandResponse.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemCommandResponse.java index 9575007074f..ba8937e964d 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemCommandResponse.java +++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemCommandResponse.java @@ -19,7 +19,8 @@ * "id": "UUID", * "result": { * "stdout": string[], - * "stderr": string[] + * "stderr": string[], + * "exitcode": number (exit code of application: 0 = successful; otherwise error) * } * } * @@ -28,11 +29,13 @@ public class ExecuteSystemCommandResponse extends JsonrpcResponseSuccess { private final String[] stdout; private final String[] stderr; + private final int exitcode; - public ExecuteSystemCommandResponse(UUID id, String[] stdout, String[] stderr) { + public ExecuteSystemCommandResponse(UUID id, String[] stdout, String[] stderr, int exitcode) { super(id); this.stdout = stdout; this.stderr = stderr; + this.exitcode = exitcode; } @Override @@ -48,7 +51,20 @@ public JsonObject getResult() { return JsonUtils.buildJsonObject() // .add("stdout", stdout) // .add("stderr", stderr) // + .addProperty("exitcode", this.exitcode) // .build(); } + public String[] getStdout() { + return this.stdout; + } + + public String[] getStderr() { + return this.stderr; + } + + public int getExitCode() { + return this.exitcode; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemUpdateRequest.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemUpdateRequest.java new file mode 100644 index 00000000000..90b25ecdccd --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemUpdateRequest.java @@ -0,0 +1,65 @@ + +package io.openems.edge.core.host.jsonrpc; + +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.utils.JsonUtils; + +/** + * Executes a System Update. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "executeSystemUpdate",
+ *   "params": {
+ *     "isDebug": boolean
+ *   }
+ * }
+ * 
+ */ +public class ExecuteSystemUpdateRequest extends JsonrpcRequest { + + public static final String METHOD = "executeSystemUpdate"; + + /** + * Parses a generic {@link JsonrpcRequest} to a + * {@link ExecuteSystemUpdateRequest}. + * + * @param r the {@link JsonrpcRequest} + * @return the {@link ExecuteSystemUpdateRequest} + * @throws OpenemsNamedException on error + */ + public static ExecuteSystemUpdateRequest from(JsonrpcRequest r) throws OpenemsNamedException { + JsonObject p = r.getParams(); + boolean isDebug = JsonUtils.getAsBoolean(p, "isDebug"); + return new ExecuteSystemUpdateRequest(r, isDebug); + } + + private final boolean isDebug; + + public ExecuteSystemUpdateRequest(boolean isDebug) { + super(METHOD); + this.isDebug = isDebug; + } + + private ExecuteSystemUpdateRequest(JsonrpcRequest request, boolean isDebug) { + super(request, METHOD); + this.isDebug = isDebug; + } + + @Override + public JsonObject getParams() { + return JsonUtils.buildJsonObject() // + .addProperty("isDebug", this.isDebug) // + .build(); + } + + public boolean isDebug() { + return this.isDebug; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetSystemUpdateStateRequest.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetSystemUpdateStateRequest.java new file mode 100644 index 00000000000..e4fdb48b156 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetSystemUpdateStateRequest.java @@ -0,0 +1,54 @@ + +package io.openems.edge.core.host.jsonrpc; + +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.utils.JsonUtils; + +/** + * Gets the System Update State. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "getSystemUpdateState",
+ *   "params": {
+ *   	"debug"?: boolean
+ *   }
+ * }
+ * 
+ */ +public class GetSystemUpdateStateRequest extends JsonrpcRequest { + + public static final String METHOD = "getSystemUpdateState"; + + /** + * Parses a generic {@link JsonrpcRequest} to a + * {@link GetSystemUpdateStateRequest}. + * + * @param r the {@link JsonrpcRequest} + * @return the {@link GetSystemUpdateStateRequest} + * @throws OpenemsNamedException on error + */ + public static GetSystemUpdateStateRequest from(JsonrpcRequest r) throws OpenemsNamedException { + return new GetSystemUpdateStateRequest(r); + } + + public GetSystemUpdateStateRequest() { + super(METHOD); + } + + private GetSystemUpdateStateRequest(JsonrpcRequest request) { + super(request, METHOD); + } + + @Override + public JsonObject getParams() { + return JsonUtils.buildJsonObject() // + .build(); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetSystemUpdateStateResponse.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetSystemUpdateStateResponse.java new file mode 100644 index 00000000000..9fbc200aa97 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetSystemUpdateStateResponse.java @@ -0,0 +1,280 @@ +package io.openems.edge.core.host.jsonrpc; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonObject; + +import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; +import io.openems.common.types.SemanticVersion; +import io.openems.common.utils.JsonUtils; +import io.openems.common.utils.JsonUtils.JsonArrayBuilder; +import io.openems.edge.core.host.SystemUpdateHandler; + +/** + * JSON-RPC Response to "getSystemUpdateState" Request. + * + *

+ * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "result": {
+ *     // State is unknown (e.g. internet connection limited by firewall)
+ *     "unknown"?: {
+ *     }
+ *     // Latest version is already installed
+ *     "updated"?: {
+ *       "version": "XXXX"
+ *     }
+ *     // Update is available
+ *     "available"?: {
+ *       "currentVersion": "XXXX",
+ *       "latestVersion": "XXXX"
+ *     },
+ *     // Update is currently running
+ *     "running"?: {
+ *       "percentCompleted": number,
+ *       "logs": string[]
+ *     }
+ *   }
+ * }
+ * 
+ */ +public class GetSystemUpdateStateResponse extends JsonrpcResponseSuccess { + + private static interface SystemUpdateState { + public JsonObject toJsonObject(); + } + + private static class Unknown implements SystemUpdateState { + public Unknown() { + } + + @Override + public JsonObject toJsonObject() { + return JsonUtils.buildJsonObject() // + .add("unknown", new JsonObject()) // + .build(); + } + } + + private static class Updated implements SystemUpdateState { + private final SemanticVersion version; + + public Updated(SemanticVersion version) { + this.version = version; + } + + @Override + public JsonObject toJsonObject() { + return JsonUtils.buildJsonObject() // + .add("updated", JsonUtils.buildJsonObject() // + .addProperty("version", this.version.toString()) // + .build()) // + .build(); + } + } + + private static class Available implements SystemUpdateState { + private final SemanticVersion currentVersion; + private final SemanticVersion latestVersion; + + public Available(SemanticVersion currentVersion, SemanticVersion latestVersion) { + this.currentVersion = currentVersion; + this.latestVersion = latestVersion; + } + + @Override + public JsonObject toJsonObject() { + return JsonUtils.buildJsonObject() // + .add("available", JsonUtils.buildJsonObject() // + .addProperty("currentVersion", this.currentVersion.toString()) // + .addProperty("latestVersion", this.latestVersion.toString()) // + .build()) // + .build(); + } + } + + public static class UpdateState { + private final Logger log = LoggerFactory.getLogger(SystemUpdateHandler.class); + + private final AtomicBoolean isRunning = new AtomicBoolean(false); + private final AtomicInteger percentCompleted = new AtomicInteger(0); + private List logs = new ArrayList<>(); + private boolean debugMode = false; + + public UpdateState() { + this.reset(); + } + + public void setRunning(boolean isRunning) { + this.isRunning.set(isRunning); + } + + public boolean isRunning() { + return this.isRunning.get(); + } + + public void setPercentCompleted(int percentCompleted) { + this.percentCompleted.set(percentCompleted); + } + + public synchronized void setDebugMode(boolean debugMode) { + this.debugMode = debugMode; + } + + /** + * Adds a line to the log. + * + * @param line the line + */ + public void addLog(String line) { + synchronized (this.log) { + this.log.info("System-Update: " + line); + if (this.debugMode) { + this.logs.add(line); + } + } + } + + /** + * Adds a {@link Exception} to the log. + * + * @param e the {@link Exception} + */ + public void addLog(Exception e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + this.addLog(pw.toString()); + } + + /** + * Adds a {@link ExecuteSystemCommandResponse} with a label to the log. + * + * @param label the label + * @param response the {@link ExecuteSystemCommandResponse} + */ + public void addLog(String label, ExecuteSystemCommandResponse response) { + synchronized (this.log) { + String[] stdout = response.getStdout(); + if (stdout.length > 0) { + this.addLog(label + ": STDOUT"); + for (String line : stdout) { + this.addLog(label + ": " + line); + } + } + String[] stderr = response.getStderr(); + if (stderr.length > 0) { + this.addLog(label + ": STDERR"); + for (String line : stderr) { + this.addLog(label + ": " + line); + } + } + if (response.getExitCode() == 0) { + this.addLog(label + ": FINISHED SUCCESSFULLY"); + } else { + this.addLog(label + ": FINISHED WITH ERROR CODE [" + response.getExitCode() + "]"); + } + } + } + + protected JsonObject toJsonObject() { + JsonArrayBuilder logs = JsonUtils.buildJsonArray(); + synchronized (this.log) { + for (String log : this.logs) { + logs.add(log); + } + } + return JsonUtils.buildJsonObject() // + .add("running", JsonUtils.buildJsonObject() // + .addProperty("percentCompleted", this.percentCompleted.get()) // + .add("logs", logs.build()) // + .build()) // + .build(); + } + + /** + * Resets the {@link UpdateState} object. + */ + public synchronized void reset() { + this.isRunning.set(false); + this.percentCompleted.set(0); + synchronized (this.log) { + this.logs.clear(); + } + } + } + + private static class Running implements SystemUpdateState { + private final UpdateState updateState; + + public Running(UpdateState updateState) { + this.updateState = updateState; + } + + @Override + public JsonObject toJsonObject() { + return this.updateState.toJsonObject(); + } + } + + private final SystemUpdateState state; + + /** + * Builds a {@link GetSystemUpdateStateResponse} for {@link Running} state. + * + * @param id the request ID + * @param updateState the {@link UpdateState} + * @return the {@link GetSystemUpdateStateResponse} + */ + public static GetSystemUpdateStateResponse isRunning(UUID id, UpdateState updateState) { + return new GetSystemUpdateStateResponse(id, new Running(updateState)); + } + + /** + * Builds a {@link GetSystemUpdateStateResponse} for {@link Unknown}, + * {@link Updated} or {@link Available} state. + * + * @param id the request ID + * @param currentVersion the current version + * @param latestVersion the latest version + * @return the {@link GetSystemUpdateStateResponse} + */ + public static GetSystemUpdateStateResponse from(UUID id, String currentVersion, String latestVersion) { + final SemanticVersion current; + final SemanticVersion latest; + try { + current = SemanticVersion.fromString(currentVersion); + latest = SemanticVersion.fromString(latestVersion); + } catch (NumberFormatException e) { + return new GetSystemUpdateStateResponse(id, new Unknown()); + } + if (current.isAtLeast(latest)) { + return new GetSystemUpdateStateResponse(id, new Updated(current)); + } else { + return new GetSystemUpdateStateResponse(id, new Available(current, latest)); + } + } + + private GetSystemUpdateStateResponse(UUID id, SystemUpdateState state) { + super(id); + this.state = state; + } + + @Override + public JsonObject getResult() { + return this.state.toJsonObject(); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/SystemUpdateRequest.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/SystemUpdateRequest.java new file mode 100644 index 00000000000..8e8d76ba775 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/SystemUpdateRequest.java @@ -0,0 +1,45 @@ +package io.openems.edge.core.host.jsonrpc; + +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.base.JsonrpcRequest; + +/** + * Represents a JSON-RPC Request to execute a system update. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "systemUpdate",
+ *   "params": {}
+ * }
+ * 
+ */ +public class SystemUpdateRequest extends JsonrpcRequest { + + public static final String METHOD = "systemUpdate"; + + /** + * Parses a generic {@link JsonrpcRequest} to a {@link SystemUpdateRequest}. + * + * @param r the {@link JsonrpcRequest} + * @return the {@link SystemUpdateRequest} + * @throws OpenemsNamedException on error + */ + public static SystemUpdateRequest from(JsonrpcRequest r) throws OpenemsException { + return new SystemUpdateRequest(r); + } + + private SystemUpdateRequest(JsonrpcRequest request) { + super(request, METHOD); + } + + @Override + public JsonObject getParams() { + return new JsonObject(); + } + +} diff --git a/io.openems.edge.core/test/io/openems/edge/core/host/HostImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/host/HostImplTest.java new file mode 100644 index 00000000000..1af427c587b --- /dev/null +++ b/io.openems.edge.core/test/io/openems/edge/core/host/HostImplTest.java @@ -0,0 +1,56 @@ +package io.openems.edge.core.host; + +/** + * This is not a real JUnit test, but can be used to mock the JsonRpc-Calls from + * UI. + */ +public class HostImplTest { + +// private final static User OWNER = new DummyUser("owner", "owner", Role.OWNER); +// +// @Test +// public void test() throws OpenemsException, Exception { +// final DummyConfigurationAdmin cm = new DummyConfigurationAdmin(); +// cm.getOrCreateEmptyConfiguration(Host.SINGLETON_SERVICE_PID); +// final HostImpl sut = new HostImpl(); +// +// new ComponentTest(sut) // +// .addReference("cm", cm) // +// .activate(MyConfig.create() // +// .setNetworkConfiguration("") // +// .setUsbConfiguration("") // +// .build()); +// +// { +// CompletableFuture future = sut.handleJsonrpcRequest(OWNER, +// new GetSystemUpdateStateRequest()); +// Thread.sleep(1000); +// JsonrpcResponseSuccess response = future.get(); +// System.out.println(response.getResult()); +// } +// { +// CompletableFuture future = sut.handleJsonrpcRequest(OWNER, +// new ExecuteSystemUpdateRequest(true)); +// for (int i = 0; i < 2; i++) { +// Thread.sleep(500); +// CompletableFuture future2 = sut.handleJsonrpcRequest(OWNER, +// new GetSystemUpdateStateRequest()); +// JsonrpcResponseSuccess response = future2.get(); +// System.out.println(response.getResult()); +// } +// +// JsonrpcResponseSuccess response = future.get(); +// System.out.println("FINISHED"); +// JsonUtils.prettyPrint(response.getResult()); +// } +// +// Thread.sleep(2000); +// CompletableFuture future2 = sut.handleJsonrpcRequest(OWNER, +// new GetSystemUpdateStateRequest()); +// JsonrpcResponseSuccess response = future2.get(); +// System.out.println(response.getResult()); +// +// Thread.sleep(10000); +// } + +} diff --git a/io.openems.edge.core/test/io/openems/edge/core/host/MyConfig.java b/io.openems.edge.core/test/io/openems/edge/core/host/MyConfig.java new file mode 100644 index 00000000000..3c20a88b8ab --- /dev/null +++ b/io.openems.edge.core/test/io/openems/edge/core/host/MyConfig.java @@ -0,0 +1,57 @@ +package io.openems.edge.core.host; + +import io.openems.edge.common.host.Host; +import io.openems.edge.common.test.AbstractComponentConfig; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + public String networkConfiguration = null; + public String usbConfiguration = null; + + private Builder() { + } + + public Builder setNetworkConfiguration(String networkConfiguration) { + this.networkConfiguration = networkConfiguration; + return this; + } + + public Builder setUsbConfiguration(String usbConfiguration) { + this.usbConfiguration = usbConfiguration; + return this; + } + + public MyConfig build() { + return new MyConfig(this); + } + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfig(Builder builder) { + super(Config.class, Host.SINGLETON_COMPONENT_ID); + this.builder = builder; + } + + @Override + public String networkConfiguration() { + return this.builder.networkConfiguration; + } + + @Override + public String usbConfiguration() { + return this.builder.usbConfiguration; + } + +} \ No newline at end of file diff --git a/io.openems.edge.evcs.cluster/src/io/openems/edge/evcs/cluster/EvcsClusterPeakShaving.java b/io.openems.edge.evcs.cluster/src/io/openems/edge/evcs/cluster/EvcsClusterPeakShaving.java index d3d4678aaaf..98925e7c334 100644 --- a/io.openems.edge.evcs.cluster/src/io/openems/edge/evcs/cluster/EvcsClusterPeakShaving.java +++ b/io.openems.edge.evcs.cluster/src/io/openems/edge/evcs/cluster/EvcsClusterPeakShaving.java @@ -28,6 +28,7 @@ import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.common.sum.Sum; import io.openems.edge.ess.api.ManagedSymmetricEss; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.meter.api.AsymmetricMeter; @@ -66,7 +67,7 @@ public class EvcsClusterPeakShaving extends AbstractEvcsCluster implements Opene protected Sum sum; @Reference - private ManagedSymmetricEss ess; + private SymmetricEss ess; @Reference private SymmetricMeter meter; @@ -182,12 +183,18 @@ public int getMaximumPowerToDistribute() { int maxEssDischarge = 0; long maxAvailableStoragePower = 0; - maxEssDischarge = this.ess.getAllowedDischargePower().orElse(0); + if(this.ess instanceof ManagedSymmetricEss) { + maxEssDischarge = ((ManagedSymmetricEss)this.ess).getAllowedDischargePower().orElse(0); + // TODO: Use PowerComponent + } else { + maxEssDischarge = this.ess.getMaxApparentPower().orElse(0); + } + if (this.config.enable_secure_ess_discharge()) { maxEssDischarge = this.getSecureEssDischargePower(maxEssDischarge); this.channel(AbstractEvcsCluster.ChannelId.USED_ESS_MAXIMUM_DISCHARGE_POWER).setNextValue(maxEssDischarge); } - // TODO: Should I use power component here + // TODO: Calculate the available ESS charge power, depending on a specific ESS // component (e.g. If there is a ESS cluster) diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/Config.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/Config.java index 0e44b13f3a3..32e35968062 100644 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/Config.java +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/Config.java @@ -23,12 +23,12 @@ @AttributeDefinition(name = "IP-Address", description = "The IP address of the charging station. If the charger has two connectors, the second/slave evcs has the IP 192.168.25.31.", required = true) String ip() default "192.168.25.30"; - @AttributeDefinition(name = "Minimum power", description = "Minimum current of the Charger in mA.", required = true) + @AttributeDefinition(name = "Minimum hardware current", description = "Minimum current of the Charger in mA.", required = true) int minHwCurrent() default 6000; - @AttributeDefinition(name = "Maximum power", description = "Maximum current of the Charger in mA.", required = true) + @AttributeDefinition(name = "Maximum hardware current", description = "Maximum current of the Charger in mA.", required = true) int maxHwCurrent() default 32000; - + String webconsole_configurationFactory_nameHint() default "EVCS Hardy Barth [{id}]"; } \ No newline at end of file diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarth.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarth.java index a75a28975a3..a3dd6349081 100644 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarth.java +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarth.java @@ -7,8 +7,11 @@ import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.BooleanReadChannel; import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.type.TypeUtils; -interface HardyBarth { +public interface HardyBarth { + + public static final double SCALE_FACTOR_MINUS_1 = 0.1; public enum ChannelId implements io.openems.edge.common.channel.ChannelId { @@ -40,6 +43,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { // SALIA RAW_SALIA_CHARGE_MODE(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "chargemode"), // + RAW_SALIA_CHANGE_METER(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "changemeter"), // RAW_SALIA_AUTHMODE(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "authmode"), // RAW_SALIA_FIRMWARESTATE(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "firmwarestate"), // RAW_SALIA_FIRMWAREPROGRESS(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "firmwareprogress"), // @@ -73,23 +77,34 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { }), "secc", "port0", "metering", "meter", "available"), // // METERING - POWER - RAW_ACTIVE_POWER_L1(Doc.of(OpenemsType.LONG), "secc", "port0", "metering", "power", "active", "ac", "l1", - "actual"), // - RAW_ACTIVE_POWER_L2(Doc.of(OpenemsType.LONG), "secc", "port0", "metering", "power", "active", "ac", "l2", - "actual"), // - RAW_ACTIVE_POWER_L3(Doc.of(OpenemsType.LONG), "secc", "port0", "metering", "power", "active", "ac", "l2", - "actual"), // + RAW_ACTIVE_POWER_L1(Doc.of(OpenemsType.LONG).unit(Unit.WATT), (value) -> { + Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, value); + return TypeUtils.getAsType(OpenemsType.LONG, TypeUtils.multiply(doubleValue, SCALE_FACTOR_MINUS_1)); + }, "secc", "port0", "metering", "power", "active", "ac", "l1", "actual"), // + + RAW_ACTIVE_POWER_L2(Doc.of(OpenemsType.LONG).unit(Unit.WATT), (value) -> { + Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, value); + return TypeUtils.getAsType(OpenemsType.LONG, TypeUtils.multiply(doubleValue, SCALE_FACTOR_MINUS_1)); + }, "secc", "port0", "metering", "power", "active", "ac", "l2", "actual"), // + + RAW_ACTIVE_POWER_L3(Doc.of(OpenemsType.LONG).unit(Unit.WATT), (value) -> { + Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, value); + return TypeUtils.getAsType(OpenemsType.LONG, TypeUtils.multiply(doubleValue, SCALE_FACTOR_MINUS_1)); + }, "secc", "port0", "metering", "power", "active", "ac", "l2", "actual"), // // METERING - CURRENT - RAW_ACTIVE_CURRENT_L1(Doc.of(OpenemsType.STRING), "secc", "port0", "metering", "current", "ac", "l1", "actual"), // - RAW_ACTIVE_CURRENT_L2(Doc.of(OpenemsType.STRING), "secc", "port0", "metering", "current", "ac", "l2", "actual"), // - RAW_ACTIVE_CURRENT_L3(Doc.of(OpenemsType.STRING), "secc", "port0", "metering", "current", "ac", "l3", "actual"), // + RAW_ACTIVE_CURRENT_L1(Doc.of(OpenemsType.LONG).unit(Unit.MILLIAMPERE), "secc", "port0", "metering", "current", + "ac", "l1", "actual"), // + RAW_ACTIVE_CURRENT_L2(Doc.of(OpenemsType.LONG).unit(Unit.MILLIAMPERE), "secc", "port0", "metering", "current", + "ac", "l2", "actual"), // + RAW_ACTIVE_CURRENT_L3(Doc.of(OpenemsType.LONG).unit(Unit.MILLIAMPERE), "secc", "port0", "metering", "current", + "ac", "l3", "actual"), // // METERING - ENERGY - RAW_ACTIVE_ENERGY_TOTAL(Doc.of(OpenemsType.DOUBLE), "secc", "port0", "metering", "energy", "active_total", - "actual"), // - RAW_ACTIVE_ENERGY_EXPORT(Doc.of(OpenemsType.DOUBLE), "secc", "port0", "metering", "energy", "active_export", - "actual"), // + RAW_ACTIVE_ENERGY_TOTAL(Doc.of(OpenemsType.DOUBLE).unit(Unit.WATT_HOURS), "secc", "port0", "metering", "energy", + "active_total", "actual"), // + RAW_ACTIVE_ENERGY_EXPORT(Doc.of(OpenemsType.DOUBLE).unit(Unit.WATT_HOURS), "secc", "port0", "metering", + "energy", "active_export", "actual"), // // EMERGENCY SHUTDOWN RAW_EMERGENCY_SHUTDOWN(Doc.of(OpenemsType.STRING), "secc", "port0", "emergency_shutdown"), // diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadWorker.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadWorker.java index a59d95fe793..fe4f9a9015e 100644 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadWorker.java +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadWorker.java @@ -9,12 +9,14 @@ import io.openems.common.utils.JsonUtils; import io.openems.common.worker.AbstractCycleWorker; import io.openems.edge.common.channel.ChannelId; +import io.openems.edge.common.type.TypeUtils; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.Status; public class HardyBarthReadWorker extends AbstractCycleWorker { private final HardyBarthImpl parent; + private int chargingFinishedCounter = 0; public HardyBarthReadWorker(HardyBarthImpl parent) { this.parent = parent; @@ -59,15 +61,6 @@ protected void forever() throws OpenemsNamedException { */ private void setEvcsChannelIds(JsonElement json) { - // CHARGE_POWER - Long chargePower = (Long) this.getValueFromJson(Evcs.ChannelId.CHARGE_POWER, json, (value) -> { - if (value == null) { - return null; - } - return Math.round((Integer) value * 0.1); - }, "secc", "port0", "metering", "power", "active_total", "actual"); - this.parent._setChargePower(chargePower == null ? null : chargePower.intValue()); - // ENERGY_SESSION Double energy = (Double) this.getValueFromJson(Evcs.ChannelId.ENERGY_SESSION, OpenemsType.STRING, json, (value) -> { @@ -77,7 +70,8 @@ private void setEvcsChannelIds(JsonElement json) { Double rawEnergy = null; String[] chargedata = value.toString().split("\\|"); if (chargedata.length == 3) { - rawEnergy = Double.parseDouble(chargedata[2]) * 1000; + Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, chargedata[2]); + rawEnergy = doubleValue * 1000; } return rawEnergy; @@ -86,35 +80,33 @@ private void setEvcsChannelIds(JsonElement json) { // ACTIVE_CONSUMPTION_ENERGY Long activeConsumptionEnergy = (Long) this.getValueFromJson(Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY, json, - (value) -> (long) (Double.parseDouble(value.toString()) * 0.1), "secc", "port0", "metering", "energy", - "active_import", "actual"); // + (value) -> { + Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, value); + return TypeUtils.getAsType(OpenemsType.LONG, doubleValue); + + }, "secc", "port0", "metering", "energy", "active_import", "actual"); // this.parent._setActiveConsumptionEnergy(activeConsumptionEnergy); // PHASES - Double powerL1 = (Double) this.getValueFromJson(HardyBarth.ChannelId.RAW_ACTIVE_POWER_L1, json, - (value) -> Double.parseDouble(value.toString()) * 0.1, "secc", "port0", "metering", "power", "active", - "ac", "l1", "actual"); - Double powerL2 = (Double) this.getValueFromJson(HardyBarth.ChannelId.RAW_ACTIVE_POWER_L2, json, - (value) -> Double.parseDouble(value.toString()) * 0.1, "secc", "port0", "metering", "power", "active", - "ac", "l2", "actual"); - Double powerL3 = (Double) this.getValueFromJson(HardyBarth.ChannelId.RAW_ACTIVE_POWER_L3, json, - (value) -> Double.parseDouble(value.toString()) * 0.1, "secc", "port0", "metering", "power", "active", - "ac", "l3", "actual"); + Long powerL1 = (Long) this.getValueForChannel(HardyBarth.ChannelId.RAW_ACTIVE_POWER_L1, json); + Long powerL2 = (Long) this.getValueForChannel(HardyBarth.ChannelId.RAW_ACTIVE_POWER_L2, json); + Long powerL3 = (Long) this.getValueForChannel(HardyBarth.ChannelId.RAW_ACTIVE_POWER_L3, json); + Integer phases = null; if (powerL1 != null && powerL2 != null && powerL3 != null) { - Double sum = powerL1 + powerL2 + powerL3; + Long sum = powerL1 + powerL2 + powerL3; - if (sum > 300) { + if (sum > 900) { phases = 0; - if (powerL1 >= 100) { + if (powerL1 >= 300) { phases += 1; } - if (powerL2 >= 100) { + if (powerL2 >= 300) { phases += 1; } - if (powerL3 >= 100) { + if (powerL3 >= 300) { phases += 1; } } @@ -124,24 +116,61 @@ private void setEvcsChannelIds(JsonElement json) { this.parent.debugLog("Used phases: " + phases); } + this.parent._setMinimumHardwarePower(this.parent.config.minHwCurrent() / 1000 * 3 * 230); + this.parent._setMaximumHardwarePower(this.parent.config.maxHwCurrent() / 1000 * 3 * 230); + + // CHARGE_POWER + Long chargePowerLong = (Long) this.getValueFromJson(Evcs.ChannelId.CHARGE_POWER, json, (value) -> { + Integer integerValue = TypeUtils.getAsType(OpenemsType.INTEGER, value); + if (integerValue == null) { + return null; + } + + long activePower = Math.round(integerValue * HardyBarth.SCALE_FACTOR_MINUS_1); + + // Ignore the consumption of the charger itself + return activePower < 100 ? 0 : activePower; + }, "secc", "port0", "metering", "power", "active_total", "actual"); + + // + this.parent._setChargePower(chargePowerLong == null ? null : chargePowerLong.intValue()); + // STATUS Status status = (Status) this.getValueFromJson(HardyBarth.ChannelId.RAW_CHARGE_STATUS_CHARGEPOINT, json, (value) -> { + String stringValue = TypeUtils.getAsType(OpenemsType.STRING, value); + if (stringValue == null) { + return Status.UNDEFINED; + } + Status rawStatus = Status.UNDEFINED; - switch (value.toString()) { + switch (stringValue) { case "A": rawStatus = Status.NOT_READY_FOR_CHARGING; break; case "B": - rawStatus = Status.CHARGING_REJECTED; - if (this.parent.getSetChargePowerLimit().orElse(0) > this.parent.getMinimumHardwarePower() - .orElse(0)) { - rawStatus = Status.CHARGING_FINISHED; + rawStatus = Status.READY_FOR_CHARGING; + + // Detect if the car is full + int chargePower = chargePowerLong == null ? 0 : chargePowerLong.intValue(); + if (this.parent.getSetChargePowerLimit().orElse(0) >= this.parent.getMinimumHardwarePower() + .orElse(0) && chargePower <= 0) { + + if (this.chargingFinishedCounter >= 90) { + rawStatus = Status.CHARGING_FINISHED; + } else { + this.chargingFinishedCounter++; + } + } else { + this.chargingFinishedCounter = 0; + + // Charging rejected because we are forcing to pause charging + if (this.parent.getSetChargePowerLimit().orElse(0) == 0) { + rawStatus = Status.CHARGING_REJECTED; + } } break; case "C": - rawStatus = Status.CHARGING; - break; case "D": rawStatus = Status.CHARGING; break; @@ -153,12 +182,28 @@ private void setEvcsChannelIds(JsonElement json) { rawStatus = Status.UNDEFINED; break; } + if (stringValue.equals("B")) { + this.chargingFinishedCounter = 0; + } return rawStatus; }, "secc", "port0", "ci", "charge", "cp", "status"); this.parent._setStatus(status); } + /** + * Call the getValueFromJson with the detailed information of the channel. + * + * @param channelId Channel that value will be detect. + * @param json Whole JSON path, where the JsonElement for the given channel + * is located. + * @return Value of the last JsonElement by running through the specified JSON + * path. + */ + private Object getValueForChannel(HardyBarth.ChannelId channelId, JsonElement json) { + return this.getValueFromJson(channelId, json, channelId.converter, channelId.getJsonPaths()); + } + /** * Call the getValueFromJson without a divergent type in the raw json. * diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthWriteHandler.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthWriteHandler.java index 13219634a01..1099e364bab 100644 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthWriteHandler.java +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthWriteHandler.java @@ -24,7 +24,7 @@ public class HardyBarthWriteHandler implements Runnable { /* * Minimum pause between two consecutive writes. */ - private static final int WRITE_INTERVAL_SECONDS = 30; + private static final int WRITE_INTERVAL_SECONDS = 1; public HardyBarthWriteHandler(HardyBarthImpl parent) { this.parent = parent; @@ -38,6 +38,8 @@ public void run() { } this.setManualMode(); + this.setHeartbeat(); + // this.enableExternalMeter(); this.setEnergyLimit(); this.setPower(); } @@ -65,6 +67,60 @@ private void setManualMode() { } } + /** + * Set heartbeat. + * + *

+ * Sets the heartbeat to on or off. + */ + private void setHeartbeat() { + // The internal heartbeat is currently too fast - it is not enough to write + // every second by default. We have to disable it to run the evcs + // properly. + // TODO: The manufacturer must be asked if it is possible to read the heartbeat + // status so that we can check if the heartbeat is really disabled and if the + // heartbeat time can be increased to be able to use this feature. + + try { + this.parent.api.sendPutRequest("/api/secc", "salia/heartbeat", "off"); + } catch (OpenemsNamedException e) { + e.printStackTrace(); + } + } + + /** + * Enable external meter. + * + *

+ * Enables the external meter if not set. + */ + // TODO: Set the external meter to true because it's disabled per default. + // Not usable for now, because we haven't an update process defined and + // this REST Entry is only available with a beta firmware + // (http://salia.echarge.de/firmware/firmware_1.37.8_beta.image) or the next + // higher stable version. Be aware that the REST call and the update should not + // be called every cycle + /* + * private void enableExternalMeter() { + * + * BooleanReadChannel channelChargeMode = + * this.parent.channel(HardyBarth.ChannelId.RAW_SALIA_CHANGE_METER); + * Optional valueOpt = channelChargeMode.value().asOptional(); if + * (valueOpt.isPresent()) { if (!valueOpt.get().equals(true)) { // Enable + * external meter try { + * this.parent.debugLog("Enable external meter of HardyBarth " + + * this.parent.id()); JsonElement result = + * this.parent.api.sendPutRequest("/api/secc", "salia/changemeter", + * "enable | /dev/ttymxc0 | klefr | 9600 | none | 1"); + * this.parent.debugLog(result.toString()); + * + * if (result.toString().equals("{\"result\":\"ok\"}")) { // Reboot the charger + * this.parent.debugLog("Reboot of HardyBarth " + this.parent.id()); JsonElement + * resultReboot = this.parent.api.sendPutRequest("/api/secc", + * "salia/servicereboot", "1"); this.parent.debugLog(resultReboot.toString()); } + * } catch (OpenemsNamedException e) { e.printStackTrace(); } } } } + */ + /** * Sets the current from SET_CHARGE_POWER channel. * @@ -128,17 +184,28 @@ private void setPower() { */ private void setTarget(int current, int power) { try { + JsonElement resultPause; + if (current > 0) { + // Send stop pause request + resultPause = this.parent.api.sendPutRequest("/api/secc", "salia/pausecharging", "" + 0); + this.parent.debugLog("Wake up HardyBarth " + this.parent.alias() + " from the pause"); + } else { + // Send pause charging request + resultPause = this.parent.api.sendPutRequest("/api/secc", "salia/pausecharging", "" + 1); + this.parent.debugLog("Setting HardyBarth " + this.parent.alias() + " to pause"); + } + // Send charge power limit JsonElement result = this.parent.api.sendPutRequest("/api/secc", "grid_current_limit", "" + current); // Set results - this.parent._setSetChargePowerLimit(current); - this.parent.debugLog(result.toString()); + this.parent._setSetChargePowerLimit(power); + this.parent.debugLog("Pause: " + resultPause.toString()); + this.parent.debugLog("SetActivePower: " + result.toString()); // Prepare next write this.nextCurrentWrite = LocalDateTime.now().plusSeconds(WRITE_INTERVAL_SECONDS); this.lastCurrent = current; - this.parent._setSetChargePowerLimit(power); } catch (OpenemsNamedException e) { e.printStackTrace(); } diff --git a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/KebaKeContact.java b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/KebaKeContact.java index 0594c8592a7..456f26c3689 100644 --- a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/KebaKeContact.java +++ b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/KebaKeContact.java @@ -81,7 +81,7 @@ void activate(ComponentContext context, Config config) throws UnknownHostExcepti this.channel(KebaChannelId.ALIAS).setNextValue(config.alias()); - this.ip = Inet4Address.getByName(config.ip()); + this.ip = Inet4Address.getByName(config.ip().trim()); this.config = config; this._setPowerPrecision(0.23); diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java index 462768b9996..fce0e05867f 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java @@ -288,18 +288,20 @@ private void setBatteryLimits(Battery battery) throws OpenemsNamedException { if ((bmsChargeMaxCurrent.isDefined() && !Objects.equals(bmsChargeMaxCurrent.get(), setChargeMaxCurrent)) || (bmsDischargeMaxCurrent.isDefined() && !Objects.equals(bmsDischargeMaxCurrent.get(), setDischargeMaxCurrent)) - || (bmsSocUnderMin.isDefined() && !Objects.equals(bmsSocUnderMin.get(), setSocUnderMin))) { + || (bmsSocUnderMin.isDefined() && !Objects.equals(bmsSocUnderMin.get(), setSocUnderMin)) + || (bmsOfflineSocUnderMin.isDefined() + && !Objects.equals(bmsOfflineSocUnderMin.get(), setOfflineSocUnderMin))) { // Update is required this.logInfo(this.log, "Update for PV-Master BMS Registers is required." // + " Voltages" // - + " [Discharge" + bmsDischargeMinVoltage.get() + " -> " + setDischargeMinVoltage + "]" // - + " [Charge" + bmsChargeMaxVoltage + " -> " + setChargeMaxVoltage + "]" // + + " [Discharge " + bmsDischargeMinVoltage.get() + " -> " + setDischargeMinVoltage + "]" // + + " [Charge " + bmsChargeMaxVoltage.get() + " -> " + setChargeMaxVoltage + "]" // + " Currents " // + " [Charge " + bmsChargeMaxCurrent.get() + " -> " + setChargeMaxCurrent + "]" // + " [Discharge " + bmsDischargeMaxCurrent.get() + " -> " + setDischargeMaxCurrent + "]" // - + " MinSoc " // - + " [" + bmsSocUnderMin.get() + " -> " + setSocUnderMin + "] " // - + " [" + bmsOfflineSocUnderMin.get() + " -> " + setOfflineSocUnderMin + "]"); + + " MinSoc [" // + + " [On-Grid " + bmsSocUnderMin.get() + " -> " + setSocUnderMin + "] " // + + " [Off-Grid " + bmsOfflineSocUnderMin.get() + " -> " + setOfflineSocUnderMin + "]"); this.writeToChannel(GoodWe.ChannelId.BATTERY_PROTOCOL_ARM, 287); // EMS-Mode diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java index 659a948343e..c8e64305848 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java @@ -991,7 +991,10 @@ protected final ModbusProtocol defineModbusProtocol() throws OpenemsException { ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // [400*N,480*N] m(GoodWe.ChannelId.BMS_DISCHARGE_MAX_CURRENT, new UnsignedWordElement(45355), ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // [0,1000] - m(GoodWe.ChannelId.BMS_SOC_UNDER_MIN, new UnsignedWordElement(45356))), // [0,100] + m(GoodWe.ChannelId.BMS_SOC_UNDER_MIN, new UnsignedWordElement(45356)), // [0,100] + m(GoodWe.ChannelId.BMS_OFFLINE_DISCHARGE_MIN_VOLTAGE, new UnsignedWordElement(45357), + ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // ), // + m(GoodWe.ChannelId.BMS_OFFLINE_SOC_UNDER_MIN, new UnsignedWordElement(45358))), // // Safety Parameters new FC16WriteRegistersTask(45400, // diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java index f4f2b11f964..3af174eda81 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java @@ -339,7 +339,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId STATE_51(Doc.of(Level.INFO).text("Battery lock")), // STATE_52(Doc.of(Level.INFO).text("Discharge circuit fault")), // STATE_53(Doc.of(Level.INFO).text("Charging circuit failure")), // - STATE_54(Doc.of(Level.INFO).text("Communication failure 2")), // + STATE_54(Doc.of(Level.INFO).text("Communication failure")), // STATE_55(Doc.of(Level.INFO).text("Cell high temperature 3")), // STATE_56(Doc.of(Level.INFO).text("Discharge under voltage 3")), // STATE_57(Doc.of(Level.INFO).text("Charging under voltage 3")), // @@ -605,7 +605,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .unit(Unit.VOLT) // .accessMode(AccessMode.READ_WRITE)), BMS_OFFLINE_SOC_UNDER_MIN(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // + .unit(Unit.PERCENT) // .accessMode(AccessMode.READ_WRITE)), CLEAR_BATTERY_SETTING(Doc.of(OpenemsType.INTEGER) // .accessMode(AccessMode.WRITE_ONLY)), diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterImpl.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterImpl.java index acee4cb41f3..be0de1cfbbb 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterImpl.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterImpl.java @@ -151,13 +151,11 @@ private void calculateEnergy() { this.calculateProductionEnergy.update(null); this.calculateConsumptionEnergy.update(null); } else if (activePower > 0) { - // Sell-To-Grid - this.calculateProductionEnergy.update(0); - this.calculateConsumptionEnergy.update(activePower * -1); - } else { - // Buy-From-Grid this.calculateProductionEnergy.update(activePower); this.calculateConsumptionEnergy.update(0); + } else { + this.calculateProductionEnergy.update(0); + this.calculateConsumptionEnergy.update(activePower * -1); } } diff --git a/io.openems.edge.meter.pqplus.umd97/readme.adoc b/io.openems.edge.meter.pqplus.umd97/readme.adoc deleted file mode 100644 index f8ebe109fa0..00000000000 --- a/io.openems.edge.meter.pqplus.umd97/readme.adoc +++ /dev/null @@ -1,9 +0,0 @@ -= PQ Plus UMD 97 Meter - -Applies also to UMD 96, UMD 97, UMD 98, UMD 807, UMD 701, UMD 704, UMD 705, UMD 706, UMD 707, UMD 709, UMD 710, UMD 913, UMC 26 - -Implemented Natures:: -- SymmetricMeter -- AsymmetricMeter - -https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.meter.pqplus.umd97[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.meter.pqplus.umd97/.classpath b/io.openems.edge.meter.pqplus/.classpath similarity index 100% rename from io.openems.edge.meter.pqplus.umd97/.classpath rename to io.openems.edge.meter.pqplus/.classpath diff --git a/io.openems.edge.meter.pqplus.umd97/.gitignore b/io.openems.edge.meter.pqplus/.gitignore similarity index 100% rename from io.openems.edge.meter.pqplus.umd97/.gitignore rename to io.openems.edge.meter.pqplus/.gitignore diff --git a/io.openems.edge.meter.pqplus.umd97/.project b/io.openems.edge.meter.pqplus/.project similarity index 90% rename from io.openems.edge.meter.pqplus.umd97/.project rename to io.openems.edge.meter.pqplus/.project index a91bad24c5e..d8762fd65ea 100644 --- a/io.openems.edge.meter.pqplus.umd97/.project +++ b/io.openems.edge.meter.pqplus/.project @@ -1,6 +1,6 @@ - io.openems.edge.meter.pqplus.umd97 + io.openems.edge.meter.pqplus diff --git a/io.openems.edge.meter.pqplus.umd97/bnd.bnd b/io.openems.edge.meter.pqplus/bnd.bnd similarity index 86% rename from io.openems.edge.meter.pqplus.umd97/bnd.bnd rename to io.openems.edge.meter.pqplus/bnd.bnd index 9fbaa113ab4..38050940f32 100644 --- a/io.openems.edge.meter.pqplus.umd97/bnd.bnd +++ b/io.openems.edge.meter.pqplus/bnd.bnd @@ -1,4 +1,4 @@ -Bundle-Name: OpenEMS Edge Meter PQ Plus UMD 97 +Bundle-Name: OpenEMS Edge Meter PQ Plus Bundle-Vendor: FENECON GmbH Bundle-License: https://opensource.org/licenses/EPL-2.0 Bundle-Version: 1.0.0.${tstamp} diff --git a/io.openems.edge.meter.pqplus.umd97/doc/PQPlus-Com-Protokoll_Modbus.pdf b/io.openems.edge.meter.pqplus/doc/PQPlus-Com-Protokoll_Modbus.pdf similarity index 100% rename from io.openems.edge.meter.pqplus.umd97/doc/PQPlus-Com-Protokoll_Modbus.pdf rename to io.openems.edge.meter.pqplus/doc/PQPlus-Com-Protokoll_Modbus.pdf diff --git a/io.openems.edge.meter.pqplus/doc/pqplus-com-protokoll-modbus_3_0.pdf b/io.openems.edge.meter.pqplus/doc/pqplus-com-protokoll-modbus_3_0.pdf new file mode 100644 index 00000000000..80d83dcacd5 Binary files /dev/null and b/io.openems.edge.meter.pqplus/doc/pqplus-com-protokoll-modbus_3_0.pdf differ diff --git a/io.openems.edge.meter.pqplus/readme.adoc b/io.openems.edge.meter.pqplus/readme.adoc new file mode 100644 index 00000000000..079f9436692 --- /dev/null +++ b/io.openems.edge.meter.pqplus/readme.adoc @@ -0,0 +1,15 @@ += PQ-Plus Meters UMD96 | UMD97 + +Implemented Natures:: +- SymmetricMeter +- AsymmetricMeter + +Details of the meter is present in + +UMD 96 +- https://www.pq-plus.de/produkte/hardwarekomponenten/umd-96/ + +UMD 97 +- https://www.pq-plus.de/en/products/hardware-components/umd-97/ + +https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.meter.pqplus[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/Config.java b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/Config.java new file mode 100644 index 00000000000..ee9703714c0 --- /dev/null +++ b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/Config.java @@ -0,0 +1,35 @@ +package io.openems.edge.meter.pqplus.umd96; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +import io.openems.edge.meter.api.MeterType; + +@ObjectClassDefinition(// + name = "Meter PQ-Plus UMD 96", // + description = "Implements the PQ-Plus UMD 96 power meter.") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "meter0"; + + @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") + String alias() default ""; + + @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") + boolean enabled() default true; + + @AttributeDefinition(name = "Meter-Type", description = "What is measured by this Meter?") + MeterType type() default MeterType.PRODUCTION; + + @AttributeDefinition(name = "Modbus-ID", description = "ID of Modbus bridge.") + String modbus_id() default "modbus0"; + + @AttributeDefinition(name = "Modbus Unit-ID", description = "The Unit-ID of the Modbus device. Defaults to '1' for Modbus/TCP.") + int modbusUnitId() default 1; + + @AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.") + String Modbus_target() default "(enabled=true)"; + + String webconsole_configurationFactory_nameHint() default "Meter PQ-Plus UMD 96 [{id}]"; +} \ No newline at end of file diff --git a/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96.java b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96.java new file mode 100644 index 00000000000..c41869fbd9a --- /dev/null +++ b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96.java @@ -0,0 +1,24 @@ +package io.openems.edge.meter.pqplus.umd96; + +import io.openems.edge.bridge.modbus.api.ModbusComponent; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.meter.api.AsymmetricMeter; +import io.openems.edge.meter.api.SymmetricMeter; + +public interface MeterPqplusUmd96 extends SymmetricMeter, AsymmetricMeter, ModbusComponent, OpenemsComponent { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + ; + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + public Doc doc() { + return this.doc; + } + } + +} \ No newline at end of file diff --git a/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96Impl.java b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96Impl.java new file mode 100644 index 00000000000..5246de7d901 --- /dev/null +++ b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96Impl.java @@ -0,0 +1,134 @@ +package io.openems.edge.meter.pqplus.umd96; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.osgi.service.metatype.annotations.Designate; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; +import io.openems.edge.bridge.modbus.api.BridgeModbus; +import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; +import io.openems.edge.bridge.modbus.api.ModbusComponent; +import io.openems.edge.bridge.modbus.api.ModbusProtocol; +import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; +import io.openems.edge.bridge.modbus.api.element.FloatDoublewordElement; +import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.taskmanager.Priority; +import io.openems.edge.meter.api.AsymmetricMeter; +import io.openems.edge.meter.api.MeterType; +import io.openems.edge.meter.api.SymmetricMeter; + +/** + * Implements the PQ Plus UMD 96 meter. + * + *

    + *
  • https://www.pq-plus.de/produkte/hardwarekomponenten/umd-96/ + *
  • https://www.pq-plus.de/site/assets/files/2795/pqplus-com-protokoll-modbus_3_0.pdf + *
+ */ +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "Meter.PqPlus.UMD96", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class MeterPqplusUmd96Impl extends AbstractOpenemsModbusComponent + implements MeterPqplusUmd96, SymmetricMeter, AsymmetricMeter, ModbusComponent, OpenemsComponent { + + private MeterType meterType = MeterType.PRODUCTION; + + @Reference + private ConfigurationAdmin cm; + + public MeterPqplusUmd96Impl() { + super(// + OpenemsComponent.ChannelId.values(), // + ModbusComponent.ChannelId.values(), // + SymmetricMeter.ChannelId.values(), // + AsymmetricMeter.ChannelId.values(), // + MeterPqplusUmd96.ChannelId.values() // + ); + } + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + protected void setModbus(BridgeModbus modbus) { + super.setModbus(modbus); + } + + @Activate + void activate(ComponentContext context, Config config) throws OpenemsException { + this.meterType = config.type(); + + if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, + "Modbus", config.modbus_id())) { + return; + } + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + @Override + public MeterType getMeterType() { + return this.meterType; + } + + @Override + protected ModbusProtocol defineModbusProtocol() throws OpenemsException { + return new ModbusProtocol(this, // + // Frequency + new FC3ReadRegistersTask(0x1004, Priority.LOW, // + m(SymmetricMeter.ChannelId.FREQUENCY, new FloatDoublewordElement(0x1004), + ElementToChannelConverter.SCALE_FACTOR_3)), + // Voltages + new FC3ReadRegistersTask(0x1100, Priority.HIGH, // + m(new FloatDoublewordElement(0x1100)) // + .m(AsymmetricMeter.ChannelId.VOLTAGE_L1, ElementToChannelConverter.SCALE_FACTOR_3) // + .m(SymmetricMeter.ChannelId.VOLTAGE, ElementToChannelConverter.SCALE_FACTOR_3) // + .build(), // + m(AsymmetricMeter.ChannelId.VOLTAGE_L2, new FloatDoublewordElement(0x1102), + ElementToChannelConverter.SCALE_FACTOR_3), + m(AsymmetricMeter.ChannelId.VOLTAGE_L3, new FloatDoublewordElement(0x1104), + ElementToChannelConverter.SCALE_FACTOR_3)), + // Currents + new FC3ReadRegistersTask(0x1200, Priority.HIGH, // + m(new FloatDoublewordElement(0x1200)) + .m(AsymmetricMeter.ChannelId.CURRENT_L1, ElementToChannelConverter.SCALE_FACTOR_3) // + .m(SymmetricMeter.ChannelId.CURRENT, ElementToChannelConverter.SCALE_FACTOR_3) // + .build(), // + m(AsymmetricMeter.ChannelId.CURRENT_L2, new FloatDoublewordElement(0x1202), // + ElementToChannelConverter.SCALE_FACTOR_3), + m(AsymmetricMeter.ChannelId.CURRENT_L3, new FloatDoublewordElement(0x1204), // + ElementToChannelConverter.SCALE_FACTOR_3)), + // Power values + new FC3ReadRegistersTask(0x1314, Priority.HIGH, // + m(SymmetricMeter.ChannelId.ACTIVE_POWER, new FloatDoublewordElement(0x1314)), + m(SymmetricMeter.ChannelId.REACTIVE_POWER, new FloatDoublewordElement(0x1316)), // + new DummyRegisterElement(0x1318, 0x131F), // + m(AsymmetricMeter.ChannelId.ACTIVE_POWER_L1, new FloatDoublewordElement(0x1320)), + m(AsymmetricMeter.ChannelId.ACTIVE_POWER_L2, new FloatDoublewordElement(0x1322)), + m(AsymmetricMeter.ChannelId.ACTIVE_POWER_L3, new FloatDoublewordElement(0x1324)), + new DummyRegisterElement(0x1326, 0x1327), // + m(AsymmetricMeter.ChannelId.REACTIVE_POWER_L1, new FloatDoublewordElement(0x1328)), + m(AsymmetricMeter.ChannelId.REACTIVE_POWER_L2, new FloatDoublewordElement(0x132A)), + m(AsymmetricMeter.ChannelId.REACTIVE_POWER_L3, new FloatDoublewordElement(0x132C)))// + ); + + } + + @Override + public String debugLog() { + return "L:" + this.getActivePower().asString(); + } +} diff --git a/io.openems.edge.meter.pqplus.umd97/src/io/openems/edge/meter/pqplus/umd97/Config.java b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/Config.java similarity index 98% rename from io.openems.edge.meter.pqplus.umd97/src/io/openems/edge/meter/pqplus/umd97/Config.java rename to io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/Config.java index a5cce62db9f..6f158db2353 100644 --- a/io.openems.edge.meter.pqplus.umd97/src/io/openems/edge/meter/pqplus/umd97/Config.java +++ b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/Config.java @@ -5,7 +5,7 @@ import io.openems.edge.meter.api.MeterType; -@ObjectClassDefinition( // +@ObjectClassDefinition(// name = "Meter PQ-Plus UMD 97", // description = "Implements the PQ-Plus UMD 97 power meter.") @interface Config { diff --git a/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97.java b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97.java new file mode 100644 index 00000000000..e0efffe443f --- /dev/null +++ b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97.java @@ -0,0 +1,24 @@ +package io.openems.edge.meter.pqplus.umd97; + +import io.openems.edge.bridge.modbus.api.ModbusComponent; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.meter.api.AsymmetricMeter; +import io.openems.edge.meter.api.SymmetricMeter; + +public interface MeterPqplusUmd97 extends SymmetricMeter, AsymmetricMeter, ModbusComponent, OpenemsComponent { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + ; + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + public Doc doc() { + return this.doc; + } + } + +} \ No newline at end of file diff --git a/io.openems.edge.meter.pqplus.umd97/src/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97.java b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97Impl.java similarity index 89% rename from io.openems.edge.meter.pqplus.umd97/src/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97.java rename to io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97Impl.java index db755ede2a5..f60d5180028 100644 --- a/io.openems.edge.meter.pqplus.umd97/src/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97.java +++ b/io.openems.edge.meter.pqplus/src/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97Impl.java @@ -21,7 +21,6 @@ import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement; import io.openems.edge.bridge.modbus.api.element.FloatDoublewordElement; import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; -import io.openems.edge.common.channel.Doc; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.AsymmetricMeter; @@ -31,25 +30,30 @@ /** * Implements the PQ Plus UMD 97 meter. * + *

* https://www.pq-plus.de/news/pqplus/umd-97-messgeraet.html */ @Designate(ocd = Config.class, factory = true) -@Component(name = "Meter.PqPlus.UMD97", immediate = true, configurationPolicy = ConfigurationPolicy.REQUIRE) -public class MeterPqplusUmd97 extends AbstractOpenemsModbusComponent - implements SymmetricMeter, AsymmetricMeter, ModbusComponent, OpenemsComponent { +@Component(// + name = "Meter.PqPlus.UMD97", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class MeterPqplusUmd97Impl extends AbstractOpenemsModbusComponent + implements MeterPqplusUmd97, SymmetricMeter, AsymmetricMeter, ModbusComponent, OpenemsComponent { private MeterType meterType = MeterType.PRODUCTION; @Reference - protected ConfigurationAdmin cm; + private ConfigurationAdmin cm; - public MeterPqplusUmd97() { + public MeterPqplusUmd97Impl() { super(// OpenemsComponent.ChannelId.values(), // ModbusComponent.ChannelId.values(), // SymmetricMeter.ChannelId.values(), // AsymmetricMeter.ChannelId.values(), // - ChannelId.values() // + MeterPqplusUmd97.ChannelId.values() // ); } @@ -73,19 +77,6 @@ protected void deactivate() { super.deactivate(); } - public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - ; - private final Doc doc; - - private ChannelId(Doc doc) { - this.doc = doc; - } - - public Doc doc() { - return this.doc; - } - } - @Override public MeterType getMeterType() { return this.meterType; diff --git a/io.openems.edge.meter.pqplus.umd97/test/.gitignore b/io.openems.edge.meter.pqplus/test/.gitignore similarity index 100% rename from io.openems.edge.meter.pqplus.umd97/test/.gitignore rename to io.openems.edge.meter.pqplus/test/.gitignore diff --git a/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96Test.java b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96Test.java new file mode 100644 index 00000000000..b233a26564d --- /dev/null +++ b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96Test.java @@ -0,0 +1,28 @@ +package io.openems.edge.meter.pqplus.umd96; + +import org.junit.Test; + +import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.meter.api.MeterType; + +public class MeterPqplusUmd96Test { + + private static final String METER_ID = "meter0"; + private static final String MODBUS_ID = "modbus0"; + + @Test + public void test() throws Exception { + new ComponentTest(new MeterPqplusUmd96Impl()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .activate(MyConfig.create() // + .setId(METER_ID) // + .setModbusId(MODBUS_ID) // + .setType(MeterType.GRID) // + .build()) // + ; + } + +} diff --git a/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MyConfig.java b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MyConfig.java new file mode 100644 index 00000000000..fa40fbc58c7 --- /dev/null +++ b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MyConfig.java @@ -0,0 +1,75 @@ +package io.openems.edge.meter.pqplus.umd96; + +import io.openems.common.utils.ConfigUtils; +import io.openems.edge.common.test.AbstractComponentConfig; +import io.openems.edge.meter.api.MeterType; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + private String id; + private String modbusId; + public int modbusUnitId; + public MeterType type; + + private Builder() { + } + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setModbusId(String modbusId) { + this.modbusId = modbusId; + return this; + } + + public Builder setType(MeterType type) { + this.type = type; + return this; + } + + public MyConfig build() { + return new MyConfig(this); + } + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfig(Builder builder) { + super(Config.class, builder.id); + this.builder = builder; + } + + @Override + public String modbus_id() { + return this.builder.modbusId; + } + + @Override + public String Modbus_target() { + return ConfigUtils.generateReferenceTargetFilter(this.id(), this.modbus_id()); + } + + @Override + public int modbusUnitId() { + return this.builder.modbusUnitId; + } + + @Override + public MeterType type() { + return this.builder.type; + } + +} \ No newline at end of file diff --git a/io.openems.edge.meter.pqplus.umd97/test/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97Test.java b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97Test.java similarity index 93% rename from io.openems.edge.meter.pqplus.umd97/test/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97Test.java rename to io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97Test.java index 1af10aaf467..13e045190c2 100644 --- a/io.openems.edge.meter.pqplus.umd97/test/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97Test.java +++ b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97Test.java @@ -14,7 +14,7 @@ public class MeterPqplusUmd97Test { @Test public void test() throws Exception { - new ComponentTest(new MeterPqplusUmd97()) // + new ComponentTest(new MeterPqplusUmd97Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // .activate(MyConfig.create() // @@ -24,4 +24,5 @@ public void test() throws Exception { .build()) // ; } -} \ No newline at end of file + +} diff --git a/io.openems.edge.meter.pqplus.umd97/test/io/openems/edge/meter/pqplus/umd97/MyConfig.java b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MyConfig.java similarity index 95% rename from io.openems.edge.meter.pqplus.umd97/test/io/openems/edge/meter/pqplus/umd97/MyConfig.java rename to io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MyConfig.java index 61722c52782..a5ce3774014 100644 --- a/io.openems.edge.meter.pqplus.umd97/test/io/openems/edge/meter/pqplus/umd97/MyConfig.java +++ b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MyConfig.java @@ -8,8 +8,8 @@ public class MyConfig extends AbstractComponentConfig implements Config { protected static class Builder { - private String id = null; - private String modbusId = null; + private String id; + private String modbusId; public int modbusUnitId; public MeterType type; diff --git a/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/RecordWorker.java b/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/RecordWorker.java index 265a177b420..22edc9cc19c 100644 --- a/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/RecordWorker.java +++ b/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/RecordWorker.java @@ -220,6 +220,7 @@ private Function getChannelAggregateFunction(Unit case AMPERE_HOURS: case DEGREE_CELSIUS: case DEZIDEGREE_CELSIUS: + case EUROS_PER_MEGAWATT_HOUR: case HERTZ: case HOUR: case KILOAMPERE_HOURS: diff --git a/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jTimedataImpl.java b/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jTimedataImpl.java index 266f55e37fd..719e19b5d6f 100644 --- a/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jTimedataImpl.java +++ b/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jTimedataImpl.java @@ -487,6 +487,7 @@ private ChannelDef getDsDefForChannel(Unit channelUnit) { case AMPERE_HOURS: case DEGREE_CELSIUS: case DEZIDEGREE_CELSIUS: + case EUROS_PER_MEGAWATT_HOUR: case HERTZ: case HOUR: case KILOAMPERE_HOURS: diff --git a/io.openems.edge.timeofusetariff.api/.classpath b/io.openems.edge.timeofusetariff.api/.classpath new file mode 100644 index 00000000000..7a6fc254361 --- /dev/null +++ b/io.openems.edge.timeofusetariff.api/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/io.openems.edge.timeofusetariff.api/.gitignore b/io.openems.edge.timeofusetariff.api/.gitignore new file mode 100644 index 00000000000..c2b941a96de --- /dev/null +++ b/io.openems.edge.timeofusetariff.api/.gitignore @@ -0,0 +1,2 @@ +/bin_test/ +/generated/ diff --git a/io.openems.edge.timeofusetariff.api/.project b/io.openems.edge.timeofusetariff.api/.project new file mode 100644 index 00000000000..bb2aa4912dd --- /dev/null +++ b/io.openems.edge.timeofusetariff.api/.project @@ -0,0 +1,23 @@ + + + io.openems.edge.timeofusetariff.api + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/io.openems.edge.timeofusetariff.api/bnd.bnd b/io.openems.edge.timeofusetariff.api/bnd.bnd new file mode 100644 index 00000000000..bef6d48d25c --- /dev/null +++ b/io.openems.edge.timeofusetariff.api/bnd.bnd @@ -0,0 +1,12 @@ +Bundle-Name: OpenEMS Edge Time-Of-Use Tariff API +Bundle-Vendor: FENECON GmbH +Bundle-License: https://opensource.org/licenses/EPL-2.0 +Bundle-Version: 1.0.0.${tstamp} + +-buildpath: \ + ${buildpath},\ + io.openems.common,\ + io.openems.edge.common,\ + +-testpath: \ + ${testpath} diff --git a/io.openems.edge.timeofusetariff.api/readme.adoc b/io.openems.edge.timeofusetariff.api/readme.adoc new file mode 100644 index 00000000000..702b5dbbce0 --- /dev/null +++ b/io.openems.edge.timeofusetariff.api/readme.adoc @@ -0,0 +1,7 @@ += Time-Of-Use Tariff API. + +Provides abstract access for getting prices from every "Time-Of-Use" tariff providers like: +- aWATTar +- Stromdao with Corrently + +https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.timeofusetariff.api[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/TimeOfUsePrices.java b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/TimeOfUsePrices.java new file mode 100644 index 00000000000..b417063635d --- /dev/null +++ b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/TimeOfUsePrices.java @@ -0,0 +1,57 @@ +package io.openems.edge.timeofusetariff.api; + +import java.time.ZonedDateTime; + +/** + * Holds time of use prices for 24 h and the time when it is retrieved; // + * prices are one value per 15 minutes; 96 values in total. + * + *

+ * Values have unit EUR/MWh. + */ +public class TimeOfUsePrices { + + public final static int NUMBER_OF_VALUES = 96; + + private final ZonedDateTime updateTime; + + private final Float[] values = new Float[NUMBER_OF_VALUES]; + + /** + * Constructs a {@link TimeOfUsePrices}. + * + * @param updateTime Retrieved time of the prices. + * @param values the 96 quarterly price values[24 hours]. + */ + public TimeOfUsePrices(ZonedDateTime updateTime, Float... values) { + super(); + for (int i = 0; i < NUMBER_OF_VALUES && i < values.length; i++) { + this.values[i] = values[i]; + } + this.updateTime = updateTime; + } + + /** + * Gives electricity prices for the next 24 h; one value per 15 minutes; 96 + * values in total. + * + *

+ * E.g. if called at 10:05, the first value stands for 10:00 to 10:15; second + * value for 10:15 to 10:30. + * + * @return the prices + */ + public Float[] getValues() { + return this.values; + } + + /** + * Gets the time of the last update of prices. + * + * @return the time + */ + public ZonedDateTime getUpdateTime() { + return this.updateTime; + } + +} diff --git a/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/TimeOfUseTariff.java b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/TimeOfUseTariff.java new file mode 100644 index 00000000000..f06f6f93919 --- /dev/null +++ b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/TimeOfUseTariff.java @@ -0,0 +1,24 @@ +package io.openems.edge.timeofusetariff.api; + +import org.osgi.annotation.versioning.ProviderType; + +/** + * Provides a prediction for the next 24 h; one value per 15 minutes; 96 values + * in total. + */ +@ProviderType +public interface TimeOfUseTariff { + + /** + * Gives electricity prices for the next 24 h; one value per 15 minutes; 96 + * values in total. + * + *

+ * E.g. if called at 10:05, the first value stands for 10:00 to 10:15; second + * value for 10:15 to 10:30. + * + * @return the prices + */ + public TimeOfUsePrices getPrices(); + +} diff --git a/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/package-info.java b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/package-info.java new file mode 100644 index 00000000000..01093ab3b54 --- /dev/null +++ b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.timeofusetariff.api; diff --git a/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/utils/TimeOfUseTariffUtils.java b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/utils/TimeOfUseTariffUtils.java new file mode 100644 index 00000000000..1ee6e26ec7c --- /dev/null +++ b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/utils/TimeOfUseTariffUtils.java @@ -0,0 +1,54 @@ +package io.openems.edge.timeofusetariff.api.utils; + +import java.time.Clock; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; + +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.edge.timeofusetariff.api.TimeOfUsePrices; + +public class TimeOfUseTariffUtils { + + /** + * Returns the Array of 24 hour [96 quarterly] electricity prices in EUR/MWh. + * + * @param clock the {@link Clock} + * @param priceMap {@link ImmutableSortedMap} with quarterly Time stamps + * and the price. + * @param updateTimeStamp time when prices are retrieved. + * @return the quarterly prices of next 24 hours along with the time they are + * retrieved. + */ + public static TimeOfUsePrices getNext24HourPrices(Clock clock, ImmutableSortedMap priceMap, + Clock updateTimeStamp) { + + ZonedDateTime updateTime = getNowRoundedDownToMinutes(updateTimeStamp, 15); + + // Returns the empty array if the map is empty. + if (priceMap.isEmpty()) { + return new TimeOfUsePrices(updateTime); + } + + ZonedDateTime now = getNowRoundedDownToMinutes(clock, 15); + // Converts the map values to array. + // if the map size is less than 96, rest of the values will store as null. + final Float[] priceList = priceMap.tailMap(now).values().toArray(new Float[TimeOfUsePrices.NUMBER_OF_VALUES]); + + return new TimeOfUsePrices(updateTime, priceList); + } + + /** + * Gets 'now' from the Clock and rounds it down to required minutes. + * + * @param clock the {@link Clock} + * @param minutes the custom minutes to roundoff to. + * @return the rounded result + */ + public static ZonedDateTime getNowRoundedDownToMinutes(Clock clock, int minutes) { + ZonedDateTime d = ZonedDateTime.now(clock); + int minuteOfDay = d.get(ChronoField.MINUTE_OF_DAY); + return d.with(ChronoField.NANO_OF_DAY, 0).plus(minuteOfDay / minutes * minutes, ChronoUnit.MINUTES); + } +} diff --git a/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/utils/package-info.java b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/utils/package-info.java new file mode 100644 index 00000000000..dbad637321c --- /dev/null +++ b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/api/utils/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.timeofusetariff.api.utils; diff --git a/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/test/DummyTimeOfUseTariffProvider.java b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/test/DummyTimeOfUseTariffProvider.java new file mode 100644 index 00000000000..f427362d22c --- /dev/null +++ b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/test/DummyTimeOfUseTariffProvider.java @@ -0,0 +1,39 @@ +package io.openems.edge.timeofusetariff.test; + +import java.time.ZonedDateTime; +import io.openems.edge.timeofusetariff.api.TimeOfUsePrices; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; + +public class DummyTimeOfUseTariffProvider implements TimeOfUseTariff { + + private final TimeOfUsePrices prices; + + /** + * Builds a {@link DummyTimeOfUseTariffProvider} from hourly prices. + * + * @param hourlyPrices an array of hourly prices + * @return a {@link DummyTimeOfUseTariffProvider} + */ + public static DummyTimeOfUseTariffProvider fromHourlyPrices(ZonedDateTime now, Float[] hourlyPrices) { + Float[] quarterlyPrices = new Float[96]; + + for (int i = 0; i < 24; i++) { + quarterlyPrices[i] = hourlyPrices[i]; + quarterlyPrices[i + 1] = hourlyPrices[i]; + quarterlyPrices[i + 2] = hourlyPrices[i]; + quarterlyPrices[i + 3] = hourlyPrices[i]; + } + + return new DummyTimeOfUseTariffProvider(now, quarterlyPrices); + } + + private DummyTimeOfUseTariffProvider(ZonedDateTime now, Float[] quarterlyPrices) { + this.prices = new TimeOfUsePrices(now, quarterlyPrices); + } + + @Override + public TimeOfUsePrices getPrices() { + return this.prices; + } + +} \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/test/package-info.java b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/test/package-info.java new file mode 100644 index 00000000000..785b1b54bb7 --- /dev/null +++ b/io.openems.edge.timeofusetariff.api/src/io/openems/edge/timeofusetariff/test/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.timeofusetariff.test; diff --git a/io.openems.edge.timeofusetariff.api/test/.gitignore b/io.openems.edge.timeofusetariff.api/test/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/io.openems.edge.timeofusetariff.awattar/.classpath b/io.openems.edge.timeofusetariff.awattar/.classpath new file mode 100644 index 00000000000..7a6fc254361 --- /dev/null +++ b/io.openems.edge.timeofusetariff.awattar/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/io.openems.edge.timeofusetariff.awattar/.gitignore b/io.openems.edge.timeofusetariff.awattar/.gitignore new file mode 100644 index 00000000000..c2b941a96de --- /dev/null +++ b/io.openems.edge.timeofusetariff.awattar/.gitignore @@ -0,0 +1,2 @@ +/bin_test/ +/generated/ diff --git a/io.openems.edge.timeofusetariff.awattar/.project b/io.openems.edge.timeofusetariff.awattar/.project new file mode 100644 index 00000000000..344077bede4 --- /dev/null +++ b/io.openems.edge.timeofusetariff.awattar/.project @@ -0,0 +1,23 @@ + + + io.openems.edge.timeofusetariff.awattar + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/io.openems.edge.timeofusetariff.awattar/bnd.bnd b/io.openems.edge.timeofusetariff.awattar/bnd.bnd new file mode 100644 index 00000000000..a509eceeba4 --- /dev/null +++ b/io.openems.edge.timeofusetariff.awattar/bnd.bnd @@ -0,0 +1,14 @@ +Bundle-Name: OpenEMS Edge Time-Of-Use Tariff Awattar +Bundle-Vendor: FENECON GmbH +Bundle-License: https://opensource.org/licenses/EPL-2.0 +Bundle-Version: 1.0.0.${tstamp} + +-buildpath: \ + ${buildpath},\ + io.openems.common,\ + io.openems.edge.common,\ + io.openems.edge.timeofusetariff.api,\ + io.openems.wrapper.okhttp,\ + +-testpath: \ + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.awattar/readme.adoc b/io.openems.edge.timeofusetariff.awattar/readme.adoc new file mode 100644 index 00000000000..ec3a8a13aa8 --- /dev/null +++ b/io.openems.edge.timeofusetariff.awattar/readme.adoc @@ -0,0 +1,5 @@ += Time-Of-Use Tariff Awattar + +Retrieves the hourly prices from the Awattar API and converts them into quarterly prices. Prices are updated every day at 14:00 and stored locally. + +https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.timeofusetariff.awattar[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/Awattar.java b/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/Awattar.java new file mode 100644 index 00000000000..0b79bbcbeb7 --- /dev/null +++ b/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/Awattar.java @@ -0,0 +1,26 @@ +package io.openems.edge.timeofusetariff.awattar; + +import io.openems.common.types.OpenemsType; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; + +public interface Awattar extends TimeOfUseTariff, OpenemsComponent { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + HTTP_STATUS_CODE(Doc.of(OpenemsType.INTEGER)// + .text("Displays the HTTP status code"))// + ; + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } +} diff --git a/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/AwattarImpl.java b/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/AwattarImpl.java new file mode 100644 index 00000000000..4f52816b793 --- /dev/null +++ b/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/AwattarImpl.java @@ -0,0 +1,175 @@ +package io.openems.edge.timeofusetariff.awattar; + +import java.io.IOException; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.TreeMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +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 com.google.common.collect.ImmutableSortedMap; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.utils.JsonUtils; +import io.openems.common.utils.ThreadPoolUtils; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.timeofusetariff.api.TimeOfUsePrices; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; +import io.openems.edge.timeofusetariff.api.utils.TimeOfUseTariffUtils; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "TimeOfUseTariff.Awattar", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class AwattarImpl extends AbstractOpenemsComponent implements TimeOfUseTariff, OpenemsComponent, Awattar { + + private static final String AWATTAR_API_URL = "https://api.awattar.com/v1/marketdata"; + + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + + private final AtomicReference> prices = new AtomicReference>( + ImmutableSortedMap.of()); + + private Clock updateTimeStamp = Clock.fixed(Instant.MIN, ZoneId.systemDefault()); + + private final Runnable task = () -> { + + /* + * Update Map of prices + */ + ImmutableSortedMap prices; + try { + OkHttpClient client = new OkHttpClient(); + Request request = new Request.Builder() // + .url(AWATTAR_API_URL) // + // aWATTar currently does not anymore require an Apikey. + // .header("Authorization", Credentials.basic(apikey, "")) // + .build(); + + Response response = client.newCall(request).execute(); + this.channel(Awattar.ChannelId.HTTP_STATUS_CODE).setNextValue(response.code()); + + if (!response.isSuccessful()) { + throw new IOException("Unexpected code " + response); + } + + // Parse the response for the prices + prices = AwattarImpl.parsePrices(response.body().toString()); + + // store the time stamp + this.updateTimeStamp = Clock.systemDefaultZone(); + } catch (IOException | OpenemsNamedException e) { + e.printStackTrace(); + prices = ImmutableSortedMap.of(); + // TODO Try again in x minutes + } + this.prices.set(prices); + + /* + * Schedule next price update for 2 pm + */ + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime nextRun = now.withHour(14).truncatedTo(ChronoUnit.HOURS); + if (now.isAfter(nextRun)) { + nextRun = nextRun.plusDays(1); + } + + Duration duration = Duration.between(now, nextRun); + long delay = duration.getSeconds(); + + this.executor.schedule(this.task, delay, TimeUnit.SECONDS); + }; + + @Reference + private ComponentManager componentManager; + + public AwattarImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + Awattar.ChannelId.values() // + ); + } + + @Activate + void activate(ComponentContext context, Config config) { + super.activate(context, config.id(), config.alias(), config.enabled()); + + if (!config.enabled()) { + return; + } + + this.executor.schedule(this.task, 0, TimeUnit.SECONDS); + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + ThreadPoolUtils.shutdownAndAwaitTermination(this.executor, 0); + } + + @Override + public TimeOfUsePrices getPrices() { + return TimeOfUseTariffUtils.getNext24HourPrices(Clock.systemDefaultZone() /* can be mocked for testing */, + this.prices.get(), this.updateTimeStamp); + } + + /** + * Parse the aWATTar JSON to the Price Map. + * + * @param jsonData the aWATTar JSON + * @return the Price Map + * @throws OpenemsNamedException on error + */ + public static ImmutableSortedMap parsePrices(String jsonData) throws OpenemsNamedException { + TreeMap result = new TreeMap<>(); + + if (!jsonData.isEmpty()) { + + JsonObject line = JsonUtils.getAsJsonObject(JsonUtils.parse(jsonData)); + JsonArray data = JsonUtils.getAsJsonArray(line, "data"); + + for (JsonElement element : data) { + + float marketPrice = JsonUtils.getAsFloat(element, "marketprice"); + long startTimestampLong = JsonUtils.getAsLong(element, "start_timestamp"); + + // Converting Long time stamp to ZonedDateTime. + ZonedDateTime startTimeStamp = ZonedDateTime // + .ofInstant(Instant.ofEpochMilli(startTimestampLong), ZoneId.systemDefault()) + .truncatedTo(ChronoUnit.HOURS); + + // Adding the values in the Map. + result.put(startTimeStamp, marketPrice); + result.put(startTimeStamp.plusMinutes(15), marketPrice); + result.put(startTimeStamp.plusMinutes(30), marketPrice); + result.put(startTimeStamp.plusMinutes(45), marketPrice); + } + } + return ImmutableSortedMap.copyOf(result); + } + +} diff --git a/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/Config.java b/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/Config.java new file mode 100644 index 00000000000..0b0810cf355 --- /dev/null +++ b/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/Config.java @@ -0,0 +1,21 @@ +package io.openems.edge.timeofusetariff.awattar; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(// + name = "Time-Of-Use Tariff Awattar", // + description = "Time-Of-Use Tariff implementation for aWATTar.") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "timeOfUseTariff0"; + + @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; + + String webconsole_configurationFactory_nameHint() default "Time-Of-Use Tariff Awattar [{id}]"; +} diff --git a/io.openems.edge.timeofusetariff.awattar/test/.gitignore b/io.openems.edge.timeofusetariff.awattar/test/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/io.openems.edge.timeofusetariff.awattar/test/io/openems/edge/timeofusetariff/awattar/AwattarProviderTest.java b/io.openems.edge.timeofusetariff.awattar/test/io/openems/edge/timeofusetariff/awattar/AwattarProviderTest.java new file mode 100644 index 00000000000..6dd772e8d56 --- /dev/null +++ b/io.openems.edge.timeofusetariff.awattar/test/io/openems/edge/timeofusetariff/awattar/AwattarProviderTest.java @@ -0,0 +1,99 @@ +package io.openems.edge.timeofusetariff.awattar; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.time.ZonedDateTime; +import java.util.SortedMap; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.test.ComponentTest; + +public class AwattarProviderTest { + + private static final String CTRL_ID = "ctrl0"; + + @Test + public void test() throws Exception { + new ComponentTest(new AwattarImpl()) // + .activate(MyConfig.create() // + .setId(CTRL_ID) // + .build()) // + ; + } + + @Test + public void nonEmptyStringTest() throws OpenemsNamedException { + // Parsing with custom data + SortedMap prices = AwattarImpl.parsePrices("{" + " \"object\": \"list\"," + + " \"data\": [" + " {" + " \"start_timestamp\": 1632402000000," + + " \"end_timestamp\": 1632405600000," + " \"marketprice\": 158.95," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632405600000," + + " \"end_timestamp\": 1632409200000," + " \"marketprice\": 160.98," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632409200000," + + " \"end_timestamp\": 1632412800000," + " \"marketprice\": 171.15," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632412800000," + + " \"end_timestamp\": 1632416400000," + " \"marketprice\": 174.96," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632416400000," + + " \"end_timestamp\": 1632420000000," + " \"marketprice\": 161.53," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632420000000," + + " \"end_timestamp\": 1632423600000," + " \"marketprice\": 152," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632423600000," + + " \"end_timestamp\": 1632427200000," + " \"marketprice\": 120.01," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632427200000," + + " \"end_timestamp\": 1632430800000," + " \"marketprice\": 111.03," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632430800000," + + " \"end_timestamp\": 1632434400000," + " \"marketprice\": 105.04," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632434400000," + + " \"end_timestamp\": 1632438000000," + " \"marketprice\": 105," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632438000000," + + " \"end_timestamp\": 1632441600000," + " \"marketprice\": 74.23," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632441600000," + + " \"end_timestamp\": 1632445200000," + " \"marketprice\": 73.28," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632445200000," + + " \"end_timestamp\": 1632448800000," + " \"marketprice\": 67.97," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632448800000," + + " \"end_timestamp\": 1632452400000," + " \"marketprice\": 72.53," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632452400000," + + " \"end_timestamp\": 1632456000000," + " \"marketprice\": 89.66," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632456000000," + + " \"end_timestamp\": 1632459600000," + " \"marketprice\": 150.1," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632459600000," + + " \"end_timestamp\": 1632463200000," + " \"marketprice\": 173.54," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632463200000," + + " \"end_timestamp\": 1632466800000," + " \"marketprice\": 178.4," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632466800000," + + " \"end_timestamp\": 1632470400000," + " \"marketprice\": 158.91," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632470400000," + + " \"end_timestamp\": 1632474000000," + " \"marketprice\": 140.01," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632474000000," + + " \"end_timestamp\": 1632477600000," + " \"marketprice\": 149.99," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632477600000," + + " \"end_timestamp\": 1632481200000," + " \"marketprice\": 157.43," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632481200000," + + " \"end_timestamp\": 1632484800000," + " \"marketprice\": 130.9," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632484800000," + + " \"end_timestamp\": 1632488400000," + " \"marketprice\": 120.14," + + " \"unit\": \"Eur/MWh\"" + " }" + " ]," + " \"url\": \"/at/v1/marketdata\"" + "}"); // + + // To check if the Map is not empty + assertFalse(prices.isEmpty()); + + // To check if the a value input from the string is present in map. + assertTrue(prices.containsValue(120.14f)); + + } + + @Test + public void emptyStringTest() throws OpenemsNamedException { + // Parsing with empty string + SortedMap prices = AwattarImpl.parsePrices(""); + + // To check if the map is empty. + assertTrue(prices.isEmpty()); + + } + +} diff --git a/io.openems.edge.timeofusetariff.awattar/test/io/openems/edge/timeofusetariff/awattar/MyConfig.java b/io.openems.edge.timeofusetariff.awattar/test/io/openems/edge/timeofusetariff/awattar/MyConfig.java new file mode 100644 index 00000000000..a84f0ac5f0d --- /dev/null +++ b/io.openems.edge.timeofusetariff.awattar/test/io/openems/edge/timeofusetariff/awattar/MyConfig.java @@ -0,0 +1,41 @@ +package io.openems.edge.timeofusetariff.awattar; + +import io.openems.edge.common.test.AbstractComponentConfig; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + public static class Builder { + private String id; + public int zipcode; + + private Builder() { + } + + public Builder setId(String id) { + this.id = id; + return this; + } + + public MyConfig build() { + return new MyConfig(this); + } + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfig(Builder builder) { + super(Config.class, builder.id); + this.builder = builder; + } + +} \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.corrently/.classpath b/io.openems.edge.timeofusetariff.corrently/.classpath new file mode 100644 index 00000000000..7a6fc254361 --- /dev/null +++ b/io.openems.edge.timeofusetariff.corrently/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/io.openems.edge.timeofusetariff.corrently/.gitignore b/io.openems.edge.timeofusetariff.corrently/.gitignore new file mode 100644 index 00000000000..c2b941a96de --- /dev/null +++ b/io.openems.edge.timeofusetariff.corrently/.gitignore @@ -0,0 +1,2 @@ +/bin_test/ +/generated/ diff --git a/io.openems.edge.timeofusetariff.corrently/.project b/io.openems.edge.timeofusetariff.corrently/.project new file mode 100644 index 00000000000..c425cca64b6 --- /dev/null +++ b/io.openems.edge.timeofusetariff.corrently/.project @@ -0,0 +1,23 @@ + + + io.openems.edge.timeofusetariff.corrently + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/io.openems.edge.timeofusetariff.corrently/bnd.bnd b/io.openems.edge.timeofusetariff.corrently/bnd.bnd new file mode 100644 index 00000000000..1d7bd8d6663 --- /dev/null +++ b/io.openems.edge.timeofusetariff.corrently/bnd.bnd @@ -0,0 +1,14 @@ +Bundle-Name: OpenEMS Edge Time-Of-Use Tariff Corrently by STROMDAO +Bundle-Vendor: FENECON GmbH +Bundle-License: https://opensource.org/licenses/EPL-2.0 +Bundle-Version: 1.0.0.${tstamp} + +-buildpath: \ + ${buildpath},\ + io.openems.common,\ + io.openems.edge.common,\ + io.openems.edge.timeofusetariff.api,\ + io.openems.wrapper.okhttp,\ + +-testpath: \ + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.corrently/readme.adoc b/io.openems.edge.timeofusetariff.corrently/readme.adoc new file mode 100644 index 00000000000..877a3710305 --- /dev/null +++ b/io.openems.edge.timeofusetariff.corrently/readme.adoc @@ -0,0 +1,5 @@ += Time-Of-Use Tariff Corrently by STROMDAO + +Retrieves the hourly prices from the Corrently API and converts them into quarterly prices. Prices are updated every day at 14:00 and stored locally. + +https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.timeofusetariff.corrently[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.corrently/src/io/openems/edge/timeofusetariff/corrently/Config.java b/io.openems.edge.timeofusetariff.corrently/src/io/openems/edge/timeofusetariff/corrently/Config.java new file mode 100644 index 00000000000..f6950b77299 --- /dev/null +++ b/io.openems.edge.timeofusetariff.corrently/src/io/openems/edge/timeofusetariff/corrently/Config.java @@ -0,0 +1,24 @@ +package io.openems.edge.timeofusetariff.corrently; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(// + name = "Time-Of-Use Tariff Corrently", // + description = "Time-Of-Use Tariff implementation for Corrently.") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "timeOfUseTariff0"; + + @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 = "ZIP Code", description = "ZIP Code of the customer location") + int zipcode() default 94469; + + String webconsole_configurationFactory_nameHint() default "Time-Of-Use Tariff Corrently [{id}]"; +} \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.corrently/src/io/openems/edge/timeofusetariff/corrently/Corrently.java b/io.openems.edge.timeofusetariff.corrently/src/io/openems/edge/timeofusetariff/corrently/Corrently.java new file mode 100644 index 00000000000..ec85c046c34 --- /dev/null +++ b/io.openems.edge.timeofusetariff.corrently/src/io/openems/edge/timeofusetariff/corrently/Corrently.java @@ -0,0 +1,26 @@ +package io.openems.edge.timeofusetariff.corrently; + +import io.openems.common.types.OpenemsType; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; + +public interface Corrently extends TimeOfUseTariff, OpenemsComponent { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + HTTP_STATUS_CODE(Doc.of(OpenemsType.INTEGER)// + .text("Displays the HTTP status code"))// + ; + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } +} diff --git a/io.openems.edge.timeofusetariff.corrently/src/io/openems/edge/timeofusetariff/corrently/CorrentlyImpl.java b/io.openems.edge.timeofusetariff.corrently/src/io/openems/edge/timeofusetariff/corrently/CorrentlyImpl.java new file mode 100644 index 00000000000..ae091ad0d2e --- /dev/null +++ b/io.openems.edge.timeofusetariff.corrently/src/io/openems/edge/timeofusetariff/corrently/CorrentlyImpl.java @@ -0,0 +1,178 @@ +package io.openems.edge.timeofusetariff.corrently; + +import java.io.IOException; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.TreeMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +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 com.google.common.collect.ImmutableSortedMap; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.utils.JsonUtils; +import io.openems.common.utils.ThreadPoolUtils; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.timeofusetariff.api.TimeOfUsePrices; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; +import io.openems.edge.timeofusetariff.api.utils.TimeOfUseTariffUtils; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "TimeOfUseTariff.Corrently", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class CorrentlyImpl extends AbstractOpenemsComponent implements TimeOfUseTariff, OpenemsComponent, Corrently { + + private static final String CORRENTLY_API_URL = "https://api.corrently.io/v2.0/gsi/marketdata?zipcode="; + + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + + private Config config = null; + + private final AtomicReference> prices = new AtomicReference>( + ImmutableSortedMap.of()); + + private Clock updateTimeStamp = Clock.fixed(Instant.MIN, ZoneId.systemDefault()); + + private final Runnable task = () -> { + + /* + * Update Map of prices + */ + ImmutableSortedMap prices; + try { + Integer zipcode = this.config.zipcode(); + OkHttpClient client = new OkHttpClient(); + Request request = new Request.Builder() // + .url(CORRENTLY_API_URL.concat(zipcode.toString())) // + // .header("Authorization", Credentials.basic(apikey, "")) // + .build(); + + Response response = client.newCall(request).execute(); + this.channel(Corrently.ChannelId.HTTP_STATUS_CODE).setNextValue(response.code()); + + if (!response.isSuccessful()) { + throw new IOException("Unexpected code " + response); + } + + // Parse the response for the prices + prices = CorrentlyImpl.parsePrices(response.body().toString()); + + // store the time stamp + this.updateTimeStamp = Clock.systemDefaultZone(); + + } catch (IOException | OpenemsNamedException e) { + e.printStackTrace(); + prices = ImmutableSortedMap.of(); + } + + this.prices.set(prices); + + /* + * Schedule next price update for 2 pm + */ + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime nextRun = now.withHour(14).truncatedTo(ChronoUnit.HOURS); + if (now.isAfter(nextRun)) { + nextRun = nextRun.plusDays(1); + } + + Duration duration = Duration.between(now, nextRun); + long delay = duration.getSeconds(); + + this.executor.schedule(this.task, delay, TimeUnit.SECONDS); + }; + + @Reference + private ComponentManager componentManager; + + public CorrentlyImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + Corrently.ChannelId.values() // + ); + } + + @Activate + void activate(ComponentContext context, Config config) { + super.activate(context, config.id(), config.alias(), config.enabled()); + + if (!config.enabled()) { + return; + } + this.config = config; + this.executor.schedule(this.task, 0, TimeUnit.SECONDS); + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + ThreadPoolUtils.shutdownAndAwaitTermination(this.executor, 0); + } + + @Override + public TimeOfUsePrices getPrices() { + return TimeOfUseTariffUtils.getNext24HourPrices(Clock.systemDefaultZone() /* can be mocked for testing */, + this.prices.get(), this.updateTimeStamp); + } + + /** + * Parse the Corrently JSON to the Price Map. + * + * @param jsonData the Corrently JSON + * @return the Price Map + * @throws OpenemsNamedException on error + */ + public static ImmutableSortedMap parsePrices(String jsonData) throws OpenemsNamedException { + TreeMap result = new TreeMap<>(); + + if (!jsonData.isEmpty()) { + + JsonObject line = JsonUtils.getAsJsonObject(JsonUtils.parse(jsonData)); + JsonArray data = JsonUtils.getAsJsonArray(line, "data"); + + for (JsonElement element : data) { + + float marketPrice = JsonUtils.getAsFloat(element, "marketprice"); + long startTimestampLong = JsonUtils.getAsLong(element, "start_timestamp"); + + // Converting Long time stamp to ZonedDateTime. + ZonedDateTime startTimeStamp = ZonedDateTime // + .ofInstant(Instant.ofEpochMilli(startTimestampLong), ZoneId.systemDefault()) + .truncatedTo(ChronoUnit.HOURS); + + // Adding the values in the Map. + result.put(startTimeStamp, marketPrice); + result.put(startTimeStamp.plusMinutes(15), marketPrice); + result.put(startTimeStamp.plusMinutes(30), marketPrice); + result.put(startTimeStamp.plusMinutes(45), marketPrice); + } + } + return ImmutableSortedMap.copyOf(result); + } + +} diff --git a/io.openems.edge.timeofusetariff.corrently/test/.gitignore b/io.openems.edge.timeofusetariff.corrently/test/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/io.openems.edge.timeofusetariff.corrently/test/io/openems/edge/timeofusetariff/corrently/CorrentlyProviderTest.java b/io.openems.edge.timeofusetariff.corrently/test/io/openems/edge/timeofusetariff/corrently/CorrentlyProviderTest.java new file mode 100644 index 00000000000..738283bbd3b --- /dev/null +++ b/io.openems.edge.timeofusetariff.corrently/test/io/openems/edge/timeofusetariff/corrently/CorrentlyProviderTest.java @@ -0,0 +1,98 @@ +package io.openems.edge.timeofusetariff.corrently; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.time.ZonedDateTime; +import java.util.SortedMap; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.test.ComponentTest; + +public class CorrentlyProviderTest { + + private static final String CTRL_ID = "ctrl0"; + + @Test + public void test() throws Exception { + new ComponentTest(new CorrentlyImpl()) // + .activate(MyConfig.create() // + .setId(CTRL_ID) // + .setZipcode(94469 /* Deggendorf, Germany */) // + .build()) // + ; + } + + @Test + public void nonEmptyStringTest() throws OpenemsNamedException { + // Parsing with custom data + SortedMap prices = CorrentlyImpl.parsePrices("{" + " \"object\": \"list\"," + + " \"data\": [" + " {" + " \"start_timestamp\": 1632402000000," + + " \"end_timestamp\": 1632405600000," + " \"marketprice\": 158.95," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632405600000," + + " \"end_timestamp\": 1632409200000," + " \"marketprice\": 160.98," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632409200000," + + " \"end_timestamp\": 1632412800000," + " \"marketprice\": 171.15," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632412800000," + + " \"end_timestamp\": 1632416400000," + " \"marketprice\": 174.96," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632416400000," + + " \"end_timestamp\": 1632420000000," + " \"marketprice\": 161.53," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632420000000," + + " \"end_timestamp\": 1632423600000," + " \"marketprice\": 152," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632423600000," + + " \"end_timestamp\": 1632427200000," + " \"marketprice\": 120.01," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632427200000," + + " \"end_timestamp\": 1632430800000," + " \"marketprice\": 111.03," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632430800000," + + " \"end_timestamp\": 1632434400000," + " \"marketprice\": 105.04," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632434400000," + + " \"end_timestamp\": 1632438000000," + " \"marketprice\": 105," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632438000000," + + " \"end_timestamp\": 1632441600000," + " \"marketprice\": 74.23," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632441600000," + + " \"end_timestamp\": 1632445200000," + " \"marketprice\": 73.28," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632445200000," + + " \"end_timestamp\": 1632448800000," + " \"marketprice\": 67.97," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632448800000," + + " \"end_timestamp\": 1632452400000," + " \"marketprice\": 72.53," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632452400000," + + " \"end_timestamp\": 1632456000000," + " \"marketprice\": 89.66," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632456000000," + + " \"end_timestamp\": 1632459600000," + " \"marketprice\": 150.1," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632459600000," + + " \"end_timestamp\": 1632463200000," + " \"marketprice\": 173.54," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632463200000," + + " \"end_timestamp\": 1632466800000," + " \"marketprice\": 178.4," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632466800000," + + " \"end_timestamp\": 1632470400000," + " \"marketprice\": 158.91," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632470400000," + + " \"end_timestamp\": 1632474000000," + " \"marketprice\": 140.01," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632474000000," + + " \"end_timestamp\": 1632477600000," + " \"marketprice\": 149.99," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632477600000," + + " \"end_timestamp\": 1632481200000," + " \"marketprice\": 157.43," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632481200000," + + " \"end_timestamp\": 1632484800000," + " \"marketprice\": 130.9," + + " \"unit\": \"Eur/MWh\"" + " }," + " {" + " \"start_timestamp\": 1632484800000," + + " \"end_timestamp\": 1632488400000," + " \"marketprice\": 120.14," + + " \"unit\": \"Eur/MWh\"" + " }" + " ]," + " \"url\": \"/at/v1/marketdata\"" + "}"); // + + // To check if the Map is not empty + assertFalse(prices.isEmpty()); + + // To check if the a value input from the string is present in map. + assertTrue(prices.containsValue(120.14f)); + + } + + @Test + public void emptyStringTest() throws OpenemsNamedException { + // Parsing with empty string + SortedMap prices = CorrentlyImpl.parsePrices(""); + + // To check if the map is empty. + assertTrue(prices.isEmpty()); + } +} diff --git a/io.openems.edge.timeofusetariff.corrently/test/io/openems/edge/timeofusetariff/corrently/MyConfig.java b/io.openems.edge.timeofusetariff.corrently/test/io/openems/edge/timeofusetariff/corrently/MyConfig.java new file mode 100644 index 00000000000..280be7d9b49 --- /dev/null +++ b/io.openems.edge.timeofusetariff.corrently/test/io/openems/edge/timeofusetariff/corrently/MyConfig.java @@ -0,0 +1,51 @@ +package io.openems.edge.timeofusetariff.corrently; + +import io.openems.edge.common.test.AbstractComponentConfig; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + public static class Builder { + private String id; + public int zipcode; + + private Builder() { + } + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setZipcode(int zipcode) { + this.zipcode = zipcode; + return this; + } + + public MyConfig build() { + return new MyConfig(this); + } + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfig(Builder builder) { + super(Config.class, builder.id); + this.builder = builder; + } + + @Override + public int zipcode() { + return this.builder.zipcode; + } + +} \ No newline at end of file diff --git a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConnector.java b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConnector.java index 735f82e8e77..f8fcc7b65db 100644 --- a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConnector.java +++ b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConnector.java @@ -34,6 +34,7 @@ import com.google.gson.JsonNull; import com.google.gson.JsonPrimitive; +import io.openems.common.OpenemsOEM; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.types.ChannelAddress; @@ -244,7 +245,7 @@ public SortedMap queryHistoricEnergy(Optional "); b.append(String.valueOf(fromDate.toEpochSecond())); @@ -293,7 +294,7 @@ public SortedMap> queryHis b.append(InfluxConnector.toChannelAddressStringNonNegativeDifferenceLast(channels)); b.append(" FROM data WHERE "); if (influxEdgeId.isPresent()) { - b.append(InfluxConstants.TAG + " = '" + influxEdgeId.get() + "' AND "); + b.append(OpenemsOEM.INFLUXDB_TAG + " = '" + influxEdgeId.get() + "' AND "); } b.append("time > "); b.append(String.valueOf(fromDate.toEpochSecond())); @@ -335,7 +336,7 @@ public SortedMap> queryHis query.append(InfluxConnector.toChannelAddressStringData(channels)); query.append(" FROM data WHERE "); if (influxEdgeId.isPresent()) { - query.append(InfluxConstants.TAG + " = '" + influxEdgeId.get() + "' AND "); + query.append(OpenemsOEM.INFLUXDB_TAG + " = '" + influxEdgeId.get() + "' AND "); } query.append("time > "); query.append(String.valueOf(fromDate.toEpochSecond())); diff --git a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConstants.java b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConstants.java deleted file mode 100644 index 8a4f290c24f..00000000000 --- a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConstants.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.openems.shared.influxdb; - -public class InfluxConstants { - - public static final String TAG = "edge"; - -} diff --git a/ui/README.md b/ui/README.md index 2e1a2108cb0..ff26b922296 100644 --- a/ui/README.md +++ b/ui/README.md @@ -20,7 +20,7 @@ This project was generated with [angular-cli](https://github.com/angular/angular - Build Production - `ng build -c "openems,openems-edge-dev,prod"` + `ng build -c "openems,openems-edge-prod,prod"` - OpenEMS Backend - expects a Backend *Ui.Websocket* on default port `8082` @@ -80,4 +80,4 @@ ngOnDestroy() { this.stopOnDestroy.next(); this.stopOnDestroy.complete(); } -``` \ No newline at end of file +``` diff --git a/ui/angular.json b/ui/angular.json index 943c94e2618..48e1bb581a4 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -153,4 +153,4 @@ "styleext": "scss" } } -} \ No newline at end of file +} diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts index a9136ae3314..7dc96c1dc6d 100644 --- a/ui/src/app/app-routing.module.ts +++ b/ui/src/app/app-routing.module.ts @@ -17,6 +17,7 @@ import { ProductionChartOverviewComponent } from './edge/history/production/prod import { SelfconsumptionChartOverviewComponent } from './edge/history/selfconsumption/selfconsumptionchartoverview/selfconsumptionchartoverview.component'; import { SinglethresholdChartOverviewComponent } from './edge/history/singlethreshold/singlethresholdchartoverview/singlethresholdchartoverview.component'; import { StorageChartOverviewComponent } from './edge/history/storage/storagechartoverview/storagechartoverview.component'; +import { TimeOfUseTariffDischargeChartOverviewComponent } from './edge/history/timeofusetariffdischarge/timeofusetariffdischargeoverview/timeofusetariffdischargechartoverview.component'; import { LiveComponent as EdgeLiveComponent } from './edge/live/live.component'; import { ChannelsComponent as EdgeSettingsChannelsComponent } from './edge/settings/channels/channels.component'; import { IndexComponent as EdgeSettingsComponentInstallIndexComponentComponent } from './edge/settings/component/install/index.component'; @@ -29,6 +30,7 @@ import { ProfileComponent as EdgeSettingsProfileComponent } from './edge/setting import { SettingsComponent as EdgeSettingsComponent } from './edge/settings/settings.component'; import { SystemExecuteComponent as EdgeSettingsSystemExecuteComponent } from './edge/settings/systemexecute/systemexecute.component'; import { SystemLogComponent as EdgeSettingsSystemLogComponent } from './edge/settings/systemlog/systemlog.component'; +import { SystemUpdateComponent as EdgeSettingsSystemUpdateComponent } from './edge/settings/systemupdate/systemupdate.component'; import { IndexComponent } from './index/index.component'; import { UserComponent } from './user/user.component'; @@ -54,6 +56,7 @@ const routes: Routes = [ { path: 'device/:edgeId/history/:componentId/singlethresholdchart', component: SinglethresholdChartOverviewComponent }, { path: 'device/:edgeId/history/:componentId/symmetricpeakshavingchart', component: SymmetricPeakshavingChartOverviewComponent }, { path: 'device/:edgeId/history/:componentId/timeslotpeakshavingchart', component: TimeslotPeakshavingChartOverviewComponent }, + { path: 'device/:edgeId/history/:componentId/timeOfUseTariffDischargeChart', component: TimeOfUseTariffDischargeChartOverviewComponent }, { path: 'device/:edgeId/history/autarchychart', component: AutarchyChartOverviewComponent }, { path: 'device/:edgeId/history/consumptionchart', component: ConsumptionChartOverviewComponent }, { path: 'device/:edgeId/history/gridchart', component: GridChartOverviewComponent }, @@ -72,6 +75,7 @@ const routes: Routes = [ { path: 'device/:edgeId/settings/profile/:componentId', component: AliasUpdateComponent }, { path: 'device/:edgeId/settings/systemexecute', component: EdgeSettingsSystemExecuteComponent }, { path: 'device/:edgeId/settings/systemlog', component: EdgeSettingsSystemLogComponent }, + { path: 'device/:edgeId/settings/systemupdate', component: EdgeSettingsSystemUpdateComponent }, ]; export const appRoutingProviders: any[] = [ diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index 014664d3999..7b51073464d 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -6,6 +6,7 @@ import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { environment } from '../environments'; import { Service, Websocket } from './shared/shared'; +import { LanguageTag } from './shared/translate/language'; @Component({ selector: 'app-root', @@ -29,7 +30,7 @@ export class AppComponent { public websocket: Websocket, private titleService: Title ) { - service.setLang(this.service.browserLangToLangTag(navigator.language)); + service.setLang(LanguageTag[localStorage.LANGUAGE] ?? this.service.browserLangToLangTag(navigator.language)); } ngOnInit() { diff --git a/ui/src/app/edge/history/abstracthistorychart.ts b/ui/src/app/edge/history/abstracthistorychart.ts index f9e0b831b79..eecdee894a7 100644 --- a/ui/src/app/edge/history/abstracthistorychart.ts +++ b/ui/src/app/edge/history/abstracthistorychart.ts @@ -69,11 +69,15 @@ export abstract class AbstractHistoryChart { * @param edge the current Edge * @param ws the websocket */ - protected queryHistoricTimeseriesData(fromDate: Date, toDate: Date): Promise { + protected queryHistoricTimeseriesData(fromDate: Date, toDate: Date, resolution?: number): Promise { // TODO should be removed, edge delivers too much data let newDate = (this.service.periodString == 'year' ? addMonths(fromDate, 1) : this.service.periodString == 'month' ? addDays(fromDate, 1) : fromDate); - let resolution = calculateResolution(this.service, fromDate, toDate); + + if (resolution == null) { + resolution = calculateResolution(this.service, fromDate, toDate); + } + return new Promise((resolve, reject) => { this.service.getCurrentEdge().then(edge => { this.service.getConfig().then(config => { @@ -105,11 +109,12 @@ export abstract class AbstractHistoryChart { // TODO should be removed, edge delivers too much data let newDate = this.service.periodString == 'year' ? addMonths(fromDate, 1) : addDays(fromDate, 1) + let resolution = calculateResolution(this.service, fromDate, toDate); + return new Promise((resolve, reject) => { this.service.getCurrentEdge().then(edge => { this.service.getConfig().then(config => { - edge.sendRequest(this.service.websocket, new queryHistoricTimeseriesEnergyPerPeriodRequest(newDate, toDate, channelAddresses, resolution)).then(response => { let result = (response as QueryHistoricTimeseriesDataResponse).result; diff --git a/ui/src/app/edge/history/autarchy/chart.component.ts b/ui/src/app/edge/history/autarchy/chart.component.ts index 54ed289f955..8fc25a50d1e 100644 --- a/ui/src/app/edge/history/autarchy/chart.component.ts +++ b/ui/src/app/edge/history/autarchy/chart.component.ts @@ -98,7 +98,6 @@ export class AutarchyChartComponent extends AbstractHistoryChart implements OnIn return CurrentData.calculateAutarchy(buyFromGridData[index], value); } }) - datasets.push({ label: this.translate.instant('General.autarchy'), data: autarchy, diff --git a/ui/src/app/edge/history/consumption/otherchart.component.ts b/ui/src/app/edge/history/consumption/otherchart.component.ts index 8795ed597b4..ffaacb5daf0 100644 --- a/ui/src/app/edge/history/consumption/otherchart.component.ts +++ b/ui/src/app/edge/history/consumption/otherchart.component.ts @@ -65,12 +65,12 @@ export class ConsumptionOtherChartComponent extends AbstractHistoryChart impleme }); }) - let totalMetersConsumption: number[] = []; + let totalMeteredConsumption: number[] = []; config.getComponentsImplementingNature("io.openems.edge.meter.api.SymmetricMeter") .filter(component => component.isEnabled && config.isTypeConsumptionMetered(component)) .forEach(component => { - totalMetersConsumption = result.data[component.id + '/ActivePower'].map((value, index) => { - return Utils.addSafely(totalMetersConsumption[index], value / 1000) + totalMeteredConsumption = result.data[component.id + '/ActivePower'].map((value, index) => { + return Utils.addSafely(totalMeteredConsumption[index], value / 1000) }) }) @@ -80,13 +80,13 @@ export class ConsumptionOtherChartComponent extends AbstractHistoryChart impleme if (value != null) { - // Check if either totalEvcsConsumption or totalMetersConsumption is not null - return Utils.subtractSafely(Utils.subtractSafely(value / 1000, totalEvcsConsumption[index]), totalMetersConsumption[index]); + // Check if either totalEvcsConsumption or totalMeteredConsumption is not null + return Utils.subtractSafely(Utils.subtractSafely(value / 1000, totalEvcsConsumption[index]), totalMeteredConsumption[index]); } }) // show other consumption if at least one of the arrays is not empty - if (totalEvcsConsumption != [] || totalMetersConsumption != []) { + if (totalEvcsConsumption != [] || totalMeteredConsumption != []) { datasets.push({ label: this.translate.instant('General.consumption'), data: otherConsumption, @@ -144,4 +144,4 @@ export class ConsumptionOtherChartComponent extends AbstractHistoryChart impleme public getChartHeight(): number { return window.innerHeight / 21 * 9; } -} \ No newline at end of file +} diff --git a/ui/src/app/edge/history/consumption/totalchart.component.ts b/ui/src/app/edge/history/consumption/totalchart.component.ts index 0bf0d1f258b..3ff9f1074b8 100644 --- a/ui/src/app/edge/history/consumption/totalchart.component.ts +++ b/ui/src/app/edge/history/consumption/totalchart.component.ts @@ -87,20 +87,14 @@ export class ConsumptionTotalChartComponent extends AbstractHistoryChart impleme // gather other Consumption (Total - EVCS - consumptionMetered) let otherConsumption: number[] = []; - if (totalEvcsConsumption != []) { - otherConsumption = result.data['_sum/ConsumptionActivePower'].map((value, index) => { - if (value != null && totalEvcsConsumption[index] != null) { - return Utils.subtractSafely(value / 1000, totalEvcsConsumption[index]); - } - }) - } - if (totalMeteredConsumption != []) { - otherConsumption = result.data['_sum/ConsumptionActivePower'].map((value, index) => { - if (value != null && totalMeteredConsumption[index] != null) { - return Utils.subtractSafely(value / 1000, totalMeteredConsumption[index]); - } - }) - } + otherConsumption = result.data['_sum/ConsumptionActivePower'].map((value, index) => { + + if (value != null) { + + // Check if either totalEvcsConsumption or totalMeteredConsumption is not null + return Utils.subtractSafely(Utils.subtractSafely(value / 1000, totalEvcsConsumption[index]), totalMeteredConsumption[index]); + } + }) // convert datasets let datasets = []; diff --git a/ui/src/app/edge/history/history.component.html b/ui/src/app/edge/history/history.component.html index 1f61d5f3d0e..e41956425fd 100644 --- a/ui/src/app/edge/history/history.component.html +++ b/ui/src/app/edge/history/history.component.html @@ -20,10 +20,10 @@ - + - + @@ -61,6 +61,13 @@ + + + + + + diff --git a/ui/src/app/edge/history/history.module.ts b/ui/src/app/edge/history/history.module.ts index dbcc2445a00..23cf27e2b97 100644 --- a/ui/src/app/edge/history/history.module.ts +++ b/ui/src/app/edge/history/history.module.ts @@ -1,8 +1,7 @@ -import { AsymmetricPeakshavingChartComponent } from './peakshaving/asymmetric/chart.component'; -import { AsymmetricPeakshavingChartOverviewComponent } from './peakshaving/asymmetric/asymmetricpeakshavingchartoverview/asymmetricpeakshavingchartoverview.component'; -import { AsymmetricPeakshavingWidgetComponent } from './peakshaving/asymmetric/widget.component'; -import { AutarchyChartComponent } from './autarchy/chart.component'; +import { NgModule } from '@angular/core'; +import { SharedModule } from '../../shared/shared.module'; import { AutarchyChartOverviewComponent } from './autarchy/autarchychartoverview/autarchychartoverview.component'; +import { AutarchyChartComponent } from './autarchy/chart.component'; import { AutarchyWidgetComponent } from './autarchy/widget.component'; import { ChannelthresholdChartOverviewComponent } from './channelthreshold/channelthresholdchartoverview/channelthresholdchartoverview.component'; import { ChannelthresholdSingleChartComponent } from './channelthreshold/singlechart.component'; @@ -11,12 +10,12 @@ import { ChannelthresholdWidgetComponent } from './channelthreshold/widget.compo import { ChpSocChartComponent } from './chpsoc/chart.component'; import { ChpSocWidgetComponent } from './chpsoc/widget.component'; import { ConsumptionChartOverviewComponent } from './consumption/consumptionchartoverview/consumptionchartoverview.component'; -import { ConsumptionComponent } from './consumption/widget.component'; import { ConsumptionEvcsChartComponent } from './consumption/evcschart.component'; import { ConsumptionMeterChartComponent } from './consumption/meterchart.component'; import { ConsumptionOtherChartComponent } from './consumption/otherchart.component'; import { ConsumptionSingleChartComponent } from './consumption/singlechart.component'; import { ConsumptionTotalChartComponent } from './consumption/totalchart.component'; +import { ConsumptionComponent } from './consumption/widget.component'; import { DelayedSellToGridChartComponent } from './delayedselltogrid/chart.component'; import { DelayedSellToGridChartOverviewComponent } from './delayedselltogrid/symmetricpeakshavingchartoverview/delayedselltogridchartoverview.component'; import { DelayedSellToGridWidgetComponent } from './delayedselltogrid/widget.component'; @@ -29,9 +28,10 @@ import { FixDigitalOutputWidgetComponent } from './fixdigitaloutput/widget.compo import { GridChartComponent } from './grid/chart.component'; import { GridChartOverviewComponent } from './grid/gridchartoverview/gridchartoverview.component'; import { GridComponent } from './grid/widget.component'; -import { GridOptimizedChargeWidgetComponent } from './gridoptimizedcharge/widget.component'; import { GridOptimizedChargeChartComponent } from './gridoptimizedcharge/chart.component'; import { GridOptimizedChargeChartOverviewComponent } from './gridoptimizedcharge/gridoptimizedchargechartoverview/gridoptimizedchargechartoverview.component'; +import { SellToGridLimitChartComponent } from './gridoptimizedcharge/sellToGridLimitChart.component'; +import { GridOptimizedChargeWidgetComponent } from './gridoptimizedcharge/widget.component'; import { HeatingelementChartComponent } from './heatingelement/chart.component'; import { HeatingelementChartOverviewComponent } from './heatingelement/heatingelementchartoverview/heatingelementchartoverview.component'; import { HeatingelementWidgetComponent } from './heatingelement/widget.component'; @@ -39,36 +39,39 @@ import { HeatPumpChartComponent } from './heatpump/chart.component'; import { HeatPumpChartOverviewComponent } from './heatpump/heatpumpchartoverview/heatpumpchartoverview.component'; import { HeatpumpWidgetComponent } from './heatpump/widget.component'; import { HistoryComponent } from './history.component'; -import { NgModule } from '@angular/core'; +import { AsymmetricPeakshavingChartOverviewComponent } from './peakshaving/asymmetric/asymmetricpeakshavingchartoverview/asymmetricpeakshavingchartoverview.component'; +import { AsymmetricPeakshavingChartComponent } from './peakshaving/asymmetric/chart.component'; +import { AsymmetricPeakshavingWidgetComponent } from './peakshaving/asymmetric/widget.component'; +import { SymmetricPeakshavingChartComponent } from './peakshaving/symmetric/chart.component'; +import { SymmetricPeakshavingChartOverviewComponent } from './peakshaving/symmetric/symmetricpeakshavingchartoverview/symmetricpeakshavingchartoverview.component'; +import { SymmetricPeakshavingWidgetComponent } from './peakshaving/symmetric/widget.component'; +import { TimeslotPeakshavingChartComponent } from './peakshaving/timeslot/chart.component'; +import { TimeslotPeakshavingChartOverviewComponent } from './peakshaving/timeslot/timeslotpeakshavingchartoverview/timeslotpeakshavingchartoverview.component'; +import { TimeslotPeakshavingWidgetComponent } from './peakshaving/timeslot/widget.component'; import { ProductionChargerChartComponent } from './production/chargerchart.component'; import { ProductionChartOverviewComponent } from './production/productionchartoverview/productionchartoverview.component'; -import { ProductionComponent } from './production/widget.component'; import { ProductionMeterChartComponent } from './production/productionmeterchart'; import { ProductionSingleChartComponent } from './production/singlechart'; import { ProductionTotalAcChartComponent } from './production/totalacchart'; import { ProductionTotalChartComponent } from './production/totalchart'; import { ProductionTotalDcChartComponent } from './production/totaldcchart'; +import { ProductionComponent } from './production/widget.component'; import { SelfconsumptionChartComponent } from './selfconsumption/chart.component'; import { SelfconsumptionChartOverviewComponent } from './selfconsumption/selfconsumptionchartoverview/selfconsumptionchartoverview.component'; import { SelfconsumptionWidgetComponent } from './selfconsumption/widget.component'; -import { SellToGridLimitChartComponent } from './gridoptimizedcharge/sellToGridLimitChart.component'; -import { SharedModule } from '../../shared/shared.module'; import { SinglethresholdChartComponent } from './singlethreshold/chart.component'; import { SinglethresholdChartOverviewComponent } from './singlethreshold/singlethresholdchartoverview/singlethresholdchartoverview.component'; import { SinglethresholdWidgetComponent } from './singlethreshold/widget.component'; -import { SocStorageChartComponent } from './storage/socchart.component'; import { StorageChargerChartComponent } from './storage/chargerchart.component'; -import { StorageChartOverviewComponent } from './storage/storagechartoverview/storagechartoverview.component'; -import { StorageComponent } from './storage/widget.component'; import { StorageESSChartComponent } from './storage/esschart.component'; import { StorageSingleChartComponent } from './storage/singlechart.component'; +import { SocStorageChartComponent } from './storage/socchart.component'; +import { StorageChartOverviewComponent } from './storage/storagechartoverview/storagechartoverview.component'; import { StorageTotalChartComponent } from './storage/totalchart.component'; -import { SymmetricPeakshavingChartComponent } from './peakshaving/symmetric/chart.component'; -import { SymmetricPeakshavingChartOverviewComponent } from './peakshaving/symmetric/symmetricpeakshavingchartoverview/symmetricpeakshavingchartoverview.component'; -import { SymmetricPeakshavingWidgetComponent } from './peakshaving/symmetric/widget.component'; -import { TimeslotPeakshavingChartComponent } from './peakshaving/timeslot/chart.component'; -import { TimeslotPeakshavingChartOverviewComponent } from './peakshaving/timeslot/timeslotpeakshavingchartoverview/timeslotpeakshavingchartoverview.component'; -import { TimeslotPeakshavingWidgetComponent } from './peakshaving/timeslot/widget.component'; +import { StorageComponent } from './storage/widget.component'; +import { TimeOfUseTariffDischargeChartComponent } from './timeofusetariffdischarge/chart.component'; +import { TimeOfUseTariffDischargeChartOverviewComponent } from './timeofusetariffdischarge/timeofusetariffdischargeoverview/timeofusetariffdischargechartoverview.component'; +import { TimeOfUseTariffDischargeWidgetComponent } from './timeofusetariffdischarge/widget.component'; @NgModule({ imports: [ @@ -144,6 +147,9 @@ import { TimeslotPeakshavingWidgetComponent } from './peakshaving/timeslot/widge SymmetricPeakshavingChartComponent, SymmetricPeakshavingChartOverviewComponent, SymmetricPeakshavingWidgetComponent, + TimeOfUseTariffDischargeChartComponent, + TimeOfUseTariffDischargeChartOverviewComponent, + TimeOfUseTariffDischargeWidgetComponent, TimeslotPeakshavingChartComponent, TimeslotPeakshavingChartOverviewComponent, TimeslotPeakshavingWidgetComponent, diff --git a/ui/src/app/edge/history/storage/socchart.component.ts b/ui/src/app/edge/history/storage/socchart.component.ts index e12293dc134..8d68ac3317f 100644 --- a/ui/src/app/edge/history/storage/socchart.component.ts +++ b/ui/src/app/edge/history/storage/socchart.component.ts @@ -14,6 +14,7 @@ import { TranslateService } from '@ngx-translate/core'; export class SocStorageChartComponent extends AbstractHistoryChart implements OnInit, OnChanges { @Input() public period: DefaultTypes.HistoryPeriod; + private emergencyCapacityReserveComponents: EdgeConfig.Component[] = []; ngOnChanges() { this.updateChart(); @@ -95,11 +96,15 @@ export class SocStorageChartComponent extends AbstractHistoryChart implements On }) } } - if (channelAddress.channelId == '_PropertyReserveSoc') { + if (channelAddress.channelId == '_PropertyReserveSoc' && + this.emergencyCapacityReserveComponents.find( + element => element.id == channelAddress.componentId) + .properties.isReserveSocEnabled) { datasets.push({ - label: component.alias, + label: + this.emergencyCapacityReserveComponents.length > 1 ? component.alias : this.translate.instant("Edge.Index.EmergencyReserve.emergencyReserve"), data: data, - borderDash: [3, 3] + borderDash: [3, 3], }) this.colors.push({ backgroundColor: 'rgba(1, 1, 1,0)', @@ -133,8 +138,11 @@ export class SocStorageChartComponent extends AbstractHistoryChart implements On let channeladdresses: ChannelAddress[] = []; channeladdresses.push(new ChannelAddress('_sum', 'EssSoc')); - config.getComponentsImplementingNature('io.openems.edge.controller.ess.emergencycapacityreserve.EmergencyCapacityReserve') + + this.emergencyCapacityReserveComponents = config.getComponentsByFactory('Controller.Ess.EmergencyCapacityReserve') .filter(component => component.isEnabled) + + this.emergencyCapacityReserveComponents .forEach(component => channeladdresses.push(new ChannelAddress(component.id, '_PropertyReserveSoc')) ); @@ -164,4 +172,4 @@ export class SocStorageChartComponent extends AbstractHistoryChart implements On public getChartHeight(): number { return window.innerHeight / 21 * 9; } -} \ No newline at end of file +} diff --git a/ui/src/app/edge/history/timeofusetariffdischarge/chart.component.ts b/ui/src/app/edge/history/timeofusetariffdischarge/chart.component.ts new file mode 100644 index 00000000000..12d1cd7a5b6 --- /dev/null +++ b/ui/src/app/edge/history/timeofusetariffdischarge/chart.component.ts @@ -0,0 +1,318 @@ +import { formatNumber } from '@angular/common'; +import { Component, Input, OnChanges, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { differenceInDays } from 'date-fns'; +import { DefaultTypes } from 'src/app/shared/service/defaulttypes'; +import { QueryHistoricTimeseriesDataResponse } from '../../../shared/jsonrpc/response/queryHistoricTimeseriesDataResponse'; +import { ChannelAddress, Edge, EdgeConfig, Service } from '../../../shared/shared'; +import { AbstractHistoryChart } from '../abstracthistorychart'; +import { Data, TooltipItem } from '../shared'; + +@Component({ + selector: 'timeOfUseTariffDischargeChart', + templateUrl: '../abstracthistorychart.html' +}) +export class TimeOfUseTariffDischargeChartComponent extends AbstractHistoryChart implements OnInit, OnChanges { + + @Input() public period: DefaultTypes.HistoryPeriod; + @Input() public componentId: string; + + ngOnChanges() { + this.updateChart(); + }; + + constructor( + protected service: Service, + protected translate: TranslateService, + private route: ActivatedRoute, + ) { + super(service, translate); + } + + ngOnInit() { + this.spinnerId = "timeOfUseTariffDischarge-chart"; + this.service.startSpinner(this.spinnerId); + this.service.setCurrentComponent('', this.route); + } + + ngOnDestroy() { + this.unsubscribeChartRefresh() + } + + protected updateChart() { + this.autoSubscribeChartRefresh(); + this.service.startSpinner(this.spinnerId); + this.colors = []; + this.loading = true; + + this.queryHistoricTimeseriesData(this.period.from, this.period.to, 900).then(response => { + this.service.getConfig().then(config => { + let result = (response as QueryHistoricTimeseriesDataResponse).result; + + // convert labels + let labels: Date[] = []; + for (let timestamp of result.timestamps) { + // Only use full hours as a timestamp + labels.push(new Date(timestamp)); + } + this.labels = labels; + + // convert datasets + let datasets = []; + let quarterlyPrices = this.componentId + '/QuarterlyPrices'; + let TimeOfUseTariffState = this.componentId + '/StateMachine'; + // let predictedSocWithoutLogic = this.componentId + '/PredictedSocWithoutLogic'; + + if (TimeOfUseTariffState in result.data && quarterlyPrices in result.data) { + + // Get only the 15 minute value + let quarterlyPricesStandbyModeData = []; + let quarterlyPricesNightData = []; + let quarterlyPricesDelayedDischargeData = []; + // let predictedSocWithoutLogicData = []; + + for (let i = 0; i < 96; i++) { + let quarterlyPrice = this.formatPrice(result.data[quarterlyPrices][i]); + let state = result.data[TimeOfUseTariffState][i]; + + if (state == null) { + quarterlyPricesDelayedDischargeData.push(null); + quarterlyPricesNightData.push(null); + quarterlyPricesStandbyModeData.push(null); + } else { + switch (state) { + case 0: + // delayed + quarterlyPricesDelayedDischargeData.push(quarterlyPrice); + quarterlyPricesNightData.push(null); + quarterlyPricesStandbyModeData.push(null); + break; + case 1: + // allowsDischarge + quarterlyPricesDelayedDischargeData.push(null); + quarterlyPricesNightData.push(quarterlyPrice) + quarterlyPricesStandbyModeData.push(null); + break; + case -1: + // notStarted + case 2: + // standby + quarterlyPricesDelayedDischargeData.push(null); + quarterlyPricesNightData.push(null); + quarterlyPricesStandbyModeData.push(quarterlyPrice); + break; + } + } + } + + + // Set dataset for no limit + datasets.push({ + type: 'bar', + label: this.translate.instant('Edge.Index.Energymonitor.storageDischarge'), + data: quarterlyPricesNightData, + order: 3, + }); + this.colors.push({ + // Dark Green + backgroundColor: 'rgba(51,102,0,0.8)', + borderColor: 'rgba(51,102,0,1)', + }) + + // Set dataset for buy from grid + datasets.push({ + type: 'bar', + label: this.translate.instant('General.gridBuy'), + data: quarterlyPricesDelayedDischargeData, + order: 4, + }); + this.colors.push({ + // Black + backgroundColor: 'rgba(0,0,0,0.8)', + borderColor: 'rgba(0,0,0,0.9)', + + }) + + // Set dataset for Quarterly Prices outside zone + datasets.push({ + type: 'bar', + label: this.translate.instant('Edge.Index.Widgets.TimeOfUseTariff.State.standby'), + data: quarterlyPricesStandbyModeData, + order: 3, + }); + this.colors.push({ + // Dark Blue + backgroundColor: 'rgba(0,0,200,0.7)', + borderColor: 'rgba(0,0,200,0.9)', + }) + + // Predicted SoC is not shown for now, because it is not inteligent enough with the simple prediction + // if (predictedSocWithoutLogic in result.data) { + // for (let i = 0; i < 96; i++) { + // let predictedSoc = result.data[predictedSocWithoutLogic][i]; + // predictedSocWithoutLogicData.push(predictedSoc); + // } + // } + + // let predictedSocLabel = "Predicted Soc without logic"; + // datasets.push({ + // type: 'line', + // label: predictedSocLabel, + // data: predictedSocWithoutLogicData, + // hidden: false, + // yAxisID: 'yAxis2', + // position: 'right', + // borderDash: [10, 10], + // order: 2, + // }); + // this.colors.push({ + // backgroundColor: 'rgba(255,0,0,0.01)', + // borderColor: 'rgba(255,0,0,1)' + // }) + } + + // State of charge data + if ('_sum/EssSoc' in result.data) { + let socData = result.data['_sum/EssSoc'].map(value => { + if (value == null) { + return null + } else if (value > 100 || value < 0) { + return null; + } else { + return value; + } + }) + datasets.push({ + type: 'line', + label: this.translate.instant('General.soc'), + data: socData, + hidden: false, + yAxisID: 'yAxis2', + position: 'right', + borderDash: [10, 10], + order: 1, + }) + this.colors.push({ + backgroundColor: 'rgba(189, 195, 199,0.2)', + borderColor: 'rgba(189, 195, 199,1)', + }) + } + + this.datasets = datasets; + this.loading = false; + this.service.stopSpinner(this.spinnerId); + }).catch(reason => { + console.error(reason); // TODO error message + this.initializeChart(); + return; + }); + }).catch(reason => { + console.error(reason); // TODO error message + this.initializeChart(); + return; + }); + } + + /** + * Converts a value in €/MWh to €/kWh. + * + * @param price the price value + * @returns the converted price + */ + private formatPrice(price: number): number { + if (price == null || price == NaN) { + return null; + } else if (price == 0) { + return 0; + } else { + price = (price / 10.0); + return Math.round(price * 10000) / 10000.0; + } + } + + protected getChannelAddresses(edge: Edge, config: EdgeConfig): Promise { + return new Promise((resolve) => { + resolve( + [ + new ChannelAddress(this.componentId, 'Delayed'), + new ChannelAddress(this.componentId, 'QuarterlyPrices'), + new ChannelAddress(this.componentId, 'StateMachine'), + new ChannelAddress('_sum', 'EssSoc'), + // new ChannelAddress(this.componentId, 'PredictedSocWithoutLogic'), + ]); + }); + } + + protected setLabel(config: EdgeConfig) { + let options = this.createDefaultChartOptions(); + let translate = this.translate; + + console.log('options: ', options); + + // Adds second y-axis to chart + options.scales.yAxes.push({ + id: 'yAxis2', + position: 'right', + scaleLabel: { + display: true, + labelString: "%", + padding: -2, + fontSize: 11 + }, + gridLines: { + display: false + }, + ticks: { + beginAtZero: true, + max: 100, + padding: -5, + stepSize: 20 + } + }) + options.layout = { + padding: { + left: 2, + right: 2, + top: 0, + bottom: 0 + } + } + + options.scales.xAxes[0].stacked = true; + + //x-axis + if (differenceInDays(this.service.historyPeriod.to, this.service.historyPeriod.from) >= 5) { + options.scales.xAxes[0].time.unit = "day"; + } else { + options.scales.xAxes[0].time.unit = "hour"; + } + + //y-axis + options.scales.yAxes[0].id = "yAxis1" + options.scales.yAxes[0].scaleLabel.labelString = "Cent / kWh"; + options.scales.yAxes[0].scaleLabel.padding = -2; + options.scales.yAxes[0].scaleLabel.fontSize = 11; + options.scales.yAxes[0].ticks.padding = -5; + options.tooltips.callbacks.label = function (tooltipItem: TooltipItem, data: Data) { + let label = data.datasets[tooltipItem.datasetIndex].label; + let value = tooltipItem.yLabel; + + if (!value) { + return; + } + if (label == translate.instant('General.soc')) { + return label + ": " + formatNumber(value, 'de', '1.0-0') + " %"; + // } else if (label == 'Predicted Soc without logic') { + // return label + ": " + formatNumber(value, 'de', '1.0-0') + " %"; + } else { + return label + ": " + formatNumber(value, 'de', '1.0-4') + " Cent/kWh"; + } + } + this.options = options; + } + + public getChartHeight(): number { + return window.innerHeight / 1.3; + } +} \ No newline at end of file diff --git a/ui/src/app/edge/history/timeofusetariffdischarge/timeofusetariffdischargeoverview/timeofusetariffdischargechartoverview.component.html b/ui/src/app/edge/history/timeofusetariffdischarge/timeofusetariffdischargeoverview/timeofusetariffdischargechartoverview.component.html new file mode 100644 index 00000000000..da8d9f66cf4 --- /dev/null +++ b/ui/src/app/edge/history/timeofusetariffdischarge/timeofusetariffdischargeoverview/timeofusetariffdischargechartoverview.component.html @@ -0,0 +1,30 @@ + + + + {{ component.alias }} + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+
\ No newline at end of file diff --git a/ui/src/app/edge/history/timeofusetariffdischarge/timeofusetariffdischargeoverview/timeofusetariffdischargechartoverview.component.ts b/ui/src/app/edge/history/timeofusetariffdischarge/timeofusetariffdischargeoverview/timeofusetariffdischargechartoverview.component.ts new file mode 100644 index 00000000000..5cc8201f183 --- /dev/null +++ b/ui/src/app/edge/history/timeofusetariffdischarge/timeofusetariffdischargeoverview/timeofusetariffdischargechartoverview.component.ts @@ -0,0 +1,30 @@ +import { ActivatedRoute } from '@angular/router'; +import { Component } from '@angular/core'; +import { Service, Utils, EdgeConfig, Edge } from '../../../../shared/shared'; + +@Component({ + selector: TimeOfUseTariffDischargeChartOverviewComponent.SELECTOR, + templateUrl: './timeofusetariffdischargechartoverview.component.html' +}) +export class TimeOfUseTariffDischargeChartOverviewComponent { + + private static readonly SELECTOR = "timeofusetariffdischarge-chart-overview"; + + public edge: Edge = null; + + public component: EdgeConfig.Component = null; + + constructor( + public service: Service, + private route: ActivatedRoute, + ) { } + + ngOnInit() { + this.service.setCurrentComponent('', this.route).then(edge => { + this.service.getConfig().then(config => { + this.edge = edge; + this.component = config.getComponent(this.route.snapshot.params.componentId); + }) + }); + } +} \ No newline at end of file diff --git a/ui/src/app/edge/history/timeofusetariffdischarge/widget.component.html b/ui/src/app/edge/history/timeofusetariffdischarge/widget.component.html new file mode 100644 index 00000000000..09384ac3a39 --- /dev/null +++ b/ui/src/app/edge/history/timeofusetariffdischarge/widget.component.html @@ -0,0 +1,18 @@ + + + + {{ component.alias }} + + + + + + + +
+ Edge.Index.Widgets.TimeOfUseTariff.delayedDischarge + {{ activeTimeOverPeriod | sectohour }} +
+
+
\ No newline at end of file diff --git a/ui/src/app/edge/history/timeofusetariffdischarge/widget.component.ts b/ui/src/app/edge/history/timeofusetariffdischarge/widget.component.ts new file mode 100644 index 00000000000..69ee3c72fcf --- /dev/null +++ b/ui/src/app/edge/history/timeofusetariffdischarge/widget.component.ts @@ -0,0 +1,67 @@ +import { Component, Input, OnChanges, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { DefaultTypes } from 'src/app/shared/service/defaulttypes'; +import { ChannelAddress, Edge, EdgeConfig, Service } from '../../../shared/shared'; +import { AbstractHistoryWidget } from '../abstracthistorywidget'; + +@Component({ + selector: TimeOfUseTariffDischargeWidgetComponent.SELECTOR, + templateUrl: './widget.component.html' +}) +export class TimeOfUseTariffDischargeWidgetComponent extends AbstractHistoryWidget implements OnInit, OnChanges { + + @Input() public period: DefaultTypes.HistoryPeriod; + @Input() public componentId: string; + + private static readonly SELECTOR = "timeOfUseTariffDischargeWidget"; + + public activeTimeOverPeriod: number = null; + public edge: Edge = null; + public component: EdgeConfig.Component = null; + + constructor( + public service: Service, + private route: ActivatedRoute, + ) { + super(service); + } + + ngOnInit() { + this.service.setCurrentComponent('', this.route).then(response => { + this.edge = response; + this.service.getConfig().then(config => { + this.component = config.getComponent(this.componentId); + }) + }); + } + + ngOnDestroy() { + this.unsubscribeWidgetRefresh() + } + + ngOnChanges() { + this.updateValues(); + }; + + // Calculate active time based on a time counter + protected updateValues() { + + this.service.getConfig().then(config => { + this.getChannelAddresses(this.edge, config).then(channels => { + this.service.queryEnergy(this.period.from, this.period.to, channels).then(response => { + let result = response.result; + if (this.componentId + '/DelayedTime' in result.data) { + this.activeTimeOverPeriod = result.data[this.componentId + '/DelayedTime']; + } + }) + }); + }); + } + + protected getChannelAddresses(edge: Edge, config: EdgeConfig): Promise { + + return new Promise((resolve) => { + resolve([new ChannelAddress(this.componentId, 'DelayedTime')]); + }); + } +} \ No newline at end of file diff --git a/ui/src/app/edge/live/Controller/Channelthreshold/Channelthreshold.ts b/ui/src/app/edge/live/Controller/Channelthreshold/Channelthreshold.ts index 004909dd9b9..fb137e28382 100644 --- a/ui/src/app/edge/live/Controller/Channelthreshold/Channelthreshold.ts +++ b/ui/src/app/edge/live/Controller/Channelthreshold/Channelthreshold.ts @@ -1,6 +1,6 @@ import { ChannelAddress, CurrentData } from '../../../../shared/shared'; import { Component } from '@angular/core'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; import { Icon } from 'src/app/shared/type/widget'; @Component({ diff --git a/ui/src/app/edge/live/Controller/ChpSoc/ChpSoc.ts b/ui/src/app/edge/live/Controller/ChpSoc/ChpSoc.ts index b2623c0caf9..d30e52b1043 100644 --- a/ui/src/app/edge/live/Controller/ChpSoc/ChpSoc.ts +++ b/ui/src/app/edge/live/Controller/ChpSoc/ChpSoc.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { Icon } from 'src/app/shared/type/widget'; import { ChannelAddress, CurrentData } from '../../../../shared/shared'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; import { Controller_ChpSocModalComponent } from './modal/modal.component'; @Component({ diff --git a/ui/src/app/edge/live/Controller/Ess_FixActivePower/Ess_FixActivePower.html b/ui/src/app/edge/live/Controller/Ess_FixActivePower/Ess_FixActivePower.html deleted file mode 100644 index aeb6ee7422e..00000000000 --- a/ui/src/app/edge/live/Controller/Ess_FixActivePower/Ess_FixActivePower.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/ui/src/app/edge/live/Controller/Ess_FixActivePower/Ess_FixActivePower.ts b/ui/src/app/edge/live/Controller/Ess_FixActivePower/Ess_FixActivePower.ts index 381f17739b3..e5965505b82 100644 --- a/ui/src/app/edge/live/Controller/Ess_FixActivePower/Ess_FixActivePower.ts +++ b/ui/src/app/edge/live/Controller/Ess_FixActivePower/Ess_FixActivePower.ts @@ -1,56 +1,24 @@ -import { Component } from '@angular/core'; -import { ChannelAddress, CurrentData } from 'src/app/shared/shared'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; -import { Controller_Ess_FixActivePowerModalComponent } from './modal/modal.component'; +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { SharedModule } from 'src/app/shared/shared.module'; +import { Flat } from './flat/flat'; +import { Modal } from './modal/modal'; -@Component({ - selector: 'Controller_Ess_FixActivePower', - templateUrl: './Ess_FixActivePower.html' +@NgModule({ + imports: [ + BrowserModule, + SharedModule, + ], + entryComponents: [ + Flat, + Modal, + ], + declarations: [ + Flat, + Modal, + ], + exports: [ + Flat + ] }) -export class Controller_Ess_FixActivePower extends AbstractFlatWidget { - - private static PROPERTY_POWER: string = "_PropertyPower"; - - public chargeState: string; - public chargeStateValue: number; - - public stateConverter = (value: any): string => { - if (value === 'MANUAL_ON') { - return this.translate.instant('General.on'); - } else if (value === 'MANUAL_OFF') { - return this.translate.instant('General.off'); - } else { - return '-'; - } - } - - protected getChannelAddresses(): ChannelAddress[] { - let channelAddresses: ChannelAddress[] = [new ChannelAddress(this.componentId, Controller_Ess_FixActivePower.PROPERTY_POWER)] - return channelAddresses; - } - - protected onCurrentData(currentData: CurrentData) { - let channelPower = currentData.thisComponent['_PropertyPower']; - if (channelPower >= 0) { - this.chargeState = 'General.dischargePower'; - this.chargeStateValue = channelPower - } else { - this.chargeState = 'General.chargePower'; - this.chargeStateValue = channelPower * -1; - } - } - - async presentModal() { - if (!this.isInitialized) { - return; - } - const modal = await this.modalController.create({ - component: Controller_Ess_FixActivePowerModalComponent, - componentProps: { - component: this.component, - edge: this.edge, - } - }); - return await modal.present(); - } -} +export class Controller_Ess_FixActivePower { } diff --git a/ui/src/app/edge/live/Controller/Ess_FixActivePower/flat/flat.html b/ui/src/app/edge/live/Controller/Ess_FixActivePower/flat/flat.html new file mode 100644 index 00000000000..55b1aa87dc2 --- /dev/null +++ b/ui/src/app/edge/live/Controller/Ess_FixActivePower/flat/flat.html @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/app/edge/live/Controller/Ess_FixActivePower/flat/flat.ts b/ui/src/app/edge/live/Controller/Ess_FixActivePower/flat/flat.ts new file mode 100644 index 00000000000..bdc861657fa --- /dev/null +++ b/ui/src/app/edge/live/Controller/Ess_FixActivePower/flat/flat.ts @@ -0,0 +1,43 @@ +import { Component } from '@angular/core'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; +import { DefaultTypes } from 'src/app/shared/service/defaulttypes'; +import { ChannelAddress, CurrentData, Utils } from 'src/app/shared/shared'; +import { Modal } from '../modal/modal'; + +@Component({ + selector: 'Controller_Ess_FixActivePower', + templateUrl: './flat.html' +}) +export class Flat extends AbstractFlatWidget { + + public readonly CONVERT_WATT_TO_KILOWATT = Utils.CONVERT_WATT_TO_KILOWATT; + public readonly CONVERT_MANUAL_ON_OFF = Utils.CONVERT_MANUAL_ON_OFF(this.translate); + + public chargeDischargePower: { name: string, value: number }; + public propertyMode: DefaultTypes.ManualOnOff = null; + + protected override getChannelAddresses(): ChannelAddress[] { + return [ + new ChannelAddress(this.component.id, "_PropertyPower"), + new ChannelAddress(this.component.id, "_PropertyMode") + ]; + } + + protected override onCurrentData(currentData: CurrentData) { + this.chargeDischargePower = Utils.convertChargeDischargePower(this.translate, currentData.thisComponent['_PropertyPower']); + this.propertyMode = currentData.thisComponent['_PropertyMode']; + } + + async presentModal() { + if (!this.isInitialized) { + return; + } + const modal = await this.modalController.create({ + component: Modal, + componentProps: { + component: this.component + } + }); + return await modal.present(); + } +} diff --git a/ui/src/app/edge/live/Controller/Ess_FixActivePower/modal/modal.component.html b/ui/src/app/edge/live/Controller/Ess_FixActivePower/modal/modal.component.html deleted file mode 100644 index abaa0c685d1..00000000000 --- a/ui/src/app/edge/live/Controller/Ess_FixActivePower/modal/modal.component.html +++ /dev/null @@ -1,105 +0,0 @@ - - - {{ component.alias }} - - - - - - - - - - -

- - - - - - - - - - - - - - -
- General.state - - - General.on - - - General.off - - -
- General.dischargePower - - {{ component.properties.power | unitvalue:'W' }} -
- General.chargePower - - {{ (component.properties.power) * -1 | unitvalue:'W' }} -
-
- - - - - General.on - - - - - - General.off - - - - - - - - - - - -
- General.power - - - - -  W - -
-
- - - - Edge.Index.Widgets.InfoStorageForCharge - - - - Edge.Index.Widgets.InfoStorageForDischarge - - - - - - - - - -
- - - - - \ No newline at end of file diff --git a/ui/src/app/edge/live/Controller/Ess_FixActivePower/modal/modal.component.ts b/ui/src/app/edge/live/Controller/Ess_FixActivePower/modal/modal.component.ts deleted file mode 100644 index 5987afa9544..00000000000 --- a/ui/src/app/edge/live/Controller/Ess_FixActivePower/modal/modal.component.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; -import { ModalController } from '@ionic/angular'; -import { TranslateService } from '@ngx-translate/core'; -import { Edge, EdgeConfig, Service, Websocket } from '../../../../../shared/shared'; - -@Component({ - selector: 'fixactivepower-modal', - templateUrl: './modal.component.html' -}) -export class Controller_Ess_FixActivePowerModalComponent { - - @Input() public edge: Edge | null = null; - @Input() public component: EdgeConfig.Component | null = null; - - public formGroup: FormGroup; - public loading: boolean = false; - - constructor( - public modalCtrl: ModalController, - public service: Service, - public formBuilder: FormBuilder, - public websocket: Websocket, - public translate: TranslateService, - ) { } - - ngOnInit() { - this.formGroup = this.formBuilder.group({ - mode: new FormControl(this.component.properties.mode), - power: new FormControl(this.component.properties.power), - }) - } - - public updateControllerMode(event: CustomEvent) { - let oldMode = this.component.properties['mode']; - let newMode = event.detail.value; - - if (this.edge != null) { - this.edge.updateComponentConfig(this.websocket, this.component.id, [ - { name: 'mode', value: newMode } - ]).then(() => { - this.component.properties.mode = newMode; - this.formGroup.markAsPristine(); - this.service.toast(this.translate.instant('General.changeAccepted'), 'success'); - }).catch(reason => { - this.component.properties.mode = oldMode; - this.service.toast(this.translate.instant('General.changeFailed') + '\n' + reason.error.message, 'danger'); - console.warn(reason); - }); - } - } - - applyChanges() { - if (this.edge != null) { - if (this.edge.roleIsAtLeast('owner')) { - let updateComponentArray = []; - Object.keys(this.formGroup.controls).forEach((element, index) => { - if (this.formGroup.controls[element].dirty) { - updateComponentArray.push({ name: Object.keys(this.formGroup.controls)[index], value: this.formGroup.controls[element].value }) - } - }) - this.loading = true; - this.edge.updateComponentConfig(this.websocket, this.component.id, updateComponentArray).then(() => { - this.component.properties.mode = this.formGroup.controls['mode'].value; - this.component.properties.power = this.formGroup.controls['power'].value; - this.loading = false; - this.service.toast(this.translate.instant('General.changeAccepted'), 'success'); - }).catch(reason => { - this.formGroup.controls['mode'].setValue(this.component.properties.mode); - this.formGroup.controls['power'].setValue(this.component.properties.power); - this.loading = false; - this.service.toast(this.translate.instant('General.changeFailed') + '\n' + reason.error.message, 'danger'); - console.warn(reason); - }) - this.formGroup.markAsPristine() - } else { - this.service.toast(this.translate.instant('General.insufficientRights'), 'danger'); - } - } - } -} \ No newline at end of file diff --git a/ui/src/app/edge/live/Controller/Ess_FixActivePower/modal/modal.html b/ui/src/app/edge/live/Controller/Ess_FixActivePower/modal/modal.html new file mode 100644 index 00000000000..112b76ea758 --- /dev/null +++ b/ui/src/app/edge/live/Controller/Ess_FixActivePower/modal/modal.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/app/edge/live/Controller/Ess_FixActivePower/modal/modal.ts b/ui/src/app/edge/live/Controller/Ess_FixActivePower/modal/modal.ts new file mode 100644 index 00000000000..c49b9d9c576 --- /dev/null +++ b/ui/src/app/edge/live/Controller/Ess_FixActivePower/modal/modal.ts @@ -0,0 +1,32 @@ +import { Component } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +import { AbstractModal } from 'src/app/shared/genericComponents/modal/abstractModal'; +import { ChannelAddress, CurrentData, Utils } from 'src/app/shared/shared'; + +@Component({ + templateUrl: './modal.html' +}) +export class Modal extends AbstractModal { + + public chargeDischargePower: { name: string, value: number }; + + public readonly CONVERT_TO_WATT = Utils.CONVERT_TO_WATT; + public readonly CONVERT_MANUAL_ON_OFF = Utils.CONVERT_MANUAL_ON_OFF(this.translate); + + protected override getChannelAddresses(): ChannelAddress[] { + return [ + new ChannelAddress(this.component.id, "_PropertyPower"), + ]; + } + + protected override onCurrentData(currentData: CurrentData) { + this.chargeDischargePower = Utils.convertChargeDischargePower(this.translate, currentData.thisComponent['_PropertyPower']); + } + + protected override getFormGroup(): FormGroup { + return this.formBuilder.group({ + mode: new FormControl(this.component.properties.mode), + power: new FormControl(this.component.properties.power), + }) + } +} \ No newline at end of file diff --git a/ui/src/app/edge/live/Controller/Ess_Time-Of-Use-Tariff_Discharge/Ess_Time-Of-Use-Tariff_Discharge.html b/ui/src/app/edge/live/Controller/Ess_Time-Of-Use-Tariff_Discharge/Ess_Time-Of-Use-Tariff_Discharge.html new file mode 100644 index 00000000000..8920f08d689 --- /dev/null +++ b/ui/src/app/edge/live/Controller/Ess_Time-Of-Use-Tariff_Discharge/Ess_Time-Of-Use-Tariff_Discharge.html @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/ui/src/app/edge/live/Controller/Ess_Time-Of-Use-Tariff_Discharge/Ess_Time-Of-Use-Tariff_Discharge.ts b/ui/src/app/edge/live/Controller/Ess_Time-Of-Use-Tariff_Discharge/Ess_Time-Of-Use-Tariff_Discharge.ts new file mode 100644 index 00000000000..8a1aec4535b --- /dev/null +++ b/ui/src/app/edge/live/Controller/Ess_Time-Of-Use-Tariff_Discharge/Ess_Time-Of-Use-Tariff_Discharge.ts @@ -0,0 +1,72 @@ +import { formatNumber } from '@angular/common'; +import { Component } from '@angular/core'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; +import { ChannelAddress, CurrentData } from 'src/app/shared/shared'; +import { Controller_Ess_TimeOfUseTariff_DischargeModalComponent } from './modal/modal.component'; + +@Component({ + selector: 'Controller_Ess_TimeOfUseTariff_Discharge', + templateUrl: './Ess_Time-Of-Use-Tariff_Discharge.html' +}) +export class Controller_Ess_TimeOfUseTariff_Discharge extends AbstractFlatWidget { + + public state: string; + public mode: string; + public priceConverter = (value: any): string => { + if (!value) { + return '- Cent/kWh'; + } + return formatNumber(value / 10, 'de', '1.0-2') + ' Cent/kWh' + } + + protected onCurrentData(currentData: CurrentData) { + + // State + let channelState = currentData.thisComponent['StateMachine']; + switch (channelState) { + case -1: + this.state = this.translate.instant('Edge.Index.Widgets.TimeOfUseTariff.State.notStarted') + break; + case 0: + this.state = this.translate.instant('Edge.Index.Widgets.TimeOfUseTariff.State.delayed') + break; + case 1: + this.state = this.translate.instant('Edge.Index.Widgets.TimeOfUseTariff.State.allowsDischarge') + break; + case 2: + this.state = this.translate.instant('Edge.Index.Widgets.TimeOfUseTariff.State.standby') + break; + } + + // Mode + let modeValue = currentData.allComponents[this.component.id + '/_PropertyMode'] + switch (modeValue) { + case 'OFF': + this.mode = this.translate.instant('General.off'); + break; + case 'AUTOMATIC': + this.mode = this.translate.instant('General.automatic'); + } + } + + protected getChannelAddresses() { + return [ + new ChannelAddress(this.componentId, 'Delayed'), + new ChannelAddress(this.componentId, 'QuarterlyPrices'), + new ChannelAddress(this.componentId, 'StateMachine'), + new ChannelAddress(this.componentId, '_PropertyMode'), + ] + } + + async presentModal() { + const modal = await this.modalController.create({ + component: Controller_Ess_TimeOfUseTariff_DischargeModalComponent, + componentProps: { + component: this.component, + edge: this.edge, + config: this.config, + } + }); + return await modal.present(); + } +} \ No newline at end of file diff --git a/ui/src/app/edge/live/Controller/Ess_Time-Of-Use-Tariff_Discharge/modal/modal.component.html b/ui/src/app/edge/live/Controller/Ess_Time-Of-Use-Tariff_Discharge/modal/modal.component.html new file mode 100644 index 00000000000..beda8f42142 --- /dev/null +++ b/ui/src/app/edge/live/Controller/Ess_Time-Of-Use-Tariff_Discharge/modal/modal.component.html @@ -0,0 +1,106 @@ + + + {{ component.alias }} + + + + + + + + + + + + + + + + + + + + + + + +
+ Edge.Index.Widgets.TimeOfUseTariff.storageDischarge + + {{ getState(currentData[component.id + '/StateMachine'] )}} +
+ Edge.Index.Widgets.TimeOfUseTariff.currentTariff + + {{currentData[component.id + '/QuarterlyPrices'] / 10 | number: '1.0-4'}} Cent/kWh +
+
+ +
+ + + + + General.mode +
+ + + + General.automatic + + + + + + + General.off + + + + +
+ + + + + + + + +
+ +
+
+
+ + + + + + + + + +
+ + + +
+
+
+
+ + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/ui/src/app/edge/live/Controller/Ess_Time-Of-Use-Tariff_Discharge/modal/modal.component.ts b/ui/src/app/edge/live/Controller/Ess_Time-Of-Use-Tariff_Discharge/modal/modal.component.ts new file mode 100644 index 00000000000..623ba0376c9 --- /dev/null +++ b/ui/src/app/edge/live/Controller/Ess_Time-Of-Use-Tariff_Discharge/modal/modal.component.ts @@ -0,0 +1,91 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { ModalController } from '@ionic/angular'; +import { TranslateService } from '@ngx-translate/core'; +import { Edge, EdgeConfig, Service, Websocket } from 'src/app/shared/shared'; +import { Role } from 'src/app/shared/type/role'; + +type Mode = 'OFF' | 'AUTOMATIC'; + +@Component({ + selector: Controller_Ess_TimeOfUseTariff_DischargeModalComponent.SELECTOR, + templateUrl: './modal.component.html', +}) +export class Controller_Ess_TimeOfUseTariff_DischargeModalComponent implements OnInit { + + @Input() public edge: Edge; + @Input() public config: EdgeConfig; + @Input() public component: EdgeConfig.Component; + + private static readonly SELECTOR = "timeofusetariffdischarge-modal"; + + public formGroup: FormGroup; + public loading: boolean = false; + public pickerOptions: any; + public isInstaller: boolean; + public refreshChart: boolean; + + constructor( + public formBuilder: FormBuilder, + public modalCtrl: ModalController, + public service: Service, + public translate: TranslateService, + public websocket: Websocket, + ) { } + + ngOnInit() { + this.refreshChart = false; + if (this.edge.roleIsAtLeast(Role.INSTALLER)) { + this.isInstaller = true; + } + this.formGroup = this.formBuilder.group({ + mode: new FormControl(this.component.properties.mode), + }) + }; + + updateProperty(property: string, event: CustomEvent) { + this.formGroup.controls[property].setValue(event.detail.value); + this.formGroup.controls[property].markAsDirty() + } + + applyChanges() { + if (this.edge != null) { + if (this.edge.roleIsAtLeast('owner')) { + let updateComponentArray = []; + Object.keys(this.formGroup.controls).forEach((element, index) => { + if (this.formGroup.controls[element].dirty) { + updateComponentArray.push({ name: Object.keys(this.formGroup.controls)[index], value: this.formGroup.controls[element].value }) + } + }); + + this.loading = true; + this.edge.updateComponentConfig(this.websocket, this.component.id, updateComponentArray).then(() => { + this.component.properties.mode = this.formGroup.controls['mode'].value; + this.loading = false; + this.refreshChart = true; + this.service.toast(this.translate.instant('General.changeAccepted'), 'success'); + }).catch(reason => { + this.formGroup.controls['mode'].setValue(this.component.properties.mode); + this.loading = false; + console.warn(reason); + }); + this.formGroup.markAsPristine() + } else { + this.service.toast(this.translate.instant('General.insufficientRights'), 'danger'); + } + } + } + + getState(state: number) { + switch (state) { + case -1: + return this.translate.instant('Edge.Index.Widgets.TimeOfUseTariff.State.notStarted'); + case 0: + return this.translate.instant('Edge.Index.Widgets.TimeOfUseTariff.State.delayed'); + case 1: + return this.translate.instant('Edge.Index.Widgets.TimeOfUseTariff.State.allowsDischarge'); + case 2: + return this.translate.instant('Edge.Index.Widgets.TimeOfUseTariff.State.standby'); + } + } +} \ No newline at end of file diff --git a/ui/src/app/edge/live/Controller/Evcs/Evcs.ts b/ui/src/app/edge/live/Controller/Evcs/Evcs.ts index 7b5682224fd..41926e2e8f2 100644 --- a/ui/src/app/edge/live/Controller/Evcs/Evcs.ts +++ b/ui/src/app/edge/live/Controller/Evcs/Evcs.ts @@ -1,7 +1,7 @@ import { ChannelAddress, CurrentData, EdgeConfig, Utils } from '../../../../shared/shared'; import { Component } from '@angular/core'; import { Controller_EvcsModalComponent } from './modal/modal.page'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; @Component({ selector: 'Controller_Evcs', diff --git a/ui/src/app/edge/live/Controller/Io_ChannelSingleThreshold/Io_ChannelSingleThreshold.ts b/ui/src/app/edge/live/Controller/Io_ChannelSingleThreshold/Io_ChannelSingleThreshold.ts index 6ae31143a30..860a633ef4d 100644 --- a/ui/src/app/edge/live/Controller/Io_ChannelSingleThreshold/Io_ChannelSingleThreshold.ts +++ b/ui/src/app/edge/live/Controller/Io_ChannelSingleThreshold/Io_ChannelSingleThreshold.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { Icon } from 'src/app/shared/type/widget'; import { ChannelAddress, CurrentData, Utils } from '../../../../shared/shared'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; import { Controller_Io_ChannelSingleThresholdModalComponent } from './modal/modal.component'; @Component({ diff --git a/ui/src/app/edge/live/Controller/Io_FixDigitalOutput/Io_FixDigitalOutput.ts b/ui/src/app/edge/live/Controller/Io_FixDigitalOutput/Io_FixDigitalOutput.ts index 91db9c98714..d5cdba6ecb4 100644 --- a/ui/src/app/edge/live/Controller/Io_FixDigitalOutput/Io_FixDigitalOutput.ts +++ b/ui/src/app/edge/live/Controller/Io_FixDigitalOutput/Io_FixDigitalOutput.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { ChannelAddress, CurrentData } from 'src/app/shared/shared'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; import { Controller_Io_FixDigitalOutputModalComponent } from './modal/modal.component'; diff --git a/ui/src/app/edge/live/Controller/Io_HeatingElement/Io_HeatingElement.ts b/ui/src/app/edge/live/Controller/Io_HeatingElement/Io_HeatingElement.ts index 0f5fea91628..9d368289a59 100644 --- a/ui/src/app/edge/live/Controller/Io_HeatingElement/Io_HeatingElement.ts +++ b/ui/src/app/edge/live/Controller/Io_HeatingElement/Io_HeatingElement.ts @@ -2,7 +2,7 @@ import { ChannelAddress, EdgeConfig, CurrentData, Utils } from '../../../../shar import { Component } from '@angular/core'; import { Controller_Io_HeatingElementModalComponent } from './modal/modal.component'; import { BehaviorSubject } from 'rxjs'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; @Component({ selector: 'Controller_Io_HeatingElement', diff --git a/ui/src/app/edge/live/Controller/Io_Heatpump/Io_Heatpump.ts b/ui/src/app/edge/live/Controller/Io_Heatpump/Io_Heatpump.ts index fd8416b7236..05bc0c9484f 100644 --- a/ui/src/app/edge/live/Controller/Io_Heatpump/Io_Heatpump.ts +++ b/ui/src/app/edge/live/Controller/Io_Heatpump/Io_Heatpump.ts @@ -2,7 +2,7 @@ import { BehaviorSubject } from 'rxjs'; import { ChannelAddress, CurrentData, EdgeConfig } from '../../../../shared/shared'; import { Component } from '@angular/core'; import { Controller_Io_HeatpumpModalComponent } from './modal/modal.component'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; @Component({ selector: 'Controller_Io_Heatpump', diff --git a/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/Asymmetric.ts b/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/Asymmetric.ts index 277894d8d17..2f7b055e09d 100644 --- a/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/Asymmetric.ts +++ b/ui/src/app/edge/live/Controller/PeakShaving/Asymmetric/Asymmetric.ts @@ -2,7 +2,7 @@ import { Controller_Asymmetric_PeakShavingModalComponent } from './modal/modal.c import { BehaviorSubject } from 'rxjs'; import { ChannelAddress, CurrentData, Utils } from '../../../../../shared/shared'; import { Component } from '@angular/core'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; @Component({ selector: 'Controller_Asymmetric_PeakShaving', diff --git a/ui/src/app/edge/live/Controller/PeakShaving/Symmetric/Symmetric.ts b/ui/src/app/edge/live/Controller/PeakShaving/Symmetric/Symmetric.ts index ba12e72780a..71e4772a429 100644 --- a/ui/src/app/edge/live/Controller/PeakShaving/Symmetric/Symmetric.ts +++ b/ui/src/app/edge/live/Controller/PeakShaving/Symmetric/Symmetric.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { ChannelAddress, CurrentData, Utils } from '../../../../../shared/shared'; import { Controller_Symmetric_PeakShavingModalComponent } from './modal/modal.component'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; @Component({ selector: 'Controller_Symmetric_PeakShaving', diff --git a/ui/src/app/edge/live/Controller/PeakShaving/Symmetric_TimeSlot/Symmetric_TimeSlot.ts b/ui/src/app/edge/live/Controller/PeakShaving/Symmetric_TimeSlot/Symmetric_TimeSlot.ts index 241b35f96fc..21156aeddfe 100644 --- a/ui/src/app/edge/live/Controller/PeakShaving/Symmetric_TimeSlot/Symmetric_TimeSlot.ts +++ b/ui/src/app/edge/live/Controller/PeakShaving/Symmetric_TimeSlot/Symmetric_TimeSlot.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { ChannelAddress, CurrentData, Utils } from '../../../../../shared/shared'; import { Controller_Symmetric_TimeSlot_PeakShavingModalComponent } from './modal/modal.component'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; @Component({ selector: 'Controller_Symmetric_TimeSlot_PeakShaving', diff --git a/ui/src/app/edge/live/Io/Api_DigitalInput/Io_Api_DigitalInput.ts b/ui/src/app/edge/live/Io/Api_DigitalInput/Io_Api_DigitalInput.ts index 52d797ff9c6..1ef300dce9d 100644 --- a/ui/src/app/edge/live/Io/Api_DigitalInput/Io_Api_DigitalInput.ts +++ b/ui/src/app/edge/live/Io/Api_DigitalInput/Io_Api_DigitalInput.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { Io_Api_DigitalInput_ModalComponent } from './modal/modal.component'; import { ChannelAddress, EdgeConfig } from 'src/app/shared/shared'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; @Component({ selector: 'Io_Api_DigitalInput', diff --git a/ui/src/app/edge/live/Multiple/Evcs_Api_Cluster/Evcs_Api_Cluster.ts b/ui/src/app/edge/live/Multiple/Evcs_Api_Cluster/Evcs_Api_Cluster.ts index b6a6883edeb..5b6c2a79fcf 100644 --- a/ui/src/app/edge/live/Multiple/Evcs_Api_Cluster/Evcs_Api_Cluster.ts +++ b/ui/src/app/edge/live/Multiple/Evcs_Api_Cluster/Evcs_Api_Cluster.ts @@ -1,7 +1,7 @@ import { ChannelAddress, CurrentData, EdgeConfig, Utils } from '../../../../shared/shared'; import { Component } from '@angular/core'; import { Evcs_Api_ClusterModalComponent } from './modal/evcsCluster-modal.page'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; @Component({ selector: 'Evcs_Api_Cluster', diff --git a/ui/src/app/edge/live/common/autarchy/Common_Autarchy.ts b/ui/src/app/edge/live/common/autarchy/Common_Autarchy.ts new file mode 100644 index 00000000000..44629bce56f --- /dev/null +++ b/ui/src/app/edge/live/common/autarchy/Common_Autarchy.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { SharedModule } from 'src/app/shared/shared.module'; +import { Flat } from './flat/flat'; +import { Modal } from './modal/modal'; + +@NgModule({ + imports: [ + BrowserModule, + SharedModule, + ], + entryComponents: [ + Flat, + Modal, + ], + declarations: [ + Flat, + Modal, + ], + exports: [ + Flat + ] +}) +export class Common_Autarchy { } diff --git a/ui/src/app/edge/live/common/autarchy/autarchy.component.ts b/ui/src/app/edge/live/common/autarchy/autarchy.component.ts deleted file mode 100644 index df9809a3027..00000000000 --- a/ui/src/app/edge/live/common/autarchy/autarchy.component.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Component } from '@angular/core'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; -import { ChannelAddress, CurrentData } from '../../../../shared/shared'; -import { AutarchyModalComponent } from './modal/modal.component'; - -@Component({ - selector: 'autarchy', - templateUrl: './autarchy.component.html' -}) -export class AutarchyComponent extends AbstractFlatWidget { - - public percentageValue: number; - - private static readonly SUM_GRID_ACTIVE_POWER: ChannelAddress = new ChannelAddress('_sum', 'GridActivePower'); - private static readonly SUM_CONSUMPTION_ACTIVE_POWER: ChannelAddress = new ChannelAddress('_sum', 'ConsumptionActivePower'); - - protected getChannelAddresses(): ChannelAddress[] { - return [ - AutarchyComponent.SUM_GRID_ACTIVE_POWER, - AutarchyComponent.SUM_CONSUMPTION_ACTIVE_POWER, - ]; - } - - protected onCurrentData(currentData: CurrentData) { - this.percentageValue = this.calculateAutarchy( - currentData.allComponents[AutarchyComponent.SUM_GRID_ACTIVE_POWER.toString()], - currentData.allComponents[AutarchyComponent.SUM_CONSUMPTION_ACTIVE_POWER.toString()] - ); - } - - private calculateAutarchy(buyFromGrid: number, consumptionActivePower: number): number | null { - if (buyFromGrid != null && consumptionActivePower != null) { - if (consumptionActivePower <= 0) { - /* avoid divide by zero; consumption == 0 -> autarchy 100 % */ - return 100; - - } else { - return /* min 0 */ Math.max(0, - /* max 100 */ Math.min(100, - /* calculate autarchy */(1 - buyFromGrid / consumptionActivePower) * 100 - )); - } - - } else { - return null; - } - } - - async presentModal() { - const modal = await this.modalController.create({ - component: AutarchyModalComponent, - }); - return await modal.present(); - } - -} diff --git a/ui/src/app/edge/live/common/autarchy/autarchy.component.html b/ui/src/app/edge/live/common/autarchy/flat/flat.html similarity index 100% rename from ui/src/app/edge/live/common/autarchy/autarchy.component.html rename to ui/src/app/edge/live/common/autarchy/flat/flat.html diff --git a/ui/src/app/edge/live/common/autarchy/flat/flat.ts b/ui/src/app/edge/live/common/autarchy/flat/flat.ts new file mode 100644 index 00000000000..29509b5449a --- /dev/null +++ b/ui/src/app/edge/live/common/autarchy/flat/flat.ts @@ -0,0 +1,53 @@ +import { Component } from '@angular/core'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; +import { ChannelAddress, CurrentData } from 'src/app/shared/shared'; +import { Modal } from '../modal/modal'; + +@Component({ + selector: 'Common_Autarchy', + templateUrl: './flat.html' +}) +export class Flat extends AbstractFlatWidget { + + public percentageValue: number; + + protected override getChannelAddresses(): ChannelAddress[] { + return [ + new ChannelAddress('_sum', 'GridActivePower'), + new ChannelAddress('_sum', 'ConsumptionActivePower'), + ]; + } + + protected override onCurrentData(currentData: CurrentData) { + this.percentageValue = this.calculateAutarchy( + currentData.allComponents['_sum/GridActivePower'], + currentData.allComponents['_sum/ConsumptionActivePower'] + ); + } + + private calculateAutarchy(buyFromGrid: number, consumptionActivePower: number): number | null { + if (buyFromGrid != null && consumptionActivePower != null) { + if (consumptionActivePower <= 0) { + /* avoid divide by zero; consumption == 0 -> autarchy 100 % */ + return 100; + + } else { + return /* min 0 */ Math.max(0, + /* max 100 */ Math.min(100, + /* calculate autarchy */(1 - buyFromGrid / consumptionActivePower) * 100 + )); + } + + } else { + return null; + } + } + + async presentModal() { + const modal = await this.modalController.create({ + component: Modal, + }); + return await modal.present(); + } + +} diff --git a/ui/src/app/edge/live/common/autarchy/modal/modal.component.html b/ui/src/app/edge/live/common/autarchy/modal/modal.component.html deleted file mode 100644 index 577ac5ca75f..00000000000 --- a/ui/src/app/edge/live/common/autarchy/modal/modal.component.html +++ /dev/null @@ -1,26 +0,0 @@ - - - General.autarchy - - - - - - - - - - - - - - - - - - Edge.Index.Widgets.autarchyInfo - - - - - \ No newline at end of file diff --git a/ui/src/app/edge/live/common/autarchy/modal/modal.component.ts b/ui/src/app/edge/live/common/autarchy/modal/modal.component.ts deleted file mode 100644 index 34419239aad..00000000000 --- a/ui/src/app/edge/live/common/autarchy/modal/modal.component.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Component } from '@angular/core'; -import { ModalController } from '@ionic/angular'; -import { Service } from '../../../../../shared/shared'; - -@Component({ - selector: AutarchyModalComponent.SELECTOR, - templateUrl: './modal.component.html' -}) -export class AutarchyModalComponent { - - private static readonly SELECTOR = "autarchy-modal"; - - constructor( - public modalCtrl: ModalController, - public service: Service, - ) { } - -} \ No newline at end of file diff --git a/ui/src/app/edge/live/common/autarchy/modal/modal.html b/ui/src/app/edge/live/common/autarchy/modal/modal.html new file mode 100644 index 00000000000..d8544d75e09 --- /dev/null +++ b/ui/src/app/edge/live/common/autarchy/modal/modal.html @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ui/src/app/edge/live/common/autarchy/modal/modal.ts b/ui/src/app/edge/live/common/autarchy/modal/modal.ts new file mode 100644 index 00000000000..cdb1b8f157b --- /dev/null +++ b/ui/src/app/edge/live/common/autarchy/modal/modal.ts @@ -0,0 +1,6 @@ +import { Component } from '@angular/core'; + +@Component({ + templateUrl: './modal.html' +}) +export class Modal { } \ No newline at end of file diff --git a/ui/src/app/edge/live/common/consumption/consumption.component.ts b/ui/src/app/edge/live/common/consumption/consumption.component.ts index c2b97bd7e3a..bfa5609028c 100644 --- a/ui/src/app/edge/live/common/consumption/consumption.component.ts +++ b/ui/src/app/edge/live/common/consumption/consumption.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { ChannelAddress, CurrentData, EdgeConfig, Utils } from '../../../../shared/shared'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; import { ConsumptionModalComponent } from './modal/modal.component'; @Component({ diff --git a/ui/src/app/edge/live/common/grid/grid.component.ts b/ui/src/app/edge/live/common/grid/grid.component.ts index 48d48b3bba2..4b6f5b67598 100644 --- a/ui/src/app/edge/live/common/grid/grid.component.ts +++ b/ui/src/app/edge/live/common/grid/grid.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { ChannelAddress, CurrentData, GridMode, Utils } from 'src/app/shared/shared'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; import { GridModalComponent } from './modal/modal.component'; @Component({ diff --git a/ui/src/app/edge/live/common/selfconsumption/Common_Selfconsumption.ts b/ui/src/app/edge/live/common/selfconsumption/Common_Selfconsumption.ts new file mode 100644 index 00000000000..43754f53491 --- /dev/null +++ b/ui/src/app/edge/live/common/selfconsumption/Common_Selfconsumption.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { SharedModule } from 'src/app/shared/shared.module'; +import { Flat } from './flat/flat'; +import { Modal } from './modal/modal'; + +@NgModule({ + imports: [ + BrowserModule, + SharedModule, + ], + entryComponents: [ + Flat, + Modal, + ], + declarations: [ + Flat, + Modal, + ], + exports: [ + Flat + ] +}) +export class Common_Selfconsumption { } diff --git a/ui/src/app/edge/live/common/selfconsumption/selfconsumption.component.html b/ui/src/app/edge/live/common/selfconsumption/flat/flat.html similarity index 100% rename from ui/src/app/edge/live/common/selfconsumption/selfconsumption.component.html rename to ui/src/app/edge/live/common/selfconsumption/flat/flat.html diff --git a/ui/src/app/edge/live/common/selfconsumption/flat/flat.ts b/ui/src/app/edge/live/common/selfconsumption/flat/flat.ts new file mode 100644 index 00000000000..558179b2649 --- /dev/null +++ b/ui/src/app/edge/live/common/selfconsumption/flat/flat.ts @@ -0,0 +1,37 @@ +import { Component } from '@angular/core'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; +import { ChannelAddress, CurrentData, Utils } from 'src/app/shared/shared'; +import { Modal } from '../modal/modal'; + +@Component({ + selector: 'Common_Selfconsumption', + templateUrl: './flat.html' +}) +export class Flat extends AbstractFlatWidget { + + public calculatedSelfConsumption: number; + + protected getChannelAddresses() { + return [ + new ChannelAddress('_sum', 'GridActivePower'), + new ChannelAddress('_sum', 'ProductionActivePower') + ]; + } + + protected onCurrentData(currentData: CurrentData) { + this.calculatedSelfConsumption = Utils.calculateSelfConsumption( + Utils.multiplySafely( + currentData.allComponents['_sum/GridActivePower'], + -1 + ), + currentData.allComponents['_sum/ProductionActivePower'] + ) + } + + async presentModal() { + const modal = await this.modalController.create({ + component: Modal, + }); + return await modal.present(); + } +} diff --git a/ui/src/app/edge/live/common/selfconsumption/modal/modal.component.html b/ui/src/app/edge/live/common/selfconsumption/modal/modal.component.html deleted file mode 100644 index 9a257651f84..00000000000 --- a/ui/src/app/edge/live/common/selfconsumption/modal/modal.component.html +++ /dev/null @@ -1,25 +0,0 @@ - - - General.selfConsumption - - - - - - - - - - - - - - - - - Edge.Index.Widgets.selfconsumptionInfo - - - - - \ No newline at end of file diff --git a/ui/src/app/edge/live/common/selfconsumption/modal/modal.component.ts b/ui/src/app/edge/live/common/selfconsumption/modal/modal.component.ts deleted file mode 100644 index bdaeb6e3758..00000000000 --- a/ui/src/app/edge/live/common/selfconsumption/modal/modal.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component } from '@angular/core'; -import { ModalController } from '@ionic/angular'; -import { Service } from '../../../../../shared/shared'; - -@Component({ - selector: 'selfconsumption-modal', - templateUrl: './modal.component.html' -}) -export class SelfconsumptionModalComponent { - - constructor( - public modalCtrl: ModalController, - public service: Service, - ) { } -} \ No newline at end of file diff --git a/ui/src/app/edge/live/common/selfconsumption/modal/modal.html b/ui/src/app/edge/live/common/selfconsumption/modal/modal.html new file mode 100644 index 00000000000..2d707717e76 --- /dev/null +++ b/ui/src/app/edge/live/common/selfconsumption/modal/modal.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/ui/src/app/edge/live/common/selfconsumption/modal/modal.ts b/ui/src/app/edge/live/common/selfconsumption/modal/modal.ts new file mode 100644 index 00000000000..20566aed8fc --- /dev/null +++ b/ui/src/app/edge/live/common/selfconsumption/modal/modal.ts @@ -0,0 +1,6 @@ +import { Component } from '@angular/core'; + +@Component({ + templateUrl: './modal.html' +}) +export class Modal { } \ No newline at end of file diff --git a/ui/src/app/edge/live/common/selfconsumption/selfconsumption.component.ts b/ui/src/app/edge/live/common/selfconsumption/selfconsumption.component.ts deleted file mode 100644 index f87ff259464..00000000000 --- a/ui/src/app/edge/live/common/selfconsumption/selfconsumption.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Component } from '@angular/core'; -import { ChannelAddress, CurrentData, Utils } from 'src/app/shared/shared'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; -import { SelfconsumptionModalComponent } from './modal/modal.component'; - -@Component({ - selector: 'selfconsumption', - templateUrl: './selfconsumption.component.html' -}) -export class SelfConsumptionComponent extends AbstractFlatWidget { - - private static readonly SUM_GRID_ACTIVE_POWER: ChannelAddress = new ChannelAddress('_sum', 'GridActivePower') - private static readonly SUM_PRODUCTION_ACTIVE_POWER: ChannelAddress = new ChannelAddress('_sum', 'ProductionActivePower') - public calculatedSelfConsumption: number; - - protected getChannelAddresses() { - return [SelfConsumptionComponent.SUM_GRID_ACTIVE_POWER, SelfConsumptionComponent.SUM_PRODUCTION_ACTIVE_POWER] - } - - protected onCurrentData(currentData: CurrentData) { - this.calculatedSelfConsumption = Utils.calculateSelfConsumption( - Utils.multiplySafely(currentData.allComponents[SelfConsumptionComponent.SUM_GRID_ACTIVE_POWER.toString()], -1), - currentData.allComponents[SelfConsumptionComponent.SUM_PRODUCTION_ACTIVE_POWER.toString()]) - } - - async presentModal() { - const modal = await this.modalController.create({ - component: SelfconsumptionModalComponent, - }); - return await modal.present(); - } -} diff --git a/ui/src/app/edge/live/common/storage/modal/modal.component.html b/ui/src/app/edge/live/common/storage/modal/modal.component.html index 078558aedeb..ae9d9248be9 100644 --- a/ui/src/app/edge/live/common/storage/modal/modal.component.html +++ b/ui/src/app/edge/live/common/storage/modal/modal.component.html @@ -179,10 +179,11 @@ Edge.Index.EmergencyReserve.emergencyReserve - + {{ formGroup.value[component.id]?.reserveSoc | unitvalue:'%' }} - + diff --git a/ui/src/app/edge/live/common/storage/storage.component.ts b/ui/src/app/edge/live/common/storage/storage.component.ts index 2dbae382ff6..6572590c45d 100644 --- a/ui/src/app/edge/live/common/storage/storage.component.ts +++ b/ui/src/app/edge/live/common/storage/storage.component.ts @@ -2,7 +2,7 @@ import { formatNumber } from '@angular/common'; import { Component } from '@angular/core'; import { CurrentData } from "src/app/shared/shared"; import { ChannelAddress, EdgeConfig, Utils } from '../../../../shared/shared'; -import { AbstractFlatWidget } from 'src/app/shared/Generic_Components/flat/abstract-flat-widget'; +import { AbstractFlatWidget } from 'src/app/shared/genericComponents/flat/abstract-flat-widget'; import { StorageModalComponent } from './modal/modal.component'; @Component({ diff --git a/ui/src/app/edge/live/live.component.html b/ui/src/app/edge/live/live.component.html index e315f952fa2..8fe7a36b693 100644 --- a/ui/src/app/edge/live/live.component.html +++ b/ui/src/app/edge/live/live.component.html @@ -16,12 +16,12 @@ - - + + - - + + @@ -117,9 +117,10 @@ - - + + - OpenEMS-Edge Version - {{ edge.version }} + {{ environment.edgeShortName }} Version + {{ edge.version }} Rolle diff --git a/ui/src/app/edge/settings/settings.component.html b/ui/src/app/edge/settings/settings.component.html index 982ab62c8da..3ff0c3b7d8b 100644 --- a/ui/src/app/edge/settings/settings.component.html +++ b/ui/src/app/edge/settings/settings.component.html @@ -102,6 +102,48 @@ + + + + + {{ environment.edgeShortName }}-App Assistent + + + + + + + + + + + + + + {{ environment.edgeShortName }} Systemupdate + + + + + + + + + + + + + + {{ environment.edgeShortName }} Soltaro Service Assistent + + + + + + + + + \ No newline at end of file diff --git a/ui/src/app/edge/settings/settings.module.ts b/ui/src/app/edge/settings/settings.module.ts index aa0f8604daf..1912db95fa7 100644 --- a/ui/src/app/edge/settings/settings.module.ts +++ b/ui/src/app/edge/settings/settings.module.ts @@ -10,10 +10,11 @@ import { AliasUpdateComponent } from './profile/aliasupdate.component'; import { ProfileComponent } from './profile/profile.component'; import { SettingsComponent } from './settings.component'; import { SystemExecuteComponent } from './systemexecute/systemexecute.component'; +import { SystemUpdateComponent } from './systemupdate/systemupdate.component'; @NgModule({ imports: [ - SharedModule + SharedModule, ], declarations: [ AliasUpdateComponent, @@ -26,6 +27,7 @@ import { SystemExecuteComponent } from './systemexecute/systemexecute.component' ProfileComponent, SettingsComponent, SystemExecuteComponent, + SystemUpdateComponent, ], entryComponents: [] }) diff --git a/ui/src/app/edge/settings/systemupdate/executeSystemUpdateRequest.ts b/ui/src/app/edge/settings/systemupdate/executeSystemUpdateRequest.ts new file mode 100644 index 00000000000..cc5d3443fc1 --- /dev/null +++ b/ui/src/app/edge/settings/systemupdate/executeSystemUpdateRequest.ts @@ -0,0 +1,29 @@ +import { JsonrpcRequest } from "src/app/shared/jsonrpc/base"; + +/** + * Represents a JSON-RPC Request to execute a system update on OpenEMS Edge. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "executeSystemUpdate",
+ *   "params": {
+  *     "isDebug": boolean
+ *   }
+ * }
+ * 
+ */ +export class ExecuteSystemUpdateRequest extends JsonrpcRequest { + + static METHOD: string = "executeSystemUpdate"; + + public constructor( + public readonly params: { + isDebug: boolean + } + ) { + super(ExecuteSystemUpdateRequest.METHOD, params); + } + +} \ No newline at end of file diff --git a/ui/src/app/edge/settings/systemupdate/getSystemUpdateStateRequest.ts b/ui/src/app/edge/settings/systemupdate/getSystemUpdateStateRequest.ts new file mode 100644 index 00000000000..94092685162 --- /dev/null +++ b/ui/src/app/edge/settings/systemupdate/getSystemUpdateStateRequest.ts @@ -0,0 +1,25 @@ +import { JsonrpcRequest } from "src/app/shared/jsonrpc/base"; + +/** + * Represents a JSON-RPC Request to get the current state of system update on OpenEMS Edge. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "getSystemUpdateState",
+ *   "params": {
+ *   }
+ * }
+ * 
+ */ +export class GetSystemUpdateStateRequest extends JsonrpcRequest { + + static METHOD: string = "getSystemUpdateState"; + + public constructor( + ) { + super(GetSystemUpdateStateRequest.METHOD, {}); + } + +} \ No newline at end of file diff --git a/ui/src/app/edge/settings/systemupdate/getSystemUpdateStateResponse.ts b/ui/src/app/edge/settings/systemupdate/getSystemUpdateStateResponse.ts new file mode 100644 index 00000000000..d6ed70243e9 --- /dev/null +++ b/ui/src/app/edge/settings/systemupdate/getSystemUpdateStateResponse.ts @@ -0,0 +1,56 @@ +import { JsonrpcResponseSuccess } from "src/app/shared/jsonrpc/base"; + +export interface SystemUpdateState { + unknown?: {}, + updated?: { version: string }, + available?: { + currentVersion: string, + latestVersion: string + }, + running?: { + percentCompleted: number, + logs: string[] + } +} + +/** + * JSON-RPC Response to "getSystemUpdateState" Request. + * + *

+ * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "result": {
+ *     // State is unknown (e.g. internet connection limited by firewall)
+ *      // TODO remove unknown? Throw exception instead
+ *     "unknown"?: {
+ *     }
+ *     // Latest version is already installed
+ *     "updated"?: {
+ *       "version": "XXXX"
+ *     }
+ *     // Update is available
+ *     "available"?: {
+ *       "currentVersion": "XXXX",
+ *       "latestVersion": "XXXX"
+ *     },
+ *     // Update is currently running
+ *     "running"?: {
+ *       "percentCompleted": number,
+ *       "logs": string[]
+ *     }
+ *   }
+ * }
+ * 
+ */ +export class GetSystemUpdateStateResponse extends JsonrpcResponseSuccess { + + public constructor( + public readonly id: string, + public readonly result: SystemUpdateState + ) { + super(id, result); + } +} \ No newline at end of file diff --git a/ui/src/app/edge/settings/systemupdate/systemupdate.component.html b/ui/src/app/edge/settings/systemupdate/systemupdate.component.html new file mode 100644 index 00000000000..55c8bba11b1 --- /dev/null +++ b/ui/src/app/edge/settings/systemupdate/systemupdate.component.html @@ -0,0 +1,120 @@ +
+ + + + + + + + + + {{ edge.id }} ist nicht online! + + + + + + + + + + + + + System Update + für {{ edge.id }} + + + + + + + + + + Update Status ist unbekannt + + + {{ state | json }} + + + + + + Installierte Version: + {{ state.version }} + + + Das System ist auf dem aktuellsten Softwarestand + + + + + + Installierte Version: + {{ state.currentVersion }} + + + Neueste Version: + {{ state.latestVersion }} + + + + + Neueste Version installieren + + + + + + + + + Update wird ausgeführt... + + Update abgeschlossen + + + + + + + + + + +

+ + + + +  Details +

+

+ + {{ log }}
+
+

+
+
+
+
+
+
+
+
+ +
+
\ No newline at end of file diff --git a/ui/src/app/edge/settings/systemupdate/systemupdate.component.ts b/ui/src/app/edge/settings/systemupdate/systemupdate.component.ts new file mode 100644 index 00000000000..d107bbd8d75 --- /dev/null +++ b/ui/src/app/edge/settings/systemupdate/systemupdate.component.ts @@ -0,0 +1,100 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Subject, timer } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { ComponentJsonApiRequest } from 'src/app/shared/jsonrpc/request/componentJsonApiRequest'; +import { environment } from 'src/environments'; +import { Edge, Service, Utils, Websocket } from '../../../shared/shared'; +import { ExecuteSystemUpdateRequest } from './executeSystemUpdateRequest'; +import { GetSystemUpdateStateRequest } from './getSystemUpdateStateRequest'; +import { GetSystemUpdateStateResponse, SystemUpdateState } from './getSystemUpdateStateResponse'; + +@Component({ + selector: SystemUpdateComponent.SELECTOR, + templateUrl: './systemupdate.component.html' +}) +export class SystemUpdateComponent implements OnInit, OnDestroy { + + private static readonly SELECTOR = "systemUpdate"; + + public readonly environment = environment; + public systemUpdateState: SystemUpdateState = null; + public readonly spinnerId: string = SystemUpdateComponent.SELECTOR; + public showLog: boolean = false; + + public edge: Edge = null; + private ngUnsubscribe = new Subject(); + + constructor( + private route: ActivatedRoute, + protected utils: Utils, + private websocket: Websocket, + private service: Service, + ) { } + + ngOnInit() { + this.service.setCurrentComponent("", this.route).then(edge => { + this.edge = edge; + // Update System Update State now and every 15 seconds + const source = timer(0, 15000); + source.pipe( + takeUntil(this.ngUnsubscribe) + ).subscribe(ignore => { + if (!edge.isOnline) { + return; + } + this.refreshSystemUpdateState(); + }); + }); + } + + ngOnDestroy() { + this.stopRefreshSystemUpdateState(); + } + + private refreshSystemUpdateState() { + this.service.startSpinner(this.spinnerId); + this.edge.sendRequest(this.websocket, + new ComponentJsonApiRequest({ + componentId: "_host", + payload: new GetSystemUpdateStateRequest() + })).then(response => { + let result = (response as GetSystemUpdateStateResponse).result; + this.systemUpdateState = result; + this.service.stopSpinner(this.spinnerId); + + // Stop regular check if there is no Update available + if (result.updated || result.running?.percentCompleted == 100) { + this.stopRefreshSystemUpdateState(); + } + + }).catch(reason => { + console.error(reason.error); + this.service.toast("Error while executing system update: " + reason.error.message, 'danger'); + }); + } + + public executeSystemUpdate() { + this.service.startSpinner(this.spinnerId); + + this.edge.sendRequest(this.websocket, + new ComponentJsonApiRequest({ + componentId: "_host", + payload: new ExecuteSystemUpdateRequest({ isDebug: environment.debugMode }) + })).then(response => { + // Finished System Update (without restart of OpenEMS Edge) + this.systemUpdateState = (response as GetSystemUpdateStateResponse).result; + this.service.stopSpinner(this.spinnerId); + this.stopRefreshSystemUpdateState(); + + }).catch(reason => { + console.error(reason.error); + this.service.toast("Error while executing system update: " + reason.error.message, 'danger'); + }); + } + + private stopRefreshSystemUpdateState() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } +} \ No newline at end of file diff --git a/ui/src/app/index/index.component.html b/ui/src/app/index/index.component.html index 84f28af484b..fe40395a2d2 100644 --- a/ui/src/app/index/index.component.html +++ b/ui/src/app/index/index.component.html @@ -22,14 +22,14 @@
- - Bitte geben Sie Ihr Passwort ein oder bestätigen Sie die Voreingabe um sich als Gast anzumelden. + + Login.preamble Login.passwordLabel - + @@ -46,12 +46,14 @@ - E-Mail / Benutzername - + E-Mail / Login.passwordLabel + + - Passwort - + Login.passwordLabel + @@ -84,8 +86,8 @@ - Sie haben noch kein FEMS (Fenecon Energie - Management System) hinzugefügt. + Sie haben noch kein + {{environment.edgeShortName}} hinzugefügt.

Bitte klicken Sie unten auf den "Hinzufügen"-Button wenn sie ein FEMS in Betrieb nehmen @@ -95,11 +97,13 @@ - Leider wurde noch kein FEMS (Fenecon Energie - Management System) mit Ihrem Account verknüpft. + Leider wurde noch kein + {{environment.edgeShortName}} + mit Ihrem Account verknüpft. -

Nach dem Ihr FEMS durch einen Installateur in Betrieb genommen wurde, sehen Sie es an dieser +

Nach dem Ihr {{environment.edgeShortName}} durch einen Installateur in Betrieb genommen + wurde, sehen Sie es an dieser Stelle.

diff --git a/ui/src/app/registration/modal/modal.component.ts b/ui/src/app/registration/modal/modal.component.ts index be9acb19a57..2099aa55551 100644 --- a/ui/src/app/registration/modal/modal.component.ts +++ b/ui/src/app/registration/modal/modal.component.ts @@ -98,26 +98,26 @@ export class RegistrationModalComponent implements OnInit { firstname: new FormControl("", Validators.required), lastname: new FormControl("", Validators.required), street: new FormControl("", Validators.required), - zip: new FormControl("", [Validators.required, Validators.minLength(4), Validators.maxLength(5)]), + zip: new FormControl("", Validators.required), city: new FormControl("", Validators.required), country: new FormControl("", Validators.required), phone: new FormControl("", Validators.required), email: new FormControl("", [Validators.required, Validators.email]), password: new FormControl("", Validators.required), - confirmPassword: new FormControl("", Validators.required) + confirmPassword: new FormControl("", Validators.required), }); } else { return this.formBuilder.group({ firstname: new FormControl("", Validators.required), lastname: new FormControl("", Validators.required), street: new FormControl("", Validators.required), - zip: new FormControl("", [Validators.required, Validators.minLength(4), Validators.maxLength(5)]), + zip: new FormControl("", Validators.required), city: new FormControl("", Validators.required), country: new FormControl("", Validators.required), phone: new FormControl("", Validators.required), email: new FormControl("", [Validators.required, Validators.email]), password: new FormControl("", Validators.required), - confirmPassword: new FormControl("", Validators.required) + confirmPassword: new FormControl("", Validators.required), }); } } diff --git a/ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-line.ts b/ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-line.ts deleted file mode 100644 index bc7400c75a7..00000000000 --- a/ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-line.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Component, Input } from "@angular/core"; -import { ChannelAddress } from "src/app/shared/shared"; -import { AbstractFlatWidgetLine } from "./abstract-flat-widget-line"; - -@Component({ - selector: 'oe-flat-widget-line', - templateUrl: './flat-widget-line.html' -}) -export class FlatWidgetLine extends AbstractFlatWidgetLine { - - /** Name for parameter, displayed on the left side */ - @Input() - name: string; - - /** value defines value of the parameter, displayed on the right */ - @Input() - set value(value: any) { - this.setValue(value); - } - - /** Channel defines the channel, you need for this line */ - @Input() - set channelAddress(channelAddress: string) { - this.subscribe(ChannelAddress.fromString(channelAddress)); - } - - /** Width of left Column, right Column is (100 - width of left Column) */ - @Input() - leftColumnWidth: number; -} - diff --git a/ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-percentagebar.ts b/ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-percentagebar.ts deleted file mode 100644 index b89ba7a3d9c..00000000000 --- a/ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-percentagebar.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Component, Input } from "@angular/core"; -import { ChannelAddress } from "src/app/shared/shared"; -import { AbstractFlatWidgetLine } from "../flat-widget-line/abstract-flat-widget-line"; -@Component({ - selector: 'oe-flat-widget-percentagebar', - templateUrl: './flat-widget-percentagebar.html' -}) -export class FlatWidgetPercentagebar extends AbstractFlatWidgetLine { - /** value is the channel the percentagebar is refering to */ - @Input() - set value(value: any) { - this.setValue(value); - } - @Input() set channelAddress(channelAddress: string) { - this.subscribe(ChannelAddress.fromString(channelAddress)) - } -} \ No newline at end of file diff --git a/ui/src/app/shared/Generic_Components/modal/modal-button/modal-button.html b/ui/src/app/shared/Generic_Components/modal/modal-button/modal-button.html deleted file mode 100644 index 0f3d49cc4f8..00000000000 --- a/ui/src/app/shared/Generic_Components/modal/modal-button/modal-button.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - {{ button.name }} - - - - - - - \ No newline at end of file diff --git a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line-input/modal-line-input.html b/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line-input/modal-line-input.html deleted file mode 100644 index 7234c858921..00000000000 --- a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line-input/modal-line-input.html +++ /dev/null @@ -1,21 +0,0 @@ -
- - - - - - - -
- {{ name }} - - -
- - -
-  W  -
-
-
\ No newline at end of file diff --git a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line-input/modal-line-input.ts b/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line-input/modal-line-input.ts deleted file mode 100644 index 172b7c77372..00000000000 --- a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line-input/modal-line-input.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from "@angular/core"; -import { AbstractModalLine } from "../../abstract-modal-line"; - -/** - * Shows a Line with Input-Field on the right - */ -@Component({ - selector: 'oe-modal-line-input', - templateUrl: './modal-line-input.html', -}) -export class ModalLineInput extends AbstractModalLine { } - diff --git a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line-note/modal-line-note.html b/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line-note/modal-line-note.html deleted file mode 100644 index 6d5262378b7..00000000000 --- a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line-note/modal-line-note.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - {{text}} - - - \ No newline at end of file diff --git a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line-note/modal-line-note.ts b/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line-note/modal-line-note.ts deleted file mode 100644 index a89cc778134..00000000000 --- a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line-note/modal-line-note.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Component, Input } from "@angular/core"; -import { Icon } from "src/app/shared/type/widget"; - -/** - * Shows the Info-Text. - */ -@Component({ - selector: 'oe-modal-line-note', - templateUrl: './modal-line-note.html' -}) -export class ModalLineNote { - - /** Icon, displayed on the left side */ - @Input() icon: Icon; - - /** InfoText, displayed on the right side */ - @Input() text: string; -} \ No newline at end of file diff --git a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line.html b/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line.html deleted file mode 100644 index 5f8aea334ae..00000000000 --- a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - -
- {{ name }} - - {{ displayValue }} -
\ No newline at end of file diff --git a/ui/src/app/shared/Generic_Components/modal/modal.ts b/ui/src/app/shared/Generic_Components/modal/modal.ts deleted file mode 100644 index 505eadab114..00000000000 --- a/ui/src/app/shared/Generic_Components/modal/modal.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Component, Input } from "@angular/core"; -import { AbstractModal } from "./abstractModal"; - -@Component({ - selector: 'oe-modal', - templateUrl: 'modal.html', - styles: [` - :host { - height: 100%; - margin-bottom: 15%; - font-size: 0.9em; - } - `] -}) -export class ModalComponent extends AbstractModal { - - /** Title in Header */ - @Input() title: string; - -} \ No newline at end of file diff --git a/ui/src/app/shared/Generic_Components/flat/flat-widget-line/abstract-flat-widget-line.ts b/ui/src/app/shared/genericComponents/flat/abstract-flat-widget-line.ts similarity index 77% rename from ui/src/app/shared/Generic_Components/flat/flat-widget-line/abstract-flat-widget-line.ts rename to ui/src/app/shared/genericComponents/flat/abstract-flat-widget-line.ts index b407f6f7ee7..f1acdbdaa53 100644 --- a/ui/src/app/shared/Generic_Components/flat/flat-widget-line/abstract-flat-widget-line.ts +++ b/ui/src/app/shared/genericComponents/flat/abstract-flat-widget-line.ts @@ -1,13 +1,14 @@ -import { Directive, Inject, Input, OnDestroy } from "@angular/core"; +import { Directive, Inject, Input, OnChanges, OnDestroy } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { ModalController } from "@ionic/angular"; import { Subject } from "rxjs"; import { takeUntil } from "rxjs/operators"; import { ChannelAddress, Edge, Service, Websocket } from "src/app/shared/shared"; import { v4 as uuidv4 } from 'uuid'; +import { UnitvaluePipe } from "../../pipe/unitvalue/unitvalue.pipe"; @Directive() -export abstract class AbstractFlatWidgetLine implements OnDestroy { +export abstract class AbstractFlatWidgetLine implements OnChanges, OnDestroy { /** * Use `converter` to convert/map a CurrentData value to another value, e.g. an Enum number to a text. @@ -18,6 +19,20 @@ export abstract class AbstractFlatWidgetLine implements OnDestroy { @Input() public converter = (value: any): string => { return value } + /** value defines value of the parameter, displayed on the right */ + @Input() + public value: any; + + /** Channel defines the channel, you need for this line */ + @Input() + set channelAddress(channelAddress: string) { + this.subscribe(ChannelAddress.fromString(channelAddress)); + } + + public ngOnChanges() { + this.setValue(this.value); + }; + /** * displayValue is the displayed @Input value in html */ diff --git a/ui/src/app/shared/Generic_Components/flat/abstract-flat-widget.ts b/ui/src/app/shared/genericComponents/flat/abstract-flat-widget.ts similarity index 100% rename from ui/src/app/shared/Generic_Components/flat/abstract-flat-widget.ts rename to ui/src/app/shared/genericComponents/flat/abstract-flat-widget.ts diff --git a/ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-horizontal-line.html b/ui/src/app/shared/genericComponents/flat/flat-widget-horizontal-line/flat-widget-horizontal-line.html similarity index 100% rename from ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-horizontal-line.html rename to ui/src/app/shared/genericComponents/flat/flat-widget-horizontal-line/flat-widget-horizontal-line.html diff --git a/ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-horizontal-line.ts b/ui/src/app/shared/genericComponents/flat/flat-widget-horizontal-line/flat-widget-horizontal-line.ts similarity index 79% rename from ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-horizontal-line.ts rename to ui/src/app/shared/genericComponents/flat/flat-widget-horizontal-line/flat-widget-horizontal-line.ts index 66149979055..2b1af8bb20c 100644 --- a/ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-horizontal-line.ts +++ b/ui/src/app/shared/genericComponents/flat/flat-widget-horizontal-line/flat-widget-horizontal-line.ts @@ -1,7 +1,7 @@ import { Component, Input } from "@angular/core"; /** - * Shows a horizontal line on all but the last entry of a flat widget. + * Shows a horizontal line on all but the last entry of a "flat-widget" or a "simple line" */ @Component({ selector: 'oe-flat-widget-horizontal-line', diff --git a/ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-line.html b/ui/src/app/shared/genericComponents/flat/flat-widget-line/flat-widget-line.html similarity index 64% rename from ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-line.html rename to ui/src/app/shared/genericComponents/flat/flat-widget-line/flat-widget-line.html index 0b8189adaff..0b01f12e956 100644 --- a/ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-line.html +++ b/ui/src/app/shared/genericComponents/flat/flat-widget-line/flat-widget-line.html @@ -1,11 +1,11 @@ - - diff --git a/ui/src/app/shared/genericComponents/flat/flat-widget-line/flat-widget-line.ts b/ui/src/app/shared/genericComponents/flat/flat-widget-line/flat-widget-line.ts new file mode 100644 index 00000000000..7180b611945 --- /dev/null +++ b/ui/src/app/shared/genericComponents/flat/flat-widget-line/flat-widget-line.ts @@ -0,0 +1,18 @@ +import { Component, Input } from "@angular/core"; +import { AbstractFlatWidgetLine } from "../abstract-flat-widget-line"; + +@Component({ + selector: 'oe-flat-widget-line', + templateUrl: './flat-widget-line.html' +}) +export class FlatWidgetLine extends AbstractFlatWidgetLine { + + /** Name for parameter, displayed on the left side */ + @Input() + name: string; + + /** Width of left Column, right Column is (100 - width of left Column) */ + @Input() + leftColumnWidth: number; + +} \ No newline at end of file diff --git a/ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-percentagebar.html b/ui/src/app/shared/genericComponents/flat/flat-widget-percentagebar/flat-widget-percentagebar.html similarity index 81% rename from ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-percentagebar.html rename to ui/src/app/shared/genericComponents/flat/flat-widget-percentagebar/flat-widget-percentagebar.html index d4bae573dc8..8c5bb50478f 100644 --- a/ui/src/app/shared/Generic_Components/flat/flat-widget-line/flat-widget-percentagebar.html +++ b/ui/src/app/shared/genericComponents/flat/flat-widget-percentagebar/flat-widget-percentagebar.html @@ -4,9 +4,7 @@ ngClass="primary-color" /> - - {{displayValue |unitvalue: '%' }} - + {{ displayValue | unitvalue: '%' }} \ No newline at end of file diff --git a/ui/src/app/shared/genericComponents/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts b/ui/src/app/shared/genericComponents/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts new file mode 100644 index 00000000000..cd3c2311117 --- /dev/null +++ b/ui/src/app/shared/genericComponents/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts @@ -0,0 +1,8 @@ +import { Component } from "@angular/core"; +import { AbstractFlatWidgetLine } from "../abstract-flat-widget-line"; + +@Component({ + selector: 'oe-flat-widget-percentagebar', + templateUrl: './flat-widget-percentagebar.html' +}) +export class FlatWidgetPercentagebar extends AbstractFlatWidgetLine { } \ No newline at end of file diff --git a/ui/src/app/shared/Generic_Components/flat/flat-widget.component.html b/ui/src/app/shared/genericComponents/flat/flat-widget.component.html similarity index 100% rename from ui/src/app/shared/Generic_Components/flat/flat-widget.component.html rename to ui/src/app/shared/genericComponents/flat/flat-widget.component.html diff --git a/ui/src/app/shared/genericComponents/flat/flat.html b/ui/src/app/shared/genericComponents/flat/flat.html new file mode 100644 index 00000000000..5efe3ab68f3 --- /dev/null +++ b/ui/src/app/shared/genericComponents/flat/flat.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + {{ title }} + + + + + + \ No newline at end of file diff --git a/ui/src/app/shared/Generic_Components/flat/flat-widget.component.ts b/ui/src/app/shared/genericComponents/flat/flat.ts similarity index 90% rename from ui/src/app/shared/Generic_Components/flat/flat-widget.component.ts rename to ui/src/app/shared/genericComponents/flat/flat.ts index b1507510353..5e5a42b8be6 100644 --- a/ui/src/app/shared/Generic_Components/flat/flat-widget.component.ts +++ b/ui/src/app/shared/genericComponents/flat/flat.ts @@ -3,7 +3,7 @@ import { Icon } from 'src/app/shared/type/widget'; @Component({ selector: 'oe-flat-widget', - templateUrl: './flat-widget.component.html' + templateUrl: './flat.html' }) export class FlatWidgetComponent { diff --git a/ui/src/app/shared/genericComponents/genericComponents.ts b/ui/src/app/shared/genericComponents/genericComponents.ts new file mode 100644 index 00000000000..098cc78f0d3 --- /dev/null +++ b/ui/src/app/shared/genericComponents/genericComponents.ts @@ -0,0 +1,59 @@ +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; +import { IonicModule } from '@ionic/angular'; +import { PipeModule } from '../pipe/pipe'; +import { FlatWidgetComponent } from './flat/flat'; +import { FlatWidgetHorizontalLine } from './flat/flat-widget-horizontal-line/flat-widget-horizontal-line'; +import { FlatWidgetLine } from './flat/flat-widget-line/flat-widget-line'; +import { FlatWidgetPercentagebar } from './flat/flat-widget-percentagebar/flat-widget-percentagebar'; +import { ModalComponent } from './modal/modal'; +import { ModalButtons } from './modal/modal-button/modal-button'; +import { ModalInfoLine } from './modal/modal-info-line/modal-info-line'; +import { ModalLine } from './modal/modal-line/modal-line'; +import { ModalHorizontalLine } from './modal/model-horizontal-line/modal-horizontal-line'; + +@NgModule({ + imports: [ + BrowserModule, + IonicModule, + PipeModule, + ReactiveFormsModule, + ], + entryComponents: [ + FlatWidgetComponent, + FlatWidgetLine, + FlatWidgetHorizontalLine, + FlatWidgetPercentagebar, + ModalButtons, + ModalInfoLine, + ModalLine, + ModalHorizontalLine, + ModalComponent + ], + declarations: [ + FlatWidgetComponent, + FlatWidgetLine, + FlatWidgetHorizontalLine, + FlatWidgetPercentagebar, + ModalButtons, + ModalInfoLine, + ModalLine, + ModalHorizontalLine, + ModalComponent, + ], + exports: [ + FlatWidgetComponent, + FlatWidgetLine, + FlatWidgetHorizontalLine, + FlatWidgetPercentagebar, + ModalButtons, + ModalInfoLine, + ModalLine, + ModalHorizontalLine, + ModalComponent, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + +}) +export class Generic_ComponentsModule { } diff --git a/ui/src/app/shared/Generic_Components/modal/abstract-modal-line.ts b/ui/src/app/shared/genericComponents/modal/abstract-modal-line.ts similarity index 94% rename from ui/src/app/shared/Generic_Components/modal/abstract-modal-line.ts rename to ui/src/app/shared/genericComponents/modal/abstract-modal-line.ts index 223dec9bf6a..a02455208d2 100644 --- a/ui/src/app/shared/Generic_Components/modal/abstract-modal-line.ts +++ b/ui/src/app/shared/genericComponents/modal/abstract-modal-line.ts @@ -3,10 +3,11 @@ import { FormBuilder, FormGroup } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; import { ModalController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; -import { UUID } from "angular2-uuid"; import { Subject } from "rxjs"; import { takeUntil } from "rxjs/operators"; import { ChannelAddress, CurrentData, Edge, EdgeConfig, Service, Websocket } from "src/app/shared/shared"; +import { v4 as uuidv4 } from 'uuid'; +import { Icon } from "../../type/widget"; @Directive() export abstract class AbstractModalLine implements OnDestroy { @@ -43,7 +44,7 @@ export abstract class AbstractModalLine implements OnDestroy { } /** Selector needed for Subscribe (Identifier) */ - private selector: string = UUID.UUID().toString(); + private selector: string = uuidv4() /** * displayValue is the displayed @Input value in html @@ -60,7 +61,13 @@ export abstract class AbstractModalLine implements OnDestroy { @Inject(ModalController) protected modalCtrl: ModalController, @Inject(TranslateService) protected translate: TranslateService, @Inject(FormBuilder) public formBuilder: FormBuilder, - ) { } + private ref: ChangeDetectorRef + ) { + ref.detach(); + setInterval(() => { + this.ref.detectChanges(); // manually trigger change detection + }, 0); + } ngOnChanges() { this.setValue(this.value) diff --git a/ui/src/app/shared/Generic_Components/modal/abstractModal.ts b/ui/src/app/shared/genericComponents/modal/abstractModal.ts similarity index 64% rename from ui/src/app/shared/Generic_Components/modal/abstractModal.ts rename to ui/src/app/shared/genericComponents/modal/abstractModal.ts index 0e6b5dea2f6..f4e1ec50804 100644 --- a/ui/src/app/shared/Generic_Components/modal/abstractModal.ts +++ b/ui/src/app/shared/genericComponents/modal/abstractModal.ts @@ -3,34 +3,32 @@ import { FormBuilder, FormGroup } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; import { ModalController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; -import { UUID } from "angular2-uuid"; import { Subject } from "rxjs"; import { takeUntil } from "rxjs/operators"; import { ChannelAddress, CurrentData, Edge, EdgeConfig, Service, Websocket } from "src/app/shared/shared"; +import { v4 as uuidv4 } from 'uuid'; @Directive() export abstract class AbstractModal implements OnInit, OnDestroy { @Input() component: EdgeConfig.Component = null; - @Input() formGroup: FormGroup = null; - @Input() controlName: string; public isInitialized: boolean = false; public edge: Edge = null; public config: EdgeConfig = null; public stopOnDestroy: Subject = new Subject(); + public formGroup: FormGroup | null = null; - private selector: string = UUID.UUID().toString(); + private selector: string = uuidv4(); constructor( @Inject(Websocket) protected websocket: Websocket, @Inject(ActivatedRoute) protected route: ActivatedRoute, @Inject(Service) protected service: Service, - @Inject(ModalController) public modalCtrl: ModalController, + @Inject(ModalController) public modalController: ModalController, @Inject(TranslateService) protected translate: TranslateService, @Inject(FormBuilder) public formBuilder: FormBuilder, private ref: ChangeDetectorRef - ) { ref.detach(); setInterval(() => { @@ -39,7 +37,6 @@ export abstract class AbstractModal implements OnInit, OnDestroy { } public ngOnInit() { - // this.getFormGroup() this.service.setCurrentComponent('', this.route).then(edge => { this.service.getConfig().then(config => { @@ -51,9 +48,6 @@ export abstract class AbstractModal implements OnInit, OnDestroy { if (this.component != null) { this.component = config.components[this.component.id]; - // announce initialized - this.isInitialized = true; - // get the channel addresses that should be subscribed let channelAddresses: ChannelAddress[] = this.getChannelAddresses(); let channelIds = this.getChannelIds(); @@ -77,6 +71,10 @@ export abstract class AbstractModal implements OnInit, OnDestroy { } this.onCurrentData({ thisComponent: thisComponent, allComponents: allComponents }); }); + this.formGroup = this.getFormGroup(); + + // announce initialized + this.isInitialized = true; } }); }); @@ -91,45 +89,6 @@ export abstract class AbstractModal implements OnInit, OnDestroy { this.stopOnDestroy.complete(); } - /** - * Applies all Value-Changes with updating ComponentConfig - */ - applyChanges() { - if (this.edge != null) { - if (this.edge.roleIsAtLeast('owner')) { - - /** fill udateComponentArray with changed formgroup.controls */ - let updateComponentArray = []; - Object.keys(this.formGroup.controls).forEach((element, index) => { - if (this.formGroup.controls[element].dirty) { - updateComponentArray.push({ name: Object.keys(this.formGroup.controls)[index], value: this.formGroup.controls[element].value }) - } - }) - - /** Update component.properties */ - this.edge.updateComponentConfig(this.websocket, this.component.id, updateComponentArray).then(() => { - - /** set Components-properties-value to FormGroup-value */ - for (let i = 0; i < updateComponentArray.length; i++) { - this.component.properties[updateComponentArray[i].name] = this.formGroup.controls[updateComponentArray[i].name].value - } - this.service.toast(this.translate.instant('General.changeAccepted'), 'success'); - }).catch(reason => { - - /** set Formgroup-value to Components-properties-value */ - for (let i = 0; i < updateComponentArray.length; i++) { - this.formGroup.controls[updateComponentArray[i].name].setValue(this.component.properties[updateComponentArray[i].name]) - } - this.service.toast(this.translate.instant('General.changeFailed') + '\n' + reason.error.message, 'danger'); - console.warn(reason); - }) - this.formGroup.markAsPristine() - } else { - this.service.toast(this.translate.instant('General.insufficientRights'), 'danger'); - } - } - } - /** * Called on every new data. * @@ -153,7 +112,7 @@ export abstract class AbstractModal implements OnInit, OnDestroy { } /** Gets the FormGroup of the current Component */ - protected getFormGroup(): FormGroup { - return + protected getFormGroup(): FormGroup | null { + return null; } } \ No newline at end of file diff --git a/ui/src/app/shared/genericComponents/modal/modal-button/modal-button.html b/ui/src/app/shared/genericComponents/modal/modal-button/modal-button.html new file mode 100644 index 00000000000..30c34f0f2df --- /dev/null +++ b/ui/src/app/shared/genericComponents/modal/modal-button/modal-button.html @@ -0,0 +1,12 @@ +
+ + + + {{ button.name }} + + + + + +
\ No newline at end of file diff --git a/ui/src/app/shared/Generic_Components/modal/modal-button/modal-button.ts b/ui/src/app/shared/genericComponents/modal/modal-button/modal-button.ts similarity index 92% rename from ui/src/app/shared/Generic_Components/modal/modal-button/modal-button.ts rename to ui/src/app/shared/genericComponents/modal/modal-button/modal-button.ts index ea96b0f0abe..472617cc434 100644 --- a/ui/src/app/shared/Generic_Components/modal/modal-button/modal-button.ts +++ b/ui/src/app/shared/genericComponents/modal/modal-button/modal-button.ts @@ -4,7 +4,7 @@ import { AbstractModalLine } from "../abstract-modal-line"; @Component({ selector: 'oe-modal-buttons', - templateUrl: './modal-button.html', + templateUrl: './modal-button.html' }) export class ModalButtons extends AbstractModalLine { diff --git a/ui/src/app/shared/genericComponents/modal/modal-info-line/modal-info-line.html b/ui/src/app/shared/genericComponents/modal/modal-info-line/modal-info-line.html new file mode 100644 index 00000000000..410761f1086 --- /dev/null +++ b/ui/src/app/shared/genericComponents/modal/modal-info-line/modal-info-line.html @@ -0,0 +1,14 @@ + + + + + + + + {{ info + }} + + + + \ No newline at end of file diff --git a/ui/src/app/shared/genericComponents/modal/modal-info-line/modal-info-line.ts b/ui/src/app/shared/genericComponents/modal/modal-info-line/modal-info-line.ts new file mode 100644 index 00000000000..d6fc1f3b31b --- /dev/null +++ b/ui/src/app/shared/genericComponents/modal/modal-info-line/modal-info-line.ts @@ -0,0 +1,16 @@ +import { Component, Input } from "@angular/core"; +import { Icon } from "src/app/shared/type/widget"; + +@Component({ + selector: 'oe-modal-info-line', + templateUrl: './modal-info-line.html' +}) +export class ModalInfoLine { + + /** Icon, displayed on the left side */ + @Input() icon: Icon; + + /** Info-Text, displayed on the right side */ + @Input() info: string; + +} \ No newline at end of file diff --git a/ui/src/app/shared/genericComponents/modal/modal-line/modal-line.html b/ui/src/app/shared/genericComponents/modal/modal-line/modal-line.html new file mode 100644 index 00000000000..58131d6598d --- /dev/null +++ b/ui/src/app/shared/genericComponents/modal/modal-line/modal-line.html @@ -0,0 +1,25 @@ +
+ {{ name }} + {{ displayValue }}
+ + + + + + + + + + + + +
+ {{ name }} + + + {{ displayValue }} + + + + +  W  + +
\ No newline at end of file diff --git a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line.ts b/ui/src/app/shared/genericComponents/modal/modal-line/modal-line.ts similarity index 60% rename from ui/src/app/shared/Generic_Components/modal/modal-line/modal-line.ts rename to ui/src/app/shared/genericComponents/modal/modal-line/modal-line.ts index 82b39c2ea9d..6dd9c61f1bf 100644 --- a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-line.ts +++ b/ui/src/app/shared/genericComponents/modal/modal-line/modal-line.ts @@ -3,6 +3,6 @@ import { AbstractModalLine } from "../abstract-modal-line"; @Component({ selector: 'oe-modal-line', - templateUrl: './modal-line.html', + templateUrl: './modal-line.html' }) -export class ModalLineComponent extends AbstractModalLine { } \ No newline at end of file +export class ModalLine extends AbstractModalLine { } \ No newline at end of file diff --git a/ui/src/app/shared/Generic_Components/modal/modal.html b/ui/src/app/shared/genericComponents/modal/modal.html similarity index 76% rename from ui/src/app/shared/Generic_Components/modal/modal.html rename to ui/src/app/shared/genericComponents/modal/modal.html index a0c1bdc3209..93a2af309ad 100644 --- a/ui/src/app/shared/Generic_Components/modal/modal.html +++ b/ui/src/app/shared/genericComponents/modal/modal.html @@ -2,18 +2,18 @@ {{title}} - + -
+
-
+ - + diff --git a/ui/src/app/shared/genericComponents/modal/modal.ts b/ui/src/app/shared/genericComponents/modal/modal.ts new file mode 100644 index 00000000000..4ff786aad70 --- /dev/null +++ b/ui/src/app/shared/genericComponents/modal/modal.ts @@ -0,0 +1,63 @@ +import { Component, Input } from "@angular/core"; +import { FormGroup } from "@angular/forms"; +import { ModalController } from "@ionic/angular"; +import { TranslateService } from "@ngx-translate/core"; +import { Edge, EdgeConfig, Service, Websocket } from "../../shared"; + +@Component({ + selector: 'oe-modal', + templateUrl: './modal.html', + styles: [` + :host { + height: 100%; + margin-bottom: 15%; + font-size: 0.9em; + } + `] +}) +export class ModalComponent { + + @Input() component: EdgeConfig.Component = null; + @Input() formGroup: FormGroup = null; + + /** Title in Header */ + @Input() title: string; + + private edge: Edge = null; + + constructor( + public modalController: ModalController, + private websocket: Websocket, + private service: Service, + private translate: TranslateService, + ) { + this.service.getCurrentEdge().then(edge => this.edge = edge); + } + + public applyChanges() { + let updateComponentArray: { name: string, value: any }[] = []; + for (let key in this.formGroup.controls) { + let control = this.formGroup.controls[key]; + + // Check if formControl-value didn't change + if (control.pristine) { + continue; + } + + updateComponentArray.push({ + name: key, + value: this.formGroup.value[key] + }) + } + + if (this.edge) { + this.edge.updateComponentConfig(this.websocket, this.component.id, updateComponentArray) + .then(() => { + this.service.toast(this.translate.instant('General.changeAccepted'), 'success'); + }).catch(reason => { + this.service.toast(this.translate.instant('General.changeFailed') + '\n' + reason.error.message, 'danger'); + }) + } + this.formGroup.markAsPristine(); + } +} \ No newline at end of file diff --git a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-horizontal-line/modal-horizontal-line.html b/ui/src/app/shared/genericComponents/modal/model-horizontal-line/modal-horizontal-line.html similarity index 99% rename from ui/src/app/shared/Generic_Components/modal/modal-line/modal-horizontal-line/modal-horizontal-line.html rename to ui/src/app/shared/genericComponents/modal/model-horizontal-line/modal-horizontal-line.html index fa07ef22ef4..d33561880db 100644 --- a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-horizontal-line/modal-horizontal-line.html +++ b/ui/src/app/shared/genericComponents/modal/model-horizontal-line/modal-horizontal-line.html @@ -3,6 +3,7 @@ style="border-bottom: 1px solid lightgray; width: 110%; margin-top: 2%; margin-bottom: 2%; margin-left: -5%;">
+
diff --git a/ui/src/app/shared/Generic_Components/modal/modal-line/modal-horizontal-line/modal-horizontal-line.ts b/ui/src/app/shared/genericComponents/modal/model-horizontal-line/modal-horizontal-line.ts similarity index 100% rename from ui/src/app/shared/Generic_Components/modal/modal-line/modal-horizontal-line/modal-horizontal-line.ts rename to ui/src/app/shared/genericComponents/modal/model-horizontal-line/modal-horizontal-line.ts diff --git a/ui/src/app/shared/jsonrpc/request/registerUserRequest.ts b/ui/src/app/shared/jsonrpc/request/registerUserRequest.ts index e6e58389439..f3f15d910e0 100644 --- a/ui/src/app/shared/jsonrpc/request/registerUserRequest.ts +++ b/ui/src/app/shared/jsonrpc/request/registerUserRequest.ts @@ -18,7 +18,7 @@ import { JsonrpcRequest } from "../base"; * "phone": string, * "email": string, * "password": string, - * "confirmPassword": string + * "confirmPassword": string, * } * } * } diff --git a/ui/src/app/shared/jsonrpc/request/updateUserLanguageRequest.ts b/ui/src/app/shared/jsonrpc/request/updateUserLanguageRequest.ts new file mode 100644 index 00000000000..52a711d9c5f --- /dev/null +++ b/ui/src/app/shared/jsonrpc/request/updateUserLanguageRequest.ts @@ -0,0 +1,28 @@ +import { JsonrpcRequest } from "../base"; + +/** + * + * Represents a JSON-RPC Response for a {@link UpdateUserLanguageRequest}. + *
+ * {
+ *   "method": "updateUserLanguage",
+ *   "id": UUID,
+ *   "params": {
+ *      "language": string
+ *   }
+ * }
+ * 
+ */ +export class UpdateUserLanguageRequest extends JsonrpcRequest { + + static METHOD: string = "updateUserLanguage"; + + public constructor( + public readonly params: { + language: string + } + ) { + super(UpdateUserLanguageRequest.METHOD, params); + } + +} \ No newline at end of file diff --git a/ui/src/app/shared/jsonrpc/shared.ts b/ui/src/app/shared/jsonrpc/shared.ts index 17a5d924f99..abe5146ee97 100644 --- a/ui/src/app/shared/jsonrpc/shared.ts +++ b/ui/src/app/shared/jsonrpc/shared.ts @@ -11,4 +11,5 @@ export type User = { id: string, name: string, globalRole: "admin" | "installer" | "owner" | "guest", + language: string }; \ No newline at end of file diff --git a/ui/src/app/shared/percentagebar/percentagebar.component.ts b/ui/src/app/shared/percentagebar/percentagebar.component.ts index 9afe9b3041e..aac7ea8317c 100644 --- a/ui/src/app/shared/percentagebar/percentagebar.component.ts +++ b/ui/src/app/shared/percentagebar/percentagebar.component.ts @@ -1,6 +1,5 @@ import { Component, Input } from '@angular/core'; - @Component({ selector: 'percentagebar', templateUrl: './percentagebar.component.html' diff --git a/ui/src/app/shared/pipe/pipe.ts b/ui/src/app/shared/pipe/pipe.ts new file mode 100644 index 00000000000..55e34b55110 --- /dev/null +++ b/ui/src/app/shared/pipe/pipe.ts @@ -0,0 +1,51 @@ +import { DecimalPipe } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { ClassnamePipe } from './classname/classname.pipe'; +import { HasclassPipe } from './hasclass/hasclass.pipe'; +import { IsclassPipe } from './isclass/isclass.pipe'; +import { KeysPipe } from './keys/keys.pipe'; +import { SecToHourMinPipe } from './sectohour/sectohour.pipe'; +import { SignPipe } from './sign/sign.pipe'; +import { UnitvaluePipe } from './unitvalue/unitvalue.pipe'; + +@NgModule({ + imports: [ + BrowserModule, + ], + entryComponents: [ + UnitvaluePipe, + SignPipe, + SecToHourMinPipe, + KeysPipe, + IsclassPipe, + HasclassPipe, + ClassnamePipe + ], + declarations: [ + UnitvaluePipe, + SignPipe, + SecToHourMinPipe, + KeysPipe, + IsclassPipe, + HasclassPipe, + ClassnamePipe + ], + exports: [ + UnitvaluePipe, + SignPipe, + SecToHourMinPipe, + KeysPipe, + IsclassPipe, + HasclassPipe, + ClassnamePipe + ], + providers: [ + DecimalPipe, + SecToHourMinPipe, + UnitvaluePipe, + ] +}) +export class PipeModule { + +} diff --git a/ui/src/app/shared/service/defaulttypes.ts b/ui/src/app/shared/service/defaulttypes.ts index c325865240e..fc7cc8d2271 100644 --- a/ui/src/app/shared/service/defaulttypes.ts +++ b/ui/src/app/shared/service/defaulttypes.ts @@ -9,6 +9,8 @@ export module DefaultTypes { [componentId: string]: string[]; } + export type ManualOnOff = 'MANUAL_ON' | 'MANUAL_OFF'; + /** * CurrentData Summary * diff --git a/ui/src/app/shared/service/utils.ts b/ui/src/app/shared/service/utils.ts index f973afcb616..7c4f44ac057 100644 --- a/ui/src/app/shared/service/utils.ts +++ b/ui/src/app/shared/service/utils.ts @@ -2,6 +2,7 @@ import { formatNumber } from '@angular/common'; import { TranslateService } from '@ngx-translate/core'; import { format } from 'date-fns'; import { saveAs } from 'file-saver-es'; +import { DefaultTypes } from 'src/app/shared/service/defaulttypes'; import { Base64PayloadResponse } from '../jsonrpc/response/base64PayloadResponse'; export class Utils { @@ -247,6 +248,40 @@ export class Utils { public static CONVERT_TO_KILO_WATTHOURS = (value: any): string => { return formatNumber(value / 1000, 'de', '1.0-1') + ' kWh' } + + /** + * Converts states 'MANUAL_ON' and 'MANUAL_OFF' to translated strings. + * + * @param value the value from passed value in html + * @returns converted value + */ + public static CONVERT_MANUAL_ON_OFF = (translate: TranslateService) => { + return (value: DefaultTypes.ManualOnOff): string => { + if (value === 'MANUAL_ON') { + return translate.instant('General.on'); + } else if (value === 'MANUAL_OFF') { + return translate.instant('General.off'); + } else { + return '-'; + } + } + } + + /** + * Takes a power value and extracts the information if it represents Charge or Discharge. + * + * @param translate the translate service + * @param power the power + * @returns an object with charge/discharge information and power value + */ + public static convertChargeDischargePower(translate: TranslateService, power: number): { name: string, value: number } { + if (power >= 0) { + return { name: translate.instant('General.dischargePower'), value: power }; + } else { + return { name: translate.instant('General.chargePower'), value: power * -1 }; + } + } + /** * Gets the image path for storage depending on State-of-Charge. * diff --git a/ui/src/app/shared/service/websocket.ts b/ui/src/app/shared/service/websocket.ts index 42002743365..54331af11d0 100644 --- a/ui/src/app/shared/service/websocket.ts +++ b/ui/src/app/shared/service/websocket.ts @@ -16,8 +16,8 @@ import { AuthenticateWithTokenRequest } from '../jsonrpc/request/authenticateWit import { EdgeRpcRequest } from '../jsonrpc/request/edgeRpcRequest'; import { LogoutRequest } from '../jsonrpc/request/logoutRequest'; import { RegisterUserRequest } from '../jsonrpc/request/registerUserRequest'; -import { SubscribeSystemLogRequest } from '../jsonrpc/request/subscribeSystemLogRequest'; import { AuthenticateResponse } from '../jsonrpc/response/authenticateResponse'; +import { LanguageTag } from '../translate/language'; import { Role } from '../type/role'; import { Service } from './service'; import { WsData } from './wsdata'; @@ -151,6 +151,9 @@ export class Websocket { public login(request: AuthenticateWithPasswordRequest | AuthenticateWithTokenRequest) { this.sendRequest(request).then(r => { let response = (r as AuthenticateResponse).result; + + localStorage.LANGUAGE = response.user.language; + this.service.setLang(LanguageTag[localStorage.LANGUAGE]) this.status = 'online'; // received login token -> save in cookie @@ -229,6 +232,13 @@ export class Websocket { if (environment.debugMode) { if (reason instanceof JsonrpcResponseError) { console.warn("Request failed [" + request.method + "]", reason.error); + + if (request instanceof EdgeRpcRequest && reason.error?.code == 3000 /* Edge is not connected */) { + let edges = this.service.metadata.value?.edges ?? {}; + if (request.params.edgeId in edges) { + edges[request.params.edgeId].isOnline = false; + } + } } else { console.warn("Request failed [" + request.method + "]", reason); } @@ -304,66 +314,24 @@ export class Websocket { let edgeId = edgeRpcNotification.params.edgeId; let message = edgeRpcNotification.params.payload; - switch (message.method) { - case EdgeConfigNotification.METHOD: - this.handleEdgeConfigNotification(edgeId, message as EdgeConfigNotification); - break; - - case CurrentDataNotification.METHOD: - this.handleCurrentDataNotification(edgeId, message as CurrentDataNotification); - break; - - case SystemLogNotification.METHOD: - this.handleSystemLogNotification(edgeId, message as SystemLogNotification); - break; - } - } - - /** - * Handles a EdgeConfigNotification. - * - * @param edgeId the Edge-ID - * @param message the EdgeConfigNotification - */ - private handleEdgeConfigNotification(edgeId: string, message: EdgeConfigNotification): void { let edges = this.service.metadata.value?.edges ?? {}; - if (edgeId in edges) { let edge = edges[edgeId]; - edge.handleEdgeConfigNotification(message); - } - } - /** - * Handles a CurrentDataNotification. - * - * @param edgeId the Edge-ID - * @param message the CurrentDataNotification - */ - private handleCurrentDataNotification(edgeId: string, message: CurrentDataNotification): void { - let edges = this.service.metadata.value?.edges ?? {}; + switch (message.method) { + case EdgeConfigNotification.METHOD: + edge.isOnline = true; // Mark Edge as online + edge.handleEdgeConfigNotification(message as EdgeConfigNotification); + break; - if (edgeId in edges) { - let edge = edges[edgeId]; - edge.handleCurrentDataNotification(message); - } - } + case CurrentDataNotification.METHOD: + edge.handleCurrentDataNotification(message as CurrentDataNotification); + break; - /** - * Handles a SystemLogNotification. - * - * @param edgeId the Edge-ID - * @param message the SystemLogNotification - */ - private handleSystemLogNotification(edgeId: string, message: SystemLogNotification): void { - let edges = this.service.metadata.value?.edges ?? {}; - - if (edgeId in edges) { - let edge = edges[edgeId]; - edge.handleSystemLogNotification(message); - } else { - this.sendRequest(new SubscribeSystemLogRequest({ subscribe: false })); + case SystemLogNotification.METHOD: + edge.handleSystemLogNotification(message as SystemLogNotification); + break; + } } } - } \ No newline at end of file diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts index e7b012028da..70f9a689ad6 100644 --- a/ui/src/app/shared/shared.module.ts +++ b/ui/src/app/shared/shared.module.ts @@ -1,4 +1,4 @@ -import { CommonModule, DecimalPipe } from '@angular/common'; +import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -15,16 +15,11 @@ import { FormlyWrapperFormField } from './formly/form-field.wrapper'; import { InputTypeComponent } from './formly/input'; import { FormlyInputSerialNumberWrapper as FormlyWrapperInputSerialNumber } from './formly/input-serial-number-wrapper'; import { RepeatTypeComponent } from './formly/repeat'; +import { Generic_ComponentsModule } from './genericComponents/genericComponents'; import { HeaderComponent } from './header/header.component'; import { PercentageBarComponent } from './percentagebar/percentagebar.component'; import { PickDateComponent } from './pickdate/pickdate.component'; -import { ClassnamePipe } from './pipe/classname/classname.pipe'; -import { HasclassPipe } from './pipe/hasclass/hasclass.pipe'; -import { IsclassPipe } from './pipe/isclass/isclass.pipe'; -import { KeysPipe } from './pipe/keys/keys.pipe'; -import { SecToHourMinPipe } from './pipe/sectohour/sectohour.pipe'; -import { SignPipe } from './pipe/sign/sign.pipe'; -import { UnitvaluePipe } from './pipe/unitvalue/unitvalue.pipe'; +import { PipeModule } from './pipe/pipe'; import { Service } from './service/service'; import { Utils } from './service/utils'; import { Websocket } from './service/websocket'; @@ -53,16 +48,10 @@ import { Language } from './translate/language'; { name: 'repeat', component: RepeatTypeComponent }, ], }), + PipeModule, + Generic_ComponentsModule ], declarations: [ - // pipes - ClassnamePipe, - HasclassPipe, - IsclassPipe, - KeysPipe, - SecToHourMinPipe, - SignPipe, - UnitvaluePipe, // components ChartOptionsComponent, HeaderComponent, @@ -75,14 +64,6 @@ import { Language } from './translate/language'; FormlyWrapperInputSerialNumber, ], exports: [ - // pipes - ClassnamePipe, - HasclassPipe, - IsclassPipe, - KeysPipe, - SecToHourMinPipe, - SignPipe, - UnitvaluePipe, // modules BrowserAnimationsModule, ChartsModule, @@ -95,6 +76,8 @@ import { Language } from './translate/language'; ReactiveFormsModule, RouterModule, TranslateModule, + PipeModule, + Generic_ComponentsModule, // components ChartOptionsComponent, HeaderComponent, @@ -103,13 +86,10 @@ import { Language } from './translate/language'; ], providers: [ appRoutingProviders, - DecimalPipe, - SecToHourMinPipe, Service, - UnitvaluePipe, Utils, Websocket, ] }) -export class SharedModule { -} + +export class SharedModule { } diff --git a/ui/src/app/shared/translate/cz.ts b/ui/src/app/shared/translate/cz.ts index e82d36cb4a3..d5f6fa79df9 100644 --- a/ui/src/app/shared/translate/cz.ts +++ b/ui/src/app/shared/translate/cz.ts @@ -112,13 +112,23 @@ export const TRANSLATION = { toEnergymonitor: 'Do Monitoringu energetických toků…', type: 'Typ:' }, + Login: { + title: "Login", + preamble: "Pro přihlášení jako host zadejte své heslo nebo potvrďte výchozí zadání.", + passwordLabel: "Heslo", + passwordReset: "Obnovení hesla", + authenticationFailed: "Ověření se nezdařilo", + }, + Register: { + title: "Vytvoření uživatelského účtu", + }, Edge: { Index: { // TODO Translations EmergencyReserve: { - InfoForEmergencyReserveSlider: 'By activating the emergency power reserve, the value can be freely selected between 5% and 100%.', - emergencyReserve: 'Emergencyreserve', + InfoForEmergencyReserveSlider: 'Aktivací nouzové rezervy výkonu lze libovolně zvolit hodnotu mezi 5 % a 100 %.', + emergencyReserve: 'pohotovostní rezerva', }, Energymonitor: { activePower: 'Činný výkon', @@ -326,7 +336,18 @@ export const TRANSLATION = { switchOnRec: 'Doporučení k zapnutí', switchOnRecShort: 'Doporučení', undefined: 'Nedefinováno', - } + }, + TimeOfUseTariff: { + currentTariff: 'Aktuální cena', + delayedDischarge: 'Opožděné propuštění', + storageDischarge: 'Vybíjení ze skladu', + State: { + notStarted: 'Ovladač ještě nezačal ', + delayed: 'Opožděné', + allowsDischarge: 'Uvolněno', + standby: 'Pohotovostní režim', + }, + }, } }, History: { diff --git a/ui/src/app/shared/translate/de.ts b/ui/src/app/shared/translate/de.ts index b0e6e4bfc20..d2e629d9524 100644 --- a/ui/src/app/shared/translate/de.ts +++ b/ui/src/app/shared/translate/de.ts @@ -119,7 +119,6 @@ export const TRANSLATION = { title: "Login", preamble: "Bitte geben Sie Ihr Passwort ein oder bestätigen Sie die Voreingabe um sich als Gast anzumelden.", passwordLabel: "Passwort", - passwordPlaceholder: "Passwort", passwordReset: "Passwort zurücksetzen", authenticationFailed: "Authentifizierung fehlgeschlagen", }, @@ -140,7 +139,7 @@ export const TRANSLATION = { phone: "Telefonnummer", email: "E-Mail Adresse", password: "Passwort", - confirmPassword: "Passwort wiederholen" + confirmPassword: "Passwort wiederholen", }, button: "Anlegen", errors: { @@ -360,7 +359,18 @@ export const TRANSLATION = { switchOnRec: 'Einschaltempfehlung', switchOnRecShort: 'Empfehlung', undefined: 'Nicht definiert', - } + }, + TimeOfUseTariff: { + currentTariff: 'Aktueller Bezugsstrompreis', + delayedDischarge: 'Entladung verzögert', + storageDischarge: 'Speicherentladung', + State: { + notStarted: 'Noch nicht gestartet', + delayed: 'Verzögert', + allowsDischarge: 'Freigegeben', + standby: 'Standby', + }, + }, } }, History: { diff --git a/ui/src/app/shared/translate/en.ts b/ui/src/app/shared/translate/en.ts index f8b73eec1e8..671e297e3a6 100644 --- a/ui/src/app/shared/translate/en.ts +++ b/ui/src/app/shared/translate/en.ts @@ -130,7 +130,7 @@ export const TRANSLATION = { phone: "Phone number", email: "E-Mail", password: "Password", - confirmPassword: "Confirm password" + confirmPassword: "Confirm password", }, button: "Create", errors: { @@ -359,7 +359,18 @@ export const TRANSLATION = { switchOnRec: 'Switch-on recommendation', switchOnRecShort: 'Recommendation', undefined: 'Undefined', - } + }, + TimeOfUseTariff: { + currentTariff: 'Current price', + delayedDischarge: 'Delayed Discharge', + storageDischarge: 'Storage Discharge', + State: { + notStarted: 'Controller has not yet started', + delayed: 'Delayed', + allowsDischarge: 'Allows Discharge', + standby: 'Standby', + }, + }, } }, History: { diff --git a/ui/src/app/shared/translate/es.ts b/ui/src/app/shared/translate/es.ts index 937accecb97..37879c8acf3 100644 --- a/ui/src/app/shared/translate/es.ts +++ b/ui/src/app/shared/translate/es.ts @@ -95,7 +95,7 @@ export const TRANSLATION = { name: 'Nombre', overview: 'estudio OpenEMS', settings: 'Ajustes', - user: 'Usuario' + user: 'Usuario', }, Index: { allConnected: 'Todas las conexiones establecidas.', @@ -108,12 +108,21 @@ export const TRANSLATION = { toEnergymonitor: 'Al monitor de energía...', type: 'Tipo:' }, + Login: { + title: "Login", + preamble: "Por favor, introduzca su contraseña o confirme la entrada por defecto para conectarse como invitado.", + passwordLabel: "Contraseña", + passwordReset: "Restablecer contraseña", + authenticationFailed: "Fallo de autentificación", + }, + Register: { + title: "Crear una cuenta de usuario", + }, Edge: { Index: { - // TODO Translations EmergencyReserve: { - InfoForEmergencyReserveSlider: 'By activating the emergency power reserve, the value can be freely selected between 5% and 100%.', - emergencyReserve: 'Emergencyreserve', + InfoForEmergencyReserveSlider: 'Al activar la reserva de energía de emergencia, el valor puede seleccionarse libremente entre el 5% y el 100%.', + emergencyReserve: 'reserva de emergencia', }, Energymonitor: { activePower: 'Potencia de salida', @@ -320,7 +329,18 @@ export const TRANSLATION = { normalOperationShort: 'Normal', switchOnComShort: 'Mando', switchOnRecShort: 'Recomendación', - } + }, + TimeOfUseTariff: { + currentTariff: 'Precio actual', + delayedDischarge: 'Retraso en el alta', + storageDischarge: 'Descarga de almacenamiento', + State: { + notStarted: 'El controlador aún no se ha iniciado ', + delayed: 'Retraso', + allowsDischarge: 'Liberado', + standby: 'Standby', + }, + }, } }, History: { diff --git a/ui/src/app/shared/translate/fr.ts b/ui/src/app/shared/translate/fr.ts index e12e1dd2f32..cc0bd78fe1c 100644 --- a/ui/src/app/shared/translate/fr.ts +++ b/ui/src/app/shared/translate/fr.ts @@ -110,12 +110,21 @@ export const TRANSLATION = { toEnergymonitor: 'Vers le moniteur d\'énergie...', type: 'Type:' }, + Login: { + title: "Login", + preamble: "Veuillez saisir votre mot de passe ou confirmer l'entrée par défaut pour vous connecter en tant qu'invité.", + passwordLabel: "mot de passe", + passwordReset: "Réinitialiser le mot de passe", + authenticationFailed: "Échec de l'authentification", + }, + Register: { + title: "Créer un compte utilisateur", + }, Edge: { Index: { - // TODO Translations EmergencyReserve: { - InfoForEmergencyReserveSlider: 'By activating the emergency power reserve, the value can be freely selected between 5% and 100%.', - emergencyReserve: 'Emergencyreserve', + InfoForEmergencyReserveSlider: "En activant la réserve d'énergie de secours, la valeur peut être librement choisie entre 5 % et 100 %.", + emergencyReserve: "réserve d'urgence", }, Energymonitor: { activePower: 'Puissance Active', @@ -322,7 +331,18 @@ export const TRANSLATION = { switchOnRec: 'Recommandation de mise en marche', switchOnRecShort: 'Recommandation', undefined: 'Indéfinie', - } + }, + TimeOfUseTariff: { + currentTariff: 'Prix actuel', + delayedDischarge: 'Sortie retardée', + storageDischarge: 'Décharge de stockage', + State: { + notStarted: 'Le contrôleur n\'a pas encore démarré', + delayed: 'Retardé', + allowsDischarge: 'Libéré', + standby: 'Standby', + }, + }, } }, History: { diff --git a/ui/src/app/shared/translate/nl.ts b/ui/src/app/shared/translate/nl.ts index 00890a00dcf..150ba6c4f0f 100644 --- a/ui/src/app/shared/translate/nl.ts +++ b/ui/src/app/shared/translate/nl.ts @@ -107,12 +107,21 @@ export const TRANSLATION = { toEnergymonitor: 'Naar Energiemonitor...', type: 'Type:' }, + Login: { + title: "Login", + preamble: "Voer uw wachtwoord in of bevestig de standaard om in te loggen als gast.", + passwordLabel: "Wachtwoord", + passwordReset: "Wachtwoord opnieuw instellen", + authenticationFailed: "Authenticatie mislukt", + }, + Register: { + title: "Gebruikersaccount aanmaken", + }, Edge: { Index: { - // TODO Translations EmergencyReserve: { - InfoForEmergencyReserveSlider: 'By activating the emergency power reserve, the value can be freely selected between 5% and 100%.', - emergencyReserve: 'Emergencyreserve', + InfoForEmergencyReserveSlider: 'Door de noodstroomreserve te activeren, kan de waarde vrij worden gekozen tussen 5% en 100%.', + emergencyReserve: 'noodreserve', }, Energymonitor: { activePower: 'Actief vermogen', @@ -318,7 +327,18 @@ export const TRANSLATION = { normalOperationShort: '', switchOnComShort: '', switchOnRecShort: '', - } + }, + TimeOfUseTariff: { + currentTariff: 'Huidige prijs', + delayedDischarge: 'Vertraagd ontslag', + storageDischarge: 'Opslag ontlading', + State: { + notStarted: 'De regelaar is nog niet gestart', + delayed: 'Vertraagd', + allowsDischarge: 'Vrijgegeven', + standby: 'Standby', + }, + }, } }, History: { diff --git a/ui/src/app/shared/type/widget.ts b/ui/src/app/shared/type/widget.ts index 17a832f4ce7..417107b88ac 100644 --- a/ui/src/app/shared/type/widget.ts +++ b/ui/src/app/shared/type/widget.ts @@ -3,8 +3,8 @@ import { EdgeConfig } from '../edge/edgeconfig'; export enum WidgetClass { 'Energymonitor', - 'Autarchy', - 'Selfconsumption', + 'Common_Autarchy', + 'Common_Selfconsumption', 'Storage', 'Grid', 'Production', @@ -24,6 +24,7 @@ export enum WidgetFactory { 'Controller.Ess.DelayedSellToGrid', 'Controller.Ess.FixActivePower', 'Controller.Ess.GridOptimizedCharge', + 'Controller.Ess.Time-Of-Use-Tariff.Discharge', 'Controller.IO.ChannelSingleThreshold', 'Controller.Io.FixDigitalOutput', 'Controller.IO.HeatingElement', @@ -57,7 +58,7 @@ export class Widgets { return true; } switch (clazz) { - case 'Autarchy': + case 'Common_Autarchy': case 'Grid': return config.hasMeter(); case 'Energymonitor': @@ -70,7 +71,7 @@ export class Widgets { case 'Storage': return config.hasStorage(); case 'Production': - case 'Selfconsumption': + case 'Common_Selfconsumption': return config.hasProducer(); }; return false; diff --git a/ui/src/app/user/user.component.ts b/ui/src/app/user/user.component.ts index 6a40c144513..dcde0bfdf29 100644 --- a/ui/src/app/user/user.component.ts +++ b/ui/src/app/user/user.component.ts @@ -1,7 +1,8 @@ import { Component } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; -import { environment } from 'src/environments'; +import { environment } from '../../environments'; +import { UpdateUserLanguageRequest } from '../shared/jsonrpc/request/updateUserLanguageRequest'; import { Service, Websocket } from '../shared/shared'; import { Language, LanguageTag } from '../shared/translate/language'; @@ -26,7 +27,9 @@ export class UserComponent { } ngOnInit() { - this.currentLanguage = this.translate.currentLang as LanguageTag; + + // Set currentLanguage to + this.currentLanguage = LanguageTag[localStorage.LANGUAGE]; this.service.setCurrentComponent(this.translate.instant('Menu.user'), this.route); } @@ -44,6 +47,17 @@ export class UserComponent { } public setLanguage(language: LanguageTag): void { + + // Get Key of LanguageTag Enum + localStorage.LANGUAGE = Object.keys(LanguageTag)[Object.values(LanguageTag).indexOf(language)] + + this.service.setLang(LanguageTag[localStorage.LANGUAGE]) + this.websocket.sendRequest(new UpdateUserLanguageRequest({ language: localStorage.LANGUAGE })).then(() => { + this.service.toast(this.translate.instant('General.changeAccepted'), 'success'); + }).catch((reason) => { + this.service.toast(this.translate.instant('General.changeFailed') + '\n' + reason.error.message, 'danger'); + }); + this.currentLanguage = language; this.translate.use(language); } diff --git a/ui/src/environments/index.ts b/ui/src/environments/index.ts index 75e4f80afcb..0cd3a84c9c1 100644 --- a/ui/src/environments/index.ts +++ b/ui/src/environments/index.ts @@ -13,4 +13,4 @@ export interface Environment { readonly production: boolean; debugMode: boolean; -} \ No newline at end of file +} diff --git a/ui/src/global.scss b/ui/src/global.scss index a3aeec098c6..59e5223359d 100644 --- a/ui/src/global.scss +++ b/ui/src/global.scss @@ -136,6 +136,34 @@ formly-input-section { flex: auto; // use all remaining space in a row } +// Added styles for alertController, because alertController doesnt take ionic default style +.alertController{ + .alert-head{ + text-align: center !important; + .alert-sub-title{ + font-size: large; + font-weight: bold; + color: $primary-color; + } + } + .alert-message{ + text-align: center; + } + .alert-wrapper{ + width: 25% !important; + .alert-button-group{ + justify-content: center; + display: grid; + grid-template-columns: auto auto; + column-gap: 35%; + .alert-button{ + color: $primary-color; + font-size: small; + } + } + } +} + :root { /* diff --git a/ui/src/themes/openems/environments/backend-prod.ts b/ui/src/themes/openems/environments/backend-prod.ts index a871983d599..8ceeb838f62 100644 --- a/ui/src/themes/openems/environments/backend-prod.ts +++ b/ui/src/themes/openems/environments/backend-prod.ts @@ -1,8 +1,11 @@ import { Environment } from "src/environments"; export const environment: Environment = { - title: "OpenEMS UI", - shortName: "OpenEMS", + theme: "OpenEMS", + + uiTitle: "OpenEMS UI", + edgeShortName: "OpenEMS", + edgeLongName: "Open Energy Management System", backend: 'OpenEMS Backend', url: "ws://" + location.hostname + ":8082", diff --git a/ui/src/themes/openems/environments/edge-dev.ts b/ui/src/themes/openems/environments/edge-dev.ts index aa1f7a38300..6153b715e5a 100644 --- a/ui/src/themes/openems/environments/edge-dev.ts +++ b/ui/src/themes/openems/environments/edge-dev.ts @@ -8,7 +8,7 @@ export const environment: Environment = { edgeLongName: "Open Energy Management System", backend: 'OpenEMS Edge', - url: "ws://" + location.hostname + ":8075", + url: "ws://" + location.hostname + ":8085", production: false, debugMode: true,