From becfbcdd2006137ac1189727824cf1f4c6a4e22e Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 10:53:57 +0200 Subject: [PATCH 01/30] - Added Translation for Edge(de,en) and UI(cz,de,en,es,fr,nl) - Reworked validation for IP-Addresses. Now it is enought if the Edge has an IP in the same network. - Added Worker for installing free Apps - Fixed deleting Component IDs in Scheduler - Fixed validation for IPs. e. g. Keba needs the IP '192.168.25.10' to access the EVCS '192.168.25.11' but if the EVCS doesnt start with '192.168.25.' it does not get added. - Fixed NullPointer if an App Name gets refactored and the app instance appid cant be mapped to an actual app - Added GridServingLoading, ManualRelayControl, ThresholdControl Apps --- .../edge/app/api/ModbusTcpApiReadOnly.java | 31 +--- .../edge/app/api/ModbusTcpApiReadWrite.java | 49 ++--- .../src/io/openems/edge/app/api/MqttApi.java | 40 ++-- .../edge/app/api/RestJsonApiReadOnly.java | 33 ++-- .../edge/app/api/RestJsonApiReadWrite.java | 45 ++--- .../io/openems/edge/app/evcs/EvcsCluster.java | 35 ++-- .../openems/edge/app/evcs/HardyBarthEvcs.java | 34 ++-- .../openems/edge/app/evcs/IesKeywattEvcs.java | 148 +++++++++++++++ .../io/openems/edge/app/evcs/KebaEvcs.java | 33 ++-- .../edge/app/hardware/KMtronic8Channel.java | 31 ++-- .../edge/app/heat/CombinedHeatAndPower.java | 70 +++---- .../io/openems/edge/app/heat/HeatPump.java | 81 ++++---- .../openems/edge/app/heat/HeatingElement.java | 90 +++++---- .../app/integratedsystem/FeneconHome.java | 174 ++++++++++++------ .../app/loadcontrol/ManualRelayControl.java | 163 ++++++++++++++++ .../app/loadcontrol/ThresholdControl.java | 168 +++++++++++++++++ .../edge/app/meter/AbstractMeterApp.java | 10 +- .../edge/app/meter/CarloGavazziMeter.java | 42 ++--- .../openems/edge/app/meter/JanitzaMeter.java | 47 ++--- .../openems/edge/app/meter/SocomecMeter.java | 42 ++--- .../app/pvinverter/AbstractPvInverter.java | 10 - .../edge/app/pvinverter/KacoPvInverter.java | 33 ++-- .../edge/app/pvinverter/KostalPvInverter.java | 39 ++-- .../edge/app/pvinverter/SmaPvInverter.java | 41 ++--- .../app/pvinverter/SolarEdgePvInverter.java | 33 ++-- .../GridOptimizedCharge.java | 152 +++++++++++++++ .../app/timeofusetariff/AwattarHourly.java | 25 +-- .../timeofusetariff/StromdaoCorrently.java | 32 ++-- .../edge/app/timeofusetariff/Tibber.java | 30 ++- .../core/appmanager/AbstractOpenemsApp.java | 75 +++++++- .../core/appmanager/AppInstallWorker.java | 83 +++++++++ .../edge/core/appmanager/AppManagerImpl.java | 111 +++++++---- .../edge/core/appmanager/ComponentUtil.java | 17 ++ .../core/appmanager/ComponentUtilImpl.java | 28 +++ .../edge/core/appmanager/JsonFormlyUtil.java | 40 ++++ .../edge/core/appmanager/OpenemsApp.java | 14 +- .../core/appmanager/OpenemsAppCategory.java | 58 ++++-- .../edge/core/appmanager/jsonrpc/GetApp.java | 12 +- .../edge/core/appmanager/jsonrpc/GetApps.java | 12 +- .../core/appmanager/translation_de.properties | 147 +++++++++++++++ .../core/appmanager/translation_en.properties | 149 +++++++++++++++ .../validator/CheckCardinality.java | 22 ++- .../core/appmanager/validator/Validator.java | 137 +++++++++----- .../edge/settings/app/index.component.html | 3 +- .../app/edge/settings/app/index.component.ts | 8 +- .../edge/settings/app/install.component.ts | 4 +- .../edge/settings/app/single.component.html | 6 +- .../edge/settings/app/update.component.html | 8 +- ui/src/app/shared/translate/cz.ts | 13 +- ui/src/app/shared/translate/de.ts | 11 ++ ui/src/app/shared/translate/en.ts | 13 +- ui/src/app/shared/translate/es.ts | 13 +- ui/src/app/shared/translate/fr.ts | 13 +- ui/src/app/shared/translate/nl.ts | 13 +- 54 files changed, 2004 insertions(+), 767 deletions(-) create mode 100644 io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java create mode 100644 io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/AppInstallWorker.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadOnly.java b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadOnly.java index 71ddea1f242..472dc88aa09 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadOnly.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadOnly.java @@ -2,7 +2,6 @@ import java.util.EnumMap; import java.util.List; -import java.util.Map; import java.util.TreeMap; import org.osgi.service.cm.ConfigurationAdmin; @@ -14,7 +13,8 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.api.ModbusTcpApiReadOnly.Property; @@ -66,8 +66,8 @@ public ModbusTcpApiReadOnly(@Reference ComponentManager componentManager, Compon } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + return AppAssistant.create(this.getName(language)) // .build(); } @@ -82,29 +82,19 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.API }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "Modbus/TCP-Api Read-Only"; - } - @Override public OpenemsAppCardinality getCardinality() { return OpenemsAppCardinality.SINGLE; } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { var controllerId = this.getId(t, p, Property.CONTROLLER_ID, "ctrlApiModbusTcp0"); List components = Lists.newArrayList(// - new EdgeConfig.Component(controllerId, this.getName(), "Controller.Api.ModbusTcp.ReadOnly", + new EdgeConfig.Component(controllerId, this.getName(l), "Controller.Api.ModbusTcp.ReadOnly", JsonUtils.buildJsonObject() // .build())); @@ -115,12 +105,11 @@ protected ThrowingBiFunction @Override public Builder getValidateBuilder() { return Validator.create() // - .setInstallableCheckableNames(new Validator.MapBuilder<>(new TreeMap>()) // - .put(CheckAppsNotInstalled.COMPONENT_NAME, // + .setInstallableCheckableConfigs(Lists.newArrayList(// + new Validator.CheckableConfig(CheckAppsNotInstalled.COMPONENT_NAME, new Validator.MapBuilder<>(new TreeMap()) // .put("appIds", new String[] { "App.Api.ModbusTcp.ReadWrite" }) // - .build()) - .build()); + .build()))); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java index 37f65e16398..65e2094db1e 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java @@ -2,7 +2,6 @@ import java.util.EnumMap; import java.util.List; -import java.util.Map; import java.util.TreeMap; import org.osgi.service.cm.ConfigurationAdmin; @@ -14,7 +13,8 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; @@ -33,7 +33,6 @@ import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.validator.CheckAppsNotInstalled; -import io.openems.edge.core.appmanager.validator.CheckNoComponentInstalledOfFactoryId; import io.openems.edge.core.appmanager.validator.Validator; import io.openems.edge.core.appmanager.validator.Validator.Builder; @@ -76,12 +75,13 @@ public ModbusTcpApiReadWrite(@Reference ComponentManager componentManager, Compo } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.API_TIMEOUT) // - .setLabel("Api-Timeout") // - .setDescription("Sets the timeout in seconds for updates on Channels set by this Api.") + .setLabel(bundle.getString("App.Api.apiTimeout.label")) // + .setDescription(bundle.getString("App.Api.apiTimeout.description")) // .setDefaultValue(60) // .isRequired(true) // .setInputType(Type.NUMBER) // @@ -91,11 +91,11 @@ public AppAssistant getAppAssistant() { .add(JsonFormlyUtil.buildSelect(Property.COMPONENT_IDS) // .isMulti(true) // .isRequired(true) // - .setLabel("Component-IDs") // - .setDescription("Components that should be made available via Modbus.") + .setLabel(bundle.getString(this.getAppId() + ".componentIds.label")) // + .setDescription(bundle.getString(this.getAppId() + ".componentIds.description")) .setOptions(this.componentManager.getAllComponents(), t -> t.id() + ": " + t.alias(), OpenemsComponent::id) - .setDefaultValue("_sum") // + .setDefaultValue(JsonUtils.buildJsonArray().add("_sum").build()) // .build()) .build()) .build(); @@ -112,24 +112,14 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.API }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "Modbus/TCP-Api Read-Write"; - } - @Override public OpenemsAppCardinality getCardinality() { return OpenemsAppCardinality.SINGLE; } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { var controllerId = this.getId(t, p, Property.CONTROLLER_ID, "ctrlApiModbusTcp0"); var apiTimeout = EnumUtils.getAsInt(p, Property.API_TIMEOUT); @@ -144,7 +134,7 @@ protected ThrowingBiFunction } List components = Lists.newArrayList(// - new EdgeConfig.Component(controllerId, this.getName(), "Controller.Api.ModbusTcp.ReadWrite", + new EdgeConfig.Component(controllerId, this.getName(l), "Controller.Api.ModbusTcp.ReadWrite", JsonUtils.buildJsonObject() // .addProperty("apiTimeout", apiTimeout) // .add("component.ids", controllerIds).build())); @@ -156,18 +146,11 @@ protected ThrowingBiFunction @Override public Builder getValidateBuilder() { return Validator.create() // - .setInstallableCheckableNames(new Validator.MapBuilder<>(new TreeMap>()) // - .put(CheckAppsNotInstalled.COMPONENT_NAME, // + .setInstallableCheckableConfigs(Lists.newArrayList(// + new Validator.CheckableConfig(CheckAppsNotInstalled.COMPONENT_NAME, new Validator.MapBuilder<>(new TreeMap()) // .put("appIds", new String[] { "App.Api.ModbusTcp.ReadOnly" }) // - .build()) - // TODO remove this if the free apps get created via App-Manager and an actual - // app instance gets created - .put(CheckNoComponentInstalledOfFactoryId.COMPONENT_NAME, // - new Validator.MapBuilder<>(new TreeMap()) // - .put("factorieId", "Controller.Api.ModbusTcp.ReadOnly") // - .build()) - .build()); + .build()))); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java b/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java index ec973bbc76c..9654c3d8776 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java @@ -11,7 +11,8 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; @@ -49,7 +50,7 @@ } * */ -@org.osgi.service.component.annotations.Component(name = "App.Api.Mqtt.ReadWrite") +@org.osgi.service.component.annotations.Component(name = "App.Api.Mqtt") public class MqttApi extends AbstractOpenemsApp implements OpenemsApp { public static enum Property { @@ -69,31 +70,32 @@ public MqttApi(@Reference ComponentManager componentManager, ComponentContext co } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.USERNAME) // - .setDescription("Username for authentication at MQTT broker.") // - .setLabel("Username") // + .setLabel(bundle.getString("username")) // + .setDescription(bundle.getString(this.getAppId() + ".Username.description")) // .isRequired(true) // .setMinLenght(3) // .setMaxLenght(18) // .build()) // .add(JsonFormlyUtil.buildInput(Property.PASSWORD) // - .setLabel("Password") // - .setDescription("Password for authentication at MQTT broker.") // + .setLabel(bundle.getString("password")) // + .setDescription(bundle.getString(this.getAppId() + ".Password.description")) // .isRequired(true) // .setInputType(Type.PASSWORD) // .build()) // .add(JsonFormlyUtil.buildInput(Property.CLIENT_ID) // - .setLabel("Client-ID") // - .setDescription("Client-ID for authentication at MQTT broker.") // + .setLabel(bundle.getString(this.getAppId() + ".EdgeId.label")) // + .setDescription(bundle.getString(this.getAppId() + ".EdgeId.description")) // .setDefaultValue("edge0") // .isRequired(true) // .build()) .add(JsonFormlyUtil.buildInput(Property.URI) // .setLabel("Uri") // - .setDescription("The connection Uri to MQTT broker.") // + .setDescription(bundle.getString(this.getAppId() + ".Uri.description")) // .setDefaultValue("tcp://localhost:1883") // .isRequired(true) // .build()) // @@ -112,24 +114,14 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.API }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "MQTT-Api"; - } - @Override public OpenemsAppCardinality getCardinality() { return OpenemsAppCardinality.SINGLE; } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { var clientId = this.getValueOrDefault(p, Property.CLIENT_ID, "edge0"); var uri = this.getValueOrDefault(p, Property.URI, "tcp://localhost:1883"); @@ -140,7 +132,7 @@ protected ThrowingBiFunction var controllerId = this.getId(t, p, Property.CONTROLLER_ID, "ctrlControllerApiMqtt0"); var components = Lists.newArrayList(// - new EdgeConfig.Component(controllerId, this.getName(), "Controller.Api.MQTT", + new EdgeConfig.Component(controllerId, this.getName(l), "Controller.Api.MQTT", JsonUtils.buildJsonObject() // .addProperty("clientId", clientId) // .addProperty("uri", uri) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java index 9f58819ebdd..6ea2422408d 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java @@ -2,7 +2,6 @@ import java.util.EnumMap; import java.util.List; -import java.util.Map; import java.util.TreeMap; import org.osgi.service.cm.ConfigurationAdmin; @@ -14,7 +13,8 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.api.RestJsonApiReadOnly.Property; @@ -66,8 +66,8 @@ public RestJsonApiReadOnly(@Reference ComponentManager componentManager, Compone } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + return AppAssistant.create(this.getName(language)) // .build(); } @@ -82,29 +82,18 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.API }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "REST/JSON-Api Read-Only"; - } - @Override public OpenemsAppCardinality getCardinality() { return OpenemsAppCardinality.SINGLE; } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { - + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { var controllerId = this.getId(t, p, Property.CONTROLLER_ID, "ctrlApiRest0"); List components = Lists.newArrayList(// - new EdgeConfig.Component(controllerId, this.getName(), "Controller.Api.Rest.ReadOnly", + new EdgeConfig.Component(controllerId, this.getName(l), "Controller.Api.Rest.ReadOnly", JsonUtils.buildJsonObject() // .build())); @@ -115,16 +104,16 @@ protected ThrowingBiFunction @Override public Builder getValidateBuilder() { return Validator.create() // - .setInstallableCheckableNames(new Validator.MapBuilder<>(new TreeMap>()) // - .put(CheckAppsNotInstalled.COMPONENT_NAME, // + .setInstallableCheckableConfigs(Lists.newArrayList(// + new Validator.CheckableConfig(CheckAppsNotInstalled.COMPONENT_NAME, new Validator.MapBuilder<>(new TreeMap()) // .put("appIds", new String[] { "App.Api.RestJson.ReadWrite" }) // - .build()) - .build()); + .build()))); } @Override protected Class getPropertyClass() { return Property.class; } + } diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java index ed46bb583b0..decdf2e5cbd 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java @@ -2,7 +2,6 @@ import java.util.EnumMap; import java.util.List; -import java.util.Map; import java.util.TreeMap; import org.osgi.service.cm.ConfigurationAdmin; @@ -14,7 +13,8 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; @@ -32,7 +32,6 @@ import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.validator.CheckAppsNotInstalled; -import io.openems.edge.core.appmanager.validator.CheckNoComponentInstalledOfFactoryId; import io.openems.edge.core.appmanager.validator.Validator; import io.openems.edge.core.appmanager.validator.Validator.Builder; @@ -73,12 +72,13 @@ public RestJsonApiReadWrite(@Reference ComponentManager componentManager, Compon } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.API_TIMEOUT) // - .setLabel("Api-Timeout") // - .setDescription("Sets the timeout in seconds for updates on Channels set by this Api.") + .setLabel(bundle.getString("App.Api.apiTimeout.label")) // + .setDescription(bundle.getString("App.Api.apiTimeout.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(60) // .setMin(30) // @@ -99,31 +99,20 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.API }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "Rest/JSON-Api Read-Write"; - } - @Override public OpenemsAppCardinality getCardinality() { return OpenemsAppCardinality.SINGLE; } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { - + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { var controllerId = this.getId(t, p, Property.CONTROLLER_ID, "ctrlApiRest0"); var apiTimeout = EnumUtils.getAsInt(p, Property.API_TIMEOUT); List components = Lists.newArrayList(// - new EdgeConfig.Component(controllerId, this.getName(), "Controller.Api.Rest.ReadWrite", + new EdgeConfig.Component(controllerId, this.getName(l), "Controller.Api.Rest.ReadWrite", JsonUtils.buildJsonObject() // .addProperty("apiTimeout", apiTimeout) // .build())); @@ -135,22 +124,16 @@ protected ThrowingBiFunction @Override public Builder getValidateBuilder() { return Validator.create() // - .setInstallableCheckableNames(new Validator.MapBuilder<>(new TreeMap>()) // - .put(CheckAppsNotInstalled.COMPONENT_NAME, // + .setInstallableCheckableConfigs(Lists.newArrayList(// + new Validator.CheckableConfig(CheckAppsNotInstalled.COMPONENT_NAME, new Validator.MapBuilder<>(new TreeMap()) // .put("appIds", new String[] { "App.Api.RestJson.ReadOnly" }) // - .build()) - // TODO remove this if the free apps get created via App-Manager and an actual - // app instance gets created - .put(CheckNoComponentInstalledOfFactoryId.COMPONENT_NAME, // - new Validator.MapBuilder<>(new TreeMap()) // - .put("factorieId", "Controller.Api.Rest.ReadOnly") // - .build()) - .build()); + .build()))); } @Override protected Class getPropertyClass() { return Property.class; } + } diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java index e72734cb5b8..9ac81a72847 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java @@ -1,6 +1,7 @@ package io.openems.edge.app.evcs; import java.util.EnumMap; +import java.util.stream.Collectors; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -11,7 +12,8 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; @@ -66,18 +68,18 @@ public EvcsCluster(@Reference ComponentManager componentManager, ComponentContex } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { var evcsClusterId = this.getId(t, p, Property.EVCS_CLUSTER_ID, "evcsCluster0"); - var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName()); + var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); var ids = EnumUtils.getAsJsonArray(p, Property.EVCS_IDS); var components = Lists.newArrayList(new EdgeConfig.Component(evcsClusterId, alias, "Evcs.Cluster.PeakShaving", JsonUtils.buildJsonObject() // - .add("evcs.ids", ids) // + .onlyIf(t.isAddOrUpdate(), j -> j.add("evcs.ids", ids)) // .build())); return new AppConfiguration(components); @@ -85,12 +87,15 @@ protected ThrowingBiFunction } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // - .add(JsonFormlyUtil.buildSelect(Property.EVCS_IDS).setLabel("EVCS-IDs") // - .setDescription("IDs of EVCS devices.") // - .setOptions(this.componentUtil.getEnabledComponentsOfStartingId("evcs"), + .add(JsonFormlyUtil.buildSelect(Property.EVCS_IDS) // + .setLabel("EVCS-IDs") // + .setDescription(bundle.getString(this.getAppId() + ".evcsIds.description")) // + .setOptions(this.componentUtil.getEnabledComponentsOfStartingId("evcs").stream() + .filter(t -> !t.id().startsWith("evcsCluster")).collect(Collectors.toList()), t -> t.alias() == null || t.alias().isEmpty() ? t.id() : t.id() + ": " + t.alias(), OpenemsComponent::id) @@ -112,16 +117,6 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.EVCS }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "Multiladepunkt-Management"; - } - @Override protected Class getPropertyClass() { return Property.class; diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java index 71463fb0466..a302712bdcb 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java @@ -12,11 +12,13 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.evcs.HardyBarthEvcs.Property; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AppAssistant; import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppDescriptor; @@ -58,7 +60,7 @@ public static enum Property implements DefaultEnum { CTRL_EVCS_ID("ctrlEvcs0"), // IP("192.168.25.30"); - private String defaultValue; + private final String defaultValue; private Property(String defaultValue) { this.defaultValue = defaultValue; @@ -78,11 +80,11 @@ public HardyBarthEvcs(@Reference ComponentManager componentManager, ComponentCon } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { // values the user enters var ip = EnumUtils.getAsOptionalString(p, Property.IP).orElse(Property.IP.getDefaultValue()); - var alias = this.getValueOrDefault(p, Property.ALIAS); + var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); // values which are being auto generated by the appmanager var evcsId = this.getId(t, p, Property.EVCS_ID); @@ -91,18 +93,18 @@ protected ThrowingBiFunction var components = this.getComponents(evcsId, alias, "Evcs.HardyBarth", ip, ctrlEvcsId); return new AppConfiguration(components, Lists.newArrayList(ctrlEvcsId, "ctrlBalancing0"), - Lists.newArrayList("192.168.25.10/24")); + ip.startsWith("192.168.25.") ? Lists.newArrayList("192.168.25.10/24") : null); }; } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel("IP-Address") // - .setDescription("The IP address of the charging station. " - + "If the charger has two connectors, the second/slave evcs has the IP 192.168.25.31.") + .setLabel(bundle.getString("ipAddress")) // + .setDescription(bundle.getString(this.getAppId() + ".Ip.description")) .setDefaultValue(Property.IP.getDefaultValue()) // .isRequired(true) // .setValidation(Validation.IP) // @@ -117,16 +119,6 @@ public AppDescriptor getAppDescriptor() { .build(); } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "eCharge Hardy Barth Ladestation"; - } - @Override protected Class getPropertyClass() { return Property.class; diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java new file mode 100644 index 00000000000..5a03f34b045 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java @@ -0,0 +1,148 @@ +package io.openems.edge.app.evcs; + +import java.util.EnumMap; + +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.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; +import io.openems.common.utils.EnumUtils; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.evcs.IesKeywattEvcs.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.DefaultEnum; +import io.openems.edge.core.appmanager.JsonFormlyUtil; +import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; + +/** + * Describes a IES Keywatt evcs App. + * + *
+  {
+    "appId":"App.Evcs.IesKeywatt",
+    "alias":"IES Keywatt Ladestation",
+    "instanceId": UUID,
+    "image": base64,
+    "properties":{
+      "EVCS_ID": "evcs0",
+      "CTRL_EVCS_ID": "ctrlEvcs0",
+      "OCCP_CHARGE_POINT_IDENTIFIER":"IES 1",
+      "OCCP_CONNECTOR_IDENTIFIER": "1"
+    },
+    "appDescriptor": {
+    	"websiteUrl": https://fenecon.de/fems-2-2/fems-app-ies-keywatt-ladestation-2//
+    }
+  }
+ * 
+ */ +@Component(name = "App.Evcs.IesKeywatt") +public class IesKeywattEvcs extends AbstractEvcsApp implements OpenemsApp { + + public static enum Property implements DefaultEnum { + ALIAS("IES Keywatt Ladestation"), // + EVCS_ID("evcs0"), // + CTRL_EVCS_ID("ctrlEvcs0"), // + OCCP_CHARGE_POINT_IDENTIFIER("IES1"), // + OCCP_CONNECTOR_IDENTIFIER("1"), // + ; + + private final String defaultValue; + + private Property(String defaultValue) { + this.defaultValue = defaultValue; + } + + @Override + public String getDefaultValue() { + return this.defaultValue; + } + + } + + @Activate + public IesKeywattEvcs(@Reference ComponentManager componentManager, ComponentContext componentContext, + @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + super(componentManager, componentContext, cm, componentUtil); + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { + // values the user enters + var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); + + // values which are being auto generated by the appmanager + var evcsId = this.getId(t, p, Property.EVCS_ID); + var ctrlEvcsId = this.getId(t, p, Property.CTRL_EVCS_ID); + var ocppId = this.getValueOrDefault(p, Property.OCCP_CHARGE_POINT_IDENTIFIER); + + var connectorId = EnumUtils.getAsInt(p, Property.OCCP_CONNECTOR_IDENTIFIER); + + var factoryId = "Evcs.Ocpp.IesKeywattSingle"; + var components = this.getComponents(evcsId, alias, factoryId, null, ctrlEvcsId); + var evcs = AbstractOpenemsApp.getComponentWithFactoryId(components, factoryId); + evcs.getProperties().put("ocpp.id", new JsonPrimitive(ocppId)); + evcs.getProperties().put("connectorId", new JsonPrimitive(connectorId)); + + return new AppConfiguration(components, Lists.newArrayList(ctrlEvcsId, "ctrlBalancing0")); + }; + } + + @Override + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // + .fields(JsonUtils.buildJsonArray() // + .add(JsonFormlyUtil.buildInput(Property.OCCP_CHARGE_POINT_IDENTIFIER) // + .setLabel(bundle.getString(this.getAppId() + ".chargepoint.label")) // + .setDescription(bundle.getString(this.getAppId() + ".chargepoint.description")) // + .setDefaultValue(Property.OCCP_CHARGE_POINT_IDENTIFIER.getDefaultValue()) // + .isRequired(true) // + .build()) // + .add(JsonFormlyUtil.buildInput(Property.OCCP_CONNECTOR_IDENTIFIER) // + .setLabel(bundle.getString(this.getAppId() + ".connector.label")) // + .setDescription(bundle.getString(this.getAppId() + ".connector.description")) // + .setDefaultValue(Property.OCCP_CONNECTOR_IDENTIFIER.getDefaultValue()) // + .isRequired(true) // + .setInputType(Type.NUMBER) // + .setMin(0) // + .build()) // + .build()) // + .build(); + } + + @Override + public AppDescriptor getAppDescriptor() { + return AppDescriptor.create() // + .build(); + } + + @Override + protected Class getPropertyClass() { + return Property.class; + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.MULTIPLE; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java index 255a07a5f15..1fb0a83ecb7 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java @@ -12,10 +12,12 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.evcs.KebaEvcs.Property; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AppAssistant; import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppDescriptor; @@ -57,7 +59,7 @@ public enum Property implements DefaultEnum { CTRL_EVCS_ID("ctrlEvcs0"), // IP("192.168.25.11"); - private String defaultValue; + private final String defaultValue; private Property(String defaultValue) { this.defaultValue = defaultValue; @@ -77,11 +79,11 @@ public KebaEvcs(@Reference ComponentManager componentManager, ComponentContext c } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { // values the user enters var ip = this.getValueOrDefault(p, Property.IP); - var alias = this.getValueOrDefault(p, Property.ALIAS); + var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); // values which are being auto generated by the appmanager var evcsId = this.getId(t, p, Property.EVCS_ID); @@ -90,17 +92,18 @@ protected ThrowingBiFunction var components = this.getComponents(evcsId, alias, "Evcs.Keba.KeContact", ip, ctrlEvcsId); return new AppConfiguration(components, Lists.newArrayList(ctrlEvcsId, "ctrlBalancing0"), - Lists.newArrayList("192.168.25.10/24")); + ip.startsWith("192.168.25.") ? Lists.newArrayList("192.168.25.10/24") : null); }; } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel("IP-Address") // - .setDescription("The IP address of the charging station.") + .setLabel(bundle.getString("ipAddress")) // + .setDescription(bundle.getString(this.getAppId() + ".Ip.description")) .setDefaultValue(Property.IP.getDefaultValue()) // .isRequired(true) // .setValidation(Validation.IP) // @@ -115,16 +118,6 @@ public AppDescriptor getAppDescriptor() { .build(); } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "KEBA Ladestation"; - } - @Override protected Class getPropertyClass() { return Property.class; diff --git a/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java b/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java index aa4e2cb4196..a9bc63b37ec 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java +++ b/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java @@ -12,7 +12,8 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.types.EdgeConfig.Component; import io.openems.common.utils.JsonUtils; @@ -67,10 +68,10 @@ public KMtronic8Channel(@Reference ComponentManager componentManager, ComponentC } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { - var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName()); + var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); var ip = this.getValueOrDefault(p, Property.IP, "192.168.1.199"); var modbusId = this.getId(t, p, Property.MODBUS_ID, "modbus10"); @@ -85,17 +86,19 @@ protected ThrowingBiFunction .addProperty("ip", ip) // .build())// ); - return new AppConfiguration(comp, null, Lists.newArrayList("192.168.1.198/28")); + return new AppConfiguration(comp, null, + ip.startsWith("192.168.1.") ? Lists.newArrayList("192.168.1.198/28") : null); }; } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel("IP-Address") // - .setDescription("The IP address of the Relay.") // + .setLabel(bundle.getString("ipAddress")) // + .setDescription(bundle.getString(this.getAppId() + ".Ip.description")) // .setDefaultValue("192.168.1.199") // .isRequired(true) // .setValidation(Validation.IP) // @@ -115,16 +118,6 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.HARDWARE }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "FEMS Relais 8-Kanal"; - } - @Override protected Class getPropertyClass() { return Property.class; diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java b/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java index 7c2e6c75b69..4f671b7764e 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java @@ -3,8 +3,8 @@ import java.util.ArrayList; import java.util.EnumMap; import java.util.List; -import java.util.Map; import java.util.TreeMap; +import java.util.stream.Collectors; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -15,8 +15,8 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.exceptions.OpenemsException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.types.EdgeConfig.Component; import io.openems.common.utils.JsonUtils; @@ -29,6 +29,7 @@ import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; import io.openems.edge.core.appmanager.DefaultEnum; +import io.openems.edge.core.appmanager.JsonFormlyUtil; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; @@ -46,7 +47,8 @@ "instanceId": UUID, "image": base64, "properties":{ - "CTRL_CHP_SOC_ID": "ctrlChpSoc0" + "CTRL_CHP_SOC_ID": "ctrlChpSoc0", + "OUTPUT_CHANNEL": "io0/Relay1" }, "appDescriptor": { } @@ -59,10 +61,11 @@ public class CombinedHeatAndPower extends AbstractOpenemsApp implement public static enum Property implements DefaultEnum { // User values ALIAS("Blockheizkraftwerk"), // + OUTPUT_CHANNEL("io0/Relay1"), // // Components CTRL_CHP_SOC_ID("ctrlChpSoc0"); - private String defaultValue; + private final String defaultValue; private Property(String defaultValue) { this.defaultValue = defaultValue; @@ -82,23 +85,13 @@ public CombinedHeatAndPower(@Reference ComponentManager componentManager, Compon } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { - + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { final var bhcId = this.getId(t, p, Property.CTRL_CHP_SOC_ID); - final var alias = this.getValueOrDefault(p, Property.ALIAS); - - var outputChannelAddress = "io0/Relay1"; + final var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); + final var outputChannelAddress = this.getValueOrDefault(p, Property.OUTPUT_CHANNEL); - if (!t.isDeleteOrTest()) { - var relays = this.componentUtil.getPreferredRelays(Lists.newArrayList(bhcId), new int[] { 1 }, - new int[] { 1 }); - if (relays == null) { - throw new OpenemsException("Not enough relays available!"); - } - outputChannelAddress = relays[0]; - } List comp = new ArrayList<>(); comp.add(new EdgeConfig.Component(bhcId, alias, "Controller.CHP.SoC", JsonUtils.buildJsonObject() // @@ -113,8 +106,26 @@ protected ThrowingBiFunction } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // + .fields(JsonUtils.buildJsonArray() // + .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL) // + .setOptions(this.componentUtil.getAllRelays() // + .stream().map(r -> r.relays).flatMap(List::stream) // + .collect(Collectors.toList())) // + .setDefaultValueWithStringSupplier(() -> { + var relays = this.componentUtil.getPreferredRelays(Lists.newArrayList(), + new int[] { 1 }, new int[] { 1 }); + if (relays == null) { + return Property.OUTPUT_CHANNEL.getDefaultValue(); + } + return relays[0]; + }) // + .setLabel(bundle.getString(this.getAppId() + ".outputChannel.label")) // + .setDescription(bundle.getString(this.getAppId() + ".outputChannel.description")) // + .build()) + .build()) .build(); } @@ -129,25 +140,14 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.HEAT }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - @Override public Builder getValidateBuilder() { return Validator.create() // - .setInstallableCheckableNames(new Validator.MapBuilder<>(new TreeMap>()) // - .put(CheckRelayCount.COMPONENT_NAME, // + .setInstallableCheckableConfigs(Lists.newArrayList(// + new Validator.CheckableConfig(CheckRelayCount.COMPONENT_NAME, new Validator.MapBuilder<>(new TreeMap()) // .put("count", 1) // - .build()) - .build()); - } - - @Override - public String getName() { - return "Blockheizkraftwerk (BHKW)"; + .build()))); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java index df575e1c97d..b3fda0be475 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java @@ -1,8 +1,9 @@ package io.openems.edge.app.heat; import java.util.EnumMap; -import java.util.Map; +import java.util.List; import java.util.TreeMap; +import java.util.stream.Collectors; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -13,8 +14,8 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.exceptions.OpenemsException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.heat.HeatPump.Property; @@ -25,6 +26,7 @@ import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.JsonFormlyUtil; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; @@ -42,7 +44,9 @@ "instanceId": UUID, "image": base64, "properties":{ - "CTRL_IO_HEAT_PUMP_ID": "ctrlIoHeatPump0" + "CTRL_IO_HEAT_PUMP_ID": "ctrlIoHeatPump0", + "OUTPUT_CHANNEL_1": "io0/Relay2", + "OUTPUT_CHANNEL_2": "io0/Relay3" }, "appDescriptor": { "websiteUrl": implements OpenemsApp { public static enum Property { - CTRL_IO_HEAT_PUMP_ID; + CTRL_IO_HEAT_PUMP_ID, // + OUTPUT_CHANNEL_1, // + OUTPUT_CHANNEL_2; + } @Activate @@ -65,28 +72,15 @@ public HeatPump(@Reference ComponentManager componentManager, ComponentContext c } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { final var ctrlIoHeatPumpId = this.getId(t, p, Property.CTRL_IO_HEAT_PUMP_ID, "ctrlIoHeatPump0"); - if (t.isDeleteOrTest()) { - var comp = Lists.newArrayList(// - new EdgeConfig.Component(ctrlIoHeatPumpId, this.getName(), "Controller.Io.HeatPump.SgReady", - JsonUtils.buildJsonObject() // - .build())); - return new AppConfiguration(comp); - } - - var relays = this.componentUtil.getPreferredRelays(Lists.newArrayList(ctrlIoHeatPumpId), new int[] { 2, 3 }, - new int[] { 2, 3 }); - if (relays == null) { - throw new OpenemsException("Not enought relays available!"); - } - var outputChannel1 = relays[0]; - var outputChannel2 = relays[1]; + var outputChannel1 = this.getValueOrDefault(p, Property.OUTPUT_CHANNEL_1, "io0/Relay2"); + var outputChannel2 = this.getValueOrDefault(p, Property.OUTPUT_CHANNEL_1, "io0/Relay3"); var comp = Lists.newArrayList(// - new EdgeConfig.Component(ctrlIoHeatPumpId, this.getName(), "Controller.Io.HeatPump.SgReady", + new EdgeConfig.Component(ctrlIoHeatPumpId, this.getName(l), "Controller.Io.HeatPump.SgReady", JsonUtils.buildJsonObject() // .addProperty("outputChannel1", outputChannel1) // .addProperty("outputChannel2", outputChannel2) // @@ -96,8 +90,28 @@ protected ThrowingBiFunction } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + var relays = this.componentUtil.getPreferredRelays(Lists.newArrayList(), new int[] { 2, 3 }, + new int[] { 2, 3 }); + var options = this.componentUtil.getAllRelays() // + .stream().map(r -> r.relays).flatMap(List::stream) // + .collect(Collectors.toList()); + return AppAssistant.create(this.getName(language)) // + .fields(JsonUtils.buildJsonArray() // + .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_1) // + .setOptions(options) // + .onlyIf(relays != null, t -> t.setDefaultValue(relays[0])) // + .setLabel(bundle.getString(this.getAppId() + ".outputChannel1.label")) + .setDescription(bundle.getString(this.getAppId() + ".outputChannel1.description")) + .build()) + .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_2) // + .setOptions(options) // + .onlyIf(relays != null, t -> t.setDefaultValue(relays[1])) // + .setLabel(bundle.getString(this.getAppId() + ".outputChannel2.label")) + .setDescription(bundle.getString(this.getAppId() + ".outputChannel2.description")) + .build()) + .build()) .build(); } @@ -112,25 +126,14 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.HEAT }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - @Override public Builder getValidateBuilder() { return Validator.create() // - .setInstallableCheckableNames(new Validator.MapBuilder<>(new TreeMap>()) // - .put(CheckRelayCount.COMPONENT_NAME, // + .setInstallableCheckableConfigs(Lists.newArrayList(// + new Validator.CheckableConfig(CheckRelayCount.COMPONENT_NAME, new Validator.MapBuilder<>(new TreeMap()) // .put("count", 2) // - .build()) - .build()); - } - - @Override - public String getName() { - return "\"SG-Ready\" Wärmepumpe"; + .build()))); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java index 42413fda113..f066a351d85 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java @@ -3,8 +3,8 @@ import java.util.ArrayList; import java.util.EnumMap; import java.util.List; -import java.util.Map; import java.util.TreeMap; +import java.util.stream.Collectors; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -15,8 +15,8 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.exceptions.OpenemsException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.types.EdgeConfig.Component; import io.openems.common.utils.JsonUtils; @@ -29,6 +29,7 @@ import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.ConfigurationTarget; import io.openems.edge.core.appmanager.DefaultEnum; +import io.openems.edge.core.appmanager.JsonFormlyUtil; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; @@ -46,7 +47,10 @@ "instanceId": UUID, "image": base64, "properties":{ - "CTRL_IO_HEATING_ELEMENT_ID": "ctrlIoHeatingElement0" + "CTRL_IO_HEATING_ELEMENT_ID": "ctrlIoHeatingElement0", + "OUTPUT_CHANNEL_PHASE_L1": "io0/Relay1", + "OUTPUT_CHANNEL_PHASE_L2": "io0/Relay2", + "OUTPUT_CHANNEL_PHASE_L3": "io0/Relay3" }, "appDescriptor": { "websiteUrl": implements Open public static enum Property implements DefaultEnum { ALIAS("Heating Element App"), // CTRL_IO_HEATING_ELEMENT_ID("ctrlIoHeatingElement0"), // + OUTPUT_CHANNEL_PHASE_L1("io0/Relay1"), // + OUTPUT_CHANNEL_PHASE_L2("io0/Relay2"), // + OUTPUT_CHANNEL_PHASE_L3("io0/Relay3"), // ; - private String defaultValue; + private final String defaultValue; private Property(String defaultValue) { this.defaultValue = defaultValue; @@ -83,38 +90,58 @@ public HeatingElement(@Reference ComponentManager componentManager, ComponentCon } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { final var heatingElementId = this.getId(t, p, Property.CTRL_IO_HEATING_ELEMENT_ID); - final var alias = this.getValueOrDefault(p, Property.ALIAS); + final var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); + final var outputChannelPhaseL1 = this.getValueOrDefault(p, Property.OUTPUT_CHANNEL_PHASE_L1); + final var outputChannelPhaseL2 = this.getValueOrDefault(p, Property.OUTPUT_CHANNEL_PHASE_L2); + final var outputChannelPhaseL3 = this.getValueOrDefault(p, Property.OUTPUT_CHANNEL_PHASE_L3); List comp = new ArrayList<>(); var jsonConfigBuilder = JsonUtils.buildJsonObject(); - if (!t.isDeleteOrTest()) { - var relays = this.componentUtil.getPreferredRelays(Lists.newArrayList(heatingElementId), - new int[] { 1, 2, 3 }, new int[] { 4, 5, 6 }); - if (relays == null) { - throw new OpenemsException("Not enought relays available!"); - } - - jsonConfigBuilder.addProperty("outputChannelPhaseL1", relays[0]) // - .addProperty("outputChannelPhaseL2", relays[1]) // - .addProperty("outputChannelPhaseL3", relays[2]); // - } - comp.add(new EdgeConfig.Component(heatingElementId, alias, "Controller.IO.HeatingElement", - jsonConfigBuilder.build()));// + jsonConfigBuilder.addProperty("outputChannelPhaseL1", outputChannelPhaseL1) // + .addProperty("outputChannelPhaseL2", outputChannelPhaseL2) // + .addProperty("outputChannelPhaseL3", outputChannelPhaseL3) // + .build()));// return new AppConfiguration(comp); }; } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + var relays = this.componentUtil.getPreferredRelays(Lists.newArrayList(), new int[] { 1, 2, 3 }, + new int[] { 4, 5, 6 }); + var options = this.componentUtil.getAllRelays() // + .stream().map(r -> r.relays).flatMap(List::stream) // + .collect(Collectors.toList()); + return AppAssistant.create(this.getName(language)) // + .fields(JsonUtils.buildJsonArray() // + .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_PHASE_L1) // + .setOptions(options) // + .onlyIf(relays != null, t -> t.setDefaultValue(relays[0])) // + .setLabel(bundle.getString(this.getAppId() + ".outputChannelPhaseL1.label")) + .setDescription(bundle.getString(this.getAppId() + ".outputChannelPhaseL1.description")) + .build()) + .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_PHASE_L2) // + .setOptions(options) // + .onlyIf(relays != null, t -> t.setDefaultValue(relays[1])) // + .setLabel(bundle.getString(this.getAppId() + ".outputChannelPhaseL2.label")) + .setDescription(bundle.getString(this.getAppId() + ".outputChannelPhaseL2.description")) + .build()) + .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_PHASE_L3) // + .setOptions(options) // + .onlyIf(relays != null, t -> t.setDefaultValue(relays[2])) // + .setLabel(bundle.getString(this.getAppId() + ".outputChannelPhaseL3.label")) + .setDescription(bundle.getString(this.getAppId() + ".outputChannelPhaseL3.description")) + .build()) + .build()) .build(); } @@ -129,25 +156,14 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.HEAT }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - @Override public Builder getValidateBuilder() { return Validator.create() // - .setInstallableCheckableNames(new Validator.MapBuilder<>(new TreeMap>()) // - .put(CheckRelayCount.COMPONENT_NAME, // + .setInstallableCheckableConfigs(Lists.newArrayList(// + new Validator.CheckableConfig(CheckRelayCount.COMPONENT_NAME, new Validator.MapBuilder<>(new TreeMap()) // .put("count", 3) // - .build()) - .build()); - } - - @Override - public String getName() { - return "Heizstab"; + .build()))); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java index 9b7432f4c7d..4fc9e63bfdc 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.EnumMap; import java.util.List; +import java.util.Optional; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -16,7 +17,8 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; @@ -100,8 +102,9 @@ public AppDescriptor getAppDescriptor() { } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, // + AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { var essId = "ess0"; var modbusIdInternal = "modbus0"; var modbusIdExternal = "modbus1"; @@ -113,9 +116,11 @@ protected ThrowingBiFunction var maxFeedInPower = EnumUtils.getAsInt(p, Property.MAX_FEED_IN_POWER); var feedInSetting = EnumUtils.getAsString(p, Property.FEED_IN_SETTING); + var bundle = AbstractOpenemsApp.getTranslationBundle(l); var components = Lists.newArrayList(// - new EdgeConfig.Component(modbusIdInternal, "Kommunikation mit der Batterie", "Bridge.Modbus.Serial", - JsonUtils.buildJsonObject() // + new EdgeConfig.Component(modbusIdInternal, + bundle.getString(this.getAppId() + "." + modbusIdInternal + ".alias"), + "Bridge.Modbus.Serial", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("portName", "/dev/busUSB1") // .addProperty("baudRate", 19200) // @@ -123,9 +128,11 @@ protected ThrowingBiFunction .addProperty("stopbits", "ONE") // .addProperty("parity", "NONE") // .addProperty("logVerbosity", "NONE") // - .addProperty("invalidateElementsAfterReadErrors", 1) // - .build()), - new EdgeConfig.Component(modbusIdExternal, "Kommunikation mit dem Batterie-Wechselrichter", + .onlyIf(t == ConfigurationTarget.ADD, // + j -> j.addProperty("invalidateElementsAfterReadErrors", 1) // + ).build()), + new EdgeConfig.Component(modbusIdExternal, + bundle.getString(this.getAppId() + "." + modbusIdExternal + ".alias"), "Bridge.Modbus.Serial", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("portName", "/dev/busUSB2") // @@ -136,19 +143,22 @@ protected ThrowingBiFunction .addProperty("logVerbosity", "NONE") // .addProperty("invalidateElementsAfterReadErrors", 1) // .build()), - new EdgeConfig.Component("meter0", "Netzzähler", "GoodWe.Grid-Meter", // + new EdgeConfig.Component("meter0", bundle.getString(this.getAppId() + ".meter0.alias"), + "GoodWe.Grid-Meter", // JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("modbus.id", modbusIdExternal) // .addProperty("modbusUnitId", 247) // .build()), - new EdgeConfig.Component("io0", "Relaisboard", "IO.KMtronic.4Port", // + new EdgeConfig.Component("io0", bundle.getString(this.getAppId() + ".io0.alias"), + "IO.KMtronic.4Port", // JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("modbus.id", modbusIdInternal) // .addProperty("modbusUnitId", 2) // .build()), - new EdgeConfig.Component("battery0", "Batterie", "Battery.Fenecon.Home", // + new EdgeConfig.Component("battery0", bundle.getString(this.getAppId() + ".battery0.alias"), + "Battery.Fenecon.Home", // JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("startStop", "AUTO") // @@ -156,33 +166,36 @@ protected ThrowingBiFunction .addProperty("modbusUnitId", 1) // .addProperty("batteryStartUpRelay", "io0/Relay4") // .build()), - new EdgeConfig.Component("batteryInverter0", "Batterie-Wechselrichter", "GoodWe.BatteryInverter", - JsonUtils.buildJsonObject() // + new EdgeConfig.Component("batteryInverter0", + bundle.getString(this.getAppId() + ".batteryInverter0.alias"), // + "GoodWe.BatteryInverter", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("modbus.id", modbusIdExternal) // .addProperty("modbusUnitId", 247) // .addProperty("safetyCountry", safetyCountry) // - .addProperty("backupEnable", emergencyReserveEnabled ? "ENABLE" : "DISABLE") // + .addProperty("backupEnable", // + emergencyReserveEnabled ? "ENABLE" : "DISABLE") // .addProperty("feedPowerEnable", "ENABLE") // .addProperty("feedPowerPara", maxFeedInPower) // .addProperty("setfeedInPowerSettings", feedInSetting) // .build()), - new EdgeConfig.Component(essId, "Speichersystem", "Ess.Generic.ManagedSymmetric", - JsonUtils.buildJsonObject() // + new EdgeConfig.Component(essId, bundle.getString(this.getAppId() + "." + essId + ".alias"), + "Ess.Generic.ManagedSymmetric", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("startStop", "START") // .addProperty("batteryInverter.id", "batteryInverter0") // .addProperty("battery.id", "battery0") // .build()), - new EdgeConfig.Component("predictor0", "Prognose", "Predictor.PersistenceModel", - JsonUtils.buildJsonObject() // + new EdgeConfig.Component("predictor0", bundle.getString(this.getAppId() + ".predictor0.alias"), + "Predictor.PersistenceModel", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .add("channelAddresses", JsonUtils.buildJsonArray() // .add("_sum/ProductionActivePower") // .add("_sum/ConsumptionActivePower") // .build()) // .build()), - new EdgeConfig.Component("ctrlGridOptimizedCharge0", "Netzdienliche Beladung", + new EdgeConfig.Component("ctrlGridOptimizedCharge0", + bundle.getString("App.PvSelfConsumption.GridOptimizedCharge.Name"), "Controller.Ess.GridOptimizedCharge", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("ess.id", essId) // @@ -190,12 +203,14 @@ protected ThrowingBiFunction .addProperty("sellToGridLimitEnabled", true) // .addProperty("maximumSellToGridPower", maxFeedInPower) // .build()), - new EdgeConfig.Component("ctrlEssSurplusFeedToGrid0", "Überschusseinspeisung", + new EdgeConfig.Component("ctrlEssSurplusFeedToGrid0", + bundle.getString(this.getAppId() + ".ctrlEssSurplusFeedToGrid0.alias"), "Controller.Ess.Hybrid.Surplus-Feed-To-Grid", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("ess.id", essId) // .build()), - new EdgeConfig.Component("ctrlBalancing0", "Eigenverbrauchsoptimierung", + new EdgeConfig.Component("ctrlBalancing0", + bundle.getString(this.getAppId() + ".ctrlBalancing0.alias"), "Controller.Symmetric.Balancing", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("ess.id", essId) // @@ -206,7 +221,8 @@ protected ThrowingBiFunction ); if (EnumUtils.getAsOptionalBoolean(p, Property.HAS_AC_METER).orElse(false)) { - components.add(new EdgeConfig.Component("meter1", "Netzzähler", "Meter.Socomec.Threephase", // + components.add(new EdgeConfig.Component("meter1", bundle.getString(this.getAppId() + ".meter1.alias"), + "Meter.Socomec.Threephase", // JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("modbus.id", modbusIdExternal) // @@ -238,7 +254,8 @@ protected ThrowingBiFunction var hasEmergencyReserve = EnumUtils.getAsOptionalBoolean(p, Property.HAS_EMERGENCY_RESERVE).orElse(false); if (hasEmergencyReserve) { - components.add(new EdgeConfig.Component("meter2", "Notstromverbraucher", "GoodWe.EmergencyPowerMeter", // + components.add(new EdgeConfig.Component("meter2", bundle.getString(this.getAppId() + ".meter2.alias"), + "GoodWe.EmergencyPowerMeter", // JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("modbus.id", modbusIdExternal) // @@ -247,7 +264,8 @@ protected ThrowingBiFunction var emergencyReserveSoc = EnumUtils.getAsInt(p, Property.EMERGENCY_RESERVE_SOC); components.add(new EdgeConfig.Component("ctrlEmergencyCapacityReserve0", - "Ansteuerung der Notstromreserve", "Controller.Ess.EmergencyCapacityReserve", // + bundle.getString(this.getAppId() + ".ctrlEmergencyCapacityReserve0.alias"), + "Controller.Ess.EmergencyCapacityReserve", // JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("ess.id", essId) // @@ -273,89 +291,129 @@ protected ThrowingBiFunction } @Override - public AppAssistant getAppAssistant() { - // Source https://formly.dev/examples/introduction - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + final var batteryInverter = this.getBatteryInverter(); + final var hasEmergencyReserve = this.componentUtil.getComponent("ctrlEmergencyCapacityReserve0", // + "Controller.Ess.EmergencyCapacityReserve").isPresent(); + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildSelect(Property.SAFETY_COUNTRY) // - .setLabel("Battery-Inverter Safety Country") // + .setLabel(bundle.getString(this.getAppId() + ".safetyCountry.label")) // .isRequired(true) // .setOptions(JsonUtils.buildJsonArray() // .add(JsonUtils.buildJsonObject() // - .addProperty("label", "Germany") // + .addProperty("label", bundle.getString("germany")) // .addProperty("value", "GERMANY") // .build()) // .add(JsonUtils.buildJsonObject() // - .addProperty("label", "Austria") // + .addProperty("label", bundle.getString("austria")) // .addProperty("value", "AUSTRIA") // .build()) // .add(JsonUtils.buildJsonObject() // - .addProperty("label", "Switzerland") // + .addProperty("label", bundle.getString("switzerland")) // .addProperty("value", "SWITZERLAND") // .build()) // .build()) // - .build()) + .onlyIf(batteryInverter.isPresent(), f -> { + f.setDefaultValue(batteryInverter.get() // + .getProperty("safetyCountry").get().getAsString()); + }).build()) .add(JsonFormlyUtil.buildInput(Property.MAX_FEED_IN_POWER) // - .setLabel("Feed-In limitation [W]") // + .setLabel(bundle.getString(this.getAppId() + ".feedInLimit.label")) // .isRequired(true) // .setInputType(Type.NUMBER) // - .build()) + .onlyIf(batteryInverter.isPresent(), f -> { + f.setDefaultValue(batteryInverter.get() // + .getProperty("feedPowerPara").get().getAsNumber()); + }).build()) .add(JsonFormlyUtil.buildSelect(Property.FEED_IN_SETTING) // - .setLabel("Feed-In Settings") // + .setLabel(bundle.getString(this.getAppId() + ".feedInSettings.label")) // .isRequired(true) // .setOptions(this.getFeedInSettingsOptions(), t -> t, t -> t) // - .build()) + .onlyIf(batteryInverter.isPresent(), f -> { + f.setDefaultValue(batteryInverter.get() // + .getProperty("setfeedInPowerSettings") // + .get().getAsString()); + }).build()) .add(JsonFormlyUtil.buildCheckbox(Property.HAS_AC_METER) // - .setLabel("Has AC meter (SOCOMEC)") // + .setLabel(bundle.getString(this.getAppId() + ".hasAcMeterSocomec.label")) // .isRequired(true) // + .setDefaultValue(this.componentUtil // + .getComponent("meter1", "Meter.Socomec.Threephase") // + .isPresent()) // .build()) .add(JsonFormlyUtil.buildCheckbox(Property.HAS_DC_PV1) // - .setLabel("Has DC-PV 1 (MPPT 1)") // + .setLabel(bundle.getString(this.getAppId() + ".hasDcPV1.label")) // .isRequired(true) // + .setDefaultValue(this.componentUtil // + .getComponent("charger0", "GoodWe.Charger-PV1").isPresent()) .build()) .add(JsonFormlyUtil.buildInput(Property.DC_PV1_ALIAS) // .setDefaultValue("DC-PV1") // - .setLabel("DC-PV 1 Alias") // .onlyShowIfChecked(Property.HAS_DC_PV1) // - .build()) + .setDefaultValueWithStringSupplier(() -> { + var charger = this.componentUtil // + .getComponent("charger0", "GoodWe.Charger-PV1"); + if (charger.isEmpty()) { + return null; + } + return charger.get().getAlias(); + }).build()) .add(JsonFormlyUtil.buildCheckbox(Property.HAS_DC_PV2) // - .setLabel("Has DC-PV 2 (MPPT 2)") // + .setLabel(bundle.getString(this.getAppId() + ".hasDcPV2.label")) // .isRequired(true) // + .setDefaultValue(this.componentUtil // + .getComponent("charger1", "GoodWe.Charger-PV2").isPresent()) .build()) .add(JsonFormlyUtil.buildInput(Property.DC_PV2_ALIAS) // - .setDefaultValue("DC-PV 2") // .setLabel("DC-PV 2 Alias") // .onlyShowIfChecked(Property.HAS_DC_PV2) // - .build()) + .setDefaultValueWithStringSupplier(() -> { + var charger = this.componentUtil // + .getComponent("charger1", "GoodWe.Charger-PV2"); + if (charger.isEmpty()) { + return null; + } + return charger.get().getAlias(); + }).build()) .add(JsonFormlyUtil.buildCheckbox(Property.EMERGENCY_RESERVE_ENABLED) // - .setLabel("Activate Emergency power supply") // + .setLabel(bundle.getString(this.getAppId() + ".emergencyPowerSupply.label")) // .isRequired(true) // - .build()) + .onlyIf(batteryInverter.isPresent(), f -> { + f.setDefaultValue(batteryInverter.get().getProperty("backupEnable").get() + .getAsString().equals("ENABLE")); + }).build()) .add(JsonFormlyUtil.buildCheckbox(Property.HAS_EMERGENCY_RESERVE) // - .setLabel("Activate Emergency Reserve Energy") // + .setLabel(bundle.getString(this.getAppId() + ".emergencyPowerEnergy.label")) // + .setDefaultValue(hasEmergencyReserve) // .onlyShowIfChecked(Property.EMERGENCY_RESERVE_ENABLED) // .build()) .add(JsonFormlyUtil.buildInput(Property.EMERGENCY_RESERVE_SOC) // - .setLabel("Emergency Reserve Energy (State-of-Charge)") // + .setLabel(bundle.getString(this.getAppId() + ".reserveEnergy.label")) // + .setInputType(Type.NUMBER) // .onlyShowIfChecked(Property.HAS_EMERGENCY_RESERVE) // - .build()) + .onlyIf(hasEmergencyReserve, f -> { + f.setDefaultValue(this.componentManager.getEdgeConfig() + .getComponent("ctrlEmergencyCapacityReserve0").get() + .getProperty("reserveSoc").get().getAsNumber()); + }).build()) .build()) // .build(); } - @Override - public OpenemsAppCategory[] getCategorys() { - return new OpenemsAppCategory[] { OpenemsAppCategory.INTEGRATED_SYSTEM }; - } - - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; + private final Optional getBatteryInverter() { + var batteryInverter = this.componentManager.getEdgeConfig().getComponent("batteryInverter0"); + if (batteryInverter.isPresent() // + && !batteryInverter.get().getFactoryId().equals("GoodWe.BatteryInverter")) { + batteryInverter = Optional.empty(); + } + return batteryInverter; } @Override - public String getName() { - return "FENECON Home"; + public OpenemsAppCategory[] getCategorys() { + return new OpenemsAppCategory[] { OpenemsAppCategory.INTEGRATED_SYSTEM }; } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java new file mode 100644 index 00000000000..82ebb9d0246 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java @@ -0,0 +1,163 @@ +package io.openems.edge.app.loadcontrol; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.TreeMap; +import java.util.stream.Collectors; + +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.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; +import io.openems.common.types.EdgeConfig; +import io.openems.common.types.EdgeConfig.Component; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.loadcontrol.ManualRelayControl.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.DefaultEnum; +import io.openems.edge.core.appmanager.JsonFormlyUtil; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.validator.CheckRelayCount; +import io.openems.edge.core.appmanager.validator.Validator; +import io.openems.edge.core.appmanager.validator.Validator.Builder; + +/** + * Describes a App for a manual relay control. + * + *
+  {
+    "appId":"App.LoadControl.ManualRelayControl",
+    "alias":"Manuelle Relaissteuerung",
+    "instanceId": UUID,
+    "image": base64,
+    "properties":{
+    	"CTRL_IO_FIX_DIGITAL_OUTPUT_ID": "ctrlIoFixDigitalOutput0",
+    	"OUTPUT_CHANNEL": "io1/Relay1"
+    },
+    "appDescriptor": {
+    	"websiteUrl": https://fenecon.de/fems-2-2/fems-app-manuelle-relaissteuerung/
+    }
+  }
+ * 
+ */ +@org.osgi.service.component.annotations.Component(name = "App.LoadControl.ManualRelayControl") +public class ManualRelayControl extends AbstractOpenemsApp implements OpenemsApp { + + public static enum Property implements DefaultEnum { + // User values + ALIAS("Manuelle Relaissteuerung"), // + OUTPUT_CHANNEL("io0/Relay1"), // + // Components + CTRL_IO_FIX_DIGITAL_OUTPUT_ID("ctrlIoFixDigitalOutput0"); + + private final String defaultValue; + + private Property(String defaultValue) { + this.defaultValue = defaultValue; + } + + @Override + public String getDefaultValue() { + return this.defaultValue; + } + + } + + @Activate + public ManualRelayControl(@Reference ComponentManager componentManager, ComponentContext componentContext, + @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + super(componentManager, componentContext, cm, componentUtil); + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { + + final var ctrlIoFixDigitalOutputId = this.getId(t, p, Property.CTRL_IO_FIX_DIGITAL_OUTPUT_ID); + + final var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); + + final var outputChannelAddress = this.getValueOrDefault(p, Property.OUTPUT_CHANNEL); + + List comp = new ArrayList<>(); + + comp.add(new EdgeConfig.Component(ctrlIoFixDigitalOutputId, alias, "Controller.Io.FixDigitalOutput", + JsonUtils.buildJsonObject() // + .addProperty("outputChannelAddress", outputChannelAddress) // + .build()));// + + return new AppConfiguration(comp); + }; + } + + @Override + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // + .fields(JsonUtils.buildJsonArray() // + .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL) // + .setOptions(this.componentUtil.getAllRelays() // + .stream().map(r -> r.relays).flatMap(List::stream) // + .collect(Collectors.toList())) // + .setDefaultValueWithStringSupplier(() -> { + var relays = this.componentUtil.getPreferredRelays(Lists.newArrayList(), + new int[] { 1 }, new int[] { 1 }); + return relays == null ? null : relays[0]; + }) // + .isRequired(true) // + .setLabel(bundle.getString(this.getAppId() + ".outputChannel.label")) // + .setDescription(bundle.getString(this.getAppId() + ".outputChannel.description")) // + .build()) + .build()) + .build(); + } + + @Override + public AppDescriptor getAppDescriptor() { + return AppDescriptor.create() // + .build(); + } + + @Override + public OpenemsAppCategory[] getCategorys() { + return new OpenemsAppCategory[] { OpenemsAppCategory.LOAD_CONTROL }; + } + + @Override + public Builder getValidateBuilder() { + return Validator.create() // + .setInstallableCheckableConfigs(Lists.newArrayList(// + new Validator.CheckableConfig(CheckRelayCount.COMPONENT_NAME, + new Validator.MapBuilder<>(new TreeMap()) // + .put("count", 1) // + .build()))); + } + + @Override + protected Class getPropertyClass() { + return Property.class; + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.MULTIPLE; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java new file mode 100644 index 00000000000..9a37903c739 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java @@ -0,0 +1,168 @@ +package io.openems.edge.app.loadcontrol; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.TreeMap; +import java.util.stream.Collectors; + +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.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; +import io.openems.common.types.EdgeConfig; +import io.openems.common.types.EdgeConfig.Component; +import io.openems.common.utils.EnumUtils; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.loadcontrol.ThresholdControl.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.DefaultEnum; +import io.openems.edge.core.appmanager.JsonFormlyUtil; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.validator.CheckRelayCount; +import io.openems.edge.core.appmanager.validator.Validator; +import io.openems.edge.core.appmanager.validator.Validator.Builder; + +/** + * Describes a App for a Threshold Controller. + * + *
+  {
+    "appId":"App.LoadControl.ThresholdControl",
+    "alias":"Schwellwertsteuerung",
+    "instanceId": UUID,
+    "image": base64,
+    "properties":{
+    	"CTRL_IO_CHANNEL_SINGLE_THRESHOLD_ID": "ctrlIoChannelSingleThreshold0",
+    	"OUTPUT_CHANNELS":['io1/Relay1', 'io1/Relay2']
+    },
+    "appDescriptor": {
+    	"websiteUrl": https://fenecon.de/fems-2-2/fems-app-schwellwert-steuerung/
+    }
+  }
+ * 
+ */ +@org.osgi.service.component.annotations.Component(name = "App.LoadControl.ThresholdControl") +public class ThresholdControl extends AbstractOpenemsApp implements OpenemsApp { + + public static enum Property implements DefaultEnum { + // User values + ALIAS("Schwellwertsteuerung"), // + OUTPUT_CHANNELS("['io0/Relay1']"), // + // Components + CTRL_IO_CHANNEL_SINGLE_THRESHOLD_ID("ctrlIoChannelSingleThreshold0"); + + private final String defaultValue; + + private Property(String defaultValue) { + this.defaultValue = defaultValue; + } + + @Override + public String getDefaultValue() { + return this.defaultValue; + } + + } + + @Activate + public ThresholdControl(@Reference ComponentManager componentManager, ComponentContext componentContext, + @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + super(componentManager, componentContext, cm, componentUtil); + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { + + final var ctrlIoChannelSingleThresholdId = this.getId(t, p, Property.CTRL_IO_CHANNEL_SINGLE_THRESHOLD_ID); + + final var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); + + final var outputChannelAddress = EnumUtils.getAsJsonArray(p, Property.OUTPUT_CHANNELS); + + List comp = new ArrayList<>(); + + comp.add(new EdgeConfig.Component(ctrlIoChannelSingleThresholdId, alias, + "Controller.IO.ChannelSingleThreshold", JsonUtils.buildJsonObject() // + .onlyIf(t == ConfigurationTarget.ADD, + j -> j.addProperty("inputChannelAddress", "_sum/EssSoc")) + .add("outputChannelAddress", outputChannelAddress) // + .onlyIf(t == ConfigurationTarget.ADD, b -> b.addProperty("threshold", 50)) // + .build()));// + + return new AppConfiguration(comp); + }; + } + + @Override + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // + .fields(JsonUtils.buildJsonArray() // + .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNELS) // + .isMulti(true) // + .setOptions(this.componentUtil.getAllRelays() // + .stream().map(r -> r.relays).flatMap(List::stream) // + .collect(Collectors.toList())) // + .setDefaultValueWithStringSupplier(() -> { + var relays = this.componentUtil.getPreferredRelays(Lists.newArrayList(), + new int[] { 1 }, new int[] { 1 }); + return relays == null ? null : relays[0]; + }) // + .isRequired(true) // + .setLabel(bundle.getString(this.getAppId() + ".outputChannels.label")) // + .setDescription(bundle.getString(this.getAppId() + ".outputChannels.description")) // + .build()) + .build()) + .build(); + } + + @Override + public AppDescriptor getAppDescriptor() { + return AppDescriptor.create() // + .build(); + } + + @Override + public OpenemsAppCategory[] getCategorys() { + return new OpenemsAppCategory[] { OpenemsAppCategory.LOAD_CONTROL }; + } + + @Override + public Builder getValidateBuilder() { + return Validator.create() // + .setInstallableCheckableConfigs(Lists.newArrayList(// + new Validator.CheckableConfig(CheckRelayCount.COMPONENT_NAME, + new Validator.MapBuilder<>(new TreeMap()) // + .put("count", 1) // + .build()))); + } + + @Override + protected Class getPropertyClass() { + return Property.class; + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.MULTIPLE; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/AbstractMeterApp.java b/io.openems.edge.core/src/io/openems/edge/app/meter/AbstractMeterApp.java index 7d98beee953..96ef28c8fbe 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/AbstractMeterApp.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/AbstractMeterApp.java @@ -5,6 +5,7 @@ import com.google.gson.JsonArray; +import io.openems.common.session.Language; import io.openems.common.utils.JsonUtils; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.core.appmanager.AbstractOpenemsApp; @@ -23,18 +24,19 @@ public final OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.METER }; } - protected final JsonArray buildMeterOptions() { + protected final JsonArray buildMeterOptions(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); return JsonUtils.buildJsonArray() // .add(JsonUtils.buildJsonObject() // - .addProperty("label", "Erzeugung/Production") // + .addProperty("label", bundle.getString("App.Meter.production")) // .addProperty("value", "PRODUCTION") // .build()) .add(JsonUtils.buildJsonObject() // - .addProperty("label", "Netzzähler/Grid-Meter") // + .addProperty("label", bundle.getString("App.Meter.gridMeter")) // .addProperty("value", "GRID") // .build()) .add(JsonUtils.buildJsonObject() // - .addProperty("label", "Verbrauchszähler/Consumption-Meter") // + .addProperty("label", bundle.getString("App.Meter.consumtionMeter")) // .addProperty("value", "CONSUMPTION_METERED") // .build()) .build(); diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java index 84560aace0d..8c5a351b83e 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.EnumMap; -import java.util.Map; import java.util.TreeMap; import org.osgi.service.cm.ConfigurationAdmin; @@ -11,15 +10,18 @@ import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; +import com.google.common.collect.Lists; import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.meter.CarloGavazziMeter.Property; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AppAssistant; import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppDescriptor; @@ -74,14 +76,14 @@ public CarloGavazziMeter(@Reference ComponentManager componentManager, Component } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { // modbus id for connection to battery-inverter for a HOME var modbusId = "modbus1"; var meterId = this.getId(t, p, Property.METER_ID, "meter1"); - var alias = this.getValueOrDefault(p, Property.ALIAS, "PV"); + var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); var type = this.getValueOrDefault(p, Property.TYPE, "PRODUCTION"); var modbusUnitId = EnumUtils.getAsInt(p, Property.MODBUS_UNIT_ID); @@ -100,16 +102,17 @@ protected ThrowingBiFunction } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildSelect(Property.TYPE) // - .setLabel("Mount Type") // - .setOptions(this.buildMeterOptions()) // + .setLabel(bundle.getString("App.Meter.mountType.label")) // + .setOptions(this.buildMeterOptions(language)) // .build()) // .add(JsonFormlyUtil.buildInput(Property.MODBUS_UNIT_ID) // - .setLabel("Modbus Unit-ID") // - .setDescription("The Unit-ID of the Modbus device.") // + .setLabel(bundle.getString("modbusUnitId")) // + .setDescription(bundle.getString("modbusUnitId.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(6) // .setMin(0) // @@ -128,21 +131,10 @@ public AppDescriptor getAppDescriptor() { @Override public Builder getValidateBuilder() { return Validator.create() // - .setCompatibleCheckableNames(new Validator.MapBuilder<>(new TreeMap>()) // - .put(CheckHome.COMPONENT_NAME, // + .setCompatibleCheckableConfigs(Lists.newArrayList(// + new Validator.CheckableConfig(CheckHome.COMPONENT_NAME, new Validator.MapBuilder<>(new TreeMap()) // - .build()) - .build()); - } - - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "Carlo Gavazzi Zähler"; + .build()))); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java index 9caa328844f..c00501ddfad 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java @@ -14,15 +14,18 @@ import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; +import com.google.common.collect.Lists; import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.meter.JanitzaMeter.Property; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AppAssistant; import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppDescriptor; @@ -84,8 +87,8 @@ public JanitzaMeter(@Reference ComponentManager componentManager, ComponentConte } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { var meterId = this.getId(t, p, Property.METER_ID, "meter1"); @@ -96,7 +99,7 @@ protected ThrowingBiFunction // var modbusId = "modbus1"; var modbusId = this.getId(t, p, Property.MODBUS_ID, "modbus2"); - var alias = this.getValueOrDefault(p, Property.ALIAS, "PV"); + var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); var factorieId = this.getValueOrDefault(p, Property.MODEL, "Meter.Janitza.UMG96RME"); var type = this.getValueOrDefault(p, Property.TYPE, "PRODUCTION"); var ip = this.getValueOrDefault(p, Property.IP, "10.4.0.12"); @@ -121,29 +124,30 @@ protected ThrowingBiFunction } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildSelect(Property.MODEL) // - .setLabel("Product Model") // + .setLabel(bundle.getString(this.getAppId() + ".productModel")) // .isRequired(true) // .setOptions(this.buildFactorieIdOptions()) // .build()) // .add(JsonFormlyUtil.buildSelect(Property.TYPE) // - .setLabel("Mount Type") // + .setLabel(bundle.getString("App.Meter.mountType.label")) // .isRequired(true) // - .setOptions(this.buildMeterOptions()) // + .setOptions(this.buildMeterOptions(language)) // .build()) // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel("IP-Address") // - .setDescription("The IP address of the Meter.") // + .setLabel(bundle.getString("ipAddress")) // + .setDescription(bundle.getString("App.Meter.ip.description")) // .isRequired(true) // .setDefaultValue("10.4.0.12") // .setValidation(Validation.IP) // .build()) .add(JsonFormlyUtil.buildInput(Property.MODBUS_UNIT_ID) // - .setLabel("Modbus Unit-ID") // - .setDescription("The Unit-ID of the Modbus device.") // + .setLabel(bundle.getString("modbusUnitId")) // + .setDescription(bundle.getString("modbusUnitId.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(1) // .setMin(0) // @@ -162,21 +166,10 @@ public AppDescriptor getAppDescriptor() { @Override public Builder getValidateBuilder() { return Validator.create() // - .setCompatibleCheckableNames(new Validator.MapBuilder<>(new TreeMap>()) // - .put(CheckHome.COMPONENT_NAME, // + .setCompatibleCheckableConfigs(Lists.newArrayList(// + new Validator.CheckableConfig(CheckHome.COMPONENT_NAME, new Validator.MapBuilder<>(new TreeMap()) // - .build()) - .build()); - } - - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "Janitza Zähler"; + .build()))); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java index 8585953f85f..a33b5874f7a 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.EnumMap; -import java.util.Map; import java.util.TreeMap; import org.osgi.service.cm.ConfigurationAdmin; @@ -11,15 +10,18 @@ import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; +import com.google.common.collect.Lists; import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.meter.SocomecMeter.Property; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AppAssistant; import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppDescriptor; @@ -74,14 +76,14 @@ public SocomecMeter(@Reference ComponentManager componentManager, ComponentConte } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { // modbus id for connection to battery-inverter for a HOME var modbusId = "modbus1"; var meterId = this.getId(t, p, Property.METER_ID, "meter1"); - var alias = this.getValueOrDefault(p, Property.ALIAS, "PV"); + var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); var type = this.getValueOrDefault(p, Property.TYPE, "PRODUCTION"); var modbusUnitId = EnumUtils.getAsInt(p, Property.MODBUS_UNIT_ID); @@ -99,16 +101,17 @@ protected ThrowingBiFunction } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildSelect(Property.TYPE) // - .setLabel("Mount Type") // - .setOptions(this.buildMeterOptions()) // + .setLabel(bundle.getString("App.Meter.mountType.label")) // + .setOptions(this.buildMeterOptions(language)) // .build()) // .add(JsonFormlyUtil.buildInput(Property.MODBUS_UNIT_ID) // - .setLabel("Modbus Unit-ID") // - .setDescription("The Unit-ID of the Modbus device.") // + .setLabel(bundle.getString("modbusUnitId")) // + .setDescription(bundle.getString("modbusUnitId.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(6) // .setMin(0) // @@ -127,21 +130,10 @@ public AppDescriptor getAppDescriptor() { @Override public Builder getValidateBuilder() { return Validator.create() // - .setCompatibleCheckableNames(new Validator.MapBuilder<>(new TreeMap>()) // - .put(CheckHome.COMPONENT_NAME, // + .setCompatibleCheckableConfigs(Lists.newArrayList(// + new Validator.CheckableConfig(CheckHome.COMPONENT_NAME, new Validator.MapBuilder<>(new TreeMap()) // - .build()) - .build()); - } - - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "Socomec Zähler"; + .build()))); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/AbstractPvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/AbstractPvInverter.java index 35422002028..5fef4c4932e 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/AbstractPvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/AbstractPvInverter.java @@ -13,7 +13,6 @@ import io.openems.edge.common.component.ComponentManager; import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.ComponentUtil; -import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCategory; public abstract class AbstractPvInverter> extends AbstractOpenemsApp { @@ -43,13 +42,4 @@ protected final List getComponents(String factoryId, String pvInverte ); } - protected final Component getComponentWithFactoryId(List components, String factoryId) { - return components.stream().filter(t -> t.getFactoryId().equals(factoryId)).findFirst().orElse(null); - } - - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - } diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KacoPvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KacoPvInverter.java index 95a53f75a75..0e9c74df2c2 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KacoPvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KacoPvInverter.java @@ -10,11 +10,13 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.pvinverter.KacoPvInverter.Property; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AppAssistant; import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppDescriptor; @@ -68,10 +70,10 @@ public KacoPvInverter(@Reference ComponentManager componentManager, ComponentCon } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { - var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName()); + var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); var ip = this.getValueOrDefault(p, Property.IP, "192.168.178.85"); var port = EnumUtils.getAsInt(p, Property.PORT); @@ -86,19 +88,20 @@ protected ThrowingBiFunction } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel("IP-Address") // - .setDescription("The IP address of the Pv-Inverter.") // + .setLabel(bundle.getString("ipAddress")) // + .setDescription(bundle.getString("App.PvInverter.ip.description")) // .setDefaultValue("192.168.178.85") // .isRequired(true) // .setValidation(Validation.IP) // .build()) // .add(JsonFormlyUtil.buildInput(Property.PORT) // - .setLabel("Port") // - .setDescription("The port of the Pv-Inverter.") // + .setLabel(bundle.getString("port")) // + .setDescription(bundle.getString("App.PvInverter.port.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(502) // .setMin(0) // @@ -114,16 +117,6 @@ public AppDescriptor getAppDescriptor() { .build(); } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "KACO PV-Wechselrichter"; - } - @Override protected Class getPropertyClass() { return Property.class; diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KostalPvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KostalPvInverter.java index 52cdcbd2acb..36af7e7c2d7 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KostalPvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KostalPvInverter.java @@ -10,11 +10,13 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.pvinverter.KostalPvInverter.Property; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AppAssistant; import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppDescriptor; @@ -71,10 +73,10 @@ public KostalPvInverter(@Reference ComponentManager componentManager, ComponentC } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { - var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName()); + var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); var ip = this.getValueOrDefault(p, Property.IP, "192.168.178.85"); var port = EnumUtils.getAsInt(p, Property.PORT); var modbusUnitId = EnumUtils.getAsInt(p, Property.MODBUS_UNIT_ID); @@ -84,7 +86,7 @@ protected ThrowingBiFunction var factoryIdInverter = "PV-Inverter.Kostal"; var components = this.getComponents(factoryIdInverter, pvInverterId, modbusId, alias, ip, port); - var inverter = this.getComponentWithFactoryId(components, factoryIdInverter); + var inverter = AbstractOpenemsApp.getComponentWithFactoryId(components, factoryIdInverter); inverter.getProperties().put("modbusUnitId", JsonUtils.parse(Integer.toString(modbusUnitId))); return new AppConfiguration(components); @@ -92,27 +94,28 @@ protected ThrowingBiFunction } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel("IP-Address") // - .setDescription("The IP address of the Pv-Inverter.") // + .setLabel(bundle.getString("ipAddress")) // + .setDescription(bundle.getString("App.PvInverter.ip.description")) // .setDefaultValue("192.168.178.85") // .isRequired(true) // .setValidation(Validation.IP) // .build()) // .add(JsonFormlyUtil.buildInput(Property.PORT) // - .setLabel("Port") // - .setDescription("The port of the Pv-Inverter.") // + .setLabel(bundle.getString("port")) // + .setDescription(bundle.getString("App.PvInverter.port.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(502) // .setMin(0) // .isRequired(true) // .build()) // .add(JsonFormlyUtil.buildInput(Property.MODBUS_UNIT_ID) // - .setLabel("Modbus Unit-ID") // - .setDescription("The Unit-ID of the Modbus device.") // + .setLabel(bundle.getString("modbusUnitId")) // + .setDescription(bundle.getString("modbusUnitId.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(71) // .setMin(0) // @@ -128,16 +131,6 @@ public AppDescriptor getAppDescriptor() { .build(); } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "Kostal PV-Wechselrichter"; - } - @Override protected Class getPropertyClass() { return Property.class; diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java index 6b35c9f7178..8ba0c913b15 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java @@ -10,11 +10,13 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.pvinverter.SmaPvInverter.Property; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AppAssistant; import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppDescriptor; @@ -70,10 +72,10 @@ public SmaPvInverter(@Reference ComponentManager componentManager, ComponentCont } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { - var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName()); + var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); var ip = this.getValueOrDefault(p, Property.IP, "192.168.178.85"); var port = EnumUtils.getAsInt(p, Property.PORT); var modbusUnitId = EnumUtils.getAsInt(p, Property.MODBUS_UNIT_ID); @@ -83,7 +85,7 @@ protected ThrowingBiFunction var factoryIdInverter = "PV-Inverter.SMA.SunnyTripower"; var components = this.getComponents(factoryIdInverter, pvInverterId, modbusId, alias, ip, port); - var inverter = this.getComponentWithFactoryId(components, factoryIdInverter); + var inverter = AbstractOpenemsApp.getComponentWithFactoryId(components, factoryIdInverter); inverter.getProperties().put("modbusUnitId", JsonUtils.parse(Integer.toString(modbusUnitId))); return new AppConfiguration(components); @@ -91,29 +93,28 @@ protected ThrowingBiFunction } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel("IP-Address") // - .setDescription("The IP address of the Pv-Inverter.") // + .setLabel(bundle.getString("ipAddress")) // + .setDescription(bundle.getString("App.PvInverter.ip.description")) // .setDefaultValue("192.168.178.85") // .isRequired(true) // .setValidation(Validation.IP) // .build()) // .add(JsonFormlyUtil.buildInput(Property.PORT) // - .setLabel("Port") // - .setDescription("The port of the Pv-Inverter.") // + .setLabel(bundle.getString("port")) // + .setDescription(bundle.getString("App.PvInverter.port.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(502) // .setMin(0) // .isRequired(true) // .build()) // .add(JsonFormlyUtil.buildInput(Property.MODBUS_UNIT_ID) // - .setLabel("Modbus Unit-ID") // - .setDescription("The Unit-ID of the Modbus device." - + "Be aware, that according to the manual you need to add '123' to the value that you configured " - + "in the SMA web interface.") // + .setLabel(bundle.getString("modbusUnitId")) // + .setDescription(bundle.getString(this.getAppId() + ".modbusUnitId.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(126) // .setMin(0) // @@ -129,16 +130,6 @@ public AppDescriptor getAppDescriptor() { .build(); } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "SMA PV-Wechselrichter"; - } - @Override protected Class getPropertyClass() { return Property.class; diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SolarEdgePvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SolarEdgePvInverter.java index 93736ec94a9..445517b5002 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SolarEdgePvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SolarEdgePvInverter.java @@ -10,11 +10,13 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.pvinverter.SolarEdgePvInverter.Property; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AppAssistant; import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.AppDescriptor; @@ -69,10 +71,10 @@ public SolarEdgePvInverter(@Reference ComponentManager componentManager, Compone } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { - var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName()); + var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); var ip = this.getValueOrDefault(p, Property.IP, "192.168.178.85"); var port = EnumUtils.getAsInt(p, Property.PORT); @@ -87,19 +89,20 @@ protected ThrowingBiFunction } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel("IP-Address") // - .setDescription("The IP address of the Pv-Inverter.") // + .setLabel(bundle.getString("ipAddress")) // + .setDescription(bundle.getString("App.PvInverter.ip.description")) // .setDefaultValue("192.168.178.85") // .isRequired(true) // .setValidation(Validation.IP) // .build()) // .add(JsonFormlyUtil.buildInput(Property.PORT) // - .setLabel("Port") // - .setDescription("The port of the Pv-Inverter.") // + .setLabel(bundle.getString("port")) // + .setDescription(bundle.getString("App.PvInverter.port.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(502) // .setMin(0) // @@ -115,16 +118,6 @@ public AppDescriptor getAppDescriptor() { .build(); } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "SolarEdge PV-Wechselrichter"; - } - @Override protected Class getPropertyClass() { return Property.class; diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java new file mode 100644 index 00000000000..4e63517d86e --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java @@ -0,0 +1,152 @@ +package io.openems.edge.app.pvselfconsumption; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.TreeMap; + +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.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; +import io.openems.common.types.EdgeConfig; +import io.openems.common.types.EdgeConfig.Component; +import io.openems.common.utils.EnumUtils; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.pvselfconsumption.GridOptimizedCharge.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.JsonFormlyUtil; +import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.validator.CheckHome; +import io.openems.edge.core.appmanager.validator.Validator; +import io.openems.edge.core.appmanager.validator.Validator.Builder; + +/** + * Describes a App for a Grid Optimized Charge. + * + *
+  {
+    "appId":"App.PvSelfConsumption.GridOptimizedCharge",
+    "alias":"Netzdienliche Beladung",
+    "instanceId": UUID,
+    "image": base64,
+    "properties":{
+    	"CTRL_GRID_OPTIMIZED_CHARGE_ID": "ctrlGridOptimizedCharge0",
+    	"MAXIMUM_SELL_TO_GRID_POWER": 10000
+    },
+    "appDescriptor": {
+    	"websiteUrl": https://fenecon.de/fems-2-2/fems-app-netzdienliche-beladung/
+    }
+  }
+ * 
+ */ +@org.osgi.service.component.annotations.Component(name = "App.PvSelfConsumption.GridOptimizedCharge") +public class GridOptimizedCharge extends AbstractOpenemsApp implements OpenemsApp { + + public static enum Property { + // User values + ALIAS, // + MAXIMUM_SELL_TO_GRID_POWER, // + // Components + CTRL_GRID_OPTIMIZED_CHARGE_ID; + + } + + @Activate + public GridOptimizedCharge(@Reference ComponentManager componentManager, ComponentContext componentContext, + @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + super(componentManager, componentContext, cm, componentUtil); + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { + + final var ctrlIoFixDigitalOutputId = this.getId(t, p, Property.CTRL_GRID_OPTIMIZED_CHARGE_ID, + "ctrlGridOptimizedCharge0"); + + final var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); + + final var maximumSellToGridPower = EnumUtils.getAsInt(p, Property.MAXIMUM_SELL_TO_GRID_POWER); + + List comp = new ArrayList<>(); + + comp.add(new EdgeConfig.Component(ctrlIoFixDigitalOutputId, alias, "Controller.Ess.GridOptimizedCharge", + JsonUtils.buildJsonObject() // + .addProperty("enabled", true) // + .onlyIf(t == ConfigurationTarget.ADD, // + j -> j.addProperty("ess.id", "ess0") // + .addProperty("meter.id", "meter0")) + .addProperty("sellToGridLimitEnabled", true) // + .addProperty("maximumSellToGridPower", maximumSellToGridPower) // + .build()));// + + return new AppConfiguration(comp); + }; + } + + @Override + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // + .fields(JsonUtils.buildJsonArray() // + .add(JsonFormlyUtil.buildInput(Property.MAXIMUM_SELL_TO_GRID_POWER) // + .setInputType(Type.NUMBER) // + .isRequired(true) // + .setMin(0) // + .setLabel(bundle.getString(this.getAppId() + ".maximumSellToGridPower.label")) // + .setDescription( + bundle.getString(this.getAppId() + ".maximumSellToGridPower.description")) // + .build()) + .build()) + .build(); + } + + @Override + public AppDescriptor getAppDescriptor() { + return AppDescriptor.create() // + .build(); + } + + @Override + public Builder getValidateBuilder() { + return Validator.create() // TODO remove later when home has dependency + .setCompatibleCheckableConfigs(Lists.newArrayList(// + new Validator.CheckableConfig(CheckHome.COMPONENT_NAME, true, + new Validator.MapBuilder<>(new TreeMap()) // + .build()))); + } + + @Override + public OpenemsAppCategory[] getCategorys() { + return new OpenemsAppCategory[] { OpenemsAppCategory.PV_SELF_CONSUMPTION }; + } + + @Override + protected Class getPropertyClass() { + return Property.class; + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.SINGLE; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java index a300a9c9b94..fb1cefa4078 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java @@ -12,7 +12,8 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.types.EdgeConfig.Component; import io.openems.common.utils.JsonUtils; @@ -62,8 +63,8 @@ public AwattarHourly(@Reference ComponentManager componentManager, ComponentCont } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { var ctrlEssTimeOfUseTariffDischargeId = this.getId(t, p, Property.CTRL_ESS_TIME_OF_USE_TARIF_DISCHARGE_ID, "ctrlEssTimeOfUseTariffDischarge0"); @@ -72,11 +73,11 @@ protected ThrowingBiFunction // TODO ess id may be changed List comp = Lists.newArrayList(// - new EdgeConfig.Component(ctrlEssTimeOfUseTariffDischargeId, "aWATTar", + new EdgeConfig.Component(ctrlEssTimeOfUseTariffDischargeId, this.getName(l), "Controller.Ess.Time-Of-Use-Tariff.Discharge", JsonUtils.buildJsonObject() // .addProperty("ess.id", "ess0") // .build()), // - new EdgeConfig.Component(timeOfUseTariffId, "timeOfUseTariff0", "TimeOfUseTariff.Awattar", + new EdgeConfig.Component(timeOfUseTariffId, this.getName(l), "TimeOfUseTariff.Awattar", JsonUtils.buildJsonObject() // .build())// ); @@ -85,8 +86,8 @@ protected ThrowingBiFunction } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + return AppAssistant.create(this.getName(language)) // .build(); } @@ -101,16 +102,6 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.TIME_OF_USE_TARIFF }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "Awattar HOURLY"; - } - @Override protected Class getPropertyClass() { return Property.class; diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java index 7d4d878830c..e81b9ab5c03 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java @@ -12,7 +12,8 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.types.EdgeConfig.Component; import io.openems.common.utils.EnumUtils; @@ -49,7 +50,7 @@ } * */ -@org.osgi.service.component.annotations.Component(name = "App.TimeVariablePrice.Stromdao") +@org.osgi.service.component.annotations.Component(name = "App.TimeOfUseTariff.Stromdao") public class StromdaoCorrently extends AbstractOpenemsApp implements OpenemsApp { public static enum Property { @@ -65,8 +66,8 @@ public StromdaoCorrently(@Reference ComponentManager componentManager, Component } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { var ctrlEssTimeOfUseTariffDischargeId = this.getId(t, p, Property.CTRL_ESS_TIME_OF_USE_TARIF_DISCHARGE_ID, "ctrlEssTimeOfUseTariffDischarge0"); @@ -76,11 +77,11 @@ protected ThrowingBiFunction // TODO ess id may be changed List comp = Lists.newArrayList(// - new EdgeConfig.Component(ctrlEssTimeOfUseTariffDischargeId, this.getName(), + new EdgeConfig.Component(ctrlEssTimeOfUseTariffDischargeId, this.getName(l), "Controller.Ess.Time-Of-Use-Tariff.Discharge", JsonUtils.buildJsonObject() // .addProperty("ess.id", "ess0") // .build()), // - new EdgeConfig.Component(timeOfUseTariffId, "timeOfUseTariff0", "TimeOfUseTariff.Corrently", + new EdgeConfig.Component(timeOfUseTariffId, this.getName(l), "TimeOfUseTariff.Corrently", JsonUtils.buildJsonObject() // .addProperty("zipcode", zipCode) // .build())// @@ -90,12 +91,13 @@ protected ThrowingBiFunction } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()) // + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.ZIP_CODE) // - .setLabel("ZIP Code") // - .setDescription("German ZIP Code of the location.") // + .setLabel(bundle.getString(this.getAppId() + ".zipCode.label")) // + .setDescription(bundle.getString(this.getAppId() + ".zipCode.description")) // .isRequired(true) // .build()) // .build()) // @@ -113,16 +115,6 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.TIME_OF_USE_TARIFF }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "Stromdao Corrently"; - } - @Override protected Class getPropertyClass() { return Property.class; diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java index ebb3a52f731..1025fa72aeb 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java @@ -12,7 +12,8 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.types.EdgeConfig.Component; import io.openems.common.utils.JsonUtils; @@ -66,8 +67,8 @@ public Tibber(@Reference ComponentManager componentManager, ComponentContext con } @Override - protected ThrowingBiFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { - return (t, p) -> { + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { var ctrlEssTimeOfUseTariffDischargeId = this.getId(t, p, Property.CTRL_ESS_TIME_OF_USE_TARIF_DISCHARGE_ID, "ctrlEssTimeOfUseTariffDischarge0"); @@ -77,11 +78,11 @@ protected ThrowingBiFunction // TODO ess id may be changed List comp = Lists.newArrayList(// - new EdgeConfig.Component(ctrlEssTimeOfUseTariffDischargeId, this.getName(), + new EdgeConfig.Component(ctrlEssTimeOfUseTariffDischargeId, this.getName(l), "Controller.Ess.Time-Of-Use-Tariff.Discharge", JsonUtils.buildJsonObject() // .addProperty("ess.id", "ess0") // .build()), // - new EdgeConfig.Component(timeOfUseTariffId, "timeOfUseTariff0", "TimeOfUseTariff.Tibber", + new EdgeConfig.Component(timeOfUseTariffId, this.getName(l), "TimeOfUseTariff.Tibber", JsonUtils.buildJsonObject() // .onlyIf(t.isAddOrUpdate(), c -> c.addProperty("accessToken", accessToken)) // .build())// @@ -95,12 +96,13 @@ protected ThrowingBiFunction } @Override - public AppAssistant getAppAssistant() { - return AppAssistant.create(this.getName()).fields(// + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)).fields(// JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.ACCESS_TOKEN) // - .setLabel("Access token") // - .setDescription("Access token for the Tibber API.") // + .setLabel(bundle.getString(this.getAppId() + ".accessToken.label")) // + .setDescription(bundle.getString(this.getAppId() + ".accessToken.description")) // .setInputType(Type.PASSWORD) // .isRequired(true) // .build()) // @@ -119,16 +121,6 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.TIME_OF_USE_TARIFF }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - - @Override - public String getName() { - return "Tibber"; - } - @Override protected Class getPropertyClass() { return Property.class; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java index cfbd207bf2d..d2300dfe163 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.ResourceBundle; import java.util.TreeMap; import java.util.stream.Collectors; @@ -21,6 +22,8 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.function.ThrowingBiFunction; import io.openems.common.function.ThrowingFunction; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.types.EdgeConfig.Component; import io.openems.edge.common.component.ComponentManager; @@ -51,9 +54,10 @@ protected AbstractOpenemsApp(ComponentManager componentManager, ComponentContext * from a {@link EnumMap} of configuration properties for a given * {@link ConfigurationTarget}. */ - protected abstract ThrowingBiFunction, // configuration properties + Language, // the language AppConfiguration, // return value of the function OpenemsNamedException> // Exception on error appConfigurationFactory(); @@ -78,13 +82,14 @@ protected final void assertCheckables(ConfigurationTarget t, Checkable... checka * * @param errors a collection of validation errors * @param configurationTarget the target of the configuration + * @param language the language of the configuration * @param properties the configured App properties * @return the {@link AppConfiguration} or null */ private AppConfiguration configuration(ArrayList errors, ConfigurationTarget configurationTarget, - EnumMap properties) { + Language language, EnumMap properties) { try { - return this.appConfigurationFactory().apply(configurationTarget, properties); + return this.appConfigurationFactory().apply(configurationTarget, properties, language); } catch (OpenemsNamedException e) { errors.add(e.getMessage()); return null; @@ -124,11 +129,11 @@ private EnumMap convertToEnumMap(ArrayList errors } @Override - public AppConfiguration getAppConfiguration(ConfigurationTarget target, JsonObject config) + public AppConfiguration getAppConfiguration(ConfigurationTarget target, JsonObject config, Language language) throws OpenemsNamedException { var errors = new ArrayList(); var enumMap = this.convertToEnumMap(target != ConfigurationTarget.TEST ? errors : new ArrayList<>(), config); - var c = this.configuration(errors, target, enumMap); + var c = this.configuration(errors, target, language, enumMap); // TODO remove and maybe add @AttributeDefinition above enums // this is for removing passwords so they do not get saved @@ -220,7 +225,7 @@ private List getValidationErrors(JsonObject jProperties) { final var errors = new ArrayList(); final var properties = this.convertToEnumMap(errors, jProperties); - final var appConfiguration = this.configuration(errors, ConfigurationTarget.VALIDATE, properties); + final var appConfiguration = this.configuration(errors, ConfigurationTarget.VALIDATE, null, properties); if (appConfiguration == null) { return errors; } @@ -246,7 +251,9 @@ public final Validator getValidator() { // add check for cardinality for every app var validator = this.getValidateBuilder().build(); - validator.getInstallableCheckableNames().put(CheckCardinality.COMPONENT_NAME, properties); + + validator.getInstallableCheckableConfigs() + .add(new Validator.CheckableConfig(CheckCardinality.COMPONENT_NAME, properties)); if (this.installationValidation() != null) { validator.setConfigurationValidation((t, u) -> { @@ -358,7 +365,8 @@ private void validateComponentConfigurations(ArrayList errors, EdgeConfi missingComponents.add(componentId); continue; } - // ALIAS is not really necessary to validate + // ALIAS should not be validated because it can be different depending on the + // language ComponentUtilImpl.isSameConfigurationWithoutAlias(errors, expectedComponent, actualComponent); } @@ -388,7 +396,18 @@ private void validateIps(ArrayList errors, EdgeConfig actualEdgeConfig, var interfaces = this.componentUtil.getInterfaces(); var eth0 = interfaces.stream().filter(t -> t.getName().equals("eth0")).findFirst().get(); var eth0Adresses = eth0.getAddresses(); - addresses.removeAll(eth0Adresses.getValue()); + + var availableAddresses = new LinkedList(); + for (var address : addresses) { + for (var eth0Address : eth0Adresses.getValue()) { + if (eth0Address.isInSameNetwork(address)) { + availableAddresses.add(address); + break; + } + } + } + + addresses.removeAll(availableAddresses); for (var address : addresses) { errors.add("Address '" + address + "' is not added."); } @@ -432,4 +451,42 @@ private void validateScheduler(ArrayList errors, EdgeConfig actualEdgeCo errors.add("Controller [" + nextControllerId + "] is not/wrongly configured in Scheduler"); } } + + @Override + public String getName(Language language) { + return AbstractOpenemsApp.getTranslation(language, this.getAppId() + ".Name"); + } + + @Override + public String getImage() { + return OpenemsApp.FALLBACK_IMAGE; + } + + protected static String getTranslation(Language language, String key) { + return getTranslationBundle(language).getString(key); + } + + protected static ResourceBundle getTranslationBundle(Language language) { + if (language == null) { + language = Language.DEFAULT; + } + // TODO add language support + switch (language) { + case CZ: + case ES: + case FR: + case NL: + language = Language.EN; + break; + case DE: + case EN: + break; + } + return ResourceBundle.getBundle("io.openems.edge.core.appmanager.translation", language.getLocal()); + } + + protected static final Component getComponentWithFactoryId(List components, String factoryId) { + return components.stream().filter(t -> t.getFactoryId().equals(factoryId)).findFirst().orElse(null); + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppInstallWorker.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppInstallWorker.java new file mode 100644 index 00000000000..2767c6c5469 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppInstallWorker.java @@ -0,0 +1,83 @@ +package io.openems.edge.core.appmanager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.utils.JsonUtils; +import io.openems.common.worker.AbstractWorker; +import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance; + +public class AppInstallWorker extends AbstractWorker { + + /** + * Time to wait before doing the check. This allows the system to completely + * boot and read configurations. And enough time to allow the user to delete the + * ReadOnly App and let him install the ReadWrite one. + */ + private static final int INITIAL_WAIT_TIME = 60_000; // in ms + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private final AppManagerImpl parent; + + public AppInstallWorker(AppManagerImpl parent) { + this.parent = parent; + } + + private void installFreeApps() { + + // install ModbusTcp.ReadOnly + if (this.parent.getInstantiatedApps().stream().noneMatch( + t -> t.appId.equals("App.Api.ModbusTcp.ReadOnly") || t.appId.equals("App.Api.ModbusTcp.ReadWrite"))) { + + // TODO this is only required if the ReadWrite controller exists without an App + if (this.parent.componentManager.getEdgeConfig() + .getComponentIdsByFactory("Controller.Api.ModbusTcp.ReadWrite").size() == 0) { + + try { + this.parent.handleAddAppInstanceRequest(null, new AddAppInstance.Request( + "App.Api.ModbusTcp.ReadOnly", "", JsonUtils.buildJsonObject().build())); + } catch (OpenemsNamedException e) { + this.log.info("Unable to install free App[ModbusTcp.ReadOnly]"); + } + + } else { + this.log.warn("Unable to create App[App.Api.ModbusTcp.ReadOnly] because a " + + "Component with the FactoryId[Controller.Api.ModbusTcp.ReadWrite] exists!"); + } + } + + // install RestJson.ReadOnly + if (this.parent.getInstantiatedApps().stream().noneMatch( + t -> t.appId.equals("App.Api.RestJson.ReadOnly") || t.appId.equals("App.Api.RestJson.ReadWrite"))) { + + // TODO this is only required if the ReadWrite controller exists without an App + if (this.parent.componentManager.getEdgeConfig().getComponentIdsByFactory("Controller.Api.Rest.ReadWrite") + .size() == 0) { + + try { + this.parent.handleAddAppInstanceRequest(null, new AddAppInstance.Request( + "App.Api.RestJson.ReadOnly", "", JsonUtils.buildJsonObject().build())); + } catch (OpenemsNamedException e) { + this.log.info("Unable to install free App[RestJson.ReadOnly]"); + } + } else { + this.log.warn("Unable to create App[App.Api.RestJson.ReadOnly] because a " + + "Component with the FactoryId[Controller.Api.Rest.ReadWrite] exists!"); + } + } + + } + + @Override + protected void forever() throws Throwable { + this.installFreeApps(); + } + + @Override + protected int getCycleTime() { + return INITIAL_WAIT_TIME; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java index 5106e95ea0b..8fd8d32b83d 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java @@ -7,6 +7,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.UUID; import java.util.Vector; @@ -21,6 +22,7 @@ import org.osgi.service.cm.ConfigurationListener; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Modified; import org.osgi.service.component.annotations.Reference; @@ -39,8 +41,9 @@ import io.openems.common.jsonrpc.request.DeleteComponentConfigRequest; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest.Property; +import io.openems.common.session.Language; import io.openems.common.session.Role; -import io.openems.common.types.EdgeConfig.Component; +import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.ComponentManager; @@ -55,9 +58,10 @@ import io.openems.edge.core.appmanager.jsonrpc.GetAppInstances; import io.openems.edge.core.appmanager.jsonrpc.GetApps; import io.openems.edge.core.appmanager.jsonrpc.UpdateAppInstance; +import io.openems.edge.core.componentmanager.ComponentManagerImpl; @Designate(ocd = Config.class, factory = false) -@org.osgi.service.component.annotations.Component(// +@Component(// name = AppManager.SINGLETON_SERVICE_PID, // immediate = true, // property = { // @@ -67,9 +71,11 @@ public class AppManagerImpl extends AbstractOpenemsComponent implements AppManager, OpenemsComponent, JsonApi, ConfigurationListener { private final AppValidateWorker worker; + private final AppInstallWorker appInstallWorker; @Reference private ConfigurationAdmin cm; + @Reference protected List availableApps; @@ -87,6 +93,7 @@ public AppManagerImpl() { AppManager.ChannelId.values() // ); this.worker = new AppValidateWorker(this); + this.appInstallWorker = new AppInstallWorker(this); } @Activate @@ -96,9 +103,10 @@ private void activate(ComponentContext componentContext, Config config) { if (OpenemsComponent.validateSingleton(this.cm, SINGLETON_SERVICE_PID, SINGLETON_COMPONENT_ID)) { return; } + this.applyConfig(config); this.worker.activate(this.id()); - this.applyConfig(config); + this.appInstallWorker.activate(this.id()); } /** @@ -208,22 +216,28 @@ private void foreachAppConfiguration(Consumer consumer, UUID.. if (skipInstance) { continue; } - var app = this.findAppById(appInstance.appId); + try { - consumer.accept(app.getAppConfiguration(ConfigurationTarget.VALIDATE, appInstance.properties)); + var app = this.findAppById(appInstance.appId); + consumer.accept(app.getAppConfiguration(ConfigurationTarget.VALIDATE, appInstance.properties, null)); } catch (OpenemsNamedException e) { // move to next app + } catch (NoSuchElementException e) { + // app not found for instance + // this may happen if the app id gets refactored + // apps which app ids are not known are printed in debug log as 'UNKNOWAPPS' } } } - private void createComponent(User user, Component comp) throws OpenemsNamedException { + private void createComponent(User user, EdgeConfig.Component comp) throws OpenemsNamedException { List properties = comp.getProperties().entrySet().stream() .map(t -> new Property(t.getKey(), t.getValue())).collect(Collectors.toList()); properties.add(new Property("id", comp.getId())); properties.add(new Property("alias", comp.getAlias())); - this.componentManager.handleJsonrpcRequest(user, + // user can be null using internal method + ((ComponentManagerImpl) this.componentManager).handleCreateComponentConfigRequest(user, new CreateComponentConfigRequest(comp.getFactoryId(), properties)); } @@ -232,6 +246,7 @@ private void createComponent(User user, Component comp) throws OpenemsNamedExcep protected void deactivate() { super.deactivate(); this.worker.deactivate(); + this.appInstallWorker.deactivate(); } @Override @@ -247,8 +262,8 @@ public String debugLog() { * @param notMyComponents other needed components from the other apps * @return the id s of the components that got deleted */ - private List deleteComponents(User user, List components, List notMyComponents) - throws OpenemsNamedException { + private List deleteComponents(User user, List components, + List notMyComponents) throws OpenemsNamedException { List errors = new ArrayList<>(); List deletedIds = new ArrayList<>(); for (var comp : components) { @@ -262,7 +277,9 @@ private List deleteComponents(User user, List components, Lis } try { - this.componentManager.handleJsonrpcRequest(user, new DeleteComponentConfigRequest(comp.getId())); + // user can be null using internal method + ((ComponentManagerImpl) this.componentManager).handleDeleteComponentConfigRequest(user, + new DeleteComponentConfigRequest(comp.getId())); deletedIds.add(comp.getId()); } catch (OpenemsNamedException e) { errors.add(e.toString()); @@ -281,7 +298,7 @@ private List deleteComponents(User user, List components, Lis * @param id of the app * @return the found app */ - public final OpenemsApp findAppById(String id) { + public final OpenemsApp findAppById(String id) throws NoSuchElementException { return this.availableApps.stream() // .filter(t -> t.getAppId().equals(id)) // .findFirst() // @@ -297,14 +314,16 @@ public final OpenemsApp findAppById(String id) { * @param newAppInstance the new {@link OpenemsAppInstance} * @param otherAppComponents the components that are used from the other * {@link OpenemsAppInstance} + * @param language the language of the new config * @return the AppConfiguration with the replaced ID s of the components * @throws OpenemsNamedException on error */ private AppConfiguration getNewAppConfigWithReplacedIds(OpenemsApp app, OpenemsAppInstance oldAppInstance, - OpenemsAppInstance newAppInstance, List otherAppComponents) throws OpenemsNamedException { + OpenemsAppInstance newAppInstance, List otherAppComponents, Language language) + throws OpenemsNamedException { var target = oldAppInstance == null ? ConfigurationTarget.ADD : ConfigurationTarget.UPDATE; - var newAppConfig = app.getAppConfiguration(target, newAppInstance.properties); + var newAppConfig = app.getAppConfiguration(target, newAppInstance.properties, language); final var replacableIds = this.getReplaceableComponentIds(app, newAppInstance.properties); @@ -325,7 +344,7 @@ private AppConfiguration getNewAppConfigWithReplacedIds(OpenemsApp app, OpenemsA var isNewComponent = true; var id = comp.getId(); var canBeReplaced = replacableIds.containsKey(id); - Component foundComponent = null; + EdgeConfig.Component foundComponent = null; // try to find a component with the necessary settings if (canBeReplaced) { @@ -371,7 +390,7 @@ private AppConfiguration getNewAppConfigWithReplacedIds(OpenemsApp app, OpenemsA newAppInstance.properties.addProperty(replacableIds.get(comp.getId()), id); } } - return app.getAppConfiguration(target, newAppInstance.properties); + return app.getAppConfiguration(target, newAppInstance.properties, language); } /** @@ -380,8 +399,8 @@ private AppConfiguration getNewAppConfigWithReplacedIds(OpenemsApp app, OpenemsA * @param thisApp the app that components should not be included * @return all components from all app instances except the given thisApp */ - private List getOtherAppComponents(OpenemsAppInstance thisApp) { - List allOtherComponents = new ArrayList<>(); + private List getOtherAppComponents(OpenemsAppInstance thisApp) { + List allOtherComponents = new ArrayList<>(); this.foreachAppConfiguration(c -> { allOtherComponents.addAll(c.components); }, thisApp.instanceId); @@ -402,6 +421,22 @@ private List getOtherAppIps(OpenemsAppInstance thisApp) { return allOtherIps; } + /** + * Gets Scheduler Order s that are needed from the other + * {@link OpenemsAppInstance}s. Every Id from the scheduler orders gets append + * to the list. + * + * @param thisApp the app which ip s should not be included + * @return all needed Scheduler Order s from the other apps + */ + private List getOtherAppSchedulerOrders(OpenemsAppInstance thisApp) { + List allOtherIps = new ArrayList<>(); + this.foreachAppConfiguration(c -> { + allOtherIps.addAll(c.schedulerExecutionOrder); + }, thisApp.instanceId); + return allOtherIps; + } + /** * Gets the component id s that can be replaced. * @@ -415,7 +450,7 @@ private List getOtherAppIps(OpenemsAppInstance thisApp) { protected final Map getReplaceableComponentIds(OpenemsApp app, JsonObject properties) throws OpenemsNamedException { final var prefix = "?_?_"; - var config = app.getAppConfiguration(ConfigurationTarget.TEST, properties); + var config = app.getAppConfiguration(ConfigurationTarget.TEST, properties, null); var copyBuilder = JsonUtils.buildJsonObject(); for (var entry : properties.entrySet()) { copyBuilder.add(entry.getKey(), entry.getValue()); @@ -424,7 +459,7 @@ protected final Map getReplaceableComponentIds(OpenemsApp app, J copyBuilder.addProperty(comp.getId(), prefix); } var copy = copyBuilder.build(); - var configWithNewIds = app.getAppConfiguration(ConfigurationTarget.TEST, copy); + var configWithNewIds = app.getAppConfiguration(ConfigurationTarget.TEST, copy, null); Map replaceableComponentIds = new HashMap<>(); for (var comp : configWithNewIds.components) { if (comp.getId().startsWith(prefix)) { @@ -450,7 +485,7 @@ protected final Map getReplaceableComponentIds(OpenemsApp app, J * @return the Future JSON-RPC Response * @throws OpenemsNamedException on error */ - private CompletableFuture handleAddAppInstanceRequest(User user, + protected CompletableFuture handleAddAppInstanceRequest(User user, AddAppInstance.Request request) throws OpenemsNamedException { var instanceId = UUID.randomUUID(); @@ -505,7 +540,8 @@ private CompletableFuture handleDeleteAppInsta return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.id)); } var app = this.findAppById(instance.appId); - var config = app.getAppConfiguration(ConfigurationTarget.DELETE, instance.properties); + + var config = app.getAppConfiguration(ConfigurationTarget.DELETE, instance.properties, null); List errors = new Vector<>(); var deleteComponents = CompletableFuture.runAsync(() -> { @@ -513,6 +549,10 @@ private CompletableFuture handleDeleteAppInsta var deletedIds = this.deleteComponents(user, config.components, this.getOtherAppComponents(instance)); deletedIds.addAll(config.schedulerExecutionOrder); + // do not remove ids in scheduler from other apps + // e. g. Home has ctrlBalancing0 in scheduler + // and also KebaEvcs has the ctrlBalancing0 in the scheduler + deletedIds.removeAll(this.getOtherAppSchedulerOrders(instance)); this.componentUtil.removeIdsInSchedulerIfExisting(user, deletedIds); } catch (OpenemsNamedException e) { errors.add(e); @@ -565,8 +605,8 @@ private CompletableFuture handleGetAppAssistantRequest(U GetAppAssistant.Request request) throws OpenemsNamedException { for (var app : this.availableApps) { if (request.appId.equals(app.getAppId())) { - return CompletableFuture - .completedFuture(new GetAppAssistant.Response(request.id, app.getAppAssistant())); + return CompletableFuture.completedFuture( + new GetAppAssistant.Response(request.id, app.getAppAssistant(user.getLanguage()))); } } throw new OpenemsException("App-ID [" + request.appId + "] is unknown"); @@ -620,7 +660,7 @@ private CompletableFuture handleGetAppRequest(User user, var app = this.availableApps.stream().filter(t -> t.getAppId().equals(request.appId)).findFirst().get(); var instances = this.instantiatedApps.stream().filter(t -> t.appId.equals(request.appId)) .collect(Collectors.toList()); - return CompletableFuture.completedFuture(new GetApp.Response(request.id, app, instances)); + return CompletableFuture.completedFuture(new GetApp.Response(request.id, app, instances, user.getLanguage())); } /** @@ -633,8 +673,8 @@ private CompletableFuture handleGetAppRequest(User user, */ private CompletableFuture handleGetAppsRequest(User user, GetApps.Request request) throws OpenemsNamedException { - return CompletableFuture - .completedFuture(new GetApps.Response(request.id, this.availableApps, this.instantiatedApps)); + return CompletableFuture.completedFuture( + new GetApps.Response(request.id, this.availableApps, this.instantiatedApps, user.getLanguage())); } @Override @@ -756,7 +796,8 @@ private List reconfigurApp(User user, OpenemsAppInstance oldAppInstance, * @param actualComp the actual component that exists * @throws OpenemsNamedException when the configuration can not be rewritten */ - private void reconfigure(User user, Component myComp, Component actualComp) throws OpenemsNamedException { + private void reconfigure(User user, EdgeConfig.Component myComp, EdgeConfig.Component actualComp) + throws OpenemsNamedException { if (ComponentUtilImpl.isSameConfiguration(null, myComp, actualComp)) { return; } @@ -767,7 +808,8 @@ private void reconfigure(User user, Component myComp, Component actualComp) thro .collect(Collectors.toList()); properties.add(new Property("alias", myComp.getAlias())); var updateRequest = new UpdateComponentConfigRequest(actualComp.getId(), properties); - this.componentManager.handleJsonrpcRequest(user, updateRequest); + // user can be null using internal method + ((ComponentManagerImpl) this.componentManager).handleUpdateComponentConfigRequest(user, updateRequest); } /** @@ -780,7 +822,8 @@ private void reconfigure(User user, Component myComp, Component actualComp) thro private void updateAppManagerConfiguration(User user, List apps) throws OpenemsNamedException { var p = new Property("apps", getJsonAppsString(apps)); var updateRequest = new UpdateComponentConfigRequest(SINGLETON_COMPONENT_ID, Arrays.asList(p)); - this.componentManager.handleJsonrpcRequest(user, updateRequest); + // user can be null using internal method + ((ComponentManagerImpl) this.componentManager).handleUpdateComponentConfigRequest(user, updateRequest); } /** @@ -802,11 +845,13 @@ public CompletableFuture updateAppSettings(List errorList, User us } else { errors = errorList; } + final var language = user != null ? user.getLanguage() : null; AppConfiguration oldAppConfigTemp = null; if (oldAppInstance != null) { oldAppInstance.properties.addProperty("ALIAS", oldAppInstance.alias); try { - oldAppConfigTemp = app.getAppConfiguration(ConfigurationTarget.VALIDATE, oldAppInstance.properties); + oldAppConfigTemp = app.getAppConfiguration(ConfigurationTarget.VALIDATE, oldAppInstance.properties, + language); } catch (OpenemsNamedException ex) { errors.add(ex.getMessage()); } @@ -817,7 +862,7 @@ public CompletableFuture updateAppSettings(List errorList, User us newAppInstance.properties.addProperty("ALIAS", newAppInstance.alias); final var otherComponents = this.getOtherAppComponents(newAppInstance); final var newAppConfig = this.getNewAppConfigWithReplacedIds(app, oldAppInstance, newAppInstance, - otherComponents); + otherComponents, language); // TODO remove 'if' if it works on windows // rewriting network configuration only works on Linux @@ -851,9 +896,9 @@ public CompletableFuture updateAppSettings(List errorList, User us // adds / updates components var updatingComponents = CompletableFuture.runAsync(() -> { - var createdComponents = new LinkedList(); + var createdComponents = new LinkedList(); // create components - for (Component comp : ComponentUtilImpl.order(newAppConfig.components)) { + for (var comp : ComponentUtilImpl.order(newAppConfig.components)) { /** * if comp already exists with same config as needed => use it. if comp exist * with different config and no other app needs it => rewrite settings. if comp diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtil.java index 474ef16110a..9824b5361dd 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtil.java @@ -1,6 +1,7 @@ package io.openems.edge.core.appmanager; import java.util.List; +import java.util.Optional; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.types.EdgeConfig; @@ -29,6 +30,14 @@ public interface ComponentUtil { */ public boolean anyComponentUses(String value, List ignoreIds); + /** + * Gets a list of current Relays. e. g. 'io0/Relay1' + * + * @return a list of Relays + * @throws OpenemsNamedException on error + */ + public List getAllRelays(); + /** * Gets a list of currently available Relays of IOs which are not used by any * component. e. g. 'io0/Relay1' @@ -201,4 +210,12 @@ public void updateScheduler(User user, List schedulerExecutionOrder, Lis */ public void updateHosts(User user, List ips, List oldIps) throws OpenemsNamedException; + /** + * Gets an {@link Optional} of an {@link EdgeConfig.Component}. + * + * @param id the id of the component + * @param factoryId the factoryId of the component + * @return the optional component + */ + public Optional getComponent(String id, String factoryId); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilImpl.java index 1af4211003e..4e48487b9ae 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilImpl.java @@ -10,6 +10,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -376,6 +377,21 @@ public boolean anyComponentUses(String value, List ignoreIds) { }); } + @Override + public List getAllRelays() { + List allDigitalOutputs = this.getEnabledComponentsOfType(DigitalOutput.class); + List relays = new LinkedList<>(); + for (DigitalOutput digitalOutput : allDigitalOutputs) { + List availableIos = new LinkedList<>(); + for (var i = 0; i < digitalOutput.digitalOutputChannels().length; i++) { + var ioName = digitalOutput.id() + "/Relay" + (i + 1); + availableIos.add(ioName); + } + relays.add(new Relay(digitalOutput.id(), availableIos, digitalOutput.digitalOutputChannels().length)); + } + return relays; + } + @Override public List getAvailableRelays() { return this.getAvailableRelays(new ArrayList<>()); @@ -760,4 +776,16 @@ public void updateHosts(User user, List ips, List oldIps) throws } } + @Override + public Optional getComponent(String id, String factoryId) { + var comp = this.componentManager.getEdgeConfig().getComponent(id); + if (comp.isEmpty()) { + return Optional.empty(); + } + if (!comp.get().getFactoryId().equals(factoryId)) { + return Optional.empty(); + } + return comp; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java index 2e8fd04db55..3cb418fae35 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java @@ -5,13 +5,17 @@ import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; 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.JsonUtils.JsonObjectBuilder; /** * Source https://formly.dev/examples/introduction. @@ -132,6 +136,24 @@ public final T setDefaultValue(Number defaultValue) { return this.getSelf(); } + + public final T setDefaultValue(JsonElement defaultValue) { + if (defaultValue != null) { + this.jsonObject.add("defaultValue", defaultValue); + } else if (this.jsonObject.has("defaultValue")) { + this.jsonObject.remove("defaultValue"); + } + + return this.getSelf(); + } + + public final T setDefaultValueWithStringSupplier(Supplier supplieDefaultValue) { + return this.setDefaultValue(supplieDefaultValue.get()); + } + + public final T setDefaultValueWithBooleanSupplier(Supplier supplieDefaultValue) { + return this.setDefaultValue(supplieDefaultValue.get()); + } public final T isRequired(boolean isRequired) { if (isRequired) { @@ -152,6 +174,20 @@ public final T setDescription(String description) { return this.getSelf(); } + /** + * Call a method on a FormlyBuilder if the expression is true. + * + * @param expression the expression + * @param consumer allows a lambda function on {@link FormlyBuilder} + * @return the {@link JsonObjectBuilder} + */ + public T onlyIf(boolean expression, Consumer consumer) { + if (expression) { + consumer.accept(this.getSelf()); + } + return this.getSelf(); + } + public final > T onlyShowIfChecked(PROPERTEY property) { this.getExpressionProperties().addProperty("templateOptions.required", "model." + property.name()); this.jsonObject.addProperty("hideExpression", "!model." + property.name()); @@ -521,6 +557,10 @@ public SelectBuilder setOptions(Set> items, Function items) { + return this.setOptions(items, t -> t, t -> t); + } + public SelectBuilder setOptions(List items, Function item2Label, Function item2Value) { var options = JsonUtils.buildJsonArray(); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java index 216b322d94d..796258aea57 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java @@ -5,6 +5,7 @@ import com.google.gson.JsonObject; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.session.Language; import io.openems.edge.core.appmanager.validator.Validator; public interface OpenemsApp { @@ -12,18 +13,20 @@ public interface OpenemsApp { /** * Gets the {@link AppAssistant} for this {@link OpenemsApp}. * + * @param language the language of the {@link AppAssistant} * @return the AppAssistant */ - public AppAssistant getAppAssistant(); + public AppAssistant getAppAssistant(Language language); /** * Gets the {@link AppConfiguration} needed for the {@link OpenemsApp}. * - * @param target the {@link ConfigurationTarget} - * @param config the configured app 'properties' + * @param target the {@link ConfigurationTarget} + * @param config the configured app 'properties' + * @param language the language of the configuration * @return the app Configuration */ - public AppConfiguration getAppConfiguration(ConfigurationTarget target, JsonObject config) + public AppConfiguration getAppConfiguration(ConfigurationTarget target, JsonObject config, Language language) throws OpenemsNamedException; /** @@ -58,9 +61,10 @@ public AppConfiguration getAppConfiguration(ConfigurationTarget target, JsonObje /** * Gets the name of the {@link OpenemsApp}. * + * @param language the language of the name * @return a human readable name */ - public String getName(); + public String getName(Language language); /** * Gets the {@link OpenemsAppCardinality} of the {@link OpenemsApp}. diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java index 382d511f80a..2c55047ae9a 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java @@ -1,72 +1,92 @@ package io.openems.edge.core.appmanager; +import java.util.ResourceBundle; + import com.google.gson.JsonObject; +import io.openems.common.session.Language; import io.openems.common.utils.JsonUtils; public enum OpenemsAppCategory { - // TODO translation - /** * Integrated Systems. */ - INTEGRATED_SYSTEM("Integrierte Systeme"), + INTEGRATED_SYSTEM("integratedSystems"), /** - * Time of use energy tariff. + * Time variable energy price. */ - TIME_OF_USE_TARIFF("Zeitvariable Stromtarife"), + TIME_OF_USE_TARIFF("timeOfUseTariff"), /** * Electric vehicle charging station. */ - EVCS("E-Mobilität"), + EVCS("evcs"), + + /** + * Heat. + */ + HEAT("heat"), /** - * Load control. + * Load Control. */ - HEAT("Wärme"), + LOAD_CONTROL("loadControl"), /** * Hardware. */ - HARDWARE("Hardware"), + HARDWARE("hardware"), /** * PV-Inverter. */ - PV_INVERTER("PV-Wechselrichter"), + PV_INVERTER("pvInverter"), + + /** + * PV self-consumption. + */ + PV_SELF_CONSUMPTION("pvSelfConsumption"), /** * Meter. */ - METER("Zähler"), + METER("meter"), /** * Apis. */ - API("Schnittstellen"); + API("api"); - private String readableName; + private String readableNameKey; - private OpenemsAppCategory(String readableName) { - this.readableName = readableName; + private OpenemsAppCategory(String readableNameKey) { + this.readableNameKey = readableNameKey; } - public String getReadableName() { - return this.readableName; + /** + * Gets the readable name in the specific language. + * + * @param language the language of the name + * @return the name + */ + public String getReadableName(Language language) { + var translationBundle = ResourceBundle.getBundle("io.openems.edge.core.appmanager.translation", + language.getLocal()); + return translationBundle.getString(this.readableNameKey); } /** * Creates a {@link JsonObject} of the {@link OpenemsAppCategory}. * + * @param language the language of the readable name * @return the {@link JsonObject} */ - public JsonObject toJsonObject() { + public JsonObject toJsonObject(Language language) { return JsonUtils.buildJsonObject() // .addProperty("name", this.name()) // - .addProperty("readableName", this.getReadableName()) // + .addProperty("readableName", this.getReadableName(language)) // .build(); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java index 5caa797bb11..1d7ae4b0cc1 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java @@ -8,6 +8,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.jsonrpc.base.JsonrpcRequest; import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; +import io.openems.common.session.Language; import io.openems.common.utils.JsonUtils; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppInstance; @@ -95,7 +96,8 @@ public JsonObject getParams() { public static class Response extends JsonrpcResponseSuccess { - private static JsonObject createAppObject(OpenemsApp app, List instantiatedApps) { + private static JsonObject createAppObject(OpenemsApp app, List instantiatedApps, + Language language) { var instanceIds = JsonUtils.buildJsonArray(); for (var instantiatedApp : instantiatedApps) { @@ -103,13 +105,13 @@ private static JsonObject createAppObject(OpenemsApp app, List instantiatedApps) { + public Response(UUID id, OpenemsApp app, List instantiatedApps, Language language) { super(id); - this.app = createAppObject(app, instantiatedApps); + this.app = createAppObject(app, instantiatedApps, language); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java index c96efa33124..bd2ca085db4 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java @@ -10,6 +10,7 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.jsonrpc.base.JsonrpcRequest; import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; +import io.openems.common.session.Language; import io.openems.common.utils.JsonUtils; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppInstance; @@ -92,7 +93,7 @@ public JsonObject getParams() { public static class Response extends JsonrpcResponseSuccess { private static JsonArray createAppsArray(List availableApps, - List instantiatedApps) { + List instantiatedApps, Language language) { var result = JsonUtils.buildJsonArray(); for (var app : availableApps) { // TODO don't show integrated systems for normal users @@ -108,13 +109,13 @@ private static JsonArray createAppsArray(List availableApps, } var categorys = JsonUtils.buildJsonArray().build(); for (var cat : app.getCategorys()) { - categorys.add(cat.toJsonObject()); + categorys.add(cat.toJsonObject(language)); } result.add(JsonUtils.buildJsonObject() // .add("categorys", categorys) // .addProperty("cardinality", app.getCardinality().name()) // .addProperty("appId", app.getAppId()) // - .addProperty("name", app.getName()) // + .addProperty("name", app.getName(language)) // .addProperty("image", app.getImage()) // .add("status", app.getValidator().toJsonObject()) // .add("instanceIds", instanceIds.build()) // @@ -125,9 +126,10 @@ private static JsonArray createAppsArray(List availableApps, private final JsonArray apps; - public Response(UUID id, List availableApps, List instantiatedApps) { + public Response(UUID id, List availableApps, List instantiatedApps, + Language language) { super(id); - this.apps = createAppsArray(availableApps, instantiatedApps); + this.apps = createAppsArray(availableApps, instantiatedApps, language); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties new file mode 100644 index 00000000000..675272bb513 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties @@ -0,0 +1,147 @@ +# Categories +integratedSystems = Integrierte Systeme +timeOfUseTariff = Zeitvariable Stromtarife +evcs = E-Mobilität +heat = Wärme +loadControl = Laststeuerung +hardware = Hardware +pvInverter = PV-Wechselrichter +pvSelfConsumption = PV-Eigenverbrauch +meter = Zähler +api = Schnittstellen + +# Global +ipAddress = IP-Adresse +port = Port +modbusUnitId = Modbus Unit-ID +modbusUnitId.description = Die Unit-ID von den Modbus Gerät. +germany = Deutschland +austria = Österreich +switzerland = Schweiz +username = Benutzername +password = Passwort + +# Api +App.Api.apiTimeout.label = Api-Timeout +App.Api.apiTimeout.description = Legt die Zeitüberschreitung in Sekunden für Aktualisierungen in den von dieser Api eingestellten Kanälen fest. + +App.Api.ModbusTcp.ReadOnly.Name = Modbus/TCP lesend + +App.Api.ModbusTcp.ReadWrite.Name = Modbus/TCP Schreibzugriff +App.Api.ModbusTcp.ReadWrite.componentIds.label = Component-IDs +App.Api.ModbusTcp.ReadWrite.componentIds.description = Komponenten, die über die Schnittstelle verfügbar gemacht werden sollen. + +App.Api.Mqtt.Name = MQTT-Api lesend +App.Api.Mqtt.Username.description = Benutzername für die Authentifizierung beim MQTT-Broker. +App.Api.Mqtt.Password.description = Passwort für die Authentifizierung beim MQTT-Broker. +App.Api.Mqtt.EdgeId.label = Client-ID +App.Api.Mqtt.EdgeId.description = Client-ID für die Authentifizierung beim MQTT-Broker. +App.Api.Mqtt.Uri.description = Die Verbindungs Uri zum MQTT-Broker. + +App.Api.RestJson.ReadOnly.Name = REST/JSON lesend +App.Api.RestJson.ReadWrite.Name = REST/JSON Schreibzugriff + +# Evcs +App.Evcs.Cluster.Name = Multiladepunkt-Management +App.Evcs.Cluster.evcsIds.description = IDs von Ladestationen. + +App.Evcs.HardyBarth.Name = eCharge Hardy Barth Ladestation +App.Evcs.HardyBarth.Ip.description = Die IP-Adresse der Ladestation. Wenn die Ladestation zwei Anschlüsse hat, hat der zweite/slave Anschluss die IP 192.168.25.31. + +App.Evcs.IesKeywatt.Name = IES Keywatt Ladestation +App.Evcs.IesKeywatt.chargepoint.label = OCPP Zapfsäulen-Kennung +App.Evcs.IesKeywatt.chargepoint.description = Die OCPP-Kennung der Ladestation. +App.Evcs.IesKeywatt.connector.label = OCPP Stecker-Kennung +App.Evcs.IesKeywatt.connector.description = Die Anschlusskennung der Stromzapfsäule (z. B. wenn es zwei Anschlüsse gibt, hat die Stromzapfsäule zwei Kennungen 1 und 2). + +App.Evcs.Keba.Name = KEBA Ladestation +App.Evcs.Keba.Ip.description = Die IP-Adresse der Ladestation. + +# Hardware +App.Hardware.KMtronic8Channel.Name = FEMS Relais 8-Kanal +App.Hardware.KMtronic8Channel.Ip.description = Die IP-Adresse des Relais. + +# Heat +App.Heat.CHP.Name = Blockheizkraftwerk (BHKW) +App.Heat.HeatingElement.Name = Heizstab +App.Heat.HeatingElement.outputChannelPhaseL1.label = Ausgangskanal Phase L1 +App.Heat.HeatingElement.outputChannelPhaseL1.description = Kanaladresse des digitalen Ausgangs für Phase L1. +App.Heat.HeatingElement.outputChannelPhaseL2.label = Ausgangskanal Phase L2 +App.Heat.HeatingElement.outputChannelPhaseL2.description = Kanaladresse des digitalen Ausgangs für Phase L2. +App.Heat.HeatingElement.outputChannelPhaseL3.label = Ausgangskanal Phase L3 +App.Heat.HeatingElement.outputChannelPhaseL3.description = Kanaladresse des digitalen Ausgangs für Phase L3. +App.Heat.HeatPump.Name = "SG-Ready" Wärmepumpe +App.Heat.HeatPump.outputChannel1.label = Ausgangskanal 1 +App.Heat.HeatPump.outputChannel1.description = Kanaladresse des digitalen Ausgangs für Eingang 1. +App.Heat.HeatPump.outputChannel2.label = Ausgangskanal 2 +App.Heat.HeatPump.outputChannel2.description = Kanaladresse des digitalen Ausgangs für Eingang 2. + +# Load control +App.LoadControl.ThresholdControl.Name = Schwellwertsteuerung +App.LoadControl.ThresholdControl.outputChannels.label = Ausgangskanal Adressen +App.LoadControl.ThresholdControl.outputChannels.description = Kanaladresse der digitalen Ausgänge, die geschaltet werden sollen. +App.LoadControl.ManualRelayControl.Name = Manuelle Relaissteuerung +App.LoadControl.ManualRelayControl.outputChannel.label = Ausgangskanal Adressen +App.LoadControl.ManualRelayControl.outputChannel.description = Kanaladresse des digitalen Ausgangs, der geschaltet werden soll. + +# Integrated System +App.FENECON.Home.Name = FENECON Home +App.FENECON.Home.safetyCountry.label = Batterie-Wechselrichter Ländereinstellung +App.FENECON.Home.feedInLimit.label = Begrenzung der Einspeisung [W] +App.FENECON.Home.feedInSettings.label = Einspeise-Einstellungen +App.FENECON.Home.hasAcMeterSocomec.label = Hat AC-Zähler (SOCOMEC) +App.FENECON.Home.hasDcPV1.label = Hat DC-PV 1 (MPPT 1) +App.FENECON.Home.hasDcPV2.label = Hat DC-PV 2 (MPPT 2) +App.FENECON.Home.emergencyPowerSupply.label = Aktivieren der Notstromversorgung +App.FENECON.Home.emergencyPowerEnergy.label = Aktivieren der Notfallreserve Energie +App.FENECON.Home.reserveEnergy.label = Notfallreserve Energie (State-of-Charge) + +App.FENECON.Home.modbus0.alias = Kommunikation mit der Batterie +App.FENECON.Home.modbus1.alias = Kommunikation mit dem Batterie-Wechselrichter +App.FENECON.Home.meter0.alias = Netzzähler +App.FENECON.Home.io0.alias = Relaisboard +App.FENECON.Home.battery0.alias = Batterie +App.FENECON.Home.batteryInverter0.alias = Batterie-Wechselrichter +App.FENECON.Home.ess0.alias = Speichersystem +App.FENECON.Home.predictor0.alias = Prognose +App.FENECON.Home.ctrlEssSurplusFeedToGrid0.alias = Überschusseinspeisung +App.FENECON.Home.ctrlBalancing0.alias = Eigenverbrauchsoptimierung +App.FENECON.Home.meter1.alias = AC Zähler +App.FENECON.Home.meter2.alias = Notstromverbraucher +App.FENECON.Home.ctrlEmergencyCapacityReserve0.alias = Ansteuerung der Notstromreserve + +# Meter +App.Meter.mountType.label = Einbindungs Typ +App.Meter.ip.description = Die IP-Adresse des Messgeräts. +App.Meter.production = Erzeugung +App.Meter.gridMeter = Netzzähler +App.Meter.consumtionMeter = Verbrauchszähler + +App.Meter.CarloGavazzi.Name = CARLO GAVAZZI Zähler +App.Meter.Janitza.Name = Janitza Zähler +App.Meter.Janitza.productModel = Produkt Model +App.Meter.Socomec.Name = SOCOMEC Zähler + +# PV-Inverter +App.PvInverter.ip.description = Die IP-Adresse des PV-Wechselrichters. +App.PvInverter.port.description = Der Port des PV-Wechselrichters. + +App.PvInverter.Kaco.Name = KACO PV-Wechselrichter +App.PvInverter.Kostal.Name = KOSTAL PV-Wechselrichter +App.PvInverter.Sma.Name = SMA PV-Wechselrichter +App.PvInverter.Sma.modbusUnitId.description = Die Unit-ID des Modbus-Geräts. Beachten Sie, dass Sie laut Handbuch den Wert, den Sie in der SMA Webschnittstelle konfiguriert haben, um '123' ergänzen müssen. +App.PvInverter.SolarEdge.Name = SolarEdge PV-Wechselrichter + +# Time of use Tarif +App.TimeOfUseTariff.Awattar.Name = Awattar HOURLY +App.TimeOfUseTariff.Stromdao.Name = Stromdao Corrently +App.TimeOfUseTariff.Stromdao.zipCode.label = Postleitzahl +App.TimeOfUseTariff.Stromdao.zipCode.description = Deutsche Postleitzahl des Ortes. +App.TimeOfUseTariff.Tibber.Name = Tibber +App.TimeOfUseTariff.Tibber.accessToken.label = Zugangstoken +App.TimeOfUseTariff.Tibber.accessToken.description = Zugangstoken für den Tibber Stromtarif. + +# PvSelfConsumption +App.PvSelfConsumption.GridOptimizedCharge.Name = Netzdienliche Beladung +App.PvSelfConsumption.GridOptimizedCharge.maximumSellToGridPower.label = Maximal zulässiger Stromverkauf an das Netz +App.PvSelfConsumption.GridOptimizedCharge.maximumSellToGridPower.description = Die Zielgrenze für den Verkauf von Strom an das Netz. \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties new file mode 100644 index 00000000000..7129a43ea62 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties @@ -0,0 +1,149 @@ +# Categories +integratedSystems = Integrated Systems +timeOfUseTariff = Time +evcs = e-mobility +heat = Heat +loadControl = Load control +hardware = Hardware +pvInverter = PV-Inverter +pvSelfConsumption = PV self-consumption +meter = Meter +api = API's + +# Global +ipAddress = IP-Address +port = Port +modbusUnitId = Modbus Unit-ID +modbusUnitId.description = The Unit-ID of the Modbus device. +germany = Germany +austria = Austria +switzerland = Switzerland +username = Username +password = Password + +# Api +App.Api.apiTimeout.label = Api-Timeout +App.Api.apiTimeout.description = Sets the timeout in seconds for updates on Channels set by this Api. + +App.Api.ModbusTcp.ReadOnly.Name = Modbus/TCP reading + +App.Api.ModbusTcp.ReadWrite.Name = Modbus/TCP Write Access +App.Api.ModbusTcp.ReadWrite.componentIds.label = Component-IDs +App.Api.ModbusTcp.ReadWrite.componentIds.description = Components that should be made available via Modbus. + +App.Api.Mqtt.Name = MQTT-Api reading +App.Api.Mqtt.Username.description = Username for authentication at MQTT broker. +App.Api.Mqtt.Password.description = Password for authentication at MQTT broker. +App.Api.Mqtt.EdgeId.label = Client-ID +App.Api.Mqtt.EdgeId.description = Client-ID for authentication at MQTT broker. +App.Api.Mqtt.Uri.description = The connection Uri to MQTT broker. + +App.Api.RestJson.ReadOnly.Name = REST/JSON reading +App.Api.RestJson.ReadWrite.Name = REST/JSON Write Access + +# Evcs +App.Evcs.Cluster.Name = Multi-charge point management +App.Evcs.Cluster.evcsIds.description = IDs of EVCS devices. + +App.Evcs.HardyBarth.Name = eCharge Hardy Barth Charging Station +App.Evcs.HardyBarth.Ip.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. + +App.Evcs.IesKeywatt.Name = IES Keywatt Charging Station +App.Evcs.IesKeywatt.chargepoint.label = OCPP chargepoint identifier +App.Evcs.IesKeywatt.chargepoint.description = The OCPP identifier of the charging station. +App.Evcs.IesKeywatt.connector.label = OCPP connector identifier +App.Evcs.IesKeywatt.connector.description = The connector id of the chargepoint (e.g. if there are two connectors, then the evcs has two id's 1 and 2). + +App.Evcs.Keba.Name = KEBA Charging Station +App.Evcs.Keba.Ip.description = The IP address of the charging station. + +# Hardware +App.Hardware.KMtronic8Channel.Name = FEMS Relay 8-Channel +App.Hardware.KMtronic8Channel.Ip.description = The IP address of the Relay. + +# Heat +App.Heat.CHP.Name = Combined heat and power plant (CHP) +App.Heat.CHP.outputChannel.label = Output Channel Address +App.Heat.CHP.outputChannel.description = Channel address of the Digital Output that should be switched. +App.Heat.HeatingElement.Name = Heating rod +App.Heat.HeatingElement.outputChannelPhaseL1.label = Output Channel Phase L1 +App.Heat.HeatingElement.outputChannelPhaseL1.description = Channel address of the Digital Output for Phase L1. +App.Heat.HeatingElement.outputChannelPhaseL2.label = Output Channel Phase L2 +App.Heat.HeatingElement.outputChannelPhaseL2.description = Channel address of the Digital Output for Phase L2. +App.Heat.HeatingElement.outputChannelPhaseL3.label = Output Channel Phase L3 +App.Heat.HeatingElement.outputChannelPhaseL3.description = Channel address of the Digital Output for Phase L3. +App.Heat.HeatPump.Name = "SG-Ready" Heat Pump +App.Heat.HeatPump.outputChannel1.label = Output Channel 1 +App.Heat.HeatPump.outputChannel1.description = Channel address of the Digital Output for input 1. +App.Heat.HeatPump.outputChannel2.label = Output Channel 2 +App.Heat.HeatPump.outputChannel2.description = Channel address of the Digital Output for input 2. + +# Load control +App.LoadControl.ThresholdControl.Name = Threshold Control +App.LoadControl.ThresholdControl.outputChannels.label = Output Channels +App.LoadControl.ThresholdControl.outputChannels.description = Channel addresses of the Digital Outputs that should be switched. +App.LoadControl.ManualRelayControl.Name = Manual Relay Control +App.LoadControl.ManualRelayControl.outputChannel.label = Output Channel +App.LoadControl.ManualRelayControl.outputChannel.description = Channel address of the Digital Output that should be switched. + +# Integrated System +App.FENECON.Home.Name = FENECON Home +App.FENECON.Home.safetyCountry.label = Battery-Inverter Safety Country +App.FENECON.Home.feedInLimit.label = Feed-In limitation [W] +App.FENECON.Home.feedInSettings.label = Feed-In Settings +App.FENECON.Home.hasAcMeterSocomec.label = Has AC meter (SOCOMEC) +App.FENECON.Home.hasDcPV1.label = Has DC-PV 1 (MPPT 1) +App.FENECON.Home.hasDcPV2.label = Has DC-PV 2 (MPPT 2) +App.FENECON.Home.emergencyPowerSupply.label = Activate Emergency power supply +App.FENECON.Home.emergencyPowerEnergy.label = Activate Emergency Reserve Energy +App.FENECON.Home.reserveEnergy.label = Emergency Reserve Energy (State-of-Charge) + +App.FENECON.Home.modbus0.alias = Communication with the battery +App.FENECON.Home.modbus1.alias = Communication with the battery inverter +App.FENECON.Home.meter0.alias = Grid meter +App.FENECON.Home.io0.alias = Relay +App.FENECON.Home.battery0.alias = battery +App.FENECON.Home.batteryInverter0.alias = battery inverter +App.FENECON.Home.ess0.alias = Storage system +App.FENECON.Home.predictor0.alias = Forecast +App.FENECON.Home.ctrlEssSurplusFeedToGrid0.alias = Excess feed-in +App.FENECON.Home.ctrlBalancing0.alias = Self-consumption optimization +App.FENECON.Home.meter1.alias = AC Meter +App.FENECON.Home.meter2.alias = Emergency power consumers +App.FENECON.Home.ctrlEmergencyCapacityReserve0.alias = Control of the emergency power reserve + +# Meter +App.Meter.mountType.label = Mount Type +App.Meter.ip.description = The IP address of the Meter. +App.Meter.production = Production +App.Meter.gridMeter = Grid-Meter +App.Meter.consumtionMeter = Consumption-Meter + +App.Meter.CarloGavazzi.Name = CARLO GAVAZZI Counter +App.Meter.Janitza.Name = Janitza Counter +App.Meter.Janitza.productModel = Product Model +App.Meter.Socomec.Name = SOCOMEC Counter + +# PV-Inverter +App.PvInverter.ip.description = The IP address of the PV-Inverter. +App.PvInverter.port.description = The port of the PV-Inverter. + +App.PvInverter.Kaco.Name = KACO PV-Inverter +App.PvInverter.Kostal.Name = KOSTAL PV-Inverter +App.PvInverter.Sma.Name = SMA PV Inverter +App.PvInverter.Sma.modbusUnitId.description = The Unit-ID of the Modbus device. Be aware, that according to the manual you need to add '123' to the value that you configured in the SMA web interface. +App.PvInverter.SolarEdge.Name = SolarEdge PV-Inverter + +# Time of use Tarif +App.TimeOfUseTariff.Awattar.Name = Awattar HOURLY +App.TimeOfUseTariff.Stromdao.Name = Stromdao Corrently +App.TimeOfUseTariff.Stromdao.zipCode.label = ZIP Code +App.TimeOfUseTariff.Stromdao.zipCode.description = German ZIP Code of the location. +App.TimeOfUseTariff.Tibber.Name = Tibber +App.TimeOfUseTariff.Tibber.accessToken.label = Access token +App.TimeOfUseTariff.Tibber.accessToken.description = Access token for the Tibber API. + +# PvSelfConsumption +App.PvSelfConsumption.GridOptimizedCharge.Name = Grid optimized charge +App.PvSelfConsumption.GridOptimizedCharge.maximumSellToGridPower.label = Maximum allowed Sell-To-Grid power +App.PvSelfConsumption.GridOptimizedCharge.maximumSellToGridPower.description = The target limit for sell-to-grid power. \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java index b7ce09ad056..f50282a3ea6 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -76,16 +77,21 @@ public boolean check() { private OpenemsAppCategory getMatchingCategorie(AppManagerImpl appManager, List instantiatedApps) { for (var openemsAppInstance : instantiatedApps) { - var app = appManager.findAppById(openemsAppInstance.appId); - if (app.getCardinality() != OpenemsAppCardinality.SINGLE_IN_CATEGORY) { - continue; - } - for (var cat : app.getCategorys()) { - for (var catOther : this.openemsApp.getCategorys()) { - if (cat == catOther) { - return cat; + try { + var app = appManager.findAppById(openemsAppInstance.appId); + if (app.getCardinality() != OpenemsAppCardinality.SINGLE_IN_CATEGORY) { + continue; + } + for (var cat : app.getCategorys()) { + for (var catOther : this.openemsApp.getCategorys()) { + if (cat == catOther) { + return cat; + } } } + } catch (NoSuchElementException e) { + // if app instance is reworked there may be no app for the instance + continue; } } return null; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java index 427ca4b66f0..3cb8d8a5d36 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java @@ -2,16 +2,15 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; import com.google.gson.JsonObject; @@ -25,10 +24,10 @@ public class Validator { - private static final Logger LOG = Logger.getLogger(Validator.class.getName()); + private static final Logger LOG = LoggerFactory.getLogger(Validator.class); - private final Map> compatibleCheckableNames; - private final Map> installableCheckableNames; + private final List compatibleCheckableConfigs; + private final List installableCheckableConfigs; private ThrowingBiFunction> compatibleCheckableNames; - private Map> installableCheckableNames; + private List compatibleCheckableConfigs; + private List installableCheckableConfigs; protected Builder() { } - public Builder setCompatibleCheckableNames(Map> compatibleCheckableNames) { - this.compatibleCheckableNames = compatibleCheckableNames; + public Builder setCompatibleCheckableConfigs(List compatibleCheckableConfigs) { + this.compatibleCheckableConfigs = compatibleCheckableConfigs; return this; } - public Builder setInstallableCheckableNames(Map> installableCheckableNames) { - this.installableCheckableNames = installableCheckableNames; + public Builder setInstallableCheckableConfigs(List installableCheckableConfigs) { + this.installableCheckableConfigs = installableCheckableConfigs; return this; } public Validator build() { - return new Validator(this.compatibleCheckableNames, this.installableCheckableNames); + return new Validator(this.compatibleCheckableConfigs, this.installableCheckableConfigs); + } + + } + + // TODO convert to record in java 17. + public static final class CheckableConfig { + + private final String checkableComponentName; + private final boolean invertResult; + private final Map properties; + + public CheckableConfig(String checkableComponentName, boolean invertResult, Map properties) { + this.checkableComponentName = checkableComponentName; + this.invertResult = invertResult; + this.properties = properties; + } + + public CheckableConfig(String checkableComponentName, Map properties) { + this(checkableComponentName, false, properties); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (this.getClass() != obj.getClass()) { + return false; + } + var other = (CheckableConfig) obj; + return java.util.Objects.equals(this.checkableComponentName, other.checkableComponentName) + && this.invertResult == other.invertResult; } } @@ -95,15 +129,14 @@ public static final Builder create() { return new Builder(); } - protected Validator(Map> compatibleCheckableNames, - Map> installableCheckableNames) { - this.compatibleCheckableNames = compatibleCheckableNames != null // - ? compatibleCheckableNames - : new HashMap<>(); - this.installableCheckableNames = installableCheckableNames != null // - ? installableCheckableNames - : new HashMap<>(); - + protected Validator(List compatibleCheckableConfigs, + List installableCheckableConfigs) { + this.compatibleCheckableConfigs = compatibleCheckableConfigs != null // + ? compatibleCheckableConfigs + : Lists.newArrayList(); + this.installableCheckableConfigs = installableCheckableConfigs != null // + ? installableCheckableConfigs + : Lists.newArrayList(); } /** @@ -112,48 +145,54 @@ protected Validator(Map> compatibleCheckableNames, * @return the error messages */ public List getErrorCompatibleMessages() { - return getErrorMessages(this.compatibleCheckableNames, false); + return getErrorMessages(this.compatibleCheckableConfigs, false); } /** * Gets the error messages for the given {@link Checkable}. * - * @param checkableNames the {@link Checkable} to be checked. - * @param returnImmediate after the first checkable who returns false + * @param checkableConfigs the {@link Checkable}s to be checked. + * @param returnImmediate after the first checkable who returns false * @return a list of errors */ - private static List getErrorMessages(Map> checkableNames, boolean returnImmediate) { - if (checkableNames == null || checkableNames.isEmpty()) { + private static List getErrorMessages(List checkableConfigs, boolean returnImmediate) { + if (checkableConfigs.isEmpty()) { return new ArrayList<>(); } - var errorMessages = new ArrayList(checkableNames.size()); + var errorMessages = new ArrayList(checkableConfigs.size()); var bundleContext = FrameworkUtil.getBundle(Checkable.class).getBundleContext(); // build filter var filterBuilder = new StringBuilder(); - if (checkableNames.size() > 1) { + if (checkableConfigs.size() > 1) { filterBuilder.append("(|"); } - checkableNames.entrySet().forEach(t -> filterBuilder.append("(component.name=" + t.getKey() + ")")); - if (checkableNames.size() > 1) { + checkableConfigs.forEach(t -> filterBuilder.append("(component.name=" + t.checkableComponentName + ")")); + if (checkableConfigs.size() > 1) { filterBuilder.append(")"); } try { // get all service references Collection> serviceReferences = bundleContext .getServiceReferences(Checkable.class, filterBuilder.toString()); - var noneExistingCheckables = Lists.newArrayList(); - checkableNames.forEach((t, u) -> noneExistingCheckables.add(t)); + var noneExistingCheckables = Lists.newArrayList(); + checkableConfigs.forEach(c -> noneExistingCheckables.add(c)); var isReturnedImmediate = false; for (var reference : serviceReferences) { var componentName = (String) reference.getProperty(OpenemsConstants.PROPERTY_OSGI_COMPONENT_NAME); - var properties = checkableNames.get(componentName); + var checkableConfig = checkableConfigs.stream() + .filter(c -> c.checkableComponentName.equals(componentName)).findFirst().orElse(null); var checkable = bundleContext.getService(reference); - if (properties != null) { - checkable.setProperties(properties); + if (checkableConfig.properties != null) { + checkable.setProperties(checkableConfig.properties); } - noneExistingCheckables.remove(componentName); - if (!checkable.check()) { - errorMessages.add(checkable.getErrorMessage()); + noneExistingCheckables.removeIf(c -> c.equals(checkableConfig)); + var result = checkable.check(); + if (result == checkableConfig.invertResult) { + var errorMessage = checkable.getErrorMessage(); + if (checkableConfig.invertResult) { + errorMessage = "Invert[" + errorMessage + "]"; + } + errorMessages.add(errorMessage); if (returnImmediate) { isReturnedImmediate = true; break; @@ -162,8 +201,8 @@ private static List getErrorMessages(Map> checkab } if (!noneExistingCheckables.isEmpty() && !isReturnedImmediate) { - LOG.log(Level.WARNING, "Checkables[" + noneExistingCheckables.stream().collect(Collectors.joining(";")) - + "] are not found!"); + LOG.warn("Checkables[" + noneExistingCheckables.stream().map(c -> c.checkableComponentName) + .collect(Collectors.joining(";")) + "] are not found!"); } // free all service references @@ -183,7 +222,7 @@ private static List getErrorMessages(Map> checkab * @return the error messages */ public List getErrorInstallableMessages() { - return getErrorMessages(this.installableCheckableNames, false); + return getErrorMessages(this.installableCheckableConfigs, false); } /** @@ -192,10 +231,10 @@ public List getErrorInstallableMessages() { * @return the Status */ public OpenemsAppStatus getStatus() { - if (!getErrorMessages(this.compatibleCheckableNames, true).isEmpty()) { + if (!getErrorMessages(this.compatibleCheckableConfigs, true).isEmpty()) { return OpenemsAppStatus.INCOMPATIBLE; } - if (!getErrorMessages(this.installableCheckableNames, true).isEmpty()) { + if (!getErrorMessages(this.installableCheckableConfigs, true).isEmpty()) { return OpenemsAppStatus.COMPATIBLE; } return OpenemsAppStatus.INSTALLABLE; @@ -243,18 +282,18 @@ public void validateConfiguration(ConfigurationTarget target, JsonObject propert if (checkables == null) { return; } - var errors = getErrorMessages(this.compatibleCheckableNames, false); + var errors = getErrorMessages(this.compatibleCheckableConfigs, false); if (!errors.isEmpty()) { throw new OpenemsException(errors.stream().collect(Collectors.joining(";"))); } } - public Map> getCompatibleCheckableNames() { - return this.compatibleCheckableNames; + public List getCompatibleCheckableConfigs() { + return this.compatibleCheckableConfigs; } - public Map> getInstallableCheckableNames() { - return this.installableCheckableNames; + public List getInstallableCheckableConfigs() { + return this.installableCheckableConfigs; } } diff --git a/ui/src/app/edge/settings/app/index.component.html b/ui/src/app/edge/settings/app/index.component.html index 83e7bdfead4..6afbfa049bc 100644 --- a/ui/src/app/edge/settings/app/index.component.html +++ b/ui/src/app/edge/settings/app/index.component.html @@ -23,8 +23,7 @@ - Der App-Manager befindet sich aktuell in einer ersten Testversion. Falls nicht alle - Apps angezeigt werden, muss evtl. die FEMS Version geupdatet werden. + Edge.Config.App.header diff --git a/ui/src/app/edge/settings/app/index.component.ts b/ui/src/app/edge/settings/app/index.component.ts index 4650b5deb03..7a866394ce6 100644 --- a/ui/src/app/edge/settings/app/index.component.ts +++ b/ui/src/app/edge/settings/app/index.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; import { ComponentJsonApiRequest } from 'src/app/shared/jsonrpc/request/componentJsonApiRequest'; import { environment } from 'src/environments'; import { Service, Websocket } from '../../../shared/shared'; @@ -16,10 +17,10 @@ export class IndexComponent { public apps: GetApps.App[] = []; - public installedApps: AppList = { name: "Installiert", appCategories: [] }; - public availableApps: AppList = { name: "Verfügbar", appCategories: [] }; + public installedApps: AppList = { name: this.translate.instant('Edge.Config.App.installed'), appCategories: [] }; + public availableApps: AppList = { name: this.translate.instant('Edge.Config.App.available'), appCategories: [] }; // TODO incompatible apps should not be shown in the future - public incompatibleApps: AppList = { name: "Incompatible", appCategories: [] }; + public incompatibleApps: AppList = { name: this.translate.instant('Edge.Config.App.incompatible'), appCategories: [] }; public appLists: AppList[] = [this.installedApps, this.availableApps, this.incompatibleApps]; @@ -29,6 +30,7 @@ export class IndexComponent { private route: ActivatedRoute, private service: Service, private websocket: Websocket, + private translate: TranslateService, ) { } diff --git a/ui/src/app/edge/settings/app/install.component.ts b/ui/src/app/edge/settings/app/install.component.ts index c6544f5b5e8..80a29d9b4ca 100644 --- a/ui/src/app/edge/settings/app/install.component.ts +++ b/ui/src/app/edge/settings/app/install.component.ts @@ -84,11 +84,11 @@ export class InstallAppComponent implements OnInit { }) })).then(response => { this.form.markAsPristine(); - this.isInstalling = false this.service.toast("Successfully installed App", 'success'); }).catch(reason => { - this.isInstalling = false this.service.toast("Error installing App:" + reason.error.message, 'danger'); + }).finally(() => { + this.isInstalling = false }); } diff --git a/ui/src/app/edge/settings/app/single.component.html b/ui/src/app/edge/settings/app/single.component.html index 5ea9f902076..15010836378 100644 --- a/ui/src/app/edge/settings/app/single.component.html +++ b/ui/src/app/edge/settings/app/single.component.html @@ -18,16 +18,16 @@
- App kaufen + Edge.Config.App.buyApp
App bearbeiten + [routerLink]="['../../update', app.appId]" translate>Edge.Config.App.modifyApp
App anlegen + [routerLink]="['../../install', app.appId]" translate>Edge.Config.App.createApp
diff --git a/ui/src/app/edge/settings/app/update.component.html b/ui/src/app/edge/settings/app/update.component.html index ef6200a98b9..1a722b5ce2c 100644 --- a/ui/src/app/edge/settings/app/update.component.html +++ b/ui/src/app/edge/settings/app/update.component.html @@ -14,12 +14,12 @@ - App aktualisieren + [disabled]="(!((!varinstance.form.pristine) && (varinstance.form.valid))) || varinstance.isDeleting || varinstance.isUpdateting" + translate> Edge.Config.App.updateApp - App entfernen + [disabled]="varinstance.isDeleting || varinstance.isUpdateting" translate> + Edge.Config.App.deleteApp diff --git a/ui/src/app/shared/translate/cz.ts b/ui/src/app/shared/translate/cz.ts index e586482f692..945f5d65aff 100644 --- a/ui/src/app/shared/translate/cz.ts +++ b/ui/src/app/shared/translate/cz.ts @@ -456,7 +456,18 @@ export const TRANSLATION = { temperatures: "Teplota buňky", insulation: "Izolace", } - } + }, + App: { + header: 'Správce aplikací je v současné době v první testovací verzi. Pokud se nezobrazují všechny aplikace, je možné, že bude třeba aktualizovat verzi FEMS.', + installed: 'Nainstalováno', + available: 'Dostupné na', + incompatible: 'Nekompatibilní', + buyApp: 'koupit aplikaci', + modifyApp: 'upravit aplikaci', + createApp: 'vytvořit aplikaci', + deleteApp: 'odstranit aplikaci', + updateApp: 'aktualizace aplikace', + }, }, About: { build: "Aktuální verze", diff --git a/ui/src/app/shared/translate/de.ts b/ui/src/app/shared/translate/de.ts index 5bc3117e918..36f905adfb9 100644 --- a/ui/src/app/shared/translate/de.ts +++ b/ui/src/app/shared/translate/de.ts @@ -472,6 +472,17 @@ export const TRANSLATION = { delay: 'Verzögerung [min]', save: 'Speichern', }, + App: { + header: 'Der App-Manager befindet sich aktuell in einer ersten Testversion. Falls nicht alle Apps angezeigt werden, muss evtl. die FEMS Version geupdatet werden.', + installed: 'Installiert', + available: 'Verfügbar', + incompatible: 'Incompatible', + buyApp: 'App kaufen', + modifyApp: 'App bearbeiten', + createApp: 'App anlegen', + deleteApp: 'App entfernen', + updateApp: 'App aktualisieren', + }, } }, About: { diff --git a/ui/src/app/shared/translate/en.ts b/ui/src/app/shared/translate/en.ts index 47cd70b605e..34da873a172 100644 --- a/ui/src/app/shared/translate/en.ts +++ b/ui/src/app/shared/translate/en.ts @@ -471,7 +471,18 @@ export const TRANSLATION = { activate: 'Activate', delay: 'Delay [min]', save: 'Save', - } + }, + App: { + header: 'The App Manager is currently in a first test version. If not all apps are displayed, the FEMS version may need to be updated.', + installed: 'Installed', + available: 'Available', + incompatible: 'Incompatible', + buyApp: 'Buy app', + modifyApp: 'Modify app', + createApp: 'Create app', + deleteApp: 'Delete app', + updateApp: 'Update app', + }, } }, About: { diff --git a/ui/src/app/shared/translate/es.ts b/ui/src/app/shared/translate/es.ts index 66b55145b27..5e1f4e9a212 100644 --- a/ui/src/app/shared/translate/es.ts +++ b/ui/src/app/shared/translate/es.ts @@ -440,7 +440,18 @@ export const TRANSLATION = { activate: 'Activar', delay: 'Retraso [min]', save: 'Guardar', - } + }, + App: { + header: 'El App Manager se encuentra actualmente en una primera versión de prueba. Si no se muestran todas las aplicaciones, es posible que haya que actualizar la versión de FEMS.', + installed: 'Instalado', + available: 'Disponible', + incompatible: 'Incompatible', + buyApp: 'Comprar aplicación', + modifyApp: 'Modificar la aplicación', + createApp: 'Crear aplicación', + deleteApp: 'Eliminar la aplicación', + updateApp: 'Actualizar la aplicación', + }, } }, About: { diff --git a/ui/src/app/shared/translate/fr.ts b/ui/src/app/shared/translate/fr.ts index 8417b15dade..4289f80c14b 100644 --- a/ui/src/app/shared/translate/fr.ts +++ b/ui/src/app/shared/translate/fr.ts @@ -442,7 +442,18 @@ export const TRANSLATION = { activate: 'Activer', delay: 'Retard [min]', save: 'Enregistrer', - } + }, + App: { + header: 'L\'App Manager est actuellement dans une première version de test. Si toutes les applications ne sont pas affichées, il est possible que la version FEMS doive être mise à jour.', + installed: 'Installé', + available: 'Disponible sur', + incompatible: 'Incompatibilité', + buyApp: 'Acheter l\'application', + modifyApp: 'Modifier l\'application', + createApp: 'Créer une application', + deleteApp: 'Supprimer l\'application', + updateApp: 'Mise à jour de l\'application', + }, } }, About: { diff --git a/ui/src/app/shared/translate/nl.ts b/ui/src/app/shared/translate/nl.ts index 4524daedcab..e05b05524ce 100644 --- a/ui/src/app/shared/translate/nl.ts +++ b/ui/src/app/shared/translate/nl.ts @@ -439,7 +439,18 @@ export const TRANSLATION = { activate: 'Activeer', delay: 'Vertraging [min]', save: 'Save', - } + }, + App: { + header: 'De App Manager bevindt zich momenteel in een eerste testversie. Als niet alle apps worden weergegeven, moet de FEMS-versie mogelijk worden bijgewerkt.', + installed: 'Geïnstalleerd', + available: 'Beschikbaar', + incompatible: 'Onverenigbaar', + buyApp: 'App kopen', + modifyApp: 'App wijzigen', + createApp: 'App maken', + deleteApp: 'App verwijderen', + updateApp: 'App bijwerken', + }, } }, About: { From 2819cc276326c5385eb4432bf82d2936d3f57d0c Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 10:55:34 +0200 Subject: [PATCH 02/30] added JsonArrayCollector --- .../io/openems/common/utils/JsonUtils.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/io.openems.common/src/io/openems/common/utils/JsonUtils.java b/io.openems.common/src/io/openems/common/utils/JsonUtils.java index 208e6542526..2cac52b61eb 100644 --- a/io.openems.common/src/io/openems/common/utils/JsonUtils.java +++ b/io.openems.common/src/io/openems/common/utils/JsonUtils.java @@ -5,12 +5,19 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Optional; +import java.util.Set; import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; import java.util.function.Consumer; +import java.util.stream.Collector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import com.google.common.collect.Sets; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -291,8 +298,44 @@ public JsonObject build() { } + public static class JsonArrayCollector implements Collector { + + @Override + public Set characteristics() { + return Sets.immutableEnumSet(Characteristics.UNORDERED); + } + + @Override + public Supplier supplier() { + return JsonUtils::buildJsonArray; + } + + @Override + public BiConsumer accumulator() { + return JsonUtils.JsonArrayBuilder::add; + } + + @Override + public BinaryOperator combiner() { + return (t, u) -> { + u.build().forEach(j -> t.add(j)); + return t; + }; + } + + @Override + public Function finisher() { + return JsonArrayBuilder::build; + } + + } + private static final Logger LOG = LoggerFactory.getLogger(JsonUtils.class); + public static Collector toJsonArray() { + return new JsonUtils.JsonArrayCollector(); + } + /** * Creates a JsonArray using a Builder. * From 0dc4e0554ae6a0d9546ce3f868b018342e4ddd4e Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 10:59:07 +0200 Subject: [PATCH 03/30] added create and delete of components and subapps --- .../app/integratedsystem/FeneconHome.java | 32 +- .../core/appmanager/AppConfiguration.java | 9 + .../edge/core/appmanager/AppManagerImpl.java | 194 ++++---- .../core/appmanager/OpenemsAppInstance.java | 9 +- .../appmanager/dependency/AggregateTask.java | 21 + .../dependency/AppManagerAppHelper.java | 47 ++ .../dependency/AppManagerAppHelperImpl.java | 440 ++++++++++++++++++ .../dependency/ComponentAggregateTask.java | 204 ++++++++ .../appmanager/dependency/Dependency.java | 33 ++ .../dependency/DependencyConfig.java | 37 ++ .../dependency/DependencyDeclaration.java | 65 +++ .../dependency/ExistingDependencyConfig.java | 26 ++ .../dependency/SchedulerAggregateTask.java | 64 +++ 13 files changed, 1090 insertions(+), 91 deletions(-) create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/Dependency.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java index 4fc9e63bfdc..5ab7fdb6ad0 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java @@ -23,6 +23,7 @@ import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.integratedsystem.FeneconHome.Property; +import io.openems.edge.app.pvselfconsumption.GridOptimizedCharge; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AppAssistant; @@ -35,6 +36,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; /** * Describes a FENECON Home energy storage system. @@ -194,15 +196,15 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { .add("_sum/ConsumptionActivePower") // .build()) // .build()), - new EdgeConfig.Component("ctrlGridOptimizedCharge0", - bundle.getString("App.PvSelfConsumption.GridOptimizedCharge.Name"), - "Controller.Ess.GridOptimizedCharge", JsonUtils.buildJsonObject() // - .addProperty("enabled", true) // - .addProperty("ess.id", essId) // - .addProperty("meter.id", "meter0") // - .addProperty("sellToGridLimitEnabled", true) // - .addProperty("maximumSellToGridPower", maxFeedInPower) // - .build()), +// new EdgeConfig.Component("ctrlGridOptimizedCharge0", +// bundle.getString("App.PvSelfConsumption.GridOptimizedCharge.Name"), +// "Controller.Ess.GridOptimizedCharge", JsonUtils.buildJsonObject() // +// .addProperty("enabled", true) // +// .addProperty("ess.id", essId) // +// .addProperty("meter.id", "meter0") // +// .addProperty("sellToGridLimitEnabled", true) // +// .addProperty("maximumSellToGridPower", maxFeedInPower) // +// .build()), new EdgeConfig.Component("ctrlEssSurplusFeedToGrid0", bundle.getString(this.getAppId() + ".ctrlEssSurplusFeedToGrid0.alias"), "Controller.Ess.Hybrid.Surplus-Feed-To-Grid", JsonUtils.buildJsonObject() // @@ -286,7 +288,17 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { schedulerExecutionOrder.add("ctrlEssSurplusFeedToGrid0"); schedulerExecutionOrder.add("ctrlBalancing0"); - return new AppConfiguration(components, schedulerExecutionOrder); + var dependencies = Lists.newArrayList(new DependencyDeclaration("GRID_OPTIMIZED_CHARGE", // + "App.PvSelfConsumption.GridOptimizedCharge", // + bundle.getString("App.PvSelfConsumption.GridOptimizedCharge.Name"), // + DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING, // + DependencyDeclaration.UpdatePolicy.ALWAYS, // + DependencyDeclaration.DeletePolicy.ALWAYS, // + JsonUtils.buildJsonObject() // + .addProperty(GridOptimizedCharge.Property.MAXIMUM_SELL_TO_GRID_POWER.name(), maxFeedInPower) // + .build())); + + return new AppConfiguration(components, schedulerExecutionOrder, null, dependencies); }; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppConfiguration.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppConfiguration.java index b13a565a2d5..204520a8221 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppConfiguration.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppConfiguration.java @@ -4,6 +4,7 @@ import java.util.List; import io.openems.common.types.EdgeConfig.Component; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; public class AppConfiguration { // the components the app needs @@ -13,6 +14,8 @@ public class AppConfiguration { // the static ips in the Network configuration to access different networks public final List ips; + public final List dependencies; + public AppConfiguration(List components) { this(components, null); } @@ -22,8 +25,14 @@ public AppConfiguration(List components, List schedulerExecut } public AppConfiguration(List components, List schedulerExecutionOrder, List ips) { + this(components, schedulerExecutionOrder, ips, null); + } + + public AppConfiguration(List components, List schedulerExecutionOrder, List ips, + List dependencies) { this.components = components; this.schedulerExecutionOrder = schedulerExecutionOrder != null ? schedulerExecutionOrder : new ArrayList<>(); this.ips = ips != null ? ips : new ArrayList<>(); + this.dependencies = dependencies != null ? dependencies : new ArrayList<>(); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java index 8fd8d32b83d..64eeea56b28 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java @@ -50,6 +50,8 @@ import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.jsonapi.JsonApi; import io.openems.edge.common.user.User; +import io.openems.edge.core.appmanager.dependency.AppManagerAppHelper; +import io.openems.edge.core.appmanager.dependency.Dependency; import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance; import io.openems.edge.core.appmanager.jsonrpc.DeleteAppInstance; import io.openems.edge.core.appmanager.jsonrpc.GetApp; @@ -79,6 +81,9 @@ public class AppManagerImpl extends AbstractOpenemsComponent @Reference protected List availableApps; + @Reference + private AppManagerAppHelper appHelper; + @Reference protected ComponentManager componentManager; @@ -125,11 +130,7 @@ public final List getInstantiatedApps() { * @return formated apps string */ private static String getJsonAppsString(List apps) { - var appsProperty = JsonUtils.buildJsonArray(); - for (var app : apps) { - appsProperty.add(app.toJsonObject()); - } - return JsonUtils.prettyToString(appsProperty.build()); + return JsonUtils.prettyToString(apps.stream().map(t -> t.toJsonObject()).collect(JsonUtils.toJsonArray())); } /** @@ -152,7 +153,18 @@ private static List parseInstantiatedApps(JsonArray apps) th errors.add("App with ID[" + instanceId + "] already exists!"); continue; } - result.add(new OpenemsAppInstance(appId, alias, instanceId, properties)); + List dependecies = null; + if (json.has("dependencies")) { + dependecies = new LinkedList<>(); + var dependecyArray = json.get("dependencies").getAsJsonArray(); + for (int i = 0; i < dependecyArray.size(); i++) { + var dependecyJson = dependecyArray.get(i).getAsJsonObject(); + var dependecy = new Dependency(dependecyJson.get("key").getAsString(), + JsonUtils.getAsUUID(dependecyJson, "instanceId")); + dependecies.add(dependecy); + } + } + result.add(new OpenemsAppInstance(appId, alias, instanceId, properties, dependecies)); } if (!errors.isEmpty()) { throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); @@ -204,7 +216,7 @@ public void configurationEvent(ConfigurationEvent event) { this.worker.configurationEvent(event); } - private void foreachAppConfiguration(Consumer consumer, UUID... excludingInstanceIds) { + public void foreachAppConfiguration(Consumer consumer, UUID... excludingInstanceIds) { for (var appInstance : this.instantiatedApps) { var skipInstance = false; for (var id : excludingInstanceIds) { @@ -305,6 +317,20 @@ public final OpenemsApp findAppById(String id) throws NoSuchElementException { .get(); } + /** + * Finds the app instance with the matching id. + * + * @param uuid the id of the instance + * @returns the instance + * @throws NoSuchElementException if no instance is present + */ + public final OpenemsAppInstance findInstaceById(UUID uuid) throws NoSuchElementException { + return this.instantiatedApps.stream() // + .filter(t -> t.instanceId.equals(uuid)) // + .findFirst() // + .get(); + } + /** * Gets an App Configuration with component id s, which can be used to create or * rewrite the settings of the component. @@ -399,11 +425,11 @@ private AppConfiguration getNewAppConfigWithReplacedIds(OpenemsApp app, OpenemsA * @param thisApp the app that components should not be included * @return all components from all app instances except the given thisApp */ - private List getOtherAppComponents(OpenemsAppInstance thisApp) { + public List getOtherAppComponents(UUID... ignoreIds) { List allOtherComponents = new ArrayList<>(); this.foreachAppConfiguration(c -> { allOtherComponents.addAll(c.components); - }, thisApp.instanceId); + }, ignoreIds); return allOtherComponents; } @@ -493,25 +519,35 @@ protected CompletableFuture handleAddAppInstanceRequest( synchronized (this.instantiatedApps) { - this.checkStatus(openemsApp); - - // create app instance - var app = new OpenemsAppInstance(request.appId, request.alias, instanceId, request.properties); - List errors = new Vector<>(); - var completable = this.updateAppSettings(errors, user, openemsApp, null, app); - - try { - // wait until everything is finished - completable.get(); - } catch (ExecutionException | CancellationException | InterruptedException e) { - errors.add(e.getMessage()); - } - if (!errors.isEmpty()) { - throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); - } + var installedApps = this.appHelper.installApp(user, request.properties, request.alias, openemsApp); +// +// this.checkStatus(openemsApp); +// +// // create app instance +// var app = new OpenemsAppInstance(request.appId, request.alias, instanceId, request.properties, null); +// List errors = new Vector<>(); +// var completable = this.updateAppSettings(errors, user, openemsApp, null, app); +// +// try { +// // wait until everything is finished +// completable.get(); +// } catch (ExecutionException | CancellationException | InterruptedException e) { +// errors.add(e.getMessage()); +// } +// if (!errors.isEmpty()) { +// throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); +// } +// // Update App-Manager configuration +// try { +// this.instantiatedApps.add(app); +// this.updateAppManagerConfiguration(user, this.instantiatedApps); +// } catch (OpenemsNamedException e) { +// throw new OpenemsException( +// "AddAppInstance: unable to update App-Manager configuration: " + e.getMessage()); +// } // Update App-Manager configuration try { - this.instantiatedApps.add(app); + this.instantiatedApps.addAll(installedApps); this.updateAppManagerConfiguration(user, this.instantiatedApps); } catch (OpenemsNamedException e) { throw new OpenemsException( @@ -539,46 +575,49 @@ private CompletableFuture handleDeleteAppInsta if (instance == null) { return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.id)); } - var app = this.findAppById(instance.appId); - - var config = app.getAppConfiguration(ConfigurationTarget.DELETE, instance.properties, null); List errors = new Vector<>(); - - var deleteComponents = CompletableFuture.runAsync(() -> { - try { - var deletedIds = this.deleteComponents(user, config.components, - this.getOtherAppComponents(instance)); - deletedIds.addAll(config.schedulerExecutionOrder); - // do not remove ids in scheduler from other apps - // e. g. Home has ctrlBalancing0 in scheduler - // and also KebaEvcs has the ctrlBalancing0 in the scheduler - deletedIds.removeAll(this.getOtherAppSchedulerOrders(instance)); - this.componentUtil.removeIdsInSchedulerIfExisting(user, deletedIds); - } catch (OpenemsNamedException e) { - errors.add(e); - } - }); - // TODO remove 'if' if it works on windows - // rewriting network configuration only works on Linux - var updateNetworkConfig = CompletableFuture.runAsync(() -> { - if (!System.getProperty("os.name").startsWith("Windows")) { - var ips = new ArrayList<>(config.ips); - ips.removeAll(this.getOtherAppIps(instance)); - try { - this.componentUtil.updateHosts(user, null, ips); - } catch (OpenemsNamedException e) { - errors.add(e); - } - } - }); - try { - // wait until everything is finished - CompletableFuture.allOf(deleteComponents, updateNetworkConfig).get(); - } catch (ExecutionException | CancellationException | InterruptedException e) { - errors.add(new OpenemsException(e.toString())); - } + var removedApps = this.appHelper.deleteApp(user, instance); + +// var app = this.findAppById(instance.appId); +// +// var config = app.getAppConfiguration(ConfigurationTarget.DELETE, instance.properties, null); +// +// var deleteComponents = CompletableFuture.runAsync(() -> { +// try { +// var deletedIds = this.deleteComponents(user, config.components, +// this.getOtherAppComponents(instance.instanceId)); +// deletedIds.addAll(config.schedulerExecutionOrder); +// // do not remove ids in scheduler from other apps +// // e. g. Home has ctrlBalancing0 in scheduler +// // and also KebaEvcs has the ctrlBalancing0 in the scheduler +// deletedIds.removeAll(this.getOtherAppSchedulerOrders(instance)); +// this.componentUtil.removeIdsInSchedulerIfExisting(user, deletedIds); +// } catch (OpenemsNamedException e) { +// errors.add(e); +// } +// }); +// // TODO remove 'if' if it works on windows +// // rewriting network configuration only works on Linux +// var updateNetworkConfig = CompletableFuture.runAsync(() -> { +// if (!System.getProperty("os.name").startsWith("Windows")) { +// var ips = new ArrayList<>(config.ips); +// ips.removeAll(this.getOtherAppIps(instance)); +// try { +// this.componentUtil.updateHosts(user, null, ips); +// } catch (OpenemsNamedException e) { +// errors.add(e); +// } +// } +// }); +// try { +// // wait until everything is finished +// CompletableFuture.allOf(deleteComponents, updateNetworkConfig).get(); +// } catch (ExecutionException | CancellationException | InterruptedException e) { +// errors.add(new OpenemsException(e.toString())); +// } try { - this.instantiatedApps.remove(instance); +// this.instantiatedApps.remove(instance); + this.instantiatedApps.removeAll(removedApps); this.updateAppManagerConfiguration(user, this.instantiatedApps); } catch (OpenemsNamedException e) { errors.add(new OpenemsException(e.toString())); @@ -622,13 +661,12 @@ private CompletableFuture handleGetAppAssistantRequest(U */ private CompletableFuture handleGetAppDescriptorRequest(User user, GetAppDescriptor.Request request) throws OpenemsNamedException { - for (var app : this.availableApps) { - if (request.appId.equals(app.getAppId())) { - return CompletableFuture - .completedFuture(new GetAppDescriptor.Response(request.id, app.getAppDescriptor())); - } + try { + var app = this.findAppById(request.appId); + return CompletableFuture.completedFuture(new GetAppDescriptor.Response(request.id, app.getAppDescriptor())); + } catch (NoSuchElementException e) { + throw new OpenemsException("App-ID [" + request.appId + "] is unknown"); } - throw new OpenemsException("App-ID [" + request.appId + "] is unknown"); } /** @@ -726,15 +764,11 @@ private CompletableFuture handleUpdateAppInstanceRequest OpenemsAppInstance newApp = null; OpenemsAppInstance oldApp = null; synchronized (this.instantiatedApps) { - for (var app : this.instantiatedApps) { - if (app.instanceId.equals(request.instanceId)) { - oldApp = app; - newApp = new OpenemsAppInstance(app.appId, request.alias, app.instanceId, request.properties); - break; - } - } - - if (newApp == null) { + try { + oldApp = this.findInstaceById(request.instanceId); + newApp = new OpenemsAppInstance(oldApp.appId, request.alias, request.instanceId, request.properties, + null); + } catch (NoSuchElementException e) { throw new OpenemsException("App-Instance-ID [" + request.instanceId + "] is unknown."); } @@ -860,7 +894,7 @@ public CompletableFuture updateAppSettings(List errorList, User us // adding alias to the properties in order to access it while defining it in the // App Configuration newAppInstance.properties.addProperty("ALIAS", newAppInstance.alias); - final var otherComponents = this.getOtherAppComponents(newAppInstance); + final var otherComponents = this.getOtherAppComponents(newAppInstance.instanceId); final var newAppConfig = this.getNewAppConfigWithReplacedIds(app, oldAppInstance, newAppInstance, otherComponents, language); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java index 36d9402b3a0..beeb3a3fd29 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java @@ -1,11 +1,13 @@ package io.openems.edge.core.appmanager; +import java.util.List; import java.util.Objects; import java.util.UUID; import com.google.gson.JsonObject; import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.appmanager.dependency.Dependency; /** * An {@link OpenemsAppInstance} is one instance of an {@link OpenemsApp} with a @@ -17,12 +19,15 @@ public class OpenemsAppInstance { public final String alias; public final UUID instanceId; public final JsonObject properties; + public final List dependencies; - public OpenemsAppInstance(String appId, String alias, UUID instanceId, JsonObject properties) { + public OpenemsAppInstance(String appId, String alias, UUID instanceId, JsonObject properties, + List dependencies) { this.appId = appId; this.alias = alias; this.instanceId = instanceId; this.properties = properties; + this.dependencies = dependencies; } @Override @@ -53,6 +58,8 @@ public JsonObject toJsonObject() { .addProperty("alias", this.alias) // .addProperty("instanceId", this.instanceId.toString()) // .add("properties", this.properties) // + .onlyIf(dependencies != null && !dependencies.isEmpty(), j -> j.add("dependencies", // + dependencies.stream().map(t -> t.toJsonObject()).collect(JsonUtils.toJsonArray()))) .build(); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java new file mode 100644 index 00000000000..b3c04702965 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java @@ -0,0 +1,21 @@ +package io.openems.edge.core.appmanager.dependency; + +import java.util.List; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.EdgeConfig; +import io.openems.edge.common.user.User; +import io.openems.edge.core.appmanager.AppConfiguration; + +public interface AggregateTask { + +// public boolean shouldAggregate(DependencyConfig instance); + + public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) throws OpenemsNamedException; + + public void create(User user, List otherAppComponents) throws OpenemsNamedException; + +// public abstract void update() throws OpenemsException; + public void delete(User user, List otherAppComponents) throws OpenemsNamedException; + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java new file mode 100644 index 00000000000..c5a6799eb35 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java @@ -0,0 +1,47 @@ +package io.openems.edge.core.appmanager.dependency; + +import java.util.List; + +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.user.User; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppInstance; + +public interface AppManagerAppHelper { + + /** + * + * @param user + * @param properties + * @param alias + * @param app + * @returns a list of the created {@link OpenemsAppInstance}s + * @throws OpenemsNamedException + */ + public List installApp(User user, JsonObject properties, String alias, OpenemsApp app) + throws OpenemsNamedException; + + /** + * + * @param user + * @param properties + * @param alias + * @param app + * @returns a list of the replaced {@link OpenemsAppInstance}s + * @throws OpenemsNamedException + */ + public List updateApp(User user, JsonObject properties, String alias, OpenemsApp app) + throws OpenemsNamedException; + + /** + * + * @param user + * @param instance + * @returns a list of the removed {@link OpenemsAppInstance}s + * @throws OpenemsNamedException + */ + public List deleteApp(User user, OpenemsAppInstance instance) throws OpenemsNamedException; + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java new file mode 100644 index 00000000000..efa7d739c80 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java @@ -0,0 +1,440 @@ +package io.openems.edge.core.appmanager.dependency; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.UUID; + +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.session.Language; +import io.openems.common.types.EdgeConfig; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.user.User; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppManager; +import io.openems.edge.core.appmanager.AppManagerImpl; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ComponentUtilImpl; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppInstance; + +@Component +public class AppManagerAppHelperImpl implements AppManagerAppHelper { + + private ComponentManager componentManager; + private ComponentUtil componentUtil; + + // tasks + private ComponentAggregateTask componentsTask; + private SchedulerAggregateTask schedulerTask; + + @Activate + public AppManagerAppHelperImpl(@Reference ComponentManager componentManager, @Reference ComponentUtil componentUtil, + @Reference(target = "(component.name=AppManager.AggregateTask.CreateComponents)") AggregateTask componentsTask, + @Reference(target = "(component.name=AppManager.AggregateTask.SchedulerAggregateTask)") AggregateTask schedulerTask) { + this.componentManager = componentManager; + this.componentUtil = componentUtil; + this.componentsTask = (ComponentAggregateTask) componentsTask; + this.schedulerTask = (SchedulerAggregateTask) schedulerTask; + + } + + @Override + public List installApp(User user, JsonObject properties, String alias, OpenemsApp app) + throws OpenemsNamedException { + final var language = user == null ? null : user.getLanguage(); + + var createdInstances = new LinkedList(); + var dependencieInstances = new HashMap(); + this.foreachDependency(app, alias, properties, ConfigurationTarget.ADD, language, dc -> { + + var appId = dc.app.getAppId(); + var neededApp = this.findNeededApp(dc, appId); + if (neededApp != null) { + if (neededApp.isPresent()) { + // TODO update app + dependencieInstances.put(dc, neededApp.get()); + return true; + } + } else { + return false; + } + + // TODO what if the created app is a dependency needed from another app? + + // map dependencies if this is the parent + var dependecies = new ArrayList(dependencieInstances.size()); + if (!dependencieInstances.isEmpty()) { + var isParent = !dc.isDependency(); + for (var dependency : dependencieInstances.entrySet()) { + if (!isParent + && !dc.config.dependencies.stream().anyMatch(t -> t.equals(dependency.getKey().sub))) { + isParent = false; + break; + } else { + isParent = true; + } + dependecies.add(new Dependency(dependency.getKey().sub.key, dependency.getValue().instanceId)); + } + if (isParent) { + dependencieInstances.clear(); + } + } + + var instance = new OpenemsAppInstance(dc.app.getAppId(), dc.alias, UUID.randomUUID(), dc.properties, + dependecies); + createdInstances.add(instance); + dependencieInstances.put(dc, instance); + + var otherAppComponents = this.getAppManagerImpl() + .getOtherAppComponents(createdInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + try { + var newAppConfig = this.getNewAppConfigWithReplacedIds(dc.app, null, instance, otherAppComponents, + language); + this.componentsTask.aggregate(newAppConfig, null); + this.schedulerTask.aggregate(newAppConfig, null); + } catch (OpenemsNamedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return true; + }); + + var otherAppComponents = this.getAppManagerImpl() + .getOtherAppComponents(createdInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + + this.componentsTask.create(user, otherAppComponents); + + this.schedulerTask.setCreatedComponents(this.componentsTask.getCreatedComponents()); + this.schedulerTask.create(user, otherAppComponents); + + return createdInstances; + } + + @Override + public List updateApp(User user, JsonObject properties, String alias, OpenemsApp app) + throws OpenemsNamedException { + // TODO Auto-generated method stub + return null; + } + + @Override + public List deleteApp(User user, OpenemsAppInstance instance) throws OpenemsNamedException { + var deletedInstances = new LinkedList(); + final var language = user == null ? null : user.getLanguage(); + + this.foreachExistingDependecy(instance, ConfigurationTarget.DELETE, language, dc -> { + + if (dc.isDependency()) { + switch (dc.sub.deletePolicy) { + case NEVER: + return false; + case IF_MINE: + if (this.getAppManagerImpl().getInstantiatedApps().stream().anyMatch(a -> !a.equals(dc.parent) + && a.dependencies != null + && a.dependencies.stream().anyMatch(d -> d.instanceId.equals(dc.instance.instanceId)))) { + return false; + } + break; + case ALWAYS: + break; + } + } + + deletedInstances.add(dc.instance); + + try { + componentsTask.aggregate(dc.config, null); + } catch (OpenemsNamedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return true; + }); + + var otherAppComponents = this.getAppManagerImpl() + .getOtherAppComponents(deletedInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + + componentsTask.delete(user, otherAppComponents); + + return deletedInstances; + } + + /** + * + * @param dc + * @param appId + * @returns null if the app can not be added; {@link Optional#absent()} if the + * app needs to be created; the {@link OpenemsAppInstance} if an + * existing app can be used + */ + private Optional findNeededApp(DependencyConfig dc, String appId) { + if (!dc.isDependency()) { + return Optional.absent(); + } + if (dc.sub.createPolicy == DependencyDeclaration.CreatePolicy.ALWAYS) { + var neededApps = this.getAppManagerImpl().getInstantiatedApps().stream().filter(t -> t.appId.equals(appId)) + .toList(); + OpenemsAppInstance availableApp = null; + for (var neededApp : neededApps) { + if (!this.getAppManagerImpl().getInstantiatedApps().stream().anyMatch( + t -> t.dependencies.stream().anyMatch(d -> d.instanceId.equals(neededApp.instanceId)))) { + availableApp = neededApp; + break; + } + } + return Optional.fromNullable(availableApp); + } else { + var neededApp = this.getAppManagerImpl().getInstantiatedApps().stream().filter(t -> t.appId.equals(appId)) + .toList(); + if (!neededApp.isEmpty()) { + return Optional.of(neededApp.get(0)); + } + if (dc.sub.createPolicy == DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING) { + return Optional.absent(); + } + } + return null; + } + + /** + * Iterates over all dependencies and the given app. + * + *

+ * Order bottom -> top. + * + * @param app + * @param defaultProperties + * @param target + * @param function returns true if the instance gets created or already + * exists + * @throws OpenemsNamedException + */ + private DependencyConfig foreachDependency(OpenemsApp app, String alias, JsonObject defaultProperties, + ConfigurationTarget target, Function function, DependencyDeclaration sub, + Language l) throws OpenemsNamedException { + var config = app.getAppConfiguration(target, defaultProperties, l); + var dependencies = new LinkedList(); + for (var dependency : config.dependencies) { + try { + var dependencyApp = this.getAppManagerImpl().findAppById(dependency.appId); + var addingConfig = this.foreachDependency(dependencyApp, dependency.alias, dependency.properties, + target, function, dependency, l); + if (addingConfig != null) { + dependencies.add(addingConfig); + } + } catch (NoSuchElementException e) { + // TODO can not find app + } + } + + var newConfig = new DependencyConfig(app, sub, config, alias, defaultProperties, dependencies); + if (function.apply(newConfig)) { + return newConfig; + } + return null; + } + + private void foreachDependency(OpenemsApp app, String alias, JsonObject defaultProperties, + ConfigurationTarget target, Language l, Function consumer) + throws OpenemsNamedException { + this.foreachDependency(app, alias, defaultProperties, target, consumer, null, l); + } + + private void foreachExistingDependecy(OpenemsAppInstance instance, ConfigurationTarget target, Language l, + Function consumer) throws OpenemsNamedException { + this.foreachExistingDependency(instance, target, consumer, null, null, l); + } + + /** + * + *

+ * Order bottom -> top. + * + * @param instance + * @param target + * @param consumer + * @param parent + * @param sub + * @param l + * @throws OpenemsNamedException + */ + private DependencyConfig foreachExistingDependency(OpenemsAppInstance instance, ConfigurationTarget target, + Function consumer, OpenemsAppInstance parent, DependencyDeclaration sub, + Language l) throws OpenemsNamedException { + var app = this.getAppManagerImpl().findAppById(instance.appId); + var config = app.getAppConfiguration(target, instance.properties, l); + + var dependecies = new ArrayList(); + if (instance.dependencies != null) { + dependecies = new ArrayList(instance.dependencies.size()); + for (var dependency : instance.dependencies) { + try { + var dependecyApp = this.getAppManagerImpl().findInstaceById(dependency.instanceId); + var subApp = config.dependencies.stream().filter(t -> t.key.equals(dependency.key)).findFirst() + .get(); + var dependecy = this.foreachExistingDependency(dependecyApp, target, consumer, instance, subApp, l); + dependecies.add(dependecy); + } catch (NoSuchElementException e) { + // can not find app + } + } + } + var newConfig = new ExistingDependencyConfig(app, sub, config, instance.alias, instance.properties, dependecies, + parent, instance); + if (consumer.apply(newConfig)) { + return newConfig; + } + return null; + } + + /** + * Gets the component id s that can be replaced. + * + * @param app the components of which app + * @param properties the default properties to create an app instance of this + * app + * @return a map of the component id s that can be replaced mapped from id to + * key to put the next id + * @throws OpenemsNamedException on error + */ + protected final Map getReplaceableComponentIds(OpenemsApp app, JsonObject properties) + throws OpenemsNamedException { + final var prefix = "?_?_"; + var config = app.getAppConfiguration(ConfigurationTarget.TEST, properties, null); + var copyBuilder = JsonUtils.buildJsonObject(); + for (var entry : properties.entrySet()) { + copyBuilder.add(entry.getKey(), entry.getValue()); + } + for (var comp : config.components) { + copyBuilder.addProperty(comp.getId(), prefix); + } + var copy = copyBuilder.build(); + var configWithNewIds = app.getAppConfiguration(ConfigurationTarget.TEST, copy, null); + Map replaceableComponentIds = new HashMap<>(); + for (var comp : configWithNewIds.components) { + if (comp.getId().startsWith(prefix)) { + // "METER_ID:meter0" + var raw = comp.getId().substring(prefix.length()); + // ["METER_ID", "meter0"] + var pieces = raw.split(":"); + // "METER_ID" + var property = pieces[0]; + // "meter0" + var defaultId = pieces[1]; + replaceableComponentIds.put(defaultId, property); + } + } + return replaceableComponentIds; + } + + /** + * Gets an App Configuration with component id s, which can be used to create or + * rewrite the settings of the component. + * + * @param app the {@link OpenemsApp} + * @param oldAppInstance the old {@link OpenemsAppInstance} + * @param newAppInstance the new {@link OpenemsAppInstance} + * @param otherAppComponents the components that are used from the other + * {@link OpenemsAppInstance} + * @param language the language of the new config + * @return the AppConfiguration with the replaced ID s of the components + * @throws OpenemsNamedException on error + */ + private AppConfiguration getNewAppConfigWithReplacedIds(OpenemsApp app, OpenemsAppInstance oldAppInstance, + OpenemsAppInstance newAppInstance, List otherAppComponents, Language language) + throws OpenemsNamedException { + + var target = oldAppInstance == null ? ConfigurationTarget.ADD : ConfigurationTarget.UPDATE; + var newAppConfig = app.getAppConfiguration(target, newAppInstance.properties, language); + + final var replacableIds = this.getReplaceableComponentIds(app, newAppInstance.properties); + + for (var comp : ComponentUtilImpl.order(newAppConfig.components)) { + // replace old id s with new ones + for (var entry : comp.getProperties().entrySet()) { + for (var replaceableId : replacableIds.entrySet()) { + if (entry.getValue().toString().contains(replaceableId.getKey())) { + var newId = entry.getValue().toString().replace(replaceableId.getKey(), + newAppInstance.properties.get(replaceableId.getValue()).getAsString()); + newId = newId.replace("\"", ""); + var newValue = JsonUtils.getAsJsonElement(newId); + comp.getProperties().put(entry.getKey(), newValue); + } + } + } + + var isNewComponent = true; + var id = comp.getId(); + var canBeReplaced = replacableIds.containsKey(id); + EdgeConfig.Component foundComponent = null; + + // try to find a component with the necessary settings + if (canBeReplaced) { + foundComponent = this.componentUtil.getComponentByConfig(comp); + if (foundComponent != null) { + id = foundComponent.getId(); + } + } + if (foundComponent == null && oldAppInstance != null && oldAppInstance.properties.has(id.toUpperCase())) { + id = oldAppInstance.properties.get(id.toUpperCase()).getAsString(); + foundComponent = this.componentManager.getEdgeConfig().getComponent(id).orElse(null); + final var tempId = id; + // other app uses the same component because they had the same configuration + // now this app needs the component with a different configuration so now create + // a new component + if (foundComponent != null && otherAppComponents.stream().anyMatch(t -> t.getId().equals(tempId))) { + foundComponent = null; + } + } + isNewComponent = isNewComponent && foundComponent == null; + if (isNewComponent) { + // if the id is not already set and there is no component with the default id + // then use the default id + foundComponent = this.componentManager.getEdgeConfig().getComponent(comp.getId()).orElse(null); + if (foundComponent == null) { + id = comp.getId(); + } else { + // replace number at the end and get the next available id + var nextAvailableId = this.componentUtil.getNextAvailableId(id.replaceAll("\\d+", ""), + otherAppComponents); + if (!nextAvailableId.equals(id) && !canBeReplaced) { + // component can not be created because the id is already used + // and the id can not be set in the configuration + continue; + } + if (canBeReplaced) { + id = nextAvailableId; + } + } + } + + if (canBeReplaced) { + newAppInstance.properties.addProperty(replacableIds.get(comp.getId()), id); + } + } + return app.getAppConfiguration(target, newAppInstance.properties, language); + } + + private final AppManagerImpl getAppManagerImpl() { +// return (AppManagerImpl) appManager; + return (AppManagerImpl) componentManager.getEnabledComponentsOfType(AppManager.class).get(0); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java new file mode 100644 index 00000000000..abc930fca4e --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java @@ -0,0 +1,204 @@ +package io.openems.edge.core.appmanager.dependency; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.request.CreateComponentConfigRequest; +import io.openems.common.jsonrpc.request.DeleteComponentConfigRequest; +import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; +import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest.Property; +import io.openems.common.types.EdgeConfig; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.user.User; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.ComponentUtilImpl; +import io.openems.edge.core.componentmanager.ComponentManagerImpl; + +@Component(name = "AppManager.AggregateTask.CreateComponents") +public class ComponentAggregateTask implements AggregateTask { + + private List components; + private List components2Delete; + + private ComponentManager componentManager; + + private List createdComponents; + private List deletedComponents; + + @Activate + public ComponentAggregateTask(@Reference ComponentManager componentManager) { + this.componentManager = componentManager; + this.components = new LinkedList<>(); + this.components2Delete = new LinkedList<>(); + } + + @Override + public void aggregate(AppConfiguration config, AppConfiguration oldConfig) throws OpenemsNamedException { + this.components.addAll(config.components); + + if (oldConfig == null) { + return; + } + var componentDiff = new ArrayList<>(config.components); + componentDiff.removeIf(t -> oldConfig.components.stream().anyMatch(c -> c.getId().equals(t.getId()))); + this.components2Delete.addAll(componentDiff); + } + + @Override + public void create(User user, List otherAppComponents) throws OpenemsNamedException { + this.createdComponents = new ArrayList(this.components.size()); + var errors = new LinkedList(); + // create components + for (var comp : ComponentUtilImpl.order(this.components)) { + /** + * if comp already exists with same config as needed => use it. if comp exist + * with different config and no other app needs it => rewrite settings. if comp + * exist with different config and other app needs it => create new comp + */ + var foundComponentWithSameId = this.componentManager.getEdgeConfig().getComponent(comp.getId()) + .orElse(null); + if (foundComponentWithSameId != null) { + + var isSameConfigWithoutAlias = ComponentUtilImpl.isSameConfigurationWithoutAlias(null, comp, + foundComponentWithSameId); + var isSameConfig = isSameConfigWithoutAlias + && comp.getAlias().equals(foundComponentWithSameId.getAlias()); + + if (isSameConfig) { + // same configuration so no reconfiguration needed + continue; + } + + // check if it is my component + if (otherAppComponents.stream().anyMatch(t -> t.getId().equals(foundComponentWithSameId.getId()))) { + // not my component but only the alias changed + if (isSameConfigWithoutAlias) { + // TODO maybe warning if the alias can't be set + continue; + } + errors.add("Configuration of component with id '" + foundComponentWithSameId.getId() + + "' can not be rewritten. Because the component belongs to another app."); + continue; + } + try { + this.reconfigure(user, comp, foundComponentWithSameId); + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); + } + continue; + } + + // create new component + try { + this.createComponent(user, comp); + createdComponents.add(comp); + } catch (OpenemsNamedException e) { + var error = "Component[" + comp.getFactoryId() + "] cant be created!"; + errors.add(error); + errors.add(e.getMessage()); + } + + } + + // delete components that were used from the old configurations + this.delete(user, this.components2Delete); + + this.components = new LinkedList<>(); + this.components2Delete = new LinkedList<>(); + } + + /** + * deletes the given components only if they are not in notMyComponents. + * + * @param user the executing user + * @param components the components that should be deleted + * @param notMyComponents other needed components from the other apps + * @return the id s of the components that got deleted + */ + @Override + public void delete(User user, List notMyComponents) throws OpenemsNamedException { + this.deletedComponents = new ArrayList<>(this.components.size()); + List errors = new ArrayList<>(); + + for (var comp : this.components) { + if (notMyComponents.stream().parallel().anyMatch(t -> t.getId().equals(comp.getId()))) { + continue; + } + var component = this.componentManager.getEdgeConfig().getComponent(comp.getId()).orElse(null); + if (component == null) { + // component does not exist + continue; + } + + try { + // user can be null using internal method + ((ComponentManagerImpl) this.componentManager).handleDeleteComponentConfigRequest(user, + new DeleteComponentConfigRequest(comp.getId())); + this.deletedComponents.add(comp.getId()); + } catch (OpenemsNamedException e) { + errors.add(e.toString()); + } + } + + if (!errors.isEmpty()) { + throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); + } + + this.components = new LinkedList<>(); + this.components2Delete = new LinkedList<>(); + } + + private void createComponent(User user, EdgeConfig.Component comp) throws OpenemsNamedException { + List properties = comp.getProperties().entrySet().stream() + .map(t -> new Property(t.getKey(), t.getValue())).collect(Collectors.toList()); + properties.add(new Property("id", comp.getId())); + properties.add(new Property("alias", comp.getAlias())); + + // user can be null using internal method + ((ComponentManagerImpl) this.componentManager).handleCreateComponentConfigRequest(user, + new CreateComponentConfigRequest(comp.getFactoryId(), properties)); + } + + /** + * checks if the settings of the component changed if there is a change it + * rewrites the settings of the given component. + * + * @param user the executing user + * @param myComp the component that configuration should be rewritten + * @param actualComp the actual component that exists + * @throws OpenemsNamedException when the configuration can not be rewritten + */ + private void reconfigure(User user, EdgeConfig.Component myComp, EdgeConfig.Component actualComp) + throws OpenemsNamedException { + if (ComponentUtilImpl.isSameConfiguration(null, myComp, actualComp)) { + return; + } + + // send update request + List properties = myComp.getProperties().entrySet().stream() + .map(t -> new Property(t.getKey(), t.getValue())) // + .collect(Collectors.toList()); + properties.add(new Property("alias", myComp.getAlias())); + var updateRequest = new UpdateComponentConfigRequest(actualComp.getId(), properties); + // user can be null using internal method + ((ComponentManagerImpl) this.componentManager).handleUpdateComponentConfigRequest(user, updateRequest); + } + + public List getCreatedComponents() { + return Collections.unmodifiableList(this.createdComponents); + } + + public List getDeletedComponents() { + return Collections.unmodifiableList(this.deletedComponents); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/Dependency.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/Dependency.java new file mode 100644 index 00000000000..e6bdaafa47e --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/Dependency.java @@ -0,0 +1,33 @@ +package io.openems.edge.core.appmanager.dependency; + +import java.util.UUID; + +import com.google.gson.JsonObject; + +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.appmanager.AppManager; + +/** + * Represents a dependency in the configuration of the {@link AppManager} of an + * app. + * + */ +public class Dependency { + + public final String key; + + public final UUID instanceId; + + public Dependency(String key, UUID instanceId) { + this.key = key; + this.instanceId = instanceId; + } + + public JsonObject toJsonObject() { + return JsonUtils.buildJsonObject() // + .addProperty("key", key) // + .addProperty("instanceId", instanceId.toString()) // + .build(); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java new file mode 100644 index 00000000000..97dddf2f9db --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java @@ -0,0 +1,37 @@ +package io.openems.edge.core.appmanager.dependency; + +import java.util.List; + +import com.google.gson.JsonObject; + +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.OpenemsApp; + +public class DependencyConfig { + + public final OpenemsApp app; +// @Nullable if not a dependency of an app + public final DependencyDeclaration sub; + public final AppConfiguration config; + + public final String alias; + public final JsonObject properties; + + public final List declarations; + + public DependencyConfig(OpenemsApp app, DependencyDeclaration sub, AppConfiguration config, String alias, + JsonObject properties, List declarations) { + super(); + this.app = app; + this.sub = sub; + this.config = config; + this.alias = alias; + this.properties = properties; + this.declarations = declarations; + } + + public final boolean isDependency() { + return this.sub != null; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java new file mode 100644 index 00000000000..a921432fcf9 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java @@ -0,0 +1,65 @@ +package io.openems.edge.core.appmanager.dependency; + +import java.util.List; +import java.util.function.Supplier; + +import com.google.common.base.Function; +import com.google.gson.JsonObject; + +import io.openems.edge.core.appmanager.OpenemsAppInstance; + +public class DependencyDeclaration { + + public final String key; + public final String appId; + public final String alias; + public final JsonObject properties; + + public final CreatePolicy createPolicy; + public final UpdatePolicy updatePolicy; + public final DeletePolicy deletePolicy; + + // Dependency Supplier? + private final Supplier supplierForAppId = null; + private final Function, String> supplierForInstanceIdFromExisting = null; + + public DependencyDeclaration(String key, String appId, String alias, CreatePolicy createPolicy, + UpdatePolicy updatePolicy, DeletePolicy deletePolicy, JsonObject properties) { + this.key = key; + this.appId = appId; + this.alias = alias; + this.properties = properties; + this.createPolicy = createPolicy; + this.updatePolicy = updatePolicy; + this.deletePolicy = deletePolicy; + } + + public static enum CreatePolicy { + /** + * Always creates the dependent app except an {@link OpenemsAppInstance} is + * already created and not a dependency of another app. + */ + ALWAYS, // + /** + * lazy singleton. + */ + IF_NOT_EXISTING, // + NEVER, // + ; + } + + public static enum UpdatePolicy { + ALWAYS, // + IF_MINE, // + NEVER, // + ; + } + + public static enum DeletePolicy { + ALWAYS, // + IF_MINE, // + NEVER, // + ; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java new file mode 100644 index 00000000000..39d862e218e --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java @@ -0,0 +1,26 @@ +package io.openems.edge.core.appmanager.dependency; + +import java.util.List; + +import com.google.gson.JsonObject; + +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppInstance; + +public class ExistingDependencyConfig extends DependencyConfig { + +// @Nullable + public final OpenemsAppInstance parent; + public final OpenemsAppInstance instance; + +// public final List existingDependencies = null; + + public ExistingDependencyConfig(OpenemsApp app, DependencyDeclaration sub, AppConfiguration config, String alias, + JsonObject properties, List declarations, OpenemsAppInstance parent, + OpenemsAppInstance instance) { + super(app, sub, config, alias, properties, declarations); + this.parent = parent; + this.instance = instance; + } +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java new file mode 100644 index 00000000000..40f108040ee --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java @@ -0,0 +1,64 @@ +package io.openems.edge.core.appmanager.dependency; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.EdgeConfig; +import io.openems.edge.common.user.User; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.ComponentUtil; + +@Component(name = "AppManager.AggregateTask.SchedulerAggregateTask") +public class SchedulerAggregateTask implements AggregateTask { + + private ComponentUtil componentUtil; + private List order; + private List removeIds; + + private List createdComponents; + + @Activate + public SchedulerAggregateTask(@Reference ComponentUtil componentUtil) { + this.componentUtil = componentUtil; + order = new LinkedList<>(); + removeIds = new LinkedList<>(); + } + + @Override + public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) throws OpenemsNamedException { + order = componentUtil.insertSchedulerOrder(order, instance.schedulerExecutionOrder); + + if (oldConfig == null) { + return; + } + var schedulerIdDiff = new ArrayList<>(oldConfig.schedulerExecutionOrder); + schedulerIdDiff.removeAll(instance.schedulerExecutionOrder); + removeIds.addAll(schedulerIdDiff); + } + + @Override + public void create(User user, List otherAppComponents) throws OpenemsNamedException { + order = componentUtil.insertSchedulerOrder(componentUtil.getSchedulerIds(), order); + componentUtil.updateScheduler(user, order, createdComponents); + + + order = new LinkedList<>(); + removeIds = new LinkedList<>(); + } + + @Override + public void delete(User user, List otherAppComponents) throws OpenemsNamedException { + + } + + public final void setCreatedComponents(List createdComponents) { + this.createdComponents = createdComponents; + } + +} From e6d1ea792ce40c2346665b3cd0c1ad59b97ef838 Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:00:42 +0200 Subject: [PATCH 04/30] added update for app and subapps --- .../app/integratedsystem/FeneconHome.java | 2 +- .../GridOptimizedCharge.java | 4 +- .../core/appmanager/AbstractOpenemsApp.java | 1 + .../edge/core/appmanager/AppManagerImpl.java | 41 ++- .../appmanager/dependency/AggregateTask.java | 5 +- .../dependency/AppManagerAppHelper.java | 3 +- .../dependency/AppManagerAppHelperImpl.java | 331 ++++++++++++++++-- .../dependency/ComponentAggregateTask.java | 35 +- .../dependency/DependencyConfig.java | 8 +- .../dependency/DependencyDeclaration.java | 64 +++- .../dependency/ExistingDependencyConfig.java | 12 +- .../dependency/SchedulerAggregateTask.java | 40 ++- .../dependency/StaticIpAggregateTask.java | 71 ++++ 13 files changed, 523 insertions(+), 94 deletions(-) create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTask.java diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java index 5ab7fdb6ad0..d2eb08c5635 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java @@ -293,7 +293,7 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { bundle.getString("App.PvSelfConsumption.GridOptimizedCharge.Name"), // DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING, // DependencyDeclaration.UpdatePolicy.ALWAYS, // - DependencyDeclaration.DeletePolicy.ALWAYS, // + DependencyDeclaration.DeletePolicy.IF_MINE, // JsonUtils.buildJsonObject() // .addProperty(GridOptimizedCharge.Property.MAXIMUM_SELL_TO_GRID_POWER.name(), maxFeedInPower) // .build())); diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java index 4e63517d86e..6df2fcca199 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java @@ -97,8 +97,10 @@ protected ThrowingTriFunction getValidationErrors(JsonObject jProperties) { this.validateComponentConfigurations(errors, edgeConfig, appConfiguration); this.validateScheduler(errors, edgeConfig, appConfiguration); + // TODO validate dependencies // TODO remove 'if' if it works on windows // changing network settings only works on linux diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java index 64eeea56b28..5fb8c9b83c0 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java @@ -193,7 +193,7 @@ private synchronized void applyConfig(Config config) { } } - protected void checkStatus(OpenemsApp openemsApp) throws OpenemsNamedException { + protected static void checkStatus(OpenemsApp openemsApp) throws OpenemsNamedException { var validator = openemsApp.getValidator(); var status = validator.getStatus(); switch (validator.getStatus()) { @@ -231,7 +231,9 @@ public void foreachAppConfiguration(Consumer consumer, UUID... try { var app = this.findAppById(appInstance.appId); + appInstance.properties.addProperty("ALIAS", appInstance.alias); consumer.accept(app.getAppConfiguration(ConfigurationTarget.VALIDATE, appInstance.properties, null)); + appInstance.properties.remove("ALIAS"); } catch (OpenemsNamedException e) { // move to next app } catch (NoSuchElementException e) { @@ -463,6 +465,14 @@ private List getOtherAppSchedulerOrders(OpenemsAppInstance thisApp) { return allOtherIps; } + public final List getOtherAppConfigurations(UUID... ignoreIds) { + List allOtherConfigs = new ArrayList<>(this.instantiatedApps.size()); + this.foreachAppConfiguration(c -> { + allOtherConfigs.add(c); + }, ignoreIds); + return allOtherConfigs; + } + /** * Gets the component id s that can be replaced. * @@ -761,32 +771,41 @@ public CompletableFuture handleJsonrpcRequest( */ private CompletableFuture handleUpdateAppInstanceRequest(User user, UpdateAppInstance.Request request) throws OpenemsNamedException { - OpenemsAppInstance newApp = null; - OpenemsAppInstance oldApp = null; + synchronized (this.instantiatedApps) { +// OpenemsAppInstance newApp = null; + OpenemsAppInstance oldApp = null; + OpenemsApp app = null; try { oldApp = this.findInstaceById(request.instanceId); - newApp = new OpenemsAppInstance(oldApp.appId, request.alias, request.instanceId, request.properties, - null); + app = this.findAppById(oldApp.appId); +// newApp = new OpenemsAppInstance(oldApp.appId, request.alias, request.instanceId, request.properties, +// null); } catch (NoSuchElementException e) { throw new OpenemsException("App-Instance-ID [" + request.instanceId + "] is unknown."); } - var errors = this.reconfigurApp(user, oldApp, newApp); + var result = this.appHelper.updateApp(user, oldApp, request.properties, request.alias, app); + +// var errors = this.reconfigurApp(user, oldApp, newApp); // Update App-Manager configuration try { - this.instantiatedApps.remove(oldApp); - this.instantiatedApps.add(newApp); + this.instantiatedApps.removeAll(result.deletedApps); + // replace old instances with new ones + this.instantiatedApps.removeAll(result.modifiedOrCreatedApps); + this.instantiatedApps.addAll(result.modifiedOrCreatedApps); +// this.instantiatedApps.remove(oldApp); +// this.instantiatedApps.add(newApp); this.updateAppManagerConfiguration(user, this.instantiatedApps); } catch (OpenemsNamedException e) { throw new OpenemsException("Unable to update App-Manager configuration for ID [" + request.instanceId + "]: " + e.getMessage()); } - if (!errors.isEmpty()) { - throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); - } +// if (!errors.isEmpty()) { +// throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); +// } } return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.id)); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java index b3c04702965..fb56cff148f 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java @@ -3,7 +3,6 @@ import java.util.List; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.types.EdgeConfig; import io.openems.edge.common.user.User; import io.openems.edge.core.appmanager.AppConfiguration; @@ -13,9 +12,9 @@ public interface AggregateTask { public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) throws OpenemsNamedException; - public void create(User user, List otherAppComponents) throws OpenemsNamedException; + public void create(User user, List otherAppConfigurations) throws OpenemsNamedException; // public abstract void update() throws OpenemsException; - public void delete(User user, List otherAppComponents) throws OpenemsNamedException; + public void delete(User user, List otherAppConfigurations) throws OpenemsNamedException; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java index c5a6799eb35..b628c56f721 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java @@ -8,6 +8,7 @@ import io.openems.edge.common.user.User; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppInstance; +import io.openems.edge.core.appmanager.dependency.AppManagerAppHelperImpl.UpdateValues; public interface AppManagerAppHelper { @@ -32,7 +33,7 @@ public List installApp(User user, JsonObject properties, Str * @returns a list of the replaced {@link OpenemsAppInstance}s * @throws OpenemsNamedException */ - public List updateApp(User user, JsonObject properties, String alias, OpenemsApp app) + public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObject properties, String alias, OpenemsApp app) throws OpenemsNamedException; /** diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java index efa7d739c80..df908895f3f 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.TreeMap; import java.util.UUID; import org.osgi.service.component.annotations.Activate; @@ -17,7 +18,6 @@ import com.google.gson.JsonObject; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.exceptions.OpenemsException; import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; @@ -41,16 +41,18 @@ public class AppManagerAppHelperImpl implements AppManagerAppHelper { // tasks private ComponentAggregateTask componentsTask; private SchedulerAggregateTask schedulerTask; + private StaticIpAggregateTask staticIpTask; @Activate public AppManagerAppHelperImpl(@Reference ComponentManager componentManager, @Reference ComponentUtil componentUtil, @Reference(target = "(component.name=AppManager.AggregateTask.CreateComponents)") AggregateTask componentsTask, - @Reference(target = "(component.name=AppManager.AggregateTask.SchedulerAggregateTask)") AggregateTask schedulerTask) { + @Reference(target = "(component.name=AppManager.AggregateTask.SchedulerAggregateTask)") AggregateTask schedulerTask, + @Reference(target = "(component.name=AppManager.AggregateTask.StaticIpAggregateTask)") AggregateTask staticIpTask) { this.componentManager = componentManager; this.componentUtil = componentUtil; this.componentsTask = (ComponentAggregateTask) componentsTask; this.schedulerTask = (SchedulerAggregateTask) schedulerTask; - + this.staticIpTask = (StaticIpAggregateTask) staticIpTask; } @Override @@ -62,12 +64,18 @@ public List installApp(User user, JsonObject properties, Str var dependencieInstances = new HashMap(); this.foreachDependency(app, alias, properties, ConfigurationTarget.ADD, language, dc -> { + // TODO could be any relay var appId = dc.app.getAppId(); + var neededApp = this.findNeededApp(dc, appId); if (neededApp != null) { if (neededApp.isPresent()) { - // TODO update app dependencieInstances.put(dc, neededApp.get()); + if (dc.sub.updatePolicy.isAllowedToUpdate(this.getAppManagerImpl().getInstantiatedApps(), null, + neededApp.get())) { + // TODO update app + + } return true; } } else { @@ -100,13 +108,14 @@ public List installApp(User user, JsonObject properties, Str createdInstances.add(instance); dependencieInstances.put(dc, instance); - var otherAppComponents = this.getAppManagerImpl() - .getOtherAppComponents(createdInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + var otherAppConfigs = this.getAppManagerImpl() + .getOtherAppConfigurations(createdInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); try { - var newAppConfig = this.getNewAppConfigWithReplacedIds(dc.app, null, instance, otherAppComponents, - language); + var newAppConfig = this.getNewAppConfigWithReplacedIds(dc.app, null, instance, + AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), language); this.componentsTask.aggregate(newAppConfig, null); this.schedulerTask.aggregate(newAppConfig, null); + this.staticIpTask.aggregate(newAppConfig, null); } catch (OpenemsNamedException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -115,22 +124,194 @@ public List installApp(User user, JsonObject properties, Str return true; }); - var otherAppComponents = this.getAppManagerImpl() - .getOtherAppComponents(createdInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + var otherAppConfigs = this.getAppManagerImpl() + .getOtherAppConfigurations(createdInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + + // TODO Validate Checkables after setting ips + this.staticIpTask.create(user, otherAppConfigs); + + this.componentsTask.create(user, otherAppConfigs); - this.componentsTask.create(user, otherAppComponents); - this.schedulerTask.setCreatedComponents(this.componentsTask.getCreatedComponents()); - this.schedulerTask.create(user, otherAppComponents); + this.schedulerTask.create(user, otherAppConfigs); return createdInstances; } @Override - public List updateApp(User user, JsonObject properties, String alias, OpenemsApp app) - throws OpenemsNamedException { - // TODO Auto-generated method stub - return null; + public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObject properties, String alias, + OpenemsApp app) throws OpenemsNamedException { + final var language = user == null ? null : user.getLanguage(); + + var oldInstances = new TreeMap(); + var dependencieInstances = new HashMap(); + // all existing app dependencies + this.foreachExistingDependecy(oldInstance, ConfigurationTarget.UPDATE, language, dc -> { + if (!dc.isDependency()) { + return true; + } + oldInstances.put(new AppIdKey(dc.parentInstance.appId, dc.sub.key), dc); + return true; + }); + + var modifiedOrCreatedApps = new LinkedList(); + // update app and its dependencies + this.foreachDependency(app, alias, properties, ConfigurationTarget.UPDATE, language, dc -> { + + ExistingDependencyConfig oldAppConfig; + if (dc.isDependency()) { + oldAppConfig = oldInstances.remove(new AppIdKey(dc.parent.getAppId(), dc.sub.key)); + if (oldAppConfig != null) { + for (var entry : oldAppConfig.properties.entrySet()) { + // add old values which are not set by the DependecyDeclaration + if (!dc.properties.has(entry.getKey())) { + dc.properties.add(entry.getKey(), entry.getValue()); + } + } + + } + } else { + oldAppConfig = new ExistingDependencyConfig(app, null, null, null, oldInstance.alias, + oldInstance.properties, null, oldInstance, oldInstance); + } + + // map dependencies if this is the parent + var dependecies = new ArrayList(dependencieInstances.size()); + if (!dependencieInstances.isEmpty()) { + var isParent = !dc.isDependency(); + for (var dependency : dependencieInstances.entrySet()) { + if (!isParent + && !dc.config.dependencies.stream().anyMatch(t -> t.equals(dependency.getKey().sub))) { + isParent = false; + break; + } else { + isParent = true; + } + dependecies.add(new Dependency(dependency.getKey().sub.key, dependency.getValue().instanceId)); + } + if (isParent) { + dependencieInstances.clear(); + } + } + + // create app or get as dependency + if (oldAppConfig == null) { + var appId = dc.app.getAppId(); + + var neededApp = this.findNeededApp(dc, appId); + if (neededApp != null) { + if (neededApp.isPresent()) { + if (dc.sub.updatePolicy.isAllowedToUpdate(this.getAppManagerImpl().getInstantiatedApps(), null, + neededApp.get())) { + try { + // TODO test update app + var oldConfig = dc.app.getAppConfiguration(ConfigurationTarget.UPDATE, + neededApp.get().properties, language); + for (var entry : neededApp.get().properties.entrySet()) { + // add old values which are not set by the DependecyDeclaration + if (!dc.properties.has(entry.getKey())) { + dc.properties.add(entry.getKey(), entry.getValue()); + } + } +// this.getNewAppConfigWithReplacedIds(app, oldInstance, oldInstance, null, null) + var newConfig = dc.app.getAppConfiguration(ConfigurationTarget.UPDATE, dc.properties, + language); + this.componentsTask.aggregate(newConfig, oldConfig); + this.schedulerTask.aggregate(newConfig, oldConfig); + + this.staticIpTask.aggregate(newConfig, oldConfig); + } catch (OpenemsNamedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), dc.alias, + neededApp.get().instanceId, dc.properties, dependecies); + modifiedOrCreatedApps.add(newAppInstance); + dependencieInstances.put(dc, newAppInstance); + return true; + } + } else { + return false; + } + + } + + var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), dc.alias, oldAppConfig.instance.instanceId, + dc.properties, dependecies); + modifiedOrCreatedApps.add(newAppInstance); + dependencieInstances.put(dc, newAppInstance); + var otherAppConfigs = this.getAppManagerImpl().getOtherAppConfigurations( + modifiedOrCreatedApps.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + + try { + var newAppConfig = this.getNewAppConfigWithReplacedIds(dc.app, oldAppConfig.instance, newAppInstance, + AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), language); + + this.componentsTask.aggregate(newAppConfig, oldAppConfig.config); + this.schedulerTask.aggregate(newAppConfig, oldAppConfig.config); + this.staticIpTask.aggregate(newAppConfig, oldAppConfig.config); + + } catch (OpenemsNamedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return true; + }); + + // TODO delete unused dependency Apps + for (var entry : oldInstances.entrySet()) { + var config = entry.getValue().config; + this.componentsTask.aggregate(null, config); + this.schedulerTask.aggregate(null, config); + this.staticIpTask.aggregate(null, config); + } + + var otherAppConfigs = this.getAppManagerImpl() + .getOtherAppConfigurations(modifiedOrCreatedApps.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + + this.componentsTask.create(user, otherAppConfigs); + + this.schedulerTask.setCreatedComponents(this.componentsTask.getCreatedComponents()); + this.schedulerTask.setDeletedComponents(this.componentsTask.getDeletedComponents()); + this.schedulerTask.create(user, otherAppConfigs); + + this.staticIpTask.create(user, otherAppConfigs); + + return new UpdateValues(modifiedOrCreatedApps, + oldInstances.entrySet().stream().map(t -> t.getValue().instance).toList()); + } + + public static final class UpdateValues { + public final List modifiedOrCreatedApps; + public final List deletedApps; + + public UpdateValues(List modifiedOrCreatedApps, List deletedApps) { + this.modifiedOrCreatedApps = modifiedOrCreatedApps; + this.deletedApps = deletedApps; + } + + } + + private static class AppIdKey implements Comparable { + public final String appId; + public final String key; + + public AppIdKey(String appId, String key) { + this.appId = appId; + this.key = key; + } + + @Override + public int compareTo(AppIdKey o) { + return this.toString().compareTo(o.toString()); + } + + @Override + public String toString() { + return appId + ":" + key; + } } @Override @@ -145,9 +326,9 @@ public List deleteApp(User user, OpenemsAppInstance instance case NEVER: return false; case IF_MINE: - if (this.getAppManagerImpl().getInstantiatedApps().stream().anyMatch(a -> !a.equals(dc.parent) - && a.dependencies != null - && a.dependencies.stream().anyMatch(d -> d.instanceId.equals(dc.instance.instanceId)))) { + if (this.getAppManagerImpl().getInstantiatedApps().stream() + .anyMatch(a -> !a.equals(dc.parentInstance) && a.dependencies != null && a.dependencies + .stream().anyMatch(d -> d.instanceId.equals(dc.instance.instanceId)))) { return false; } break; @@ -159,7 +340,9 @@ public List deleteApp(User user, OpenemsAppInstance instance deletedInstances.add(dc.instance); try { - componentsTask.aggregate(dc.config, null); + this.componentsTask.aggregate(null, dc.config); + this.schedulerTask.aggregate(null, dc.config); + this.staticIpTask.aggregate(null, dc.config); } catch (OpenemsNamedException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -168,14 +351,46 @@ public List deleteApp(User user, OpenemsAppInstance instance return true; }); - var otherAppComponents = this.getAppManagerImpl() - .getOtherAppComponents(deletedInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + var otherAppConfigs = this.getAppManagerImpl() + .getOtherAppConfigurations(deletedInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + + // delete components + this.componentsTask.delete(user, otherAppConfigs); + + // remove ids in scheduler + this.schedulerTask.setDeletedComponents(this.componentsTask.getDeletedComponents()); + this.schedulerTask.delete(user, otherAppConfigs); - componentsTask.delete(user, otherAppComponents); + // remove static ips + this.staticIpTask.delete(user, otherAppConfigs); return deletedInstances; } + protected static List getComponentsFromConfigs(List configs) { + var components = new LinkedList(); + for (var config : configs) { + components.addAll(config.components); + } + return components; + } + + protected static List getSchedulerIdsFromConfigs(List configs) { + var ids = new LinkedList(); + for (var config : configs) { + ids.addAll(config.schedulerExecutionOrder); + } + return ids; + } + + protected static List getStaticIpsFromConfigs(List configs) { + var ips = new LinkedList(); + for (var config : configs) { + ips.addAll(config.ips); + } + return ips; + } + /** * * @param dc @@ -228,14 +443,16 @@ private Optional findNeededApp(DependencyConfig dc, String a */ private DependencyConfig foreachDependency(OpenemsApp app, String alias, JsonObject defaultProperties, ConfigurationTarget target, Function function, DependencyDeclaration sub, - Language l) throws OpenemsNamedException { + Language l, OpenemsApp parent) throws OpenemsNamedException { + defaultProperties.addProperty("ALIAS", alias); var config = app.getAppConfiguration(target, defaultProperties, l); + defaultProperties.remove("ALIAS"); var dependencies = new LinkedList(); for (var dependency : config.dependencies) { try { var dependencyApp = this.getAppManagerImpl().findAppById(dependency.appId); var addingConfig = this.foreachDependency(dependencyApp, dependency.alias, dependency.properties, - target, function, dependency, l); + target, function, dependency, l, app); if (addingConfig != null) { dependencies.add(addingConfig); } @@ -244,7 +461,7 @@ private DependencyConfig foreachDependency(OpenemsApp app, String alias, JsonObj } } - var newConfig = new DependencyConfig(app, sub, config, alias, defaultProperties, dependencies); + var newConfig = new DependencyConfig(app, parent, sub, config, alias, defaultProperties, dependencies); if (function.apply(newConfig)) { return newConfig; } @@ -254,7 +471,7 @@ private DependencyConfig foreachDependency(OpenemsApp app, String alias, JsonObj private void foreachDependency(OpenemsApp app, String alias, JsonObject defaultProperties, ConfigurationTarget target, Language l, Function consumer) throws OpenemsNamedException { - this.foreachDependency(app, alias, defaultProperties, target, consumer, null, l); + this.foreachDependency(app, alias, defaultProperties, target, consumer, null, l, null); } private void foreachExistingDependecy(OpenemsAppInstance instance, ConfigurationTarget target, Language l, @@ -279,7 +496,9 @@ private DependencyConfig foreachExistingDependency(OpenemsAppInstance instance, Function consumer, OpenemsAppInstance parent, DependencyDeclaration sub, Language l) throws OpenemsNamedException { var app = this.getAppManagerImpl().findAppById(instance.appId); + instance.properties.addProperty("ALIAS", instance.alias); var config = app.getAppConfiguration(target, instance.properties, l); + instance.properties.remove("ALIAS"); var dependecies = new ArrayList(); if (instance.dependencies != null) { @@ -296,14 +515,66 @@ private DependencyConfig foreachExistingDependency(OpenemsAppInstance instance, } } } - var newConfig = new ExistingDependencyConfig(app, sub, config, instance.alias, instance.properties, dependecies, - parent, instance); + OpenemsApp parentApp = null; + if (parent != null) { + parentApp = this.getAppManagerImpl().findAppById(parent.appId); + } + var newConfig = new ExistingDependencyConfig(app, parentApp, sub, config, instance.alias, instance.properties, + dependecies, parent, instance); if (consumer.apply(newConfig)) { return newConfig; } return null; } +// private DependencyConfig foreachExistingAndNeededDependency(OpenemsAppInstance instance, ConfigurationTarget target, +// Function consumer, OpenemsAppInstance parent, DependencyDeclaration sub, +// Language l) throws OpenemsNamedException { +// var app = this.getAppManagerImpl().findAppById(instance.appId); +// instance.properties.addProperty("ALIAS", instance.alias); +// var config = app.getAppConfiguration(target, instance.properties, l); +// instance.properties.remove("ALIAS"); +// +// var notAddedDependencies = new ArrayList<>(config.dependencies); +// +// var dependecies = new ArrayList(); +// if (instance.dependencies != null) { +// dependecies = new ArrayList(instance.dependencies.size()); +// for (var dependency : instance.dependencies) { +// try { +// var dependecyApp = this.getAppManagerImpl().findInstaceById(dependency.instanceId); +// var subApp = config.dependencies.stream().filter(t -> t.key.equals(dependency.key)).findFirst() +// .get(); +// var dependecy = this.foreachExistingAndNeededDependency(dependecyApp, target, consumer, instance, +// subApp, l); +// notAddedDependencies.removeIf(d -> d.key.equals(dependency.key)); +// if (dependecy == null) { +// continue; +// } +// dependecies.add(dependecy); +// } catch (NoSuchElementException e) { +// // can not find app +// } +// } +// } +// for (var neededDependencie : notAddedDependencies) { +// var dependencieApp = this.getAppManagerImpl().findAppById(neededDependencie.appId); +// var dependecy = this.foreachDependency(dependencieApp, instance.alias, instance.properties, target, +// consumer, null, l); +// if (dependecy == null) { +// continue; +// } +// dependecies.add(dependecy); +// } +// +// var newConfig = new ExistingDependencyConfig(app, sub, config, instance.alias, instance.properties, dependecies, +// parent, instance); +// if (consumer.apply(newConfig)) { +// return newConfig; +// } +// return null; +// } + /** * Gets the component id s that can be replaced. * diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java index abc930fca4e..827ae8054e8 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java @@ -43,20 +43,23 @@ public ComponentAggregateTask(@Reference ComponentManager componentManager) { @Override public void aggregate(AppConfiguration config, AppConfiguration oldConfig) throws OpenemsNamedException { - this.components.addAll(config.components); - - if (oldConfig == null) { - return; + if (config != null) { + this.components.addAll(config.components); + } + if (oldConfig != null) { + var componentDiff = new ArrayList<>(oldConfig.components); + if (config != null) { + componentDiff.removeIf(t -> config.components.stream().anyMatch(c -> c.getId().equals(t.getId()))); + } + this.components2Delete.addAll(componentDiff); } - var componentDiff = new ArrayList<>(config.components); - componentDiff.removeIf(t -> oldConfig.components.stream().anyMatch(c -> c.getId().equals(t.getId()))); - this.components2Delete.addAll(componentDiff); } @Override - public void create(User user, List otherAppComponents) throws OpenemsNamedException { + public void create(User user, List otherAppConfigurations) throws OpenemsNamedException { this.createdComponents = new ArrayList(this.components.size()); var errors = new LinkedList(); + var otherAppComponents = AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigurations); // create components for (var comp : ComponentUtilImpl.order(this.components)) { /** @@ -100,7 +103,7 @@ public void create(User user, List otherAppComponents) thr // create new component try { this.createComponent(user, comp); - createdComponents.add(comp); + this.createdComponents.add(comp); } catch (OpenemsNamedException e) { var error = "Component[" + comp.getFactoryId() + "] cant be created!"; errors.add(error); @@ -110,10 +113,9 @@ public void create(User user, List otherAppComponents) thr } // delete components that were used from the old configurations - this.delete(user, this.components2Delete); + this.delete(user, otherAppConfigurations); this.components = new LinkedList<>(); - this.components2Delete = new LinkedList<>(); } /** @@ -125,12 +127,12 @@ public void create(User user, List otherAppComponents) thr * @return the id s of the components that got deleted */ @Override - public void delete(User user, List notMyComponents) throws OpenemsNamedException { - this.deletedComponents = new ArrayList<>(this.components.size()); + public void delete(User user, List otherAppConfigurations) throws OpenemsNamedException { + this.deletedComponents = new ArrayList<>(this.components2Delete.size()); List errors = new ArrayList<>(); - - for (var comp : this.components) { - if (notMyComponents.stream().parallel().anyMatch(t -> t.getId().equals(comp.getId()))) { + var notMyComponents = AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigurations); + for (var comp : this.components2Delete) { + if (notMyComponents.stream().anyMatch(t -> t.getId().equals(comp.getId()))) { continue; } var component = this.componentManager.getEdgeConfig().getComponent(comp.getId()).orElse(null); @@ -153,7 +155,6 @@ public void delete(User user, List notMyComponents) throws throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); } - this.components = new LinkedList<>(); this.components2Delete = new LinkedList<>(); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java index 97dddf2f9db..c30a31b1cba 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java @@ -10,6 +10,7 @@ public class DependencyConfig { public final OpenemsApp app; + public final OpenemsApp parent; // @Nullable if not a dependency of an app public final DependencyDeclaration sub; public final AppConfiguration config; @@ -19,17 +20,18 @@ public class DependencyConfig { public final List declarations; - public DependencyConfig(OpenemsApp app, DependencyDeclaration sub, AppConfiguration config, String alias, - JsonObject properties, List declarations) { + public DependencyConfig(OpenemsApp app, OpenemsApp parent, DependencyDeclaration sub, AppConfiguration config, + String alias, JsonObject properties, List declarations) { super(); this.app = app; + this.parent = parent; this.sub = sub; this.config = config; this.alias = alias; this.properties = properties; this.declarations = declarations; } - + public final boolean isDependency() { return this.sub != null; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java index a921432fcf9..11d58d945bf 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java @@ -1,11 +1,13 @@ package io.openems.edge.core.appmanager.dependency; import java.util.List; +import java.util.function.BiFunction; import java.util.function.Supplier; import com.google.common.base.Function; import com.google.gson.JsonObject; +import io.openems.common.function.ThrowingTriFunction; import io.openems.edge.core.appmanager.OpenemsAppInstance; public class DependencyDeclaration { @@ -35,24 +37,74 @@ public DependencyDeclaration(String key, String appId, String alias, CreatePolic } public static enum CreatePolicy { + /** * Always creates the dependent app except an {@link OpenemsAppInstance} is * already created and not a dependency of another app. */ - ALWAYS, // + ALWAYS((instances, app) -> true), // + /** * lazy singleton. */ - IF_NOT_EXISTING, // - NEVER, // + IF_NOT_EXISTING((instances, app) -> instances.stream().anyMatch(t -> t.appId.equals(app))), // + + /** + * Never allowed to create the app. + */ + NEVER((instances, app) -> false), // ; + + private final BiFunction, String, Boolean> isAllowedToCreateFunction; + + private CreatePolicy(BiFunction, String, Boolean> isAllowedToCreateFunction) { + this.isAllowedToCreateFunction = isAllowedToCreateFunction; + } + + /** + * Determines if the app of the given appId is allowed to create. This does not + * mean an existing app can't be used as the dependency. + * + * @param allInstances all app instances + * @param appId the appId + * @returns true if the app is allowed to create + */ + public final boolean isAllowedToCreate(List allInstances, String appId) { + return this.isAllowedToCreateFunction.apply(allInstances, appId); + } } public static enum UpdatePolicy { - ALWAYS, // - IF_MINE, // - NEVER, // + ALWAYS(v -> true), // + IF_MINE(v -> v.allInstances.stream() + .anyMatch(a -> !a.equals(v.parent) && a.dependencies != null + && a.dependencies.stream().anyMatch(d -> d.instanceId.equals(v.app2Update.instanceId)))), // + NEVER(v -> false), // ; + + private final Function isAllowedToUpdateFunction; + + private UpdatePolicy(Function isAllowedToUpdate) { + this.isAllowedToUpdateFunction = isAllowedToUpdate; + } + + public final boolean isAllowedToUpdate(List allInstances, OpenemsAppInstance parent, + OpenemsAppInstance app2Update) { + return this.isAllowedToUpdateFunction.apply(new AllowedToUpdateValues(allInstances, parent, app2Update)); + } + + private static class AllowedToUpdateValues { + public final List allInstances; + public final OpenemsAppInstance parent; + public final OpenemsAppInstance app2Update; + + public AllowedToUpdateValues(List allInstances, OpenemsAppInstance parent, + OpenemsAppInstance app2Update) { + this.allInstances = allInstances; + this.parent = parent; + this.app2Update = app2Update; + } + } } public static enum DeletePolicy { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java index 39d862e218e..33d6bfda04b 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java @@ -11,16 +11,16 @@ public class ExistingDependencyConfig extends DependencyConfig { // @Nullable - public final OpenemsAppInstance parent; + public final OpenemsAppInstance parentInstance; public final OpenemsAppInstance instance; // public final List existingDependencies = null; - public ExistingDependencyConfig(OpenemsApp app, DependencyDeclaration sub, AppConfiguration config, String alias, - JsonObject properties, List declarations, OpenemsAppInstance parent, - OpenemsAppInstance instance) { - super(app, sub, config, alias, properties, declarations); - this.parent = parent; + public ExistingDependencyConfig(OpenemsApp app, OpenemsApp parentApp, DependencyDeclaration sub, + AppConfiguration config, String alias, JsonObject properties, List declarations, + OpenemsAppInstance parent, OpenemsAppInstance instance) { + super(app, parentApp, sub, config, alias, properties, declarations); + this.parentInstance = parent; this.instance = instance; } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java index 40f108040ee..fa815c23605 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java @@ -22,6 +22,7 @@ public class SchedulerAggregateTask implements AggregateTask { private List removeIds; private List createdComponents; + private List deletedComponents; @Activate public SchedulerAggregateTask(@Reference ComponentUtil componentUtil) { @@ -32,33 +33,42 @@ public SchedulerAggregateTask(@Reference ComponentUtil componentUtil) { @Override public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) throws OpenemsNamedException { - order = componentUtil.insertSchedulerOrder(order, instance.schedulerExecutionOrder); - - if (oldConfig == null) { - return; + if (instance != null) { + this.order = this.componentUtil.insertSchedulerOrder(this.order, instance.schedulerExecutionOrder); + } + if (oldConfig != null) { + var schedulerIdDiff = new ArrayList<>(oldConfig.schedulerExecutionOrder); + if (instance != null) { + schedulerIdDiff.removeAll(instance.schedulerExecutionOrder); + } + this.removeIds.addAll(schedulerIdDiff); } - var schedulerIdDiff = new ArrayList<>(oldConfig.schedulerExecutionOrder); - schedulerIdDiff.removeAll(instance.schedulerExecutionOrder); - removeIds.addAll(schedulerIdDiff); } @Override - public void create(User user, List otherAppComponents) throws OpenemsNamedException { - order = componentUtil.insertSchedulerOrder(componentUtil.getSchedulerIds(), order); - componentUtil.updateScheduler(user, order, createdComponents); - + public void create(User user, List otherAppConfigurations) throws OpenemsNamedException { + this.order = componentUtil.insertSchedulerOrder(componentUtil.getSchedulerIds(), this.order); + this.componentUtil.updateScheduler(user, this.order, createdComponents); - order = new LinkedList<>(); - removeIds = new LinkedList<>(); + this.order = new LinkedList<>(); } @Override - public void delete(User user, List otherAppComponents) throws OpenemsNamedException { + public void delete(User user, List otherAppConfigurations) throws OpenemsNamedException { + this.removeIds.addAll(deletedComponents); + this.removeIds.removeAll(AppManagerAppHelperImpl.getSchedulerIdsFromConfigs(otherAppConfigurations)); + this.componentUtil.removeIdsInSchedulerIfExisting(user, this.removeIds); + + this.removeIds = new LinkedList<>(); } - + public final void setCreatedComponents(List createdComponents) { this.createdComponents = createdComponents; } + public final void setDeletedComponents(List deletedComponents) { + this.deletedComponents = deletedComponents; + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTask.java new file mode 100644 index 00000000000..6ed3f18de09 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTask.java @@ -0,0 +1,71 @@ +package io.openems.edge.core.appmanager.dependency; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.user.User; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.ComponentUtil; + +@Component(name = "AppManager.AggregateTask.StaticIpAggregateTask") +public class StaticIpAggregateTask implements AggregateTask { + + private final ComponentUtil componentUtil; + + private List ips; + private List ips2Delete; + + @Activate + public StaticIpAggregateTask(@Reference ComponentUtil componentUtil) { + this.componentUtil = componentUtil; + + this.ips = new LinkedList<>(); + this.ips2Delete = new LinkedList<>(); + } + + @Override + public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) throws OpenemsNamedException { + if (System.getProperty("os.name").startsWith("Windows")) { + return; + } + if (instance != null) { + ips.addAll(instance.ips); + } + if (oldConfig != null) { + var diff = new ArrayList<>(oldConfig.ips); + if (instance != null) { + diff.removeAll(instance.ips); + } + ips2Delete.addAll(diff); + } + } + + @Override + public void create(User user, List otherAppConfigurations) throws OpenemsNamedException { + if (System.getProperty("os.name").startsWith("Windows")) { + return; + } + + this.componentUtil.updateHosts(user, ips, ips2Delete); + + this.ips = new LinkedList<>(); + } + + @Override + public void delete(User user, List otherAppConfigurations) throws OpenemsNamedException { + if (System.getProperty("os.name").startsWith("Windows")) { + return; + } + ips.removeAll(AppManagerAppHelperImpl.getStaticIpsFromConfigs(otherAppConfigurations)); + this.componentUtil.updateHosts(user, null, ips2Delete); + this.ips2Delete = new LinkedList<>(); + + } + +} From 64d43d3d3f93e87657f385ce48b81a700bc9375b Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:01:19 +0200 Subject: [PATCH 05/30] fix missing translation --- .../io/openems/edge/core/appmanager/translation_de.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties index 675272bb513..1062817aea2 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties @@ -63,6 +63,8 @@ App.Hardware.KMtronic8Channel.Ip.description = Die IP-Adresse des Relais. # Heat App.Heat.CHP.Name = Blockheizkraftwerk (BHKW) +App.Heat.CHP.outputChannel.label = Ausgangskanal +App.Heat.CHP.outputChannel.description = Kanaladresse des digitalen Ausgangs. App.Heat.HeatingElement.Name = Heizstab App.Heat.HeatingElement.outputChannelPhaseL1.label = Ausgangskanal Phase L1 App.Heat.HeatingElement.outputChannelPhaseL1.description = Kanaladresse des digitalen Ausgangs für Phase L1. From 534959d325621f2995bd2419d408ad1b9ec0c8d1 Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:01:57 +0200 Subject: [PATCH 06/30] added validation for dependencies --- .../core/appmanager/AbstractOpenemsApp.java | 72 ++++++++++++++++++- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java index 280ca20f8ca..e6ab771a56a 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.NoSuchElementException; import java.util.ResourceBundle; import java.util.TreeMap; import java.util.stream.Collectors; @@ -27,6 +28,8 @@ import io.openems.common.types.EdgeConfig; import io.openems.common.types.EdgeConfig.Component; import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.dependency.Dependency; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; import io.openems.edge.core.appmanager.validator.CheckCardinality; import io.openems.edge.core.appmanager.validator.Checkable; import io.openems.edge.core.appmanager.validator.Validator; @@ -221,7 +224,7 @@ protected String getId(ConfigurationTarget t, EnumMap map * @param jProperties a JsonObject holding the App properties * @return a list of validation errors. Empty list says 'no errors' */ - private List getValidationErrors(JsonObject jProperties) { + private List getValidationErrors(JsonObject jProperties, List dependecies) { final var errors = new ArrayList(); final var properties = this.convertToEnumMap(errors, jProperties); @@ -234,7 +237,14 @@ private List getValidationErrors(JsonObject jProperties) { this.validateComponentConfigurations(errors, edgeConfig, appConfiguration); this.validateScheduler(errors, edgeConfig, appConfiguration); - // TODO validate dependencies + + try { + var appManager = (AppManagerImpl) this.componentManager.getComponent(AppManager.SINGLETON_COMPONENT_ID); + this.validateDependecies(errors, dependecies, appConfiguration.dependencies, appManager); + } catch (OpenemsNamedException e) { + // AppManager not found + errors.add("No AppManager reachabel!"); + } // TODO remove 'if' if it works on windows // changing network settings only works on linux @@ -337,7 +347,7 @@ public boolean hasProperty(String property) { @Override public void validate(OpenemsAppInstance instance) throws OpenemsNamedException { - var errors = this.getValidationErrors(instance.properties); + var errors = this.getValidationErrors(instance.properties, instance.dependencies); if (!errors.isEmpty()) { var error = errors.stream().collect(Collectors.joining("|")); throw new OpenemsException(error); @@ -453,6 +463,62 @@ private void validateScheduler(ArrayList errors, EdgeConfig actualEdgeCo } } + private void validateDependecies(List errors, List configDependencies, + List neededDependencies, AppManagerImpl appManager) { + + // find dependencies that are not in config + var notRegisteredDependencies = neededDependencies.stream().filter( + t -> configDependencies == null || !configDependencies.stream().anyMatch(o -> o.key.equals(t.key))) + .collect(Collectors.toList()); + + // check if exactly one app is available of the needed appId + for (var dependency : notRegisteredDependencies) { + var list = appManager.getInstantiatedApps().stream().filter(t -> t.appId.equals(dependency.appId)) + .collect(Collectors.toList()); + if (list.size() != 1) { + errors.add("Missing dependency with Key[" + dependency.key + "] needed App[" + dependency.appId + "]"); + } else { + checkProperties(errors, list.get(0).properties, neededDependencies, dependency.key); + } + } + + if (configDependencies == null) { + return; + } + // check if dependency apps are available + for (var dependency : configDependencies) { + try { + var appInstance = appManager.findInstaceById(dependency.instanceId); + // when available check properties + checkProperties(errors, appInstance.properties, neededDependencies, dependency.key); + } catch (NoSuchElementException e) { + errors.add("App with instance[" + dependency.instanceId + "] not available!"); + } + } + } + + private static final void checkProperties(List errors, JsonObject actualAppProperties, + List neededDependencies, String dependecyKey) { + var subApp = neededDependencies.stream().filter(t -> t.key.equals(dependecyKey)).findFirst().orElse(null); + if (subApp == null) { + errors.add("SubApp with Key[" + dependecyKey + "] not found!"); + return; + } + for (var property : subApp.properties.entrySet()) { + var actualValue = actualAppProperties.get(property.getKey()); + if (actualValue == null) { + errors.add("Value for Key[" + property.getKey() + "] not found!"); + continue; + } + var actual = actualValue.toString().replace("\"", ""); + var needed = property.getValue().toString().replace("\"", ""); + if (!actual.equals(needed)) { + errors.add("Value for Key[" + property.getKey() + "] does not match: expected[" + needed + "] actual[" + + actual + "] !"); + } + } + } + @Override public String getName(Language language) { return AbstractOpenemsApp.getTranslation(language, this.getAppId() + ".Name"); From 3b72a73c1540ecc393a76168b4f35b90055f7a2a Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:04:16 +0200 Subject: [PATCH 07/30] removed endless loop; adding and removing dependencies from other apps --- .../app/integratedsystem/FeneconHome.java | 33 +- .../edge/core/appmanager/AppManagerImpl.java | 10 +- .../appmanager/dependency/AggregateTask.java | 5 +- .../dependency/AppManagerAppHelper.java | 2 +- .../dependency/AppManagerAppHelperImpl.java | 489 +++++++++++------- .../dependency/ComponentAggregateTask.java | 17 +- .../dependency/DependencyDeclaration.java | 70 ++- .../dependency/SchedulerAggregateTask.java | 11 +- .../dependency/StaticIpAggregateTask.java | 10 +- .../core/appmanager/validator/CheckHome.java | 4 +- .../core/appmanager/AppManagerImplTest.java | 6 +- 11 files changed, 403 insertions(+), 254 deletions(-) diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java index d2eb08c5635..cc234492e8f 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java @@ -23,6 +23,7 @@ import io.openems.common.utils.EnumUtils; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.integratedsystem.FeneconHome.Property; +import io.openems.edge.app.meter.SocomecMeter; import io.openems.edge.app.pvselfconsumption.GridOptimizedCharge; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.core.appmanager.AbstractOpenemsApp; @@ -222,15 +223,15 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { ); - if (EnumUtils.getAsOptionalBoolean(p, Property.HAS_AC_METER).orElse(false)) { - components.add(new EdgeConfig.Component("meter1", bundle.getString(this.getAppId() + ".meter1.alias"), - "Meter.Socomec.Threephase", // - JsonUtils.buildJsonObject() // - .addProperty("enabled", true) // - .addProperty("modbus.id", modbusIdExternal) // - .addProperty("modbusUnitId", 6) // - .build())); - } +// if (EnumUtils.getAsOptionalBoolean(p, Property.HAS_AC_METER).orElse(false)) { +// components.add(new EdgeConfig.Component("meter1", bundle.getString(this.getAppId() + ".meter1.alias"), +// "Meter.Socomec.Threephase", // +// JsonUtils.buildJsonObject() // +// .addProperty("enabled", true) // +// .addProperty("modbus.id", modbusIdExternal) // +// .addProperty("modbusUnitId", 6) // +// .build())); +// } if (EnumUtils.getAsOptionalBoolean(p, Property.HAS_DC_PV1).orElse(false)) { var alias = EnumUtils.getAsOptionalString(p, Property.DC_PV1_ALIAS).orElse("DC-PV 1"); @@ -294,10 +295,24 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING, // DependencyDeclaration.UpdatePolicy.ALWAYS, // DependencyDeclaration.DeletePolicy.IF_MINE, // + DependencyDeclaration.DependencyDeletePolicy.NOT_ALLOWED, // JsonUtils.buildJsonObject() // .addProperty(GridOptimizedCharge.Property.MAXIMUM_SELL_TO_GRID_POWER.name(), maxFeedInPower) // .build())); + if (EnumUtils.getAsOptionalBoolean(p, Property.HAS_AC_METER).orElse(false)) { + dependencies.add(new DependencyDeclaration("AC_METER", // + "App.Meter.Socomec", // + bundle.getString("App.PvSelfConsumption.GridOptimizedCharge.Name"), // + DependencyDeclaration.CreatePolicy.ALWAYS, // + DependencyDeclaration.UpdatePolicy.ALWAYS, // + DependencyDeclaration.DeletePolicy.ALWAYS, // + DependencyDeclaration.DependencyDeletePolicy.NOT_ALLOWED, // + JsonUtils.buildJsonObject() // + .addProperty(SocomecMeter.Property.MODBUS_UNIT_ID.name(), 6) // + .build())); + + } return new AppConfiguration(components, schedulerExecutionOrder, null, dependencies); }; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java index 5fb8c9b83c0..1ee6ca8360f 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java @@ -557,6 +557,8 @@ protected CompletableFuture handleAddAppInstanceRequest( // } // Update App-Manager configuration try { + // replace old instances with new ones + this.instantiatedApps.removeAll(installedApps); this.instantiatedApps.addAll(installedApps); this.updateAppManagerConfiguration(user, this.instantiatedApps); } catch (OpenemsNamedException e) { @@ -586,7 +588,7 @@ private CompletableFuture handleDeleteAppInsta return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.id)); } List errors = new Vector<>(); - var removedApps = this.appHelper.deleteApp(user, instance); + var result = this.appHelper.deleteApp(user, instance); // var app = this.findAppById(instance.appId); // @@ -627,7 +629,11 @@ private CompletableFuture handleDeleteAppInsta // } try { // this.instantiatedApps.remove(instance); - this.instantiatedApps.removeAll(removedApps); + this.instantiatedApps.removeAll(result.deletedApps); + + // replace modified apps + this.instantiatedApps.removeAll(result.modifiedOrCreatedApps); + this.instantiatedApps.addAll(result.modifiedOrCreatedApps); this.updateAppManagerConfiguration(user, this.instantiatedApps); } catch (OpenemsNamedException e) { errors.add(new OpenemsException(e.toString())); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java index fb56cff148f..25f4daec83a 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java @@ -10,11 +10,12 @@ public interface AggregateTask { // public boolean shouldAggregate(DependencyConfig instance); - public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) throws OpenemsNamedException; + public void aggregate(AppConfiguration instance, AppConfiguration oldConfig); public void create(User user, List otherAppConfigurations) throws OpenemsNamedException; -// public abstract void update() throws OpenemsException; public void delete(User user, List otherAppConfigurations) throws OpenemsNamedException; + public void reset(); + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java index b628c56f721..629285644a8 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java @@ -43,6 +43,6 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj * @returns a list of the removed {@link OpenemsAppInstance}s * @throws OpenemsNamedException */ - public List deleteApp(User user, OpenemsAppInstance instance) throws OpenemsNamedException; + public UpdateValues deleteApp(User user, OpenemsAppInstance instance) throws OpenemsNamedException; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java index df908895f3f..cb9434f0110 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java @@ -1,13 +1,18 @@ package io.openems.edge.core.appmanager.dependency; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; import java.util.TreeMap; import java.util.UUID; +import java.util.Vector; +import java.util.stream.Collectors; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -18,6 +23,7 @@ import com.google.gson.JsonObject; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; @@ -35,13 +41,15 @@ @Component public class AppManagerAppHelperImpl implements AppManagerAppHelper { - private ComponentManager componentManager; - private ComponentUtil componentUtil; + private final ComponentManager componentManager; + private final ComponentUtil componentUtil; // tasks - private ComponentAggregateTask componentsTask; - private SchedulerAggregateTask schedulerTask; - private StaticIpAggregateTask staticIpTask; + private final ComponentAggregateTask componentsTask; + private final SchedulerAggregateTask schedulerTask; + private final StaticIpAggregateTask staticIpTask; + + private final AggregateTask[] tasks; @Activate public AppManagerAppHelperImpl(@Reference ComponentManager componentManager, @Reference ComponentUtil componentUtil, @@ -53,126 +61,62 @@ public AppManagerAppHelperImpl(@Reference ComponentManager componentManager, @Re this.componentsTask = (ComponentAggregateTask) componentsTask; this.schedulerTask = (SchedulerAggregateTask) schedulerTask; this.staticIpTask = (StaticIpAggregateTask) staticIpTask; + tasks = new AggregateTask[] { componentsTask, schedulerTask, staticIpTask }; } @Override public List installApp(User user, JsonObject properties, String alias, OpenemsApp app) throws OpenemsNamedException { - final var language = user == null ? null : user.getLanguage(); - - var createdInstances = new LinkedList(); - var dependencieInstances = new HashMap(); - this.foreachDependency(app, alias, properties, ConfigurationTarget.ADD, language, dc -> { - - // TODO could be any relay - var appId = dc.app.getAppId(); - - var neededApp = this.findNeededApp(dc, appId); - if (neededApp != null) { - if (neededApp.isPresent()) { - dependencieInstances.put(dc, neededApp.get()); - if (dc.sub.updatePolicy.isAllowedToUpdate(this.getAppManagerImpl().getInstantiatedApps(), null, - neededApp.get())) { - // TODO update app - - } - return true; - } - } else { - return false; - } - - // TODO what if the created app is a dependency needed from another app? - - // map dependencies if this is the parent - var dependecies = new ArrayList(dependencieInstances.size()); - if (!dependencieInstances.isEmpty()) { - var isParent = !dc.isDependency(); - for (var dependency : dependencieInstances.entrySet()) { - if (!isParent - && !dc.config.dependencies.stream().anyMatch(t -> t.equals(dependency.getKey().sub))) { - isParent = false; - break; - } else { - isParent = true; - } - dependecies.add(new Dependency(dependency.getKey().sub.key, dependency.getValue().instanceId)); - } - if (isParent) { - dependencieInstances.clear(); - } - } - - var instance = new OpenemsAppInstance(dc.app.getAppId(), dc.alias, UUID.randomUUID(), dc.properties, - dependecies); - createdInstances.add(instance); - dependencieInstances.put(dc, instance); - - var otherAppConfigs = this.getAppManagerImpl() - .getOtherAppConfigurations(createdInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); - try { - var newAppConfig = this.getNewAppConfigWithReplacedIds(dc.app, null, instance, - AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), language); - this.componentsTask.aggregate(newAppConfig, null); - this.schedulerTask.aggregate(newAppConfig, null); - this.staticIpTask.aggregate(newAppConfig, null); - } catch (OpenemsNamedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - return true; - }); - - var otherAppConfigs = this.getAppManagerImpl() - .getOtherAppConfigurations(createdInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); - - // TODO Validate Checkables after setting ips - this.staticIpTask.create(user, otherAppConfigs); - - this.componentsTask.create(user, otherAppConfigs); - - this.schedulerTask.setCreatedComponents(this.componentsTask.getCreatedComponents()); - this.schedulerTask.create(user, otherAppConfigs); - - return createdInstances; + return this.updateApp(user, null, properties, alias, app).modifiedOrCreatedApps; } @Override public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObject properties, String alias, OpenemsApp app) throws OpenemsNamedException { + this.resetTasks(); + // TODO maybe check for all apps + // if also checking dependencies these may be inconsistent + // e. g. install HOME is requested it may have a dependency on a SOCOMEC Meter + // but the meter has a checkable that there has to be a HOME installed + AppManagerAppHelperImpl.checkStatus(app); + final var language = user == null ? null : user.getLanguage(); + var errors = new LinkedList(); + var oldInstances = new TreeMap(); var dependencieInstances = new HashMap(); // all existing app dependencies - this.foreachExistingDependecy(oldInstance, ConfigurationTarget.UPDATE, language, dc -> { - if (!dc.isDependency()) { + if (oldInstance != null) { + this.foreachExistingDependecy(oldInstance, ConfigurationTarget.UPDATE, language, dc -> { + if (!dc.isDependency()) { + return true; + } + oldInstances.put(new AppIdKey(dc.parentInstance.appId, dc.sub.key), dc); return true; - } - oldInstances.put(new AppIdKey(dc.parentInstance.appId, dc.sub.key), dc); - return true; - }); + }); + } var modifiedOrCreatedApps = new LinkedList(); // update app and its dependencies this.foreachDependency(app, alias, properties, ConfigurationTarget.UPDATE, language, dc -> { - - ExistingDependencyConfig oldAppConfig; - if (dc.isDependency()) { - oldAppConfig = oldInstances.remove(new AppIdKey(dc.parent.getAppId(), dc.sub.key)); - if (oldAppConfig != null) { - for (var entry : oldAppConfig.properties.entrySet()) { - // add old values which are not set by the DependecyDeclaration - if (!dc.properties.has(entry.getKey())) { - dc.properties.add(entry.getKey(), entry.getValue()); + ExistingDependencyConfig oldAppConfig = null; + if (oldInstance != null) { + if (dc.isDependency()) { + oldAppConfig = oldInstances.remove(new AppIdKey(dc.parent.getAppId(), dc.sub.key)); + if (oldAppConfig != null) { + for (var entry : oldAppConfig.properties.entrySet()) { + // add old values which are not set by the DependecyDeclaration + if (!dc.properties.has(entry.getKey())) { + dc.properties.add(entry.getKey(), entry.getValue()); + } } - } + } + } else { + oldAppConfig = new ExistingDependencyConfig(app, null, null, null, oldInstance.alias, + oldInstance.properties, null, null, oldInstance); } - } else { - oldAppConfig = new ExistingDependencyConfig(app, null, null, null, oldInstance.alias, - oldInstance.properties, null, oldInstance, oldInstance); } // map dependencies if this is the parent @@ -194,18 +138,26 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj } } + var otherAppConfigs = this.getAppManagerImpl().getOtherAppConfigurations( + modifiedOrCreatedApps.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + // create app or get as dependency if (oldAppConfig == null) { var appId = dc.app.getAppId(); var neededApp = this.findNeededApp(dc, appId); if (neededApp != null) { + AppConfiguration oldConfig = null; + UUID instanceId; + OpenemsAppInstance oldInstanceOfCurrentApp = null; if (neededApp.isPresent()) { + instanceId = neededApp.get().instanceId; + oldInstanceOfCurrentApp = neededApp.get(); if (dc.sub.updatePolicy.isAllowedToUpdate(this.getAppManagerImpl().getInstantiatedApps(), null, neededApp.get())) { try { // TODO test update app - var oldConfig = dc.app.getAppConfiguration(ConfigurationTarget.UPDATE, + oldConfig = dc.app.getAppConfiguration(ConfigurationTarget.UPDATE, neededApp.get().properties, language); for (var entry : neededApp.get().properties.entrySet()) { // add old values which are not set by the DependecyDeclaration @@ -213,36 +165,67 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj dc.properties.add(entry.getKey(), entry.getValue()); } } -// this.getNewAppConfigWithReplacedIds(app, oldInstance, oldInstance, null, null) - var newConfig = dc.app.getAppConfiguration(ConfigurationTarget.UPDATE, dc.properties, - language); - this.componentsTask.aggregate(newConfig, oldConfig); - this.schedulerTask.aggregate(newConfig, oldConfig); - - this.staticIpTask.aggregate(newConfig, oldConfig); } catch (OpenemsNamedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } - var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), dc.alias, - neededApp.get().instanceId, dc.properties, dependecies); - modifiedOrCreatedApps.add(newAppInstance); - dependencieInstances.put(dc, newAppInstance); - return true; + } else { + // create app + instanceId = UUID.randomUUID(); + // TODO check if the created app can satisfy another app dependency + for (var instance : this.getAppManagerImpl().getInstantiatedApps()) { + var neededDependency = this.getNeededDependencyTo(instance, dc.app.getAppId()); + if (neededDependency == null) { + continue; + } + if (neededDependency.createPolicy == DependencyDeclaration.CreatePolicy.ALWAYS) { + continue; + } + var alreadyModifiedAppIndex = modifiedOrCreatedApps.indexOf(instance); + OpenemsAppInstance replaceApp = instance; + if (alreadyModifiedAppIndex != -1) { + replaceApp = modifiedOrCreatedApps.get(alreadyModifiedAppIndex); + } + var newDependencies = new ArrayList(); + if (replaceApp.dependencies != null) { + newDependencies.addAll(replaceApp.dependencies); + } + newDependencies.add(new Dependency(neededDependency.key, instanceId)); + modifiedOrCreatedApps.remove(replaceApp); + modifiedOrCreatedApps.add(new OpenemsAppInstance(replaceApp.appId, replaceApp.alias, + replaceApp.instanceId, replaceApp.properties, newDependencies)); + } } + var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), dc.alias, instanceId, dc.properties, + dependecies); + modifiedOrCreatedApps.add(newAppInstance); + dependencieInstances.put(dc, newAppInstance); + try { + var newConfig = this.getNewAppConfigWithReplacedIds(dc.app, oldInstanceOfCurrentApp, + newAppInstance, AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), + language); + + this.componentsTask.aggregate(newConfig, oldConfig); + this.schedulerTask.aggregate(newConfig, oldConfig); + this.staticIpTask.aggregate(newConfig, oldConfig); + } catch (OpenemsNamedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return true; } else { + return false; } } + // update existing apps var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), dc.alias, oldAppConfig.instance.instanceId, dc.properties, dependecies); modifiedOrCreatedApps.add(newAppInstance); dependencieInstances.put(dc, newAppInstance); - var otherAppConfigs = this.getAppManagerImpl().getOtherAppConfigurations( - modifiedOrCreatedApps.stream().map(t -> t.instanceId).toArray(UUID[]::new)); try { var newAppConfig = this.getNewAppConfigWithReplacedIds(dc.app, oldAppConfig.instance, newAppInstance, @@ -260,29 +243,80 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj return true; }); - // TODO delete unused dependency Apps + // add removed apps for deletion for (var entry : oldInstances.entrySet()) { - var config = entry.getValue().config; - this.componentsTask.aggregate(null, config); - this.schedulerTask.aggregate(null, config); - this.staticIpTask.aggregate(null, config); + var dc = entry.getValue(); + if (!dc.sub.deletePolicy.isAllowedToDelete(this.getAppManagerImpl().getInstantiatedApps(), + dc.parentInstance, dc.instance)) { + continue; + } + this.componentsTask.aggregate(null, dc.config); + this.schedulerTask.aggregate(null, dc.config); + this.staticIpTask.aggregate(null, dc.config); } + var ignoreInstances = new ArrayList<>(modifiedOrCreatedApps); + ignoreInstances.addAll(oldInstances.entrySet().stream().map(t -> t.getValue().instance).toList()); + var otherAppConfigs = this.getAppManagerImpl() - .getOtherAppConfigurations(modifiedOrCreatedApps.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + .getOtherAppConfigurations(ignoreInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + + try { + // create or delete unused components + this.componentsTask.create(user, otherAppConfigs); + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); + } - this.componentsTask.create(user, otherAppConfigs); + try { + // update scheduler execute order + this.schedulerTask.setCreatedComponents(this.componentsTask.getCreatedComponents()); + this.schedulerTask.setDeletedComponents(this.componentsTask.getDeletedComponents()); + this.schedulerTask.create(user, otherAppConfigs); + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); + } - this.schedulerTask.setCreatedComponents(this.componentsTask.getCreatedComponents()); - this.schedulerTask.setDeletedComponents(this.componentsTask.getDeletedComponents()); - this.schedulerTask.create(user, otherAppConfigs); - - this.staticIpTask.create(user, otherAppConfigs); + try { + // update static ips + this.staticIpTask.create(user, otherAppConfigs); + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); + } + + if (!errors.isEmpty()) { + throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); + } return new UpdateValues(modifiedOrCreatedApps, oldInstances.entrySet().stream().map(t -> t.getValue().instance).toList()); } + private final DependencyDeclaration getNeededDependencyTo(OpenemsAppInstance instance, String appId) { + var app = this.getAppManagerImpl().findAppById(instance.appId); + try { + var neededDependencies = app.getAppConfiguration(ConfigurationTarget.UPDATE, instance.properties, + null).dependencies; + if (neededDependencies == null || neededDependencies.isEmpty()) { + return null; + } + for (var neededDependency : neededDependencies) { + // remove already satisfied dependencies + if (instance.dependencies != null + && instance.dependencies.stream().anyMatch(d -> d.key.equals(neededDependency.key))) { + continue; + } + if (neededDependency.appId.equals(appId)) { + return neededDependency; + } + } + } catch (OpenemsNamedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; + } + public static final class UpdateValues { public final List modifiedOrCreatedApps; public final List deletedApps; @@ -315,12 +349,19 @@ public String toString() { } @Override - public List deleteApp(User user, OpenemsAppInstance instance) throws OpenemsNamedException { + public UpdateValues deleteApp(User user, OpenemsAppInstance instance) throws OpenemsNamedException { + this.resetTasks(); var deletedInstances = new LinkedList(); final var language = user == null ? null : user.getLanguage(); this.foreachExistingDependecy(instance, ConfigurationTarget.DELETE, language, dc -> { + // check if the app is allowed to be delete + if (!this.isAllowedToDelete(dc.instance, dc.parentInstance != null ? dc.parentInstance.instanceId : null)) { + return false; + } + + // check if dependency is allowed to be deleten by its parent if (dc.isDependency()) { switch (dc.sub.deletePolicy) { case NEVER: @@ -339,23 +380,34 @@ public List deleteApp(User user, OpenemsAppInstance instance deletedInstances.add(dc.instance); - try { - this.componentsTask.aggregate(null, dc.config); - this.schedulerTask.aggregate(null, dc.config); - this.staticIpTask.aggregate(null, dc.config); - } catch (OpenemsNamedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + this.componentsTask.aggregate(null, dc.config); + this.schedulerTask.aggregate(null, dc.config); + this.staticIpTask.aggregate(null, dc.config); return true; }); + var unmodifiedApps = this + .getAppsWithReferenceTo(deletedInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + + var modifiedApps = new ArrayList(unmodifiedApps.size()); + for (var app : unmodifiedApps) { + var dependencies = new ArrayList<>(app.dependencies); + dependencies.removeIf(d -> deletedInstances.stream().anyMatch(i -> i.instanceId.equals(d.instanceId))); + modifiedApps.add(new OpenemsAppInstance(app.appId, // + app.alias, app.instanceId, app.properties, dependencies)); + } + var otherAppConfigs = this.getAppManagerImpl() .getOtherAppConfigurations(deletedInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); - // delete components - this.componentsTask.delete(user, otherAppConfigs); + // TODO errors + try { + // delete components + this.componentsTask.delete(user, otherAppConfigs); + } catch (OpenemsNamedException e) { + + } // remove ids in scheduler this.schedulerTask.setDeletedComponents(this.componentsTask.getDeletedComponents()); @@ -364,7 +416,72 @@ public List deleteApp(User user, OpenemsAppInstance instance // remove static ips this.staticIpTask.delete(user, otherAppConfigs); - return deletedInstances; + return new UpdateValues(modifiedApps, deletedInstances); + } + + public List getAppsWithReferenceTo(UUID... instanceIds) { + return this.getAppManagerImpl().getInstantiatedApps() // + .stream() // + .filter(i -> i.dependencies != null && !i.dependencies.isEmpty()) // + .filter(i -> i.dependencies.stream().anyMatch( // + d -> Arrays.stream(instanceIds).anyMatch(id -> id.equals(d.instanceId)))) // + .toList(); + } + + public final boolean isAllowedToDelete(OpenemsAppInstance instance, UUID... ignoreIds) { + for (var i : this.getAppManagerImpl().getInstantiatedApps()) { + if (Arrays.stream(ignoreIds).anyMatch(id -> i.instanceId.equals(id))) { + continue; + } + if (i.dependencies == null || i.dependencies.isEmpty()) { + continue; + } + var app = this.getAppManagerImpl().findAppById(i.appId); + if (i.dependencies.stream().filter(d -> d.instanceId.equals(instance.instanceId)) // + .anyMatch(d -> { + try { + var dd = app.getAppConfiguration(ConfigurationTarget.UPDATE, i.properties, null) // + .dependencies.stream().filter(f -> f.key.equals(d.key)).findFirst().get(); + switch (dd.dependencyDeletePolicy) { + case ALLOWED: + return false; + case NOT_ALLOWED: + return true; + } + } catch (OpenemsNamedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return false; + })) { + return false; + } + } + return true; + } + + private void resetTasks() { + for (var task : tasks) { + task.reset(); + } + } + + protected static void checkStatus(OpenemsApp openemsApp) throws OpenemsNamedException { + var validator = openemsApp.getValidator(); + var status = validator.getStatus(); + switch (validator.getStatus()) { + case INCOMPATIBLE: + throw new OpenemsException("App is not compatible! " + + validator.getErrorCompatibleMessages().stream().collect(Collectors.joining(";"))); + case COMPATIBLE: + throw new OpenemsException("App can not be installed! " + + validator.getErrorCompatibleMessages().stream().collect(Collectors.joining(";"))); + case INSTALLABLE: + // continue + break; + default: + throw new OpenemsException("Status '" + status.name() + "' is not implemented."); + } } protected static List getComponentsFromConfigs(List configs) { @@ -435,15 +552,27 @@ private Optional findNeededApp(DependencyConfig dc, String a * Order bottom -> top. * * @param app + * @param alias * @param defaultProperties * @param target - * @param function returns true if the instance gets created or already - * exists + * @param function returns true if the instance gets created or + * already exists + * @param sub + * @param l + * @param parent + * @param alreadyIteratedApps the apps that already got iterated thru to avoid + * endless loop. e. g. if two apps have each other as + * a dependency + * @return * @throws OpenemsNamedException */ private DependencyConfig foreachDependency(OpenemsApp app, String alias, JsonObject defaultProperties, ConfigurationTarget target, Function function, DependencyDeclaration sub, - Language l, OpenemsApp parent) throws OpenemsNamedException { + Language l, OpenemsApp parent, Set alreadyIteratedApps) throws OpenemsNamedException { + if (alreadyIteratedApps == null) { + alreadyIteratedApps = new HashSet<>(); + } + alreadyIteratedApps.add(app); defaultProperties.addProperty("ALIAS", alias); var config = app.getAppConfiguration(target, defaultProperties, l); defaultProperties.remove("ALIAS"); @@ -451,8 +580,11 @@ private DependencyConfig foreachDependency(OpenemsApp app, String alias, JsonObj for (var dependency : config.dependencies) { try { var dependencyApp = this.getAppManagerImpl().findAppById(dependency.appId); + if (alreadyIteratedApps.contains(dependencyApp)) { + continue; + } var addingConfig = this.foreachDependency(dependencyApp, dependency.alias, dependency.properties, - target, function, dependency, l, app); + target, function, dependency, l, app, alreadyIteratedApps); if (addingConfig != null) { dependencies.add(addingConfig); } @@ -471,12 +603,12 @@ private DependencyConfig foreachDependency(OpenemsApp app, String alias, JsonObj private void foreachDependency(OpenemsApp app, String alias, JsonObject defaultProperties, ConfigurationTarget target, Language l, Function consumer) throws OpenemsNamedException { - this.foreachDependency(app, alias, defaultProperties, target, consumer, null, l, null); + this.foreachDependency(app, alias, defaultProperties, target, consumer, null, l, null, null); } private void foreachExistingDependecy(OpenemsAppInstance instance, ConfigurationTarget target, Language l, Function consumer) throws OpenemsNamedException { - this.foreachExistingDependency(instance, target, consumer, null, null, l); + this.foreachExistingDependency(instance, target, consumer, null, null, l, null); } /** @@ -494,7 +626,11 @@ private void foreachExistingDependecy(OpenemsAppInstance instance, Configuration */ private DependencyConfig foreachExistingDependency(OpenemsAppInstance instance, ConfigurationTarget target, Function consumer, OpenemsAppInstance parent, DependencyDeclaration sub, - Language l) throws OpenemsNamedException { + Language l, Set alreadyIteratedApps) throws OpenemsNamedException { + if (alreadyIteratedApps == null) { + alreadyIteratedApps = new HashSet<>(); + } + alreadyIteratedApps.add(instance); var app = this.getAppManagerImpl().findAppById(instance.appId); instance.properties.addProperty("ALIAS", instance.alias); var config = app.getAppConfiguration(target, instance.properties, l); @@ -505,10 +641,14 @@ private DependencyConfig foreachExistingDependency(OpenemsAppInstance instance, dependecies = new ArrayList(instance.dependencies.size()); for (var dependency : instance.dependencies) { try { - var dependecyApp = this.getAppManagerImpl().findInstaceById(dependency.instanceId); + var dependencyApp = this.getAppManagerImpl().findInstaceById(dependency.instanceId); + if (alreadyIteratedApps.contains(dependencyApp)) { + continue; + } var subApp = config.dependencies.stream().filter(t -> t.key.equals(dependency.key)).findFirst() .get(); - var dependecy = this.foreachExistingDependency(dependecyApp, target, consumer, instance, subApp, l); + var dependecy = this.foreachExistingDependency(dependencyApp, target, consumer, instance, subApp, l, + alreadyIteratedApps); dependecies.add(dependecy); } catch (NoSuchElementException e) { // can not find app @@ -527,54 +667,6 @@ private DependencyConfig foreachExistingDependency(OpenemsAppInstance instance, return null; } -// private DependencyConfig foreachExistingAndNeededDependency(OpenemsAppInstance instance, ConfigurationTarget target, -// Function consumer, OpenemsAppInstance parent, DependencyDeclaration sub, -// Language l) throws OpenemsNamedException { -// var app = this.getAppManagerImpl().findAppById(instance.appId); -// instance.properties.addProperty("ALIAS", instance.alias); -// var config = app.getAppConfiguration(target, instance.properties, l); -// instance.properties.remove("ALIAS"); -// -// var notAddedDependencies = new ArrayList<>(config.dependencies); -// -// var dependecies = new ArrayList(); -// if (instance.dependencies != null) { -// dependecies = new ArrayList(instance.dependencies.size()); -// for (var dependency : instance.dependencies) { -// try { -// var dependecyApp = this.getAppManagerImpl().findInstaceById(dependency.instanceId); -// var subApp = config.dependencies.stream().filter(t -> t.key.equals(dependency.key)).findFirst() -// .get(); -// var dependecy = this.foreachExistingAndNeededDependency(dependecyApp, target, consumer, instance, -// subApp, l); -// notAddedDependencies.removeIf(d -> d.key.equals(dependency.key)); -// if (dependecy == null) { -// continue; -// } -// dependecies.add(dependecy); -// } catch (NoSuchElementException e) { -// // can not find app -// } -// } -// } -// for (var neededDependencie : notAddedDependencies) { -// var dependencieApp = this.getAppManagerImpl().findAppById(neededDependencie.appId); -// var dependecy = this.foreachDependency(dependencieApp, instance.alias, instance.properties, target, -// consumer, null, l); -// if (dependecy == null) { -// continue; -// } -// dependecies.add(dependecy); -// } -// -// var newConfig = new ExistingDependencyConfig(app, sub, config, instance.alias, instance.properties, dependecies, -// parent, instance); -// if (consumer.apply(newConfig)) { -// return newConfig; -// } -// return null; -// } - /** * Gets the component id s that can be replaced. * @@ -704,7 +796,6 @@ private AppConfiguration getNewAppConfigWithReplacedIds(OpenemsApp app, OpenemsA } private final AppManagerImpl getAppManagerImpl() { -// return (AppManagerImpl) appManager; return (AppManagerImpl) componentManager.getEnabledComponentsOfType(AppManager.class).get(0); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java index 827ae8054e8..6c7884d4a5f 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java @@ -26,23 +26,27 @@ @Component(name = "AppManager.AggregateTask.CreateComponents") public class ComponentAggregateTask implements AggregateTask { + private ComponentManager componentManager; + private List components; private List components2Delete; - private ComponentManager componentManager; - private List createdComponents; private List deletedComponents; @Activate public ComponentAggregateTask(@Reference ComponentManager componentManager) { this.componentManager = componentManager; + } + + @Override + public void reset() { this.components = new LinkedList<>(); this.components2Delete = new LinkedList<>(); } @Override - public void aggregate(AppConfiguration config, AppConfiguration oldConfig) throws OpenemsNamedException { + public void aggregate(AppConfiguration config, AppConfiguration oldConfig) { if (config != null) { this.components.addAll(config.components); } @@ -112,10 +116,13 @@ public void create(User user, List otherAppConfigurations) thr } + if (!errors.isEmpty()) { + throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); + } + // delete components that were used from the old configurations this.delete(user, otherAppConfigurations); - this.components = new LinkedList<>(); } /** @@ -154,8 +161,6 @@ public void delete(User user, List otherAppConfigurations) thr if (!errors.isEmpty()) { throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); } - - this.components2Delete = new LinkedList<>(); } private void createComponent(User user, EdgeConfig.Component comp) throws OpenemsNamedException { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java index 11d58d945bf..6ec979c89b0 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java @@ -7,7 +7,6 @@ import com.google.common.base.Function; import com.google.gson.JsonObject; -import io.openems.common.function.ThrowingTriFunction; import io.openems.edge.core.appmanager.OpenemsAppInstance; public class DependencyDeclaration { @@ -21,12 +20,14 @@ public class DependencyDeclaration { public final UpdatePolicy updatePolicy; public final DeletePolicy deletePolicy; + public final DependencyDeletePolicy dependencyDeletePolicy; + // Dependency Supplier? private final Supplier supplierForAppId = null; private final Function, String> supplierForInstanceIdFromExisting = null; public DependencyDeclaration(String key, String appId, String alias, CreatePolicy createPolicy, - UpdatePolicy updatePolicy, DeletePolicy deletePolicy, JsonObject properties) { + UpdatePolicy updatePolicy, DeletePolicy deletePolicy, DependencyDeletePolicy dependencyDeletePolicy, JsonObject properties) { this.key = key; this.appId = appId; this.alias = alias; @@ -34,6 +35,7 @@ public DependencyDeclaration(String key, String appId, String alias, CreatePolic this.createPolicy = createPolicy; this.updatePolicy = updatePolicy; this.deletePolicy = deletePolicy; + this.dependencyDeletePolicy = dependencyDeletePolicy; } public static enum CreatePolicy { @@ -82,35 +84,63 @@ public static enum UpdatePolicy { NEVER(v -> false), // ; - private final Function isAllowedToUpdateFunction; + private final Function isAllowedToUpdateFunction; - private UpdatePolicy(Function isAllowedToUpdate) { + private UpdatePolicy(Function isAllowedToUpdate) { this.isAllowedToUpdateFunction = isAllowedToUpdate; } public final boolean isAllowedToUpdate(List allInstances, OpenemsAppInstance parent, OpenemsAppInstance app2Update) { - return this.isAllowedToUpdateFunction.apply(new AllowedToUpdateValues(allInstances, parent, app2Update)); + return this.isAllowedToUpdateFunction.apply(new AllowedToValues(allInstances, parent, app2Update)); } - private static class AllowedToUpdateValues { - public final List allInstances; - public final OpenemsAppInstance parent; - public final OpenemsAppInstance app2Update; - - public AllowedToUpdateValues(List allInstances, OpenemsAppInstance parent, - OpenemsAppInstance app2Update) { - this.allInstances = allInstances; - this.parent = parent; - this.app2Update = app2Update; - } - } } public static enum DeletePolicy { - ALWAYS, // - IF_MINE, // - NEVER, // + ALWAYS(v -> true), // + IF_MINE(v -> v.allInstances.stream() + .anyMatch(a -> !a.equals(v.parent) && a.dependencies != null + && a.dependencies.stream().anyMatch(d -> d.instanceId.equals(v.app2Update.instanceId)))), // + NEVER(v -> false), // + ; + + private final Function isAllowedToDeleteFunction; + + private DeletePolicy(Function isAllowedToDelete) { + this.isAllowedToDeleteFunction = isAllowedToDelete; + } + + public final boolean isAllowedToDelete(List allInstances, OpenemsAppInstance parent, + OpenemsAppInstance app2Delete) { + return isAllowedToDeleteFunction.apply(new AllowedToValues(allInstances, parent, app2Delete)); + } + } + + private static class AllowedToValues { + public final List allInstances; + public final OpenemsAppInstance parent; + public final OpenemsAppInstance app2Update; + + public AllowedToValues(List allInstances, OpenemsAppInstance parent, + OpenemsAppInstance app2Update) { + this.allInstances = allInstances; + this.parent = parent; + this.app2Update = app2Update; + } + } + + // TODO + public static enum DependencyUpdatePolicy { + ALL, // + ONLY_NOT_CONFIGURED_PROPERTIES, // + NO_PROPERTIES, // + ; + } + + public static enum DependencyDeletePolicy { + NOT_ALLOWED, // + ALLOWED, // ; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java index fa815c23605..9ab794aa327 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java @@ -18,6 +18,7 @@ public class SchedulerAggregateTask implements AggregateTask { private ComponentUtil componentUtil; + private List order; private List removeIds; @@ -27,12 +28,16 @@ public class SchedulerAggregateTask implements AggregateTask { @Activate public SchedulerAggregateTask(@Reference ComponentUtil componentUtil) { this.componentUtil = componentUtil; + } + + @Override + public void reset() { order = new LinkedList<>(); removeIds = new LinkedList<>(); } @Override - public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) throws OpenemsNamedException { + public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) { if (instance != null) { this.order = this.componentUtil.insertSchedulerOrder(this.order, instance.schedulerExecutionOrder); } @@ -49,8 +54,6 @@ public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) thr public void create(User user, List otherAppConfigurations) throws OpenemsNamedException { this.order = componentUtil.insertSchedulerOrder(componentUtil.getSchedulerIds(), this.order); this.componentUtil.updateScheduler(user, this.order, createdComponents); - - this.order = new LinkedList<>(); } @Override @@ -59,8 +62,6 @@ public void delete(User user, List otherAppConfigurations) thr this.removeIds.removeAll(AppManagerAppHelperImpl.getSchedulerIdsFromConfigs(otherAppConfigurations)); this.componentUtil.removeIdsInSchedulerIfExisting(user, this.removeIds); - - this.removeIds = new LinkedList<>(); } public final void setCreatedComponents(List createdComponents) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTask.java index 6ed3f18de09..27eeb1c0b25 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTask.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTask.java @@ -25,12 +25,16 @@ public class StaticIpAggregateTask implements AggregateTask { public StaticIpAggregateTask(@Reference ComponentUtil componentUtil) { this.componentUtil = componentUtil; + } + + @Override + public void reset() { this.ips = new LinkedList<>(); this.ips2Delete = new LinkedList<>(); } @Override - public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) throws OpenemsNamedException { + public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) { if (System.getProperty("os.name").startsWith("Windows")) { return; } @@ -53,8 +57,6 @@ public void create(User user, List otherAppConfigurations) thr } this.componentUtil.updateHosts(user, ips, ips2Delete); - - this.ips = new LinkedList<>(); } @Override @@ -64,8 +66,6 @@ public void delete(User user, List otherAppConfigurations) thr } ips.removeAll(AppManagerAppHelperImpl.getStaticIpsFromConfigs(otherAppConfigurations)); this.componentUtil.updateHosts(user, null, ips2Delete); - this.ips2Delete = new LinkedList<>(); - } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java index 31bf80d82c0..ab45cefbfb3 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java @@ -35,8 +35,8 @@ public boolean check() { // not every home has the home app installed but if a batterie of an home is // installed its probably a home and so the app can be used. // later there should only be checked if the home app is installed because if - // the configuration is wrong there may no home battery installed and so the app - // wouldn't be available even though it is a home + // the configuration is wrong there may be no home battery installed and so the + // app wouldn't be available even though it is a home return !batteries.isEmpty() || !this.checkAppsNotInstalled.check(); } diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java index b7510066f44..9e935254b10 100644 --- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java +++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java @@ -308,9 +308,9 @@ public void testCheckCardinalitySingle() throws Exception { @Test public void testCheckCardinalityMultiple() throws Exception { this.sut.instantiatedApps.add(new OpenemsAppInstance(this.kebaEvcsApp.getAppId(), "alias", UUID.randomUUID(), - JsonUtils.buildJsonObject().build())); + JsonUtils.buildJsonObject().build(), null)); this.sut.instantiatedApps.add(new OpenemsAppInstance(this.kebaEvcsApp.getAppId(), "alias", UUID.randomUUID(), - JsonUtils.buildJsonObject().build())); + JsonUtils.buildJsonObject().build(), null)); var checkable = new CheckCardinality(this.sut); checkable.setProperties(new Validator.MapBuilder<>(new TreeMap()) // .put("openemsApp", this.kebaEvcsApp) // @@ -322,7 +322,7 @@ public void testCheckCardinalityMultiple() throws Exception { @Test public void testCheckCardinalitySingleInCategorie() throws Exception { this.sut.instantiatedApps.add(new OpenemsAppInstance(this.awattarApp.getAppId(), "alias", UUID.randomUUID(), - JsonUtils.buildJsonObject().build())); + JsonUtils.buildJsonObject().build(), null)); var checkable = new CheckCardinality(this.sut); checkable.setProperties(new Validator.MapBuilder<>(new TreeMap()) // .put("openemsApp", this.stromdao) // From 6ca25aca58d2ddc5d3727fe004d6782572ab8c1a Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:05:39 +0200 Subject: [PATCH 08/30] added HOME setting for ripple control reveiver; minor bug fixes --- .../io/openems/edge/app/heat/HeatPump.java | 2 +- .../app/integratedsystem/FeneconHome.java | 26 +++++++++-- .../GridOptimizedCharge.java | 27 ++++++++---- .../edge/core/appmanager/JsonFormlyUtil.java | 6 +++ .../dependency/AppManagerAppHelperImpl.java | 43 +++++++++---------- .../dependency/ComponentAggregateTask.java | 9 ++-- .../core/appmanager/translation_de.properties | 2 + .../core/appmanager/translation_en.properties | 2 + 8 files changed, 80 insertions(+), 37 deletions(-) diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java index b3fda0be475..372cb9e3c1d 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java @@ -77,7 +77,7 @@ protected ThrowingTriFunction appConfigurationFactory() { var modbusIdExternal = "modbus1"; var emergencyReserveEnabled = EnumUtils.getAsBoolean(p, Property.EMERGENCY_RESERVE_ENABLED); + var rippleControlReceiverActive = EnumUtils.getAsBoolean(p, Property.RIPPLE_CONTROL_RECEIVER_AKTIV); // Battery-Inverter Settings var safetyCountry = EnumUtils.getAsString(p, Property.SAFETY_COUNTRY); - var maxFeedInPower = EnumUtils.getAsInt(p, Property.MAX_FEED_IN_POWER); + int maxFeedInPower; + if (!rippleControlReceiverActive) { + maxFeedInPower = EnumUtils.getAsInt(p, Property.MAX_FEED_IN_POWER); + } else { + maxFeedInPower = 0; + } var feedInSetting = EnumUtils.getAsString(p, Property.FEED_IN_SETTING); var bundle = AbstractOpenemsApp.getTranslationBundle(l); @@ -178,7 +189,7 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { .addProperty("safetyCountry", safetyCountry) // .addProperty("backupEnable", // emergencyReserveEnabled ? "ENABLE" : "DISABLE") // - .addProperty("feedPowerEnable", "ENABLE") // + .addProperty("feedPowerEnable", rippleControlReceiverActive ? "DISABLE" : "ENABLE") // .addProperty("feedPowerPara", maxFeedInPower) // .addProperty("setfeedInPowerSettings", feedInSetting) // .build()), @@ -297,6 +308,8 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { DependencyDeclaration.DeletePolicy.IF_MINE, // DependencyDeclaration.DependencyDeletePolicy.NOT_ALLOWED, // JsonUtils.buildJsonObject() // + .addProperty(GridOptimizedCharge.Property.SELL_TO_GRID_LIMIT_ENABLED.name(), + !rippleControlReceiverActive) // .addProperty(GridOptimizedCharge.Property.MAXIMUM_SELL_TO_GRID_POWER.name(), maxFeedInPower) // .build())); @@ -346,13 +359,20 @@ public AppAssistant getAppAssistant(Language language) { f.setDefaultValue(batteryInverter.get() // .getProperty("safetyCountry").get().getAsString()); }).build()) + .add(JsonFormlyUtil.buildCheckbox(Property.RIPPLE_CONTROL_RECEIVER_AKTIV) // + .setLabel(bundle.getString(this.getAppId() + ".rippleControlReceiver.label")) + .setDescription( + bundle.getString(this.getAppId() + ".rippleControlReceiver.description")) + .setDefaultValue(true) // + .build()) .add(JsonFormlyUtil.buildInput(Property.MAX_FEED_IN_POWER) // .setLabel(bundle.getString(this.getAppId() + ".feedInLimit.label")) // .isRequired(true) // + .onlyShowIfNotChecked(Property.RIPPLE_CONTROL_RECEIVER_AKTIV) // .setInputType(Type.NUMBER) // .onlyIf(batteryInverter.isPresent(), f -> { f.setDefaultValue(batteryInverter.get() // - .getProperty("feedPowerPara").get().getAsNumber()); + .getProperty("feedPowerPara").get()); }).build()) .add(JsonFormlyUtil.buildSelect(Property.FEED_IN_SETTING) // .setLabel(bundle.getString(this.getAppId() + ".feedInSettings.label")) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java index 6df2fcca199..5e11c76297f 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java @@ -1,6 +1,5 @@ package io.openems.edge.app.pvselfconsumption; -import java.util.ArrayList; import java.util.EnumMap; import java.util.List; import java.util.TreeMap; @@ -47,6 +46,7 @@ "instanceId": UUID, "image": base64, "properties":{ + "SELL_TO_GRID_LIMIT_ENABLED": true, "CTRL_GRID_OPTIMIZED_CHARGE_ID": "ctrlGridOptimizedCharge0", "MAXIMUM_SELL_TO_GRID_POWER": 10000 }, @@ -63,6 +63,7 @@ public class GridOptimizedCharge extends AbstractOpenemsApp implements public static enum Property { // User values ALIAS, // + SELL_TO_GRID_LIMIT_ENABLED, // MAXIMUM_SELL_TO_GRID_POWER, // // Components CTRL_GRID_OPTIMIZED_CHARGE_ID; @@ -84,20 +85,27 @@ protected ThrowingTriFunction comp = new ArrayList<>(); + final int maximumSellToGridPower; + if (sellToGridLimitEnabled) { + maximumSellToGridPower = EnumUtils.getAsInt(p, Property.MAXIMUM_SELL_TO_GRID_POWER); + } else { + maximumSellToGridPower = 0; + } - comp.add(new EdgeConfig.Component(ctrlIoFixDigitalOutputId, alias, "Controller.Ess.GridOptimizedCharge", - JsonUtils.buildJsonObject() // + List comp = Lists.newArrayList(new EdgeConfig.Component(ctrlIoFixDigitalOutputId, alias, + "Controller.Ess.GridOptimizedCharge", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .onlyIf(t == ConfigurationTarget.ADD, // j -> j.addProperty("ess.id", "ess0") // .addProperty("meter.id", "meter0")) - .addProperty("sellToGridLimitEnabled", true) // - .addProperty("maximumSellToGridPower", maximumSellToGridPower) // + .addProperty("sellToGridLimitEnabled", sellToGridLimitEnabled) // + .onlyIf(sellToGridLimitEnabled, + o -> o.addProperty("maximumSellToGridPower", maximumSellToGridPower)) // .build()));// - + var schedulerExecutionOrder = Lists.newArrayList("ctrlGridOptimizedCharge0", "ctrlEssSurplusFeedToGrid0"); return new AppConfiguration(comp, schedulerExecutionOrder); @@ -109,10 +117,13 @@ public AppAssistant getAppAssistant(Language language) { var bundle = AbstractOpenemsApp.getTranslationBundle(language); return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // + .add(JsonFormlyUtil.buildCheckbox(Property.SELL_TO_GRID_LIMIT_ENABLED) // + .build()) .add(JsonFormlyUtil.buildInput(Property.MAXIMUM_SELL_TO_GRID_POWER) // .setInputType(Type.NUMBER) // .isRequired(true) // .setMin(0) // + .onlyShowIfChecked(Property.SELL_TO_GRID_LIMIT_ENABLED) // .setLabel(bundle.getString(this.getAppId() + ".maximumSellToGridPower.label")) // .setDescription( bundle.getString(this.getAppId() + ".maximumSellToGridPower.description")) // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java index 3cb418fae35..003b2bc9081 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java @@ -194,6 +194,12 @@ public final > T onlyShowIfChecked(PROPERTEY p return this.getSelf(); } + public final > T onlyShowIfNotChecked(PROPERTEY property) { + this.getExpressionProperties().addProperty("templateOptions.required", "!model." + property.name()); + this.jsonObject.addProperty("hideExpression", "model." + property.name()); + return this.getSelf(); + } + public JsonObject build() { this.jsonObject.add("templateOptions", this.templateOptions); if (this.expressionProperties != null && this.expressionProperties.size() > 0) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java index cb9434f0110..434dbf819d4 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java @@ -78,7 +78,10 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj // if also checking dependencies these may be inconsistent // e. g. install HOME is requested it may have a dependency on a SOCOMEC Meter // but the meter has a checkable that there has to be a HOME installed - AppManagerAppHelperImpl.checkStatus(app); + // maybe add temporary apps in this component + if (oldInstance == null) { + AppManagerAppHelperImpl.checkStatus(app); + } final var language = user == null ? null : user.getLanguage(); @@ -97,7 +100,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj }); } - var modifiedOrCreatedApps = new LinkedList(); + var modifiedOrCreatedApps = new ArrayList(); // update app and its dependencies this.foreachDependency(app, alias, properties, ConfigurationTarget.UPDATE, language, dc -> { ExistingDependencyConfig oldAppConfig = null; @@ -166,14 +169,13 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj } } } catch (OpenemsNamedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + errors.add(e.getMessage()); } } } else { // create app instanceId = UUID.randomUUID(); - // TODO check if the created app can satisfy another app dependency + // check if the created app can satisfy another app dependency for (var instance : this.getAppManagerImpl().getInstantiatedApps()) { var neededDependency = this.getNeededDependencyTo(instance, dc.app.getAppId()); if (neededDependency == null) { @@ -206,12 +208,9 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj newAppInstance, AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), language); - this.componentsTask.aggregate(newConfig, oldConfig); - this.schedulerTask.aggregate(newConfig, oldConfig); - this.staticIpTask.aggregate(newConfig, oldConfig); + this.aggregateAllTasks(newConfig, oldConfig); } catch (OpenemsNamedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + errors.add(e.getMessage()); } return true; } else { @@ -231,13 +230,10 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj var newAppConfig = this.getNewAppConfigWithReplacedIds(dc.app, oldAppConfig.instance, newAppInstance, AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), language); - this.componentsTask.aggregate(newAppConfig, oldAppConfig.config); - this.schedulerTask.aggregate(newAppConfig, oldAppConfig.config); - this.staticIpTask.aggregate(newAppConfig, oldAppConfig.config); + this.aggregateAllTasks(newAppConfig, oldAppConfig.config); } catch (OpenemsNamedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + errors.add(e.getMessage()); } return true; @@ -250,9 +246,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj dc.parentInstance, dc.instance)) { continue; } - this.componentsTask.aggregate(null, dc.config); - this.schedulerTask.aggregate(null, dc.config); - this.staticIpTask.aggregate(null, dc.config); + this.aggregateAllTasks(null, dc.config); } var ignoreInstances = new ArrayList<>(modifiedOrCreatedApps); @@ -380,15 +374,14 @@ public UpdateValues deleteApp(User user, OpenemsAppInstance instance) throws Ope deletedInstances.add(dc.instance); - this.componentsTask.aggregate(null, dc.config); - this.schedulerTask.aggregate(null, dc.config); - this.staticIpTask.aggregate(null, dc.config); + this.aggregateAllTasks(null, dc.config); return true; }); var unmodifiedApps = this - .getAppsWithReferenceTo(deletedInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + .getAppsWithReferenceTo(deletedInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)).stream() + .filter(a -> !deletedInstances.stream().anyMatch(t -> t.equals(a))).collect(Collectors.toList()); var modifiedApps = new ArrayList(unmodifiedApps.size()); for (var app : unmodifiedApps) { @@ -428,6 +421,12 @@ public List getAppsWithReferenceTo(UUID... instanceIds) { .toList(); } + private final void aggregateAllTasks(AppConfiguration instance, AppConfiguration oldInstance) { + for (var task : tasks) { + task.aggregate(instance, oldInstance); + } + } + public final boolean isAllowedToDelete(OpenemsAppInstance instance, UUID... ignoreIds) { for (var i : this.getAppManagerImpl().getInstantiatedApps()) { if (Arrays.stream(ignoreIds).anyMatch(id -> i.instanceId.equals(id))) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java index 6c7884d4a5f..831e71d8d12 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java @@ -115,14 +115,17 @@ public void create(User user, List otherAppConfigurations) thr } } + try { + // delete components that were used from the old configurations + this.delete(user, otherAppConfigurations); + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); + } if (!errors.isEmpty()) { throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); } - // delete components that were used from the old configurations - this.delete(user, otherAppConfigurations); - } /** diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties index 1062817aea2..f58a24dbd1e 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties @@ -89,6 +89,8 @@ App.LoadControl.ManualRelayControl.outputChannel.description = Kanaladresse des # Integrated System App.FENECON.Home.Name = FENECON Home App.FENECON.Home.safetyCountry.label = Batterie-Wechselrichter Ländereinstellung +App.FENECON.Home.rippleControlReceiver.label = Rundsteuerempfänger aktiviert? +App.FENECON.Home.rippleControlReceiver.description = Externe Abregelung durch Netzbetreiber App.FENECON.Home.feedInLimit.label = Begrenzung der Einspeisung [W] App.FENECON.Home.feedInSettings.label = Einspeise-Einstellungen App.FENECON.Home.hasAcMeterSocomec.label = Hat AC-Zähler (SOCOMEC) diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties index 7129a43ea62..88ac7efbbac 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties @@ -89,6 +89,8 @@ App.LoadControl.ManualRelayControl.outputChannel.description = Channel address o # Integrated System App.FENECON.Home.Name = FENECON Home App.FENECON.Home.safetyCountry.label = Battery-Inverter Safety Country +App.FENECON.Home.rippleControlReceiver.label = Ripple control receiver active? +App.FENECON.Home.rippleControlReceiver.description = External balancing by grid operator App.FENECON.Home.feedInLimit.label = Feed-In limitation [W] App.FENECON.Home.feedInSettings.label = Feed-In Settings App.FENECON.Home.hasAcMeterSocomec.label = Has AC meter (SOCOMEC) From 96930509fb14947538815602ec893c3df63c4cff Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:10:11 +0200 Subject: [PATCH 09/30] added dependency for HeatPump; added policy for properties --- .../io/openems/edge/app/heat/HeatPump.java | 14 +- .../app/integratedsystem/FeneconHome.java | 27 +- .../GridOptimizedCharge.java | 3 + .../edge/core/appmanager/AppAssistant.java | 6 +- .../edge/core/appmanager/AppManagerImpl.java | 662 +++--------------- .../core/appmanager/OpenemsAppInstance.java | 2 +- .../dependency/AppManagerAppHelperImpl.java | 163 +++-- .../dependency/DependencyDeclaration.java | 42 +- .../core/appmanager/translation_de.properties | 2 + .../core/appmanager/translation_en.properties | 2 + .../core/appmanager/AppManagerImplTest.java | 16 +- .../app/edge/settings/app/index.component.ts | 7 +- .../app/edge/settings/app/update.component.ts | 2 +- 13 files changed, 261 insertions(+), 687 deletions(-) diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java index 372cb9e3c1d..30d432f96d0 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java @@ -30,6 +30,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; import io.openems.edge.core.appmanager.validator.CheckRelayCount; import io.openems.edge.core.appmanager.validator.Validator; import io.openems.edge.core.appmanager.validator.Validator.Builder; @@ -85,7 +86,18 @@ protected ThrowingTriFunction appConfigurationFactory() { .add("_sum/ConsumptionActivePower") // .build()) // .build()), -// new EdgeConfig.Component("ctrlGridOptimizedCharge0", -// bundle.getString("App.PvSelfConsumption.GridOptimizedCharge.Name"), -// "Controller.Ess.GridOptimizedCharge", JsonUtils.buildJsonObject() // -// .addProperty("enabled", true) // -// .addProperty("ess.id", essId) // -// .addProperty("meter.id", "meter0") // -// .addProperty("sellToGridLimitEnabled", true) // -// .addProperty("maximumSellToGridPower", maxFeedInPower) // -// .build()), new EdgeConfig.Component("ctrlEssSurplusFeedToGrid0", bundle.getString(this.getAppId() + ".ctrlEssSurplusFeedToGrid0.alias"), "Controller.Ess.Hybrid.Surplus-Feed-To-Grid", JsonUtils.buildJsonObject() // @@ -234,16 +225,6 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { ); -// if (EnumUtils.getAsOptionalBoolean(p, Property.HAS_AC_METER).orElse(false)) { -// components.add(new EdgeConfig.Component("meter1", bundle.getString(this.getAppId() + ".meter1.alias"), -// "Meter.Socomec.Threephase", // -// JsonUtils.buildJsonObject() // -// .addProperty("enabled", true) // -// .addProperty("modbus.id", modbusIdExternal) // -// .addProperty("modbusUnitId", 6) // -// .build())); -// } - if (EnumUtils.getAsOptionalBoolean(p, Property.HAS_DC_PV1).orElse(false)) { var alias = EnumUtils.getAsOptionalString(p, Property.DC_PV1_ALIAS).orElse("DC-PV 1"); components.add(new EdgeConfig.Component("charger0", alias, "GoodWe.Charger-PV1", // @@ -306,6 +287,7 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING, // DependencyDeclaration.UpdatePolicy.ALWAYS, // DependencyDeclaration.DeletePolicy.IF_MINE, // + DependencyDeclaration.DependencyUpdatePolicy.ALLOW_ONLY_UNCONFIGURED_PROPERTIES, // DependencyDeclaration.DependencyDeletePolicy.NOT_ALLOWED, // JsonUtils.buildJsonObject() // .addProperty(GridOptimizedCharge.Property.SELL_TO_GRID_LIMIT_ENABLED.name(), @@ -319,13 +301,14 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { bundle.getString("App.PvSelfConsumption.GridOptimizedCharge.Name"), // DependencyDeclaration.CreatePolicy.ALWAYS, // DependencyDeclaration.UpdatePolicy.ALWAYS, // - DependencyDeclaration.DeletePolicy.ALWAYS, // + DependencyDeclaration.DeletePolicy.IF_MINE, // + DependencyDeclaration.DependencyUpdatePolicy.ALLOW_ONLY_UNCONFIGURED_PROPERTIES, // DependencyDeclaration.DependencyDeletePolicy.NOT_ALLOWED, // JsonUtils.buildJsonObject() // .addProperty(SocomecMeter.Property.MODBUS_UNIT_ID.name(), 6) // .build())); - } + return new AppConfiguration(components, schedulerExecutionOrder, null, dependencies); }; } diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java index 5e11c76297f..c8f07b2c21d 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java @@ -118,6 +118,9 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildCheckbox(Property.SELL_TO_GRID_LIMIT_ENABLED) // + .setLabel(bundle.getString(this.getAppId() + ".sellToGridLimitEnabled.label")) // + .setDescription( + bundle.getString(this.getAppId() + ".sellToGridLimitEnabled.description")) // .build()) .add(JsonFormlyUtil.buildInput(Property.MAXIMUM_SELL_TO_GRID_POWER) // .setInputType(Type.NUMBER) // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppAssistant.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppAssistant.java index 121c9623d7a..3f46f03197f 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppAssistant.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppAssistant.java @@ -73,9 +73,9 @@ public static Builder create(String appname) { return new Builder().setAppName(appname); } - private final String name; - private final String alias; - private final JsonArray fields; + public final String name; + public final String alias; + public final JsonArray fields; private AppAssistant(String name, String alias, JsonArray fields) { this.name = name; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java index 1ee6ca8360f..affe6a5a617 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java @@ -1,20 +1,18 @@ package io.openems.edge.core.appmanager; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.NoSuchElementException; -import java.util.Objects; import java.util.UUID; -import java.util.Vector; -import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import java.util.stream.Collectors; import org.osgi.service.cm.ConfigurationAdmin; @@ -29,7 +27,6 @@ import org.osgi.service.metatype.annotations.Designate; import com.google.gson.JsonArray; -import com.google.gson.JsonObject; import io.openems.common.exceptions.OpenemsError; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; @@ -37,13 +34,9 @@ import io.openems.common.jsonrpc.base.GenericJsonrpcResponseSuccess; import io.openems.common.jsonrpc.base.JsonrpcRequest; import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; -import io.openems.common.jsonrpc.request.CreateComponentConfigRequest; -import io.openems.common.jsonrpc.request.DeleteComponentConfigRequest; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest.Property; -import io.openems.common.session.Language; import io.openems.common.session.Role; -import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.ComponentManager; @@ -193,66 +186,81 @@ private synchronized void applyConfig(Config config) { } } - protected static void checkStatus(OpenemsApp openemsApp) throws OpenemsNamedException { - var validator = openemsApp.getValidator(); - var status = validator.getStatus(); - switch (validator.getStatus()) { - case INCOMPATIBLE: - throw new OpenemsException("App is not compatible! " - + validator.getErrorCompatibleMessages().stream().collect(Collectors.joining(";"))); - case COMPATIBLE: - throw new OpenemsException("App can not be installed! " - + validator.getErrorCompatibleMessages().stream().collect(Collectors.joining(";"))); - case INSTALLABLE: - // continue - break; - default: - throw new OpenemsException("Status '" + status.name() + "' is not implemented."); - } - } - @Override public void configurationEvent(ConfigurationEvent event) { this.worker.configurationEvent(event); } - public void foreachAppConfiguration(Consumer consumer, UUID... excludingInstanceIds) { - for (var appInstance : this.instantiatedApps) { - var skipInstance = false; - for (var id : excludingInstanceIds) { - if (Objects.equals(id, appInstance.instanceId)) { - skipInstance = true; - break; - } - } - if (skipInstance) { - continue; - } + public void foreachAppConfiguration(BiConsumer consumer, + UUID... excludingInstanceIds) { + this.foreachAppConfiguration(this.instantiatedApps, consumer, excludingInstanceIds); + } - try { - var app = this.findAppById(appInstance.appId); - appInstance.properties.addProperty("ALIAS", appInstance.alias); - consumer.accept(app.getAppConfiguration(ConfigurationTarget.VALIDATE, appInstance.properties, null)); - appInstance.properties.remove("ALIAS"); - } catch (OpenemsNamedException e) { - // move to next app - } catch (NoSuchElementException e) { - // app not found for instance - // this may happen if the app id gets refactored - // apps which app ids are not known are printed in debug log as 'UNKNOWAPPS' - } + public void foreachAppConfiguration(List instances, + BiConsumer consumer, UUID... excludingInstanceIds) { + for (var entry : this.appConfigs(instances, excludingInstanceIds)) { + consumer.accept(entry.getKey(), entry.getValue()); } } - private void createComponent(User user, EdgeConfig.Component comp) throws OpenemsNamedException { - List properties = comp.getProperties().entrySet().stream() - .map(t -> new Property(t.getKey(), t.getValue())).collect(Collectors.toList()); - properties.add(new Property("id", comp.getId())); - properties.add(new Property("alias", comp.getAlias())); + public Iterable> appConfigs(UUID... excludingInstanceIds) { + return this.appConfigs(this.instantiatedApps, excludingInstanceIds); + } - // user can be null using internal method - ((ComponentManagerImpl) this.componentManager).handleCreateComponentConfigRequest(user, - new CreateComponentConfigRequest(comp.getFactoryId(), properties)); + public Iterable> appConfigs(List instances, + UUID... excludingInstanceIds) { + return new Iterable>() { + @Override + public Iterator> iterator() { + return appConfigIterator(instances, excludingInstanceIds); + } + }; + } + + public Iterator> appConfigIterator(List instances, + UUID... excludingInstanceIds) { + List actualInstances = instances.stream() + .filter(i -> !Arrays.stream(excludingInstanceIds).anyMatch(id -> id.equals(i.instanceId))) + .collect(Collectors.toList()); + return new Iterator>() { + + private Iterator instanceIterator = actualInstances.iterator(); + + private OpenemsAppInstance nextInstance = null; + private AppConfiguration nextConfiguration = null; + + @Override + public Entry next() { + var returnValue = new AbstractMap.SimpleEntry<>(nextInstance, nextConfiguration); + nextInstance = null; + nextConfiguration = null; + return returnValue; + } + + @Override + public boolean hasNext() { + if (this.nextConfiguration == null && !this.instanceIterator.hasNext()) { + return false; + } + this.nextInstance = this.instanceIterator.next(); + + try { + var app = findAppById(nextInstance.appId); + nextInstance.properties.addProperty("ALIAS", nextInstance.alias); + nextConfiguration = app.getAppConfiguration(ConfigurationTarget.VALIDATE, nextInstance.properties, + null); + nextInstance.properties.remove("ALIAS"); + } catch (OpenemsNamedException e) { + // move to next app + } catch (NoSuchElementException e) { + // app not found for instance + // this may happen if the app id gets refactored + // apps which app ids are not known are printed in debug log as 'UNKNOWAPPS' + } + + return nextConfiguration != null; + } + }; } @Override @@ -268,44 +276,6 @@ public String debugLog() { return this.worker.debugLog(); } - /** - * deletes the given components only if they are not in notMyComponents. - * - * @param user the executing user - * @param components the components that should be deleted - * @param notMyComponents other needed components from the other apps - * @return the id s of the components that got deleted - */ - private List deleteComponents(User user, List components, - List notMyComponents) throws OpenemsNamedException { - List errors = new ArrayList<>(); - List deletedIds = new ArrayList<>(); - for (var comp : components) { - if (notMyComponents.stream().parallel().anyMatch(t -> t.getId().equals(comp.getId()))) { - continue; - } - var component = this.componentManager.getEdgeConfig().getComponent(comp.getId()).orElse(null); - if (component == null) { - // component does not exist - continue; - } - - try { - // user can be null using internal method - ((ComponentManagerImpl) this.componentManager).handleDeleteComponentConfigRequest(user, - new DeleteComponentConfigRequest(comp.getId())); - deletedIds.add(comp.getId()); - } catch (OpenemsNamedException e) { - errors.add(e.toString()); - } - } - - if (!errors.isEmpty()) { - throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); - } - return deletedIds; - } - /** * finds the app with the matching id. * @@ -333,186 +303,14 @@ public final OpenemsAppInstance findInstaceById(UUID uuid) throws NoSuchElementE .get(); } - /** - * Gets an App Configuration with component id s, which can be used to create or - * rewrite the settings of the component. - * - * @param app the {@link OpenemsApp} - * @param oldAppInstance the old {@link OpenemsAppInstance} - * @param newAppInstance the new {@link OpenemsAppInstance} - * @param otherAppComponents the components that are used from the other - * {@link OpenemsAppInstance} - * @param language the language of the new config - * @return the AppConfiguration with the replaced ID s of the components - * @throws OpenemsNamedException on error - */ - private AppConfiguration getNewAppConfigWithReplacedIds(OpenemsApp app, OpenemsAppInstance oldAppInstance, - OpenemsAppInstance newAppInstance, List otherAppComponents, Language language) - throws OpenemsNamedException { - - var target = oldAppInstance == null ? ConfigurationTarget.ADD : ConfigurationTarget.UPDATE; - var newAppConfig = app.getAppConfiguration(target, newAppInstance.properties, language); - - final var replacableIds = this.getReplaceableComponentIds(app, newAppInstance.properties); - - for (var comp : ComponentUtilImpl.order(newAppConfig.components)) { - // replace old id s with new ones - for (var entry : comp.getProperties().entrySet()) { - for (var replaceableId : replacableIds.entrySet()) { - if (entry.getValue().toString().contains(replaceableId.getKey())) { - var newId = entry.getValue().toString().replace(replaceableId.getKey(), - newAppInstance.properties.get(replaceableId.getValue()).getAsString()); - newId = newId.replace("\"", ""); - var newValue = JsonUtils.getAsJsonElement(newId); - comp.getProperties().put(entry.getKey(), newValue); - } - } - } - - var isNewComponent = true; - var id = comp.getId(); - var canBeReplaced = replacableIds.containsKey(id); - EdgeConfig.Component foundComponent = null; - - // try to find a component with the necessary settings - if (canBeReplaced) { - foundComponent = this.componentUtil.getComponentByConfig(comp); - if (foundComponent != null) { - id = foundComponent.getId(); - } - } - if (foundComponent == null && oldAppInstance != null && oldAppInstance.properties.has(id.toUpperCase())) { - id = oldAppInstance.properties.get(id.toUpperCase()).getAsString(); - foundComponent = this.componentManager.getEdgeConfig().getComponent(id).orElse(null); - final var tempId = id; - // other app uses the same component because they had the same configuration - // now this app needs the component with a different configuration so now create - // a new component - if (foundComponent != null && otherAppComponents.stream().anyMatch(t -> t.getId().equals(tempId))) { - foundComponent = null; - } - } - isNewComponent = isNewComponent && foundComponent == null; - if (isNewComponent) { - // if the id is not already set and there is no component with the default id - // then use the default id - foundComponent = this.componentManager.getEdgeConfig().getComponent(comp.getId()).orElse(null); - if (foundComponent == null) { - id = comp.getId(); - } else { - // replace number at the end and get the next available id - var nextAvailableId = this.componentUtil.getNextAvailableId(id.replaceAll("\\d+", ""), - otherAppComponents); - if (!nextAvailableId.equals(id) && !canBeReplaced) { - // component can not be created because the id is already used - // and the id can not be set in the configuration - continue; - } - if (canBeReplaced) { - id = nextAvailableId; - } - } - } - - if (canBeReplaced) { - newAppInstance.properties.addProperty(replacableIds.get(comp.getId()), id); - } - } - return app.getAppConfiguration(target, newAppInstance.properties, language); - } - - /** - * Gets the components of all apps except the given. - * - * @param thisApp the app that components should not be included - * @return all components from all app instances except the given thisApp - */ - public List getOtherAppComponents(UUID... ignoreIds) { - List allOtherComponents = new ArrayList<>(); - this.foreachAppConfiguration(c -> { - allOtherComponents.addAll(c.components); - }, ignoreIds); - return allOtherComponents; - } - - /** - * Gets ip s that are needed from the other {@link OpenemsAppInstance}s. - * - * @param thisApp the app which ip s should not be included - * @return all needed ip s from the other apps - */ - private List getOtherAppIps(OpenemsAppInstance thisApp) { - List allOtherIps = new ArrayList<>(); - this.foreachAppConfiguration(c -> { - allOtherIps.addAll(c.ips); - }, thisApp.instanceId); - return allOtherIps; - } - - /** - * Gets Scheduler Order s that are needed from the other - * {@link OpenemsAppInstance}s. Every Id from the scheduler orders gets append - * to the list. - * - * @param thisApp the app which ip s should not be included - * @return all needed Scheduler Order s from the other apps - */ - private List getOtherAppSchedulerOrders(OpenemsAppInstance thisApp) { - List allOtherIps = new ArrayList<>(); - this.foreachAppConfiguration(c -> { - allOtherIps.addAll(c.schedulerExecutionOrder); - }, thisApp.instanceId); - return allOtherIps; - } - public final List getOtherAppConfigurations(UUID... ignoreIds) { List allOtherConfigs = new ArrayList<>(this.instantiatedApps.size()); - this.foreachAppConfiguration(c -> { + this.foreachAppConfiguration((i, c) -> { allOtherConfigs.add(c); }, ignoreIds); return allOtherConfigs; } - /** - * Gets the component id s that can be replaced. - * - * @param app the components of which app - * @param properties the default properties to create an app instance of this - * app - * @return a map of the component id s that can be replaced mapped from id to - * key to put the next id - * @throws OpenemsNamedException on error - */ - protected final Map getReplaceableComponentIds(OpenemsApp app, JsonObject properties) - throws OpenemsNamedException { - final var prefix = "?_?_"; - var config = app.getAppConfiguration(ConfigurationTarget.TEST, properties, null); - var copyBuilder = JsonUtils.buildJsonObject(); - for (var entry : properties.entrySet()) { - copyBuilder.add(entry.getKey(), entry.getValue()); - } - for (var comp : config.components) { - copyBuilder.addProperty(comp.getId(), prefix); - } - var copy = copyBuilder.build(); - var configWithNewIds = app.getAppConfiguration(ConfigurationTarget.TEST, copy, null); - Map replaceableComponentIds = new HashMap<>(); - for (var comp : configWithNewIds.components) { - if (comp.getId().startsWith(prefix)) { - // "METER_ID:meter0" - var raw = comp.getId().substring(prefix.length()); - // ["METER_ID", "meter0"] - var pieces = raw.split(":"); - // "METER_ID" - var property = pieces[0]; - // "meter0" - var defaultId = pieces[1]; - replaceableComponentIds.put(defaultId, property); - } - } - return replaceableComponentIds; - } - /** * Handles {@link AddAppInstance}. * @@ -530,31 +328,7 @@ protected CompletableFuture handleAddAppInstanceRequest( synchronized (this.instantiatedApps) { var installedApps = this.appHelper.installApp(user, request.properties, request.alias, openemsApp); -// -// this.checkStatus(openemsApp); -// -// // create app instance -// var app = new OpenemsAppInstance(request.appId, request.alias, instanceId, request.properties, null); -// List errors = new Vector<>(); -// var completable = this.updateAppSettings(errors, user, openemsApp, null, app); -// -// try { -// // wait until everything is finished -// completable.get(); -// } catch (ExecutionException | CancellationException | InterruptedException e) { -// errors.add(e.getMessage()); -// } -// if (!errors.isEmpty()) { -// throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); -// } -// // Update App-Manager configuration -// try { -// this.instantiatedApps.add(app); -// this.updateAppManagerConfiguration(user, this.instantiatedApps); -// } catch (OpenemsNamedException e) { -// throw new OpenemsException( -// "AddAppInstance: unable to update App-Manager configuration: " + e.getMessage()); -// } + // Update App-Manager configuration try { // replace old instances with new ones @@ -582,67 +356,26 @@ private CompletableFuture handleDeleteAppInsta synchronized (this.instantiatedApps) { - final var instance = this.instantiatedApps.stream().filter(t -> t.instanceId.equals(request.instanceId)) - .findFirst().orElse(null); - if (instance == null) { + final OpenemsAppInstance instance; + try { + instance = this.findInstaceById(request.instanceId); + } catch (NoSuchElementException e) { return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.id)); } - List errors = new Vector<>(); + var result = this.appHelper.deleteApp(user, instance); -// var app = this.findAppById(instance.appId); -// -// var config = app.getAppConfiguration(ConfigurationTarget.DELETE, instance.properties, null); -// -// var deleteComponents = CompletableFuture.runAsync(() -> { -// try { -// var deletedIds = this.deleteComponents(user, config.components, -// this.getOtherAppComponents(instance.instanceId)); -// deletedIds.addAll(config.schedulerExecutionOrder); -// // do not remove ids in scheduler from other apps -// // e. g. Home has ctrlBalancing0 in scheduler -// // and also KebaEvcs has the ctrlBalancing0 in the scheduler -// deletedIds.removeAll(this.getOtherAppSchedulerOrders(instance)); -// this.componentUtil.removeIdsInSchedulerIfExisting(user, deletedIds); -// } catch (OpenemsNamedException e) { -// errors.add(e); -// } -// }); -// // TODO remove 'if' if it works on windows -// // rewriting network configuration only works on Linux -// var updateNetworkConfig = CompletableFuture.runAsync(() -> { -// if (!System.getProperty("os.name").startsWith("Windows")) { -// var ips = new ArrayList<>(config.ips); -// ips.removeAll(this.getOtherAppIps(instance)); -// try { -// this.componentUtil.updateHosts(user, null, ips); -// } catch (OpenemsNamedException e) { -// errors.add(e); -// } -// } -// }); -// try { -// // wait until everything is finished -// CompletableFuture.allOf(deleteComponents, updateNetworkConfig).get(); -// } catch (ExecutionException | CancellationException | InterruptedException e) { -// errors.add(new OpenemsException(e.toString())); -// } try { -// this.instantiatedApps.remove(instance); this.instantiatedApps.removeAll(result.deletedApps); - // replace modified apps this.instantiatedApps.removeAll(result.modifiedOrCreatedApps); this.instantiatedApps.addAll(result.modifiedOrCreatedApps); this.updateAppManagerConfiguration(user, this.instantiatedApps); } catch (OpenemsNamedException e) { - errors.add(new OpenemsException(e.toString())); + throw new OpenemsException("Unable to update App-Manager configuration for ID [" + request.instanceId + + "]: " + e.getMessage()); } - if (!errors.isEmpty()) { - throw new OpenemsException( - errors.stream().map(OpenemsNamedException::toString).collect(Collectors.joining("|"))); - } } return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.id)); @@ -779,40 +512,29 @@ private CompletableFuture handleUpdateAppInstanceRequest UpdateAppInstance.Request request) throws OpenemsNamedException { synchronized (this.instantiatedApps) { -// OpenemsAppInstance newApp = null; OpenemsAppInstance oldApp = null; OpenemsApp app = null; + try { oldApp = this.findInstaceById(request.instanceId); app = this.findAppById(oldApp.appId); -// newApp = new OpenemsAppInstance(oldApp.appId, request.alias, request.instanceId, request.properties, -// null); } catch (NoSuchElementException e) { throw new OpenemsException("App-Instance-ID [" + request.instanceId + "] is unknown."); } var result = this.appHelper.updateApp(user, oldApp, request.properties, request.alias, app); -// var errors = this.reconfigurApp(user, oldApp, newApp); - // Update App-Manager configuration try { this.instantiatedApps.removeAll(result.deletedApps); // replace old instances with new ones this.instantiatedApps.removeAll(result.modifiedOrCreatedApps); this.instantiatedApps.addAll(result.modifiedOrCreatedApps); -// this.instantiatedApps.remove(oldApp); -// this.instantiatedApps.add(newApp); this.updateAppManagerConfiguration(user, this.instantiatedApps); } catch (OpenemsNamedException e) { throw new OpenemsException("Unable to update App-Manager configuration for ID [" + request.instanceId + "]: " + e.getMessage()); } - -// if (!errors.isEmpty()) { -// throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); -// } - } return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.id)); } @@ -824,53 +546,6 @@ private void modified(ComponentContext componentContext, Config config) throws O this.worker.triggerNextRun(); } - /** - * Reconfigurates an app instance. - * - * @param user the executing user - * @param oldAppInstance the old app instance with the old configuration - * @param newAppInstance the new app instance with the new configuration - * @return the errors that occurred during reconfiguration - */ - private List reconfigurApp(User user, OpenemsAppInstance oldAppInstance, OpenemsAppInstance newAppInstance) - throws OpenemsNamedException { - var app = this.findAppById(newAppInstance.appId); - List errors = new Vector<>(); - var completable = this.updateAppSettings(errors, user, app, oldAppInstance, newAppInstance); - try { - // wait until everything is finished - completable.get(); - } catch (ExecutionException | CancellationException | InterruptedException e) { - errors.add(e.getMessage()); - } - return errors; - } - - /** - * checks if the settings of the component changed if there is a change it - * rewrites the settings of the given component. - * - * @param user the executing user - * @param myComp the component that configuration should be rewritten - * @param actualComp the actual component that exists - * @throws OpenemsNamedException when the configuration can not be rewritten - */ - private void reconfigure(User user, EdgeConfig.Component myComp, EdgeConfig.Component actualComp) - throws OpenemsNamedException { - if (ComponentUtilImpl.isSameConfiguration(null, myComp, actualComp)) { - return; - } - - // send update request - List properties = myComp.getProperties().entrySet().stream() - .map(t -> new Property(t.getKey(), t.getValue())) // - .collect(Collectors.toList()); - properties.add(new Property("alias", myComp.getAlias())); - var updateRequest = new UpdateComponentConfigRequest(actualComp.getId(), properties); - // user can be null using internal method - ((ComponentManagerImpl) this.componentManager).handleUpdateComponentConfigRequest(user, updateRequest); - } - /** * updated the AppManager configuration with the given app instances. * @@ -884,183 +559,4 @@ private void updateAppManagerConfiguration(User user, List a // user can be null using internal method ((ComponentManagerImpl) this.componentManager).handleUpdateComponentConfigRequest(user, updateRequest); } - - /** - * creates the needed components of the given app with the given config and - * updates components with a new configuration and deletes unused components. - * - * @param errorList a list for the errors that occur - * @param user the executing user - * @param app the app that should be created - * @param oldAppInstance the old app instance - * @param newAppInstance the new app instance - * @return the completableFuture of this task - */ - public CompletableFuture updateAppSettings(List errorList, User user, OpenemsApp app, - OpenemsAppInstance oldAppInstance, OpenemsAppInstance newAppInstance) throws OpenemsNamedException { - final List errors; - if (errorList == null) { - errors = new Vector<>(); - } else { - errors = errorList; - } - final var language = user != null ? user.getLanguage() : null; - AppConfiguration oldAppConfigTemp = null; - if (oldAppInstance != null) { - oldAppInstance.properties.addProperty("ALIAS", oldAppInstance.alias); - try { - oldAppConfigTemp = app.getAppConfiguration(ConfigurationTarget.VALIDATE, oldAppInstance.properties, - language); - } catch (OpenemsNamedException ex) { - errors.add(ex.getMessage()); - } - } - final var oldAppConfig = oldAppConfigTemp; - // adding alias to the properties in order to access it while defining it in the - // App Configuration - newAppInstance.properties.addProperty("ALIAS", newAppInstance.alias); - final var otherComponents = this.getOtherAppComponents(newAppInstance.instanceId); - final var newAppConfig = this.getNewAppConfigWithReplacedIds(app, oldAppInstance, newAppInstance, - otherComponents, language); - - // TODO remove 'if' if it works on windows - // rewriting network configuration only works on Linux - if (!System.getProperty("os.name").startsWith("Windows")) { - try { - this.componentUtil.updateHosts(user, newAppConfig.ips, oldAppConfig != null ? oldAppConfig.ips : null); - } catch (OpenemsNamedException e) { - var error = "Can not update Host Config"; - errors.add(error); - } - } - - try { - // validate input e. g. ping a specific ip - app.getValidator().validateConfiguration(ConfigurationTarget.ADD, newAppInstance.properties); - } catch (OpenemsNamedException ex) { - // revert network configuration - errors.add(ex.getMessage()); - return CompletableFuture.runAsync(() -> { - if (!System.getProperty("os.name").startsWith("Windows")) { - var ips = new ArrayList<>(newAppConfig.ips); - ips.removeAll(this.getOtherAppIps(newAppInstance)); - try { - this.componentUtil.updateHosts(user, null, ips); - } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); - } - } - }); - } - - // adds / updates components - var updatingComponents = CompletableFuture.runAsync(() -> { - var createdComponents = new LinkedList(); - // create components - for (var comp : ComponentUtilImpl.order(newAppConfig.components)) { - /** - * if comp already exists with same config as needed => use it. if comp exist - * with different config and no other app needs it => rewrite settings. if comp - * exist with different config and other app needs it => create new comp - */ - var foundComponentWithSameId = this.componentManager.getEdgeConfig().getComponent(comp.getId()) - .orElse(null); - if (oldAppConfig != null) { - oldAppConfig.components.removeIf(t -> t.getId().equals(comp.getId())); - } - if (foundComponentWithSameId != null) { - - var isSameConfigWithoutAlias = ComponentUtilImpl.isSameConfigurationWithoutAlias(null, comp, - foundComponentWithSameId); - var isSameConfig = isSameConfigWithoutAlias - && comp.getAlias().equals(foundComponentWithSameId.getAlias()); - - if (isSameConfig) { - // same configuration so no reconfiguration needed - continue; - } - - // check if it is my component - if (otherComponents.stream().anyMatch(t -> t.getId().equals(foundComponentWithSameId.getId()))) { - // not my component but only the alias changed - if (isSameConfigWithoutAlias) { - // TODO maybe warning if the alias can't be set - continue; - } - errors.add("Configuration of component with id '" + foundComponentWithSameId.getId() - + "' can not be rewritten. Because the component belongs to another app."); - continue; - } - try { - this.reconfigure(user, comp, foundComponentWithSameId); - } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); - } - continue; - } - - // create new component - try { - this.createComponent(user, comp); - createdComponents.add(comp); - } catch (OpenemsNamedException e) { - var error = "Component[" + comp.getFactoryId() + "] cant be created!"; - errors.add(error); - errors.add(e.getMessage()); - } - - } - - // update scheduler - try { - var schedulerOrder = new ArrayList<>(newAppConfig.schedulerExecutionOrder); - // if another app needs this component for the scheduler now add it - if (createdComponents.isEmpty()) { - this.foreachAppConfiguration(c -> { - - // if any component id is included - if (!createdComponents.stream().anyMatch(t -> c.schedulerExecutionOrder.contains(t.getId()))) { - return; - } - - var temp = this.componentUtil.insertSchedulerOrder(schedulerOrder, c.schedulerExecutionOrder); - schedulerOrder.clear(); - schedulerOrder.addAll(temp); - - }); - } - this.componentUtil.updateScheduler(user, schedulerOrder, createdComponents); - } catch (OpenemsNamedException e) { - errors.add("Can't update scheduler execute order. Message: " + e.getMessage()); - } - - }); - - // deletes components that were used in the old configuration but are not in the - // new configuration - var updateSchedulerDeletingIds = updatingComponents.thenRunAsync(() -> { - if (oldAppConfig != null) { - try { - var deletedIds = this.deleteComponents(user, oldAppConfig.components, otherComponents); - oldAppConfig.schedulerExecutionOrder.removeAll(newAppConfig.schedulerExecutionOrder); - deletedIds.addAll(oldAppConfig.schedulerExecutionOrder); - this.componentUtil.removeIdsInSchedulerIfExisting(user, deletedIds); - } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); - } - - } - }); - - // remove alias so it does not get written down twice in the app configuration - var removingAlias = updateSchedulerDeletingIds.thenRunAsync(() -> { - if (oldAppInstance != null) { - oldAppInstance.properties.remove("ALIAS"); - } - newAppInstance.properties.remove("ALIAS"); - }); - - return CompletableFuture.allOf(updatingComponents, updateSchedulerDeletingIds, removingAlias); - } - } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java index beeb3a3fd29..32ea9b1954d 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java @@ -57,7 +57,7 @@ public JsonObject toJsonObject() { .addProperty("appId", this.appId) // .addProperty("alias", this.alias) // .addProperty("instanceId", this.instanceId.toString()) // - .add("properties", this.properties) // + .add("properties", this.properties) // TODO define if the field is editable .onlyIf(dependencies != null && !dependencies.isEmpty(), j -> j.add("dependencies", // dependencies.stream().map(t -> t.toJsonObject()).collect(JsonUtils.toJsonArray()))) .build(); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java index 434dbf819d4..faf6d8a181c 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java @@ -11,7 +11,6 @@ import java.util.Set; import java.util.TreeMap; import java.util.UUID; -import java.util.Vector; import java.util.stream.Collectors; import org.osgi.service.component.annotations.Activate; @@ -51,6 +50,8 @@ public class AppManagerAppHelperImpl implements AppManagerAppHelper { private final AggregateTask[] tasks; + // TODO maybe add temporary fields with currently installing apps + @Activate public AppManagerAppHelperImpl(@Reference ComponentManager componentManager, @Reference ComponentUtil componentUtil, @Reference(target = "(component.name=AppManager.AggregateTask.CreateComponents)") AggregateTask componentsTask, @@ -61,7 +62,7 @@ public AppManagerAppHelperImpl(@Reference ComponentManager componentManager, @Re this.componentsTask = (ComponentAggregateTask) componentsTask; this.schedulerTask = (SchedulerAggregateTask) schedulerTask; this.staticIpTask = (StaticIpAggregateTask) staticIpTask; - tasks = new AggregateTask[] { componentsTask, schedulerTask, staticIpTask }; + this.tasks = new AggregateTask[] { componentsTask, schedulerTask, staticIpTask }; } @Override @@ -81,6 +82,37 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj // maybe add temporary apps in this component if (oldInstance == null) { AppManagerAppHelperImpl.checkStatus(app); + } else { + // determine if properties are allowed to be updated + var references = this.getAppsWithReferenceTo(oldInstance.instanceId); + for (var entry : this.getAppManagerImpl().appConfigs(references)) { + for (var dependencieDeclaration : entry.getValue().dependencies) { + + var dd = entry.getKey().dependencies.stream() + .filter(d -> d.instanceId.equals(oldInstance.instanceId)) + .filter(d -> d.key.equals(dependencieDeclaration.key)).findAny(); + + if (dd.isEmpty()) { + continue; + } + + switch (dependencieDeclaration.dependencyUpdatePolicy) { + case ALLOW_ALL: + // everything can be changed + break; + case ALLOW_NONE: + // reset to old config + properties = oldInstance.properties; + break; + case ALLOW_ONLY_UNCONFIGURED_PROPERTIES: + // override properties + for (var propEntry : dependencieDeclaration.properties.entrySet()) { + properties.add(propEntry.getKey(), propEntry.getValue()); + } + break; + } + } + } } final var language = user == null ? null : user.getLanguage(); @@ -101,8 +133,10 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj } var modifiedOrCreatedApps = new ArrayList(); + var deletedApps = new ArrayList(); // update app and its dependencies this.foreachDependency(app, alias, properties, ConfigurationTarget.UPDATE, language, dc -> { + // get old instance if existing ExistingDependencyConfig oldAppConfig = null; if (oldInstance != null) { if (dc.isDependency()) { @@ -159,7 +193,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj if (dc.sub.updatePolicy.isAllowedToUpdate(this.getAppManagerImpl().getInstantiatedApps(), null, neededApp.get())) { try { - // TODO test update app + // update app oldConfig = dc.app.getAppConfiguration(ConfigurationTarget.UPDATE, neededApp.get().properties, language); for (var entry : neededApp.get().properties.entrySet()) { @@ -199,6 +233,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj replaceApp.instanceId, replaceApp.properties, newDependencies)); } } + var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), dc.alias, instanceId, dc.properties, dependecies); modifiedOrCreatedApps.add(newAppInstance); @@ -214,13 +249,11 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj } return true; } else { - return false; } - } - // update existing apps + // update existing app var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), dc.alias, oldAppConfig.instance.instanceId, dc.properties, dependecies); modifiedOrCreatedApps.add(newAppInstance); @@ -247,6 +280,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj continue; } this.aggregateAllTasks(null, dc.config); + deletedApps.add(dc.instance); } var ignoreInstances = new ArrayList<>(modifiedOrCreatedApps); @@ -282,8 +316,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); } - return new UpdateValues(modifiedOrCreatedApps, - oldInstances.entrySet().stream().map(t -> t.getValue().instance).toList()); + return new UpdateValues(modifiedOrCreatedApps, deletedApps); } private final DependencyDeclaration getNeededDependencyTo(OpenemsAppInstance instance, String appId) { @@ -305,8 +338,7 @@ private final DependencyDeclaration getNeededDependencyTo(OpenemsAppInstance ins } } } catch (OpenemsNamedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + // can not get app configuration } return null; } @@ -345,17 +377,19 @@ public String toString() { @Override public UpdateValues deleteApp(User user, OpenemsAppInstance instance) throws OpenemsNamedException { this.resetTasks(); + + // check if the app is allowed to be delete + if (!this.isAllowedToDelete(instance)) { + throw new OpenemsException("App is not allowed to be deleted because of a dependency constraint!"); + } + var deletedInstances = new LinkedList(); final var language = user == null ? null : user.getLanguage(); + final var errors = new LinkedList(); this.foreachExistingDependecy(instance, ConfigurationTarget.DELETE, language, dc -> { - // check if the app is allowed to be delete - if (!this.isAllowedToDelete(dc.instance, dc.parentInstance != null ? dc.parentInstance.instanceId : null)) { - return false; - } - - // check if dependency is allowed to be deleten by its parent + // check if dependency is allowed to be deleted by its parent if (dc.isDependency()) { switch (dc.sub.deletePolicy) { case NEVER: @@ -394,31 +428,47 @@ public UpdateValues deleteApp(User user, OpenemsAppInstance instance) throws Ope var otherAppConfigs = this.getAppManagerImpl() .getOtherAppConfigurations(deletedInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); - // TODO errors try { // delete components this.componentsTask.delete(user, otherAppConfigs); } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); + } + try { + // remove ids in scheduler + this.schedulerTask.setDeletedComponents(this.componentsTask.getDeletedComponents()); + this.schedulerTask.delete(user, otherAppConfigs); + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); } - // remove ids in scheduler - this.schedulerTask.setDeletedComponents(this.componentsTask.getDeletedComponents()); - this.schedulerTask.delete(user, otherAppConfigs); + try { + // remove static ips + this.staticIpTask.delete(user, otherAppConfigs); + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); + } - // remove static ips - this.staticIpTask.delete(user, otherAppConfigs); + if (!errors.isEmpty()) { + throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); + } return new UpdateValues(modifiedApps, deletedInstances); } + public void getModifiedAppAssistant(User user, OpenemsApp app) { + // TODO does not work when having multiple instances of the same app and only + // one has properties that cant be edited + } + public List getAppsWithReferenceTo(UUID... instanceIds) { return this.getAppManagerImpl().getInstantiatedApps() // .stream() // .filter(i -> i.dependencies != null && !i.dependencies.isEmpty()) // .filter(i -> i.dependencies.stream().anyMatch( // d -> Arrays.stream(instanceIds).anyMatch(id -> id.equals(d.instanceId)))) // - .toList(); + .collect(Collectors.toList()); } private final void aggregateAllTasks(AppConfiguration instance, AppConfiguration oldInstance) { @@ -427,33 +477,36 @@ private final void aggregateAllTasks(AppConfiguration instance, AppConfiguration } } + /** + * Checks if the instance is allowed to be deleted depending on other apps + * dependencies to this instance. + * + * @param instance + * @param ignoreIds + * @return + */ public final boolean isAllowedToDelete(OpenemsAppInstance instance, UUID... ignoreIds) { - for (var i : this.getAppManagerImpl().getInstantiatedApps()) { - if (Arrays.stream(ignoreIds).anyMatch(id -> i.instanceId.equals(id))) { - continue; - } - if (i.dependencies == null || i.dependencies.isEmpty()) { - continue; - } - var app = this.getAppManagerImpl().findAppById(i.appId); - if (i.dependencies.stream().filter(d -> d.instanceId.equals(instance.instanceId)) // - .anyMatch(d -> { - try { - var dd = app.getAppConfiguration(ConfigurationTarget.UPDATE, i.properties, null) // - .dependencies.stream().filter(f -> f.key.equals(d.key)).findFirst().get(); - switch (dd.dependencyDeletePolicy) { - case ALLOWED: - return false; - case NOT_ALLOWED: - return true; - } - } catch (OpenemsNamedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return false; - })) { - return false; + // check if a parent does not allow deletion of this instance + for (var entry : this.getAppManagerImpl().appConfigs(this.getAppsWithReferenceTo(instance.instanceId), + ignoreIds)) { + for (var dependency : entry.getKey().dependencies) { + if (!dependency.instanceId.equals(instance.instanceId)) { + continue; + } + var declaration = entry.getValue().dependencies.stream().filter(dd -> dd.key.equals(dependency.key)) + .findAny(); + + // declaration not found for dependency + if (declaration.isEmpty()) { + continue; + } + + switch (declaration.get().dependencyDeletePolicy) { + case ALLOWED: + break; + case NOT_ALLOWED: + return false; + } } } return true; @@ -476,11 +529,10 @@ protected static void checkStatus(OpenemsApp openemsApp) throws OpenemsNamedExce throw new OpenemsException("App can not be installed! " + validator.getErrorCompatibleMessages().stream().collect(Collectors.joining(";"))); case INSTALLABLE: - // continue - break; - default: - throw new OpenemsException("Status '" + status.name() + "' is not implemented."); + // app can be installed + return; } + throw new OpenemsException("Status '" + status.name() + "' is not implemented."); } protected static List getComponentsFromConfigs(List configs) { @@ -524,8 +576,9 @@ private Optional findNeededApp(DependencyConfig dc, String a .toList(); OpenemsAppInstance availableApp = null; for (var neededApp : neededApps) { - if (!this.getAppManagerImpl().getInstantiatedApps().stream().anyMatch( - t -> t.dependencies.stream().anyMatch(d -> d.instanceId.equals(neededApp.instanceId)))) { + if (!this.getAppManagerImpl().getInstantiatedApps().stream() + .filter(t -> t.dependencies != null && !t.dependencies.isEmpty()).anyMatch(t -> t.dependencies + .stream().anyMatch(d -> d.instanceId.equals(neededApp.instanceId)))) { availableApp = neededApp; break; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java index 6ec979c89b0..00c58817d88 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java @@ -2,7 +2,6 @@ import java.util.List; import java.util.function.BiFunction; -import java.util.function.Supplier; import com.google.common.base.Function; import com.google.gson.JsonObject; @@ -20,25 +19,36 @@ public class DependencyDeclaration { public final UpdatePolicy updatePolicy; public final DeletePolicy deletePolicy; + public final DependencyUpdatePolicy dependencyUpdatePolicy; public final DependencyDeletePolicy dependencyDeletePolicy; // Dependency Supplier? - private final Supplier supplierForAppId = null; - private final Function, String> supplierForInstanceIdFromExisting = null; +// private final Supplier supplierForAppId = null; +// private final Function, String> supplierForInstanceIdFromExisting = null; public DependencyDeclaration(String key, String appId, String alias, CreatePolicy createPolicy, - UpdatePolicy updatePolicy, DeletePolicy deletePolicy, DependencyDeletePolicy dependencyDeletePolicy, JsonObject properties) { + UpdatePolicy updatePolicy, DeletePolicy deletePolicy, DependencyUpdatePolicy dependencyUpdatePolicy, + DependencyDeletePolicy dependencyDeletePolicy, JsonObject properties) { this.key = key; this.appId = appId; this.alias = alias; - this.properties = properties; + this.properties = properties == null ? new JsonObject() : properties; this.createPolicy = createPolicy; this.updatePolicy = updatePolicy; this.deletePolicy = deletePolicy; + this.dependencyUpdatePolicy = dependencyUpdatePolicy; this.dependencyDeletePolicy = dependencyDeletePolicy; } + /** + * Defines if the dependency app should get created when creating the parent + * app. + */ public static enum CreatePolicy { + /** + * TODO + */ + HAS_TO_EXIST((t, u) -> false), // /** * Always creates the dependent app except an {@link OpenemsAppInstance} is @@ -76,6 +86,9 @@ public final boolean isAllowedToCreate(List allInstances, St } } + /** + * Defines if the dependency should get updated when updating the parent app. + */ public static enum UpdatePolicy { ALWAYS(v -> true), // IF_MINE(v -> v.allInstances.stream() @@ -97,11 +110,13 @@ public final boolean isAllowedToUpdate(List allInstances, Op } + /** + * Defines if the dependency app gets deleted when deleting its parent. + */ public static enum DeletePolicy { ALWAYS(v -> true), // - IF_MINE(v -> v.allInstances.stream() - .anyMatch(a -> !a.equals(v.parent) && a.dependencies != null - && a.dependencies.stream().anyMatch(d -> d.instanceId.equals(v.app2Update.instanceId)))), // + IF_MINE(v -> !v.allInstances.stream().filter(a -> !a.equals(v.parent) && a.dependencies != null) + .anyMatch(a -> a.dependencies.stream().anyMatch(d -> d.instanceId.equals(v.app2Update.instanceId)))), // NEVER(v -> false), // ; @@ -113,7 +128,7 @@ private DeletePolicy(Function isAllowedToDelete) { public final boolean isAllowedToDelete(List allInstances, OpenemsAppInstance parent, OpenemsAppInstance app2Delete) { - return isAllowedToDeleteFunction.apply(new AllowedToValues(allInstances, parent, app2Delete)); + return this.isAllowedToDeleteFunction.apply(new AllowedToValues(allInstances, parent, app2Delete)); } } @@ -132,12 +147,15 @@ public AllowedToValues(List allInstances, OpenemsAppInstance // TODO public static enum DependencyUpdatePolicy { - ALL, // - ONLY_NOT_CONFIGURED_PROPERTIES, // - NO_PROPERTIES, // + ALLOW_ALL, // + ALLOW_ONLY_UNCONFIGURED_PROPERTIES, // + ALLOW_NONE, // ; } + /** + * Defines if the user can delete an app which is a dependency of another app. + */ public static enum DependencyDeletePolicy { NOT_ALLOWED, // ALLOWED, // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties index f58a24dbd1e..67aa7aff168 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties @@ -147,5 +147,7 @@ App.TimeOfUseTariff.Tibber.accessToken.description = Zugangstoken für den Tibbe # PvSelfConsumption App.PvSelfConsumption.GridOptimizedCharge.Name = Netzdienliche Beladung +App.PvSelfConsumption.GridOptimizedCharge.sellToGridLimitEnabled.label = Ist der maximal Stromverkauf an das Netz aktiviert? +App.PvSelfConsumption.GridOptimizedCharge.sellToGridLimitEnabled.description = Ist die Logik für den maximal Stromverkauf an das Netz aktiviert? App.PvSelfConsumption.GridOptimizedCharge.maximumSellToGridPower.label = Maximal zulässiger Stromverkauf an das Netz App.PvSelfConsumption.GridOptimizedCharge.maximumSellToGridPower.description = Die Zielgrenze für den Verkauf von Strom an das Netz. \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties index 88ac7efbbac..67a49456990 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties @@ -147,5 +147,7 @@ App.TimeOfUseTariff.Tibber.accessToken.description = Access token for the Tibber # PvSelfConsumption App.PvSelfConsumption.GridOptimizedCharge.Name = Grid optimized charge +App.PvSelfConsumption.GridOptimizedCharge.sellToGridLimitEnabled.label = Is Sell-To-Grid-Limit enabled? +App.PvSelfConsumption.GridOptimizedCharge.sellToGridLimitEnabled.description = Is the sell to grid limit logic enabled? App.PvSelfConsumption.GridOptimizedCharge.maximumSellToGridPower.label = Maximum allowed Sell-To-Grid power App.PvSelfConsumption.GridOptimizedCharge.maximumSellToGridPower.description = The target limit for sell-to-grid power. \ No newline at end of file diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java index 9e935254b10..4478166b760 100644 --- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java +++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java @@ -281,14 +281,14 @@ public void testGetInstantiatedApps() { this.sut.getInstantiatedApps().add(null); } - @Test - public void testGetReplaceableComponentIds() throws Exception { - var replaceableIds = this.sut.getReplaceableComponentIds(this.kebaEvcsApp, JsonUtils.buildJsonObject().build()); - - assertEquals(replaceableIds.size(), 2); - assertEquals("EVCS_ID", replaceableIds.get("evcs0")); - assertEquals("CTRL_EVCS_ID", replaceableIds.get("ctrlEvcs0")); - } +// @Test TODO +// public void testGetReplaceableComponentIds() throws Exception { +// var replaceableIds = this.sut.getReplaceableComponentIds(this.kebaEvcsApp, JsonUtils.buildJsonObject().build()); +// +// assertEquals(replaceableIds.size(), 2); +// assertEquals("EVCS_ID", replaceableIds.get("evcs0")); +// assertEquals("CTRL_EVCS_ID", replaceableIds.get("ctrlEvcs0")); +// } @Test public void testFindAppById() { diff --git a/ui/src/app/edge/settings/app/index.component.ts b/ui/src/app/edge/settings/app/index.component.ts index 7a866394ce6..ddade25ab69 100644 --- a/ui/src/app/edge/settings/app/index.component.ts +++ b/ui/src/app/edge/settings/app/index.component.ts @@ -15,6 +15,11 @@ export class IndexComponent { private static readonly SELECTOR = "appIndex"; public readonly spinnerId: string = IndexComponent.SELECTOR; + /** + * e. g. if more than 4 apps are in a list the categories are displayed + */ + private static readonly MAX_APPS_IN_LIST = 4; + public apps: GetApps.App[] = []; public installedApps: AppList = { name: this.translate.instant('Edge.Config.App.installed'), appCategories: [] }; @@ -124,7 +129,7 @@ export class IndexComponent { } private showCategories(app: AppList) { - return this.sum(app) > 4 + return this.sum(app) > IndexComponent.MAX_APPS_IN_LIST } private isEmpty(app: AppList) { diff --git a/ui/src/app/edge/settings/app/update.component.ts b/ui/src/app/edge/settings/app/update.component.ts index d378bec0bfc..afc77070c1d 100644 --- a/ui/src/app/edge/settings/app/update.component.ts +++ b/ui/src/app/edge/settings/app/update.component.ts @@ -132,9 +132,9 @@ export class UpdateAppComponent implements OnInit { }) })).then(response => { this.service.toast("Successfully deleted App", 'success'); - this.instances.splice(this.instances.indexOf(instance), 1) }).catch(reason => { this.service.toast("Error deleting App:" + reason.error.message, 'danger'); + }).finally(() => { this.instances.splice(this.instances.indexOf(instance), 1) }); } From 2d0672580f1bac68432ec254e41b8e1c6a11c8df Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:12:01 +0200 Subject: [PATCH 10/30] code format; type safety --- .../app/integratedsystem/FeneconHome.java | 46 +-- .../core/appmanager/AbstractOpenemsApp.java | 1 + .../edge/core/appmanager/AppManagerImpl.java | 85 +++--- .../edge/core/appmanager/JsonFormlyUtil.java | 2 +- .../core/appmanager/OpenemsAppInstance.java | 4 +- .../appmanager/dependency/AggregateTask.java | 53 +++- .../dependency/AppManagerAppHelper.java | 47 +-- .../dependency/AppManagerAppHelperImpl.java | 274 +++++++++--------- ...k.java => ComponentAggregateTaskImpl.java} | 18 +- .../appmanager/dependency/Dependency.java | 11 +- .../dependency/DependencyConfig.java | 5 +- .../dependency/DependencyDeclaration.java | 30 +- .../dependency/ExistingDependencyConfig.java | 4 +- ...k.java => SchedulerAggregateTaskImpl.java} | 35 +-- ...sk.java => StaticIpAggregateTaskImpl.java} | 16 +- 15 files changed, 362 insertions(+), 269 deletions(-) rename io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/{ComponentAggregateTask.java => ComponentAggregateTaskImpl.java} (92%) rename io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/{SchedulerAggregateTask.java => SchedulerAggregateTaskImpl.java} (63%) rename io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/{StaticIpAggregateTask.java => StaticIpAggregateTaskImpl.java} (76%) diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java index 078f854d80e..ccc0b2faa7e 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java @@ -76,7 +76,7 @@ public static enum Property { FEED_IN_SETTING, // // (ger. Rundsteuerempfänger) - RIPPLE_CONTROL_RECEIVER_AKTIV, // + RIPPLE_CONTROL_RECEIVER_ACTIV, // // External AC PV HAS_AC_METER, // @@ -118,7 +118,7 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { var modbusIdExternal = "modbus1"; var emergencyReserveEnabled = EnumUtils.getAsBoolean(p, Property.EMERGENCY_RESERVE_ENABLED); - var rippleControlReceiverActive = EnumUtils.getAsBoolean(p, Property.RIPPLE_CONTROL_RECEIVER_AKTIV); + var rippleControlReceiverActive = EnumUtils.getAsBoolean(p, Property.RIPPLE_CONTROL_RECEIVER_ACTIV); // Battery-Inverter Settings var safetyCountry = EnumUtils.getAsString(p, Property.SAFETY_COUNTRY); @@ -342,7 +342,7 @@ public AppAssistant getAppAssistant(Language language) { f.setDefaultValue(batteryInverter.get() // .getProperty("safetyCountry").get().getAsString()); }).build()) - .add(JsonFormlyUtil.buildCheckbox(Property.RIPPLE_CONTROL_RECEIVER_AKTIV) // + .add(JsonFormlyUtil.buildCheckbox(Property.RIPPLE_CONTROL_RECEIVER_ACTIV) // .setLabel(bundle.getString(this.getAppId() + ".rippleControlReceiver.label")) .setDescription( bundle.getString(this.getAppId() + ".rippleControlReceiver.description")) @@ -351,7 +351,7 @@ public AppAssistant getAppAssistant(Language language) { .add(JsonFormlyUtil.buildInput(Property.MAX_FEED_IN_POWER) // .setLabel(bundle.getString(this.getAppId() + ".feedInLimit.label")) // .isRequired(true) // - .onlyShowIfNotChecked(Property.RIPPLE_CONTROL_RECEIVER_AKTIV) // + .onlyShowIfNotChecked(Property.RIPPLE_CONTROL_RECEIVER_ACTIV) // .setInputType(Type.NUMBER) // .onlyIf(batteryInverter.isPresent(), f -> { f.setDefaultValue(batteryInverter.get() // @@ -380,16 +380,19 @@ public AppAssistant getAppAssistant(Language language) { .getComponent("charger0", "GoodWe.Charger-PV1").isPresent()) .build()) .add(JsonFormlyUtil.buildInput(Property.DC_PV1_ALIAS) // + .setLabel("DC-PV 1 Alias") // .setDefaultValue("DC-PV1") // .onlyShowIfChecked(Property.HAS_DC_PV1) // - .setDefaultValueWithStringSupplier(() -> { - var charger = this.componentUtil // - .getComponent("charger0", "GoodWe.Charger-PV1"); - if (charger.isEmpty()) { - return null; - } - return charger.get().getAlias(); - }).build()) + .onlyIf(this.componentUtil.getComponent("charger0", "GoodWe.Charger-PV1").isPresent(), + j -> j.setDefaultValueWithStringSupplier(() -> { + var charger = this.componentUtil // + .getComponent("charger0", "GoodWe.Charger-PV1"); + if (charger.isEmpty()) { + return null; + } + return charger.get().getAlias(); + })) + .build()) .add(JsonFormlyUtil.buildCheckbox(Property.HAS_DC_PV2) // .setLabel(bundle.getString(this.getAppId() + ".hasDcPV2.label")) // .isRequired(true) // @@ -398,15 +401,18 @@ public AppAssistant getAppAssistant(Language language) { .build()) .add(JsonFormlyUtil.buildInput(Property.DC_PV2_ALIAS) // .setLabel("DC-PV 2 Alias") // + .setDefaultValue("DC-PV2") // .onlyShowIfChecked(Property.HAS_DC_PV2) // - .setDefaultValueWithStringSupplier(() -> { - var charger = this.componentUtil // - .getComponent("charger1", "GoodWe.Charger-PV2"); - if (charger.isEmpty()) { - return null; - } - return charger.get().getAlias(); - }).build()) + .onlyIf(this.componentUtil.getComponent("charger1", "GoodWe.Charger-PV2").isPresent(), + j -> j.setDefaultValueWithStringSupplier(() -> { + var charger = this.componentUtil // + .getComponent("charger1", "GoodWe.Charger-PV2"); + if (charger.isEmpty()) { + return null; + } + return charger.get().getAlias(); + })) + .build()) .add(JsonFormlyUtil.buildCheckbox(Property.EMERGENCY_RESERVE_ENABLED) // .setLabel(bundle.getString(this.getAppId() + ".emergencyPowerSupply.label")) // .isRequired(true) // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java index e6ab771a56a..760b44145a7 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java @@ -222,6 +222,7 @@ protected String getId(ConfigurationTarget t, EnumMap map * Validate the App configuration. * * @param jProperties a JsonObject holding the App properties + * @param dependecies the dependencies of the current instance * @return a list of validation errors. Empty list says 'no errors' */ private List getValidationErrors(JsonObject jProperties, List dependecies) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java index affe6a5a617..f2994591f7a 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java @@ -7,12 +7,10 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.function.BiConsumer; import java.util.stream.Collectors; import org.osgi.service.cm.ConfigurationAdmin; @@ -123,7 +121,8 @@ public final List getInstantiatedApps() { * @return formated apps string */ private static String getJsonAppsString(List apps) { - return JsonUtils.prettyToString(apps.stream().map(t -> t.toJsonObject()).collect(JsonUtils.toJsonArray())); + return JsonUtils + .prettyToString(apps.stream().map(OpenemsAppInstance::toJsonObject).collect(JsonUtils.toJsonArray())); } /** @@ -150,7 +149,7 @@ private static List parseInstantiatedApps(JsonArray apps) th if (json.has("dependencies")) { dependecies = new LinkedList<>(); var dependecyArray = json.get("dependencies").getAsJsonArray(); - for (int i = 0; i < dependecyArray.size(); i++) { + for (var i = 0; i < dependecyArray.size(); i++) { var dependecyJson = dependecyArray.get(i).getAsJsonObject(); var dependecy = new Dependency(dependecyJson.get("key").getAsString(), JsonUtils.getAsUUID(dependecyJson, "instanceId")); @@ -191,49 +190,60 @@ public void configurationEvent(ConfigurationEvent event) { this.worker.configurationEvent(event); } - public void foreachAppConfiguration(BiConsumer consumer, - UUID... excludingInstanceIds) { - this.foreachAppConfiguration(this.instantiatedApps, consumer, excludingInstanceIds); - } - - public void foreachAppConfiguration(List instances, - BiConsumer consumer, UUID... excludingInstanceIds) { - for (var entry : this.appConfigs(instances, excludingInstanceIds)) { - consumer.accept(entry.getKey(), entry.getValue()); - } - } - + /** + * Gets an {@link Iterable} that loops thru every existing app instance and its + * configuration. + * + * @param excludingInstanceIds the instance ids that that should be ignored + * @return the {@link Iterable} + */ public Iterable> appConfigs(UUID... excludingInstanceIds) { return this.appConfigs(this.instantiatedApps, excludingInstanceIds); } + /** + * Gets an {@link Iterable} that loops thru every instance and its + * configuration. + * + * @param instances the instances + * @param excludingInstanceIds the instance ids that that should be ignored + * @return the {@link Iterable} + */ public Iterable> appConfigs(List instances, UUID... excludingInstanceIds) { - return new Iterable>() { + return new Iterable<>() { @Override public Iterator> iterator() { - return appConfigIterator(instances, excludingInstanceIds); + return AppManagerImpl.this.appConfigIterator(instances, excludingInstanceIds); } }; } + /** + * Gets an {@link Iterator} that loops thru every instance and its + * configuration. + * + * @param instances the instances + * @param excludingInstanceIds the instance ids that that should be ignored + * @return the {@link Iterator} + */ public Iterator> appConfigIterator(List instances, UUID... excludingInstanceIds) { List actualInstances = instances.stream() .filter(i -> !Arrays.stream(excludingInstanceIds).anyMatch(id -> id.equals(i.instanceId))) .collect(Collectors.toList()); - return new Iterator>() { + return new Iterator<>() { - private Iterator instanceIterator = actualInstances.iterator(); + private final Iterator instanceIterator = actualInstances.iterator(); private OpenemsAppInstance nextInstance = null; private AppConfiguration nextConfiguration = null; @Override public Entry next() { - var returnValue = new AbstractMap.SimpleEntry<>(nextInstance, nextConfiguration); - nextInstance = null; - nextConfiguration = null; + var returnValue = new AbstractMap.SimpleEntry<>(this.nextInstance, this.nextConfiguration); + this.nextInstance = null; + this.nextConfiguration = null; return returnValue; } @@ -245,11 +255,11 @@ public boolean hasNext() { this.nextInstance = this.instanceIterator.next(); try { - var app = findAppById(nextInstance.appId); - nextInstance.properties.addProperty("ALIAS", nextInstance.alias); - nextConfiguration = app.getAppConfiguration(ConfigurationTarget.VALIDATE, nextInstance.properties, - null); - nextInstance.properties.remove("ALIAS"); + var app = AppManagerImpl.this.findAppById(this.nextInstance.appId); + this.nextInstance.properties.addProperty("ALIAS", this.nextInstance.alias); + this.nextConfiguration = app.getAppConfiguration(ConfigurationTarget.VALIDATE, + this.nextInstance.properties, null); + this.nextInstance.properties.remove("ALIAS"); } catch (OpenemsNamedException e) { // move to next app } catch (NoSuchElementException e) { @@ -258,7 +268,7 @@ public boolean hasNext() { // apps which app ids are not known are printed in debug log as 'UNKNOWAPPS' } - return nextConfiguration != null; + return this.nextConfiguration != null; } }; } @@ -291,9 +301,9 @@ public final OpenemsApp findAppById(String id) throws NoSuchElementException { /** * Finds the app instance with the matching id. - * + * * @param uuid the id of the instance - * @returns the instance + * @return s the instance * @throws NoSuchElementException if no instance is present */ public final OpenemsAppInstance findInstaceById(UUID uuid) throws NoSuchElementException { @@ -303,11 +313,18 @@ public final OpenemsAppInstance findInstaceById(UUID uuid) throws NoSuchElementE .get(); } + /** + * Gets all {@link AppConfiguration}s from the existing + * {@link OpenemsAppInstance}s. + * + * @param ignoreIds the id's of the instances that should be ignored + * @return the {@link AppConfiguration}s + */ public final List getOtherAppConfigurations(UUID... ignoreIds) { List allOtherConfigs = new ArrayList<>(this.instantiatedApps.size()); - this.foreachAppConfiguration((i, c) -> { - allOtherConfigs.add(c); - }, ignoreIds); + for (var entry : this.appConfigs(ignoreIds)) { + allOtherConfigs.add(entry.getValue()); + } return allOtherConfigs; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java index 003b2bc9081..39cc9285a5a 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java @@ -136,7 +136,7 @@ public final T setDefaultValue(Number defaultValue) { return this.getSelf(); } - + public final T setDefaultValue(JsonElement defaultValue) { if (defaultValue != null) { this.jsonObject.add("defaultValue", defaultValue); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java index 32ea9b1954d..d369a9dac01 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java @@ -58,8 +58,8 @@ public JsonObject toJsonObject() { .addProperty("alias", this.alias) // .addProperty("instanceId", this.instanceId.toString()) // .add("properties", this.properties) // TODO define if the field is editable - .onlyIf(dependencies != null && !dependencies.isEmpty(), j -> j.add("dependencies", // - dependencies.stream().map(t -> t.toJsonObject()).collect(JsonUtils.toJsonArray()))) + .onlyIf(this.dependencies != null && !this.dependencies.isEmpty(), j -> j.add("dependencies", // + this.dependencies.stream().map(Dependency::toJsonObject).collect(JsonUtils.toJsonArray()))) .build(); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java index 25f4daec83a..eafd5ae6ece 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AggregateTask.java @@ -3,19 +3,68 @@ import java.util.List; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.EdgeConfig; import io.openems.edge.common.user.User; import io.openems.edge.core.appmanager.AppConfiguration; public interface AggregateTask { -// public boolean shouldAggregate(DependencyConfig instance); - + /** + * Aggregates the given instance. + * + * @param instance the {@link AppConfiguration} of the instance + * @param oldConfig the old configuration of the instance + */ public void aggregate(AppConfiguration instance, AppConfiguration oldConfig); + /** + * e. g. creates components that were aggregated by the instances and my also + * delete unused components. + * + * @param user the executing user + * @param otherAppConfigurations the other existing {@link AppConfiguration}s + * @throws OpenemsNamedException on error + */ public void create(User user, List otherAppConfigurations) throws OpenemsNamedException; + /** + * e. g. deletes components that were aggregated. + * + * @param user the executing user + * @param otherAppConfigurations the other existing {@link AppConfiguration}s + * @throws OpenemsNamedException on error + */ public void delete(User user, List otherAppConfigurations) throws OpenemsNamedException; + /** + * Resets the task. + */ public void reset(); + public static interface ComponentAggregateTask extends AggregateTask { + + /** + * Gets the Components that were created. + * + * @return the created {@link EdgeConfig.Component} + */ + public List getCreatedComponents(); + + /** + * Gets the Components that were deleted. + * + * @return the id's of the deleted {@link EdgeConfig.Component} + */ + public List getDeletedComponents(); + + } + + public static interface SchedulerAggregateTask extends AggregateTask { + + } + + public static interface StaticIpAggregateTask extends AggregateTask { + + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java index 629285644a8..e729e553932 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java @@ -13,35 +13,40 @@ public interface AppManagerAppHelper { /** - * - * @param user - * @param properties - * @param alias - * @param app - * @returns a list of the created {@link OpenemsAppInstance}s - * @throws OpenemsNamedException + * Installs an {@link OpenemsApp} with all its {@link Dependency}s. + * + * @param user the executing user + * @param properties the properties of the {@link OpenemsAppInstance} + * @param alias the alias of the {@link OpenemsAppInstance} + * @param app the {@link OpenemsApp} + * @return s a list of the created {@link OpenemsAppInstance}s + * @throws OpenemsNamedException on error */ public List installApp(User user, JsonObject properties, String alias, OpenemsApp app) throws OpenemsNamedException; /** - * - * @param user - * @param properties - * @param alias - * @param app - * @returns a list of the replaced {@link OpenemsAppInstance}s - * @throws OpenemsNamedException + * Updates an existing {@link OpenemsAppInstance}. + * + * @param user the executing user + * @param oldInstance the old {@link OpenemsAppInstance} with its + * configurations. + * @param properties the properties of the new {@link OpenemsAppInstance} + * @param alias the alias of the new {@link OpenemsAppInstance} + * @param app the {@link OpenemsApp} + * @return s a list of the replaced {@link OpenemsAppInstance}s + * @throws OpenemsNamedException on error */ - public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObject properties, String alias, OpenemsApp app) - throws OpenemsNamedException; + public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObject properties, String alias, + OpenemsApp app) throws OpenemsNamedException; /** - * - * @param user - * @param instance - * @returns a list of the removed {@link OpenemsAppInstance}s - * @throws OpenemsNamedException + * Deletes an {@link OpenemsAppInstance}. + * + * @param user the executing user + * @param instance the instance to delete + * @return s a list of the removed {@link OpenemsAppInstance}s + * @throws OpenemsNamedException on error */ public UpdateValues deleteApp(User user, OpenemsAppInstance instance) throws OpenemsNamedException; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java index faf6d8a181c..0aac2febce9 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java @@ -44,9 +44,9 @@ public class AppManagerAppHelperImpl implements AppManagerAppHelper { private final ComponentUtil componentUtil; // tasks - private final ComponentAggregateTask componentsTask; - private final SchedulerAggregateTask schedulerTask; - private final StaticIpAggregateTask staticIpTask; + private final AggregateTask.ComponentAggregateTask componentsTask; + private final AggregateTask.SchedulerAggregateTask schedulerTask; + private final AggregateTask.SchedulerAggregateTask staticIpTask; private final AggregateTask[] tasks; @@ -54,14 +54,14 @@ public class AppManagerAppHelperImpl implements AppManagerAppHelper { @Activate public AppManagerAppHelperImpl(@Reference ComponentManager componentManager, @Reference ComponentUtil componentUtil, - @Reference(target = "(component.name=AppManager.AggregateTask.CreateComponents)") AggregateTask componentsTask, - @Reference(target = "(component.name=AppManager.AggregateTask.SchedulerAggregateTask)") AggregateTask schedulerTask, - @Reference(target = "(component.name=AppManager.AggregateTask.StaticIpAggregateTask)") AggregateTask staticIpTask) { + @Reference AggregateTask.ComponentAggregateTask componentsTask, + @Reference AggregateTask.SchedulerAggregateTask schedulerTask, + @Reference AggregateTask.SchedulerAggregateTask staticIpTask) { this.componentManager = componentManager; this.componentUtil = componentUtil; - this.componentsTask = (ComponentAggregateTask) componentsTask; - this.schedulerTask = (SchedulerAggregateTask) schedulerTask; - this.staticIpTask = (StaticIpAggregateTask) staticIpTask; + this.componentsTask = componentsTask; + this.schedulerTask = schedulerTask; + this.staticIpTask = staticIpTask; this.tasks = new AggregateTask[] { componentsTask, schedulerTask, staticIpTask }; } @@ -106,6 +106,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj break; case ALLOW_ONLY_UNCONFIGURED_PROPERTIES: // override properties + // TODO maybe warning that these values are not set for (var propEntry : dependencieDeclaration.properties.entrySet()) { properties.add(propEntry.getKey(), propEntry.getValue()); } @@ -151,7 +152,15 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj } } else { - oldAppConfig = new ExistingDependencyConfig(app, null, null, null, oldInstance.alias, + AppConfiguration oldAppConfiguration = null; + try { + oldAppConfiguration = dc.app.getAppConfiguration(ConfigurationTarget.UPDATE, + oldInstance.properties, language); + + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); + } + oldAppConfig = new ExistingDependencyConfig(app, null, null, oldAppConfiguration, oldInstance.alias, oldInstance.properties, null, null, oldInstance); } } @@ -165,9 +174,8 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj && !dc.config.dependencies.stream().anyMatch(t -> t.equals(dependency.getKey().sub))) { isParent = false; break; - } else { - isParent = true; } + isParent = true; dependecies.add(new Dependency(dependency.getKey().sub.key, dependency.getValue().instanceId)); } if (isParent) { @@ -183,74 +191,72 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj var appId = dc.app.getAppId(); var neededApp = this.findNeededApp(dc, appId); - if (neededApp != null) { - AppConfiguration oldConfig = null; - UUID instanceId; - OpenemsAppInstance oldInstanceOfCurrentApp = null; - if (neededApp.isPresent()) { - instanceId = neededApp.get().instanceId; - oldInstanceOfCurrentApp = neededApp.get(); - if (dc.sub.updatePolicy.isAllowedToUpdate(this.getAppManagerImpl().getInstantiatedApps(), null, - neededApp.get())) { - try { - // update app - oldConfig = dc.app.getAppConfiguration(ConfigurationTarget.UPDATE, - neededApp.get().properties, language); - for (var entry : neededApp.get().properties.entrySet()) { - // add old values which are not set by the DependecyDeclaration - if (!dc.properties.has(entry.getKey())) { - dc.properties.add(entry.getKey(), entry.getValue()); - } + if (neededApp == null) { + return false; + } + AppConfiguration oldConfig = null; + UUID instanceId; + OpenemsAppInstance oldInstanceOfCurrentApp = null; + if (neededApp.isPresent()) { + instanceId = neededApp.get().instanceId; + oldInstanceOfCurrentApp = neededApp.get(); + if (dc.sub.updatePolicy.isAllowedToUpdate(this.getAppManagerImpl().getInstantiatedApps(), null, + neededApp.get())) { + try { + // update app + oldConfig = dc.app.getAppConfiguration(ConfigurationTarget.UPDATE, + neededApp.get().properties, language); + for (var entry : neededApp.get().properties.entrySet()) { + // add old values which are not set by the DependecyDeclaration + if (!dc.properties.has(entry.getKey())) { + dc.properties.add(entry.getKey(), entry.getValue()); } - } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); } + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); } - } else { - // create app - instanceId = UUID.randomUUID(); - // check if the created app can satisfy another app dependency - for (var instance : this.getAppManagerImpl().getInstantiatedApps()) { - var neededDependency = this.getNeededDependencyTo(instance, dc.app.getAppId()); - if (neededDependency == null) { - continue; - } - if (neededDependency.createPolicy == DependencyDeclaration.CreatePolicy.ALWAYS) { - continue; - } - var alreadyModifiedAppIndex = modifiedOrCreatedApps.indexOf(instance); - OpenemsAppInstance replaceApp = instance; - if (alreadyModifiedAppIndex != -1) { - replaceApp = modifiedOrCreatedApps.get(alreadyModifiedAppIndex); - } - var newDependencies = new ArrayList(); - if (replaceApp.dependencies != null) { - newDependencies.addAll(replaceApp.dependencies); - } - newDependencies.add(new Dependency(neededDependency.key, instanceId)); - modifiedOrCreatedApps.remove(replaceApp); - modifiedOrCreatedApps.add(new OpenemsAppInstance(replaceApp.appId, replaceApp.alias, - replaceApp.instanceId, replaceApp.properties, newDependencies)); + } + } else { + // create app + instanceId = UUID.randomUUID(); + // check if the created app can satisfy another app dependency + for (var instance : this.getAppManagerImpl().getInstantiatedApps()) { + var neededDependency = this.getNeededDependencyTo(instance, dc.app.getAppId()); + if (neededDependency == null) { + continue; } + if (neededDependency.createPolicy == DependencyDeclaration.CreatePolicy.ALWAYS) { + continue; + } + var alreadyModifiedAppIndex = modifiedOrCreatedApps.indexOf(instance); + var replaceApp = instance; + if (alreadyModifiedAppIndex != -1) { + replaceApp = modifiedOrCreatedApps.get(alreadyModifiedAppIndex); + } + var newDependencies = new ArrayList(); + if (replaceApp.dependencies != null) { + newDependencies.addAll(replaceApp.dependencies); + } + newDependencies.add(new Dependency(neededDependency.key, instanceId)); + modifiedOrCreatedApps.remove(replaceApp); + modifiedOrCreatedApps.add(new OpenemsAppInstance(replaceApp.appId, replaceApp.alias, + replaceApp.instanceId, replaceApp.properties, newDependencies)); } + } - var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), dc.alias, instanceId, dc.properties, - dependecies); - modifiedOrCreatedApps.add(newAppInstance); - dependencieInstances.put(dc, newAppInstance); - try { - var newConfig = this.getNewAppConfigWithReplacedIds(dc.app, oldInstanceOfCurrentApp, - newAppInstance, AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), - language); + var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), dc.alias, instanceId, dc.properties, + dependecies); + modifiedOrCreatedApps.add(newAppInstance); + dependencieInstances.put(dc, newAppInstance); + try { + var newConfig = this.getNewAppConfigWithReplacedIds(dc.app, oldInstanceOfCurrentApp, newAppInstance, + AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), language); - this.aggregateAllTasks(newConfig, oldConfig); - } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); - } - return true; - } else { - return false; + this.aggregateAllTasks(newConfig, oldConfig); + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); } + return true; } // update existing app @@ -284,7 +290,8 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj } var ignoreInstances = new ArrayList<>(modifiedOrCreatedApps); - ignoreInstances.addAll(oldInstances.entrySet().stream().map(t -> t.getValue().instance).toList()); + ignoreInstances + .addAll(oldInstances.entrySet().stream().map(t -> t.getValue().instance).collect(Collectors.toList())); var otherAppConfigs = this.getAppManagerImpl() .getOtherAppConfigurations(ignoreInstances.stream().map(t -> t.instanceId).toArray(UUID[]::new)); @@ -298,8 +305,6 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj try { // update scheduler execute order - this.schedulerTask.setCreatedComponents(this.componentsTask.getCreatedComponents()); - this.schedulerTask.setDeletedComponents(this.componentsTask.getDeletedComponents()); this.schedulerTask.create(user, otherAppConfigs); } catch (OpenemsNamedException e) { errors.add(e.getMessage()); @@ -370,7 +375,7 @@ public int compareTo(AppIdKey o) { @Override public String toString() { - return appId + ":" + key; + return this.appId + ":" + this.key; } } @@ -437,7 +442,6 @@ public UpdateValues deleteApp(User user, OpenemsAppInstance instance) throws Ope try { // remove ids in scheduler - this.schedulerTask.setDeletedComponents(this.componentsTask.getDeletedComponents()); this.schedulerTask.delete(user, otherAppConfigs); } catch (OpenemsNamedException e) { errors.add(e.getMessage()); @@ -457,35 +461,40 @@ public UpdateValues deleteApp(User user, OpenemsAppInstance instance) throws Ope return new UpdateValues(modifiedApps, deletedInstances); } - public void getModifiedAppAssistant(User user, OpenemsApp app) { - // TODO does not work when having multiple instances of the same app and only - // one has properties that cant be edited - } + // private void getModifiedAppAssistant(User user, OpenemsApp app) { + // TODO does not work when having multiple instances of the same app and only + // one has properties that cant be edited - public List getAppsWithReferenceTo(UUID... instanceIds) { + private List getAppsWithReferenceTo(UUID... instanceIds) { return this.getAppManagerImpl().getInstantiatedApps() // .stream() // .filter(i -> i.dependencies != null && !i.dependencies.isEmpty()) // - .filter(i -> i.dependencies.stream().anyMatch( // + .filter(i -> i.dependencies.stream().anyMatch(// d -> Arrays.stream(instanceIds).anyMatch(id -> id.equals(d.instanceId)))) // .collect(Collectors.toList()); } private final void aggregateAllTasks(AppConfiguration instance, AppConfiguration oldInstance) { - for (var task : tasks) { + for (var task : this.tasks) { task.aggregate(instance, oldInstance); } } + private void resetTasks() { + for (var task : this.tasks) { + task.reset(); + } + } + /** * Checks if the instance is allowed to be deleted depending on other apps * dependencies to this instance. - * - * @param instance - * @param ignoreIds - * @return + * + * @param instance the app to delete + * @param ignoreIds the instance id's that should be ignored + * @return true if it is allowed to delete the app */ - public final boolean isAllowedToDelete(OpenemsAppInstance instance, UUID... ignoreIds) { + private final boolean isAllowedToDelete(OpenemsAppInstance instance, UUID... ignoreIds) { // check if a parent does not allow deletion of this instance for (var entry : this.getAppManagerImpl().appConfigs(this.getAppsWithReferenceTo(instance.instanceId), ignoreIds)) { @@ -512,12 +521,6 @@ public final boolean isAllowedToDelete(OpenemsAppInstance instance, UUID... igno return true; } - private void resetTasks() { - for (var task : tasks) { - task.reset(); - } - } - protected static void checkStatus(OpenemsApp openemsApp) throws OpenemsNamedException { var validator = openemsApp.getValidator(); var status = validator.getStatus(); @@ -560,12 +563,13 @@ protected static List getStaticIpsFromConfigs(List con } /** - * - * @param dc - * @param appId - * @returns null if the app can not be added; {@link Optional#absent()} if the - * app needs to be created; the {@link OpenemsAppInstance} if an - * existing app can be used + * Finds the needed app for a {@link DependencyDeclaration}. + * + * @param dc the current {@link DependencyConfig} + * @param appId the appId of the needed {@link Dependency} + * @return s null if the app can not be added; {@link Optional#absent()} if the + * app needs to be created; the {@link OpenemsAppInstance} if an + * existing app can be used */ private Optional findNeededApp(DependencyConfig dc, String appId) { if (!dc.isDependency()) { @@ -573,7 +577,7 @@ private Optional findNeededApp(DependencyConfig dc, String a } if (dc.sub.createPolicy == DependencyDeclaration.CreatePolicy.ALWAYS) { var neededApps = this.getAppManagerImpl().getInstantiatedApps().stream().filter(t -> t.appId.equals(appId)) - .toList(); + .collect(Collectors.toList()); OpenemsAppInstance availableApp = null; for (var neededApp : neededApps) { if (!this.getAppManagerImpl().getInstantiatedApps().stream() @@ -584,39 +588,38 @@ private Optional findNeededApp(DependencyConfig dc, String a } } return Optional.fromNullable(availableApp); - } else { - var neededApp = this.getAppManagerImpl().getInstantiatedApps().stream().filter(t -> t.appId.equals(appId)) - .toList(); - if (!neededApp.isEmpty()) { - return Optional.of(neededApp.get(0)); - } - if (dc.sub.createPolicy == DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING) { - return Optional.absent(); - } + } + var neededApp = this.getAppManagerImpl().getInstantiatedApps().stream().filter(t -> t.appId.equals(appId)) + .collect(Collectors.toList()); + if (!neededApp.isEmpty()) { + return Optional.of(neededApp.get(0)); + } + if (dc.sub.createPolicy == DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING) { + return Optional.absent(); } return null; } /** - * Iterates over all dependencies and the given app. - * + * Recursively iterates over all dependencies and the given app. + * *

* Order bottom -> top. - * - * @param app - * @param alias - * @param defaultProperties - * @param target + * + * @param app the app to be installed + * @param alias the alias of the current instance + * @param defaultProperties the properties of the current instane + * @param target the {@link ConfigurationTarget} * @param function returns true if the instance gets created or * already exists - * @param sub - * @param l - * @param parent + * @param sub the {@link DependencyDeclaration} + * @param l the {@link Language} + * @param parent the parent app * @param alreadyIteratedApps the apps that already got iterated thru to avoid * endless loop. e. g. if two apps have each other as * a dependency - * @return - * @throws OpenemsNamedException + * @return s the last {@link DependencyConfig} + * @throws OpenemsNamedException on error */ private DependencyConfig foreachDependency(OpenemsApp app, String alias, JsonObject defaultProperties, ConfigurationTarget target, Function function, DependencyDeclaration sub, @@ -664,17 +667,20 @@ private void foreachExistingDependecy(OpenemsAppInstance instance, Configuration } /** - * + * Recursively iterates over all existing dependencies and the given app. + * *

* Order bottom -> top. - * - * @param instance - * @param target - * @param consumer - * @param parent - * @param sub - * @param l - * @throws OpenemsNamedException + * + * @param instance the existing {@link OpenemsAppInstance} + * @param target the {@link ConfigurationTarget} + * @param consumer the consumer that gets executed for every instance + * @param parent the parent instance of the current dependency + * @param sub the {@link DependencyDeclaration} + * @param l the {@link Language} + * @param alreadyIteratedApps the already iterated app to avoid an endless loop + * @return s the last {@link DependencyConfig} + * @throws OpenemsNamedException on error */ private DependencyConfig foreachExistingDependency(OpenemsAppInstance instance, ConfigurationTarget target, Function consumer, OpenemsAppInstance parent, DependencyDeclaration sub, @@ -690,7 +696,7 @@ private DependencyConfig foreachExistingDependency(OpenemsAppInstance instance, var dependecies = new ArrayList(); if (instance.dependencies != null) { - dependecies = new ArrayList(instance.dependencies.size()); + dependecies = new ArrayList<>(instance.dependencies.size()); for (var dependency : instance.dependencies) { try { var dependencyApp = this.getAppManagerImpl().findInstaceById(dependency.instanceId); @@ -848,7 +854,7 @@ private AppConfiguration getNewAppConfigWithReplacedIds(OpenemsApp app, OpenemsA } private final AppManagerImpl getAppManagerImpl() { - return (AppManagerImpl) componentManager.getEnabledComponentsOfType(AppManager.class).get(0); + return (AppManagerImpl) this.componentManager.getEnabledComponentsOfType(AppManager.class).get(0); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java similarity index 92% rename from io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java rename to io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java index 831e71d8d12..f4c94caf877 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTask.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java @@ -23,10 +23,10 @@ import io.openems.edge.core.appmanager.ComponentUtilImpl; import io.openems.edge.core.componentmanager.ComponentManagerImpl; -@Component(name = "AppManager.AggregateTask.CreateComponents") -public class ComponentAggregateTask implements AggregateTask { +@Component +public class ComponentAggregateTaskImpl implements AggregateTask, AggregateTask.ComponentAggregateTask { - private ComponentManager componentManager; + private final ComponentManager componentManager; private List components; private List components2Delete; @@ -35,7 +35,7 @@ public class ComponentAggregateTask implements AggregateTask { private List deletedComponents; @Activate - public ComponentAggregateTask(@Reference ComponentManager componentManager) { + public ComponentAggregateTaskImpl(@Reference ComponentManager componentManager) { this.componentManager = componentManager; } @@ -61,7 +61,7 @@ public void aggregate(AppConfiguration config, AppConfiguration oldConfig) { @Override public void create(User user, List otherAppConfigurations) throws OpenemsNamedException { - this.createdComponents = new ArrayList(this.components.size()); + this.createdComponents = new ArrayList<>(this.components.size()); var errors = new LinkedList(); var otherAppComponents = AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigurations); // create components @@ -131,10 +131,8 @@ public void create(User user, List otherAppConfigurations) thr /** * deletes the given components only if they are not in notMyComponents. * - * @param user the executing user - * @param components the components that should be deleted - * @param notMyComponents other needed components from the other apps - * @return the id s of the components that got deleted + * @param user the executing user + * @param otherAppConfigurations the other {@link AppConfiguration}s */ @Override public void delete(User user, List otherAppConfigurations) throws OpenemsNamedException { @@ -202,10 +200,12 @@ private void reconfigure(User user, EdgeConfig.Component myComp, EdgeConfig.Comp ((ComponentManagerImpl) this.componentManager).handleUpdateComponentConfigRequest(user, updateRequest); } + @Override public List getCreatedComponents() { return Collections.unmodifiableList(this.createdComponents); } + @Override public List getDeletedComponents() { return Collections.unmodifiableList(this.deletedComponents); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/Dependency.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/Dependency.java index e6bdaafa47e..71ed57c8aff 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/Dependency.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/Dependency.java @@ -10,7 +10,7 @@ /** * Represents a dependency in the configuration of the {@link AppManager} of an * app. - * + * */ public class Dependency { @@ -23,10 +23,15 @@ public Dependency(String key, UUID instanceId) { this.instanceId = instanceId; } + /** + * Gets the {@link Dependency} as a {@link JsonObject}. + * + * @return the {@link JsonObject} + */ public JsonObject toJsonObject() { return JsonUtils.buildJsonObject() // - .addProperty("key", key) // - .addProperty("instanceId", instanceId.toString()) // + .addProperty("key", this.key) // + .addProperty("instanceId", this.instanceId.toString()) // .build(); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java index c30a31b1cba..34d820c56a8 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java @@ -11,7 +11,9 @@ public class DependencyConfig { public final OpenemsApp app; public final OpenemsApp parent; -// @Nullable if not a dependency of an app + + // @Nullable + // if not a dependency of an app. public final DependencyDeclaration sub; public final AppConfiguration config; @@ -22,7 +24,6 @@ public class DependencyConfig { public DependencyConfig(OpenemsApp app, OpenemsApp parent, DependencyDeclaration sub, AppConfiguration config, String alias, JsonObject properties, List declarations) { - super(); this.app = app; this.parent = parent; this.sub = sub; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java index 00c58817d88..76cedda5e1a 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java @@ -23,8 +23,9 @@ public class DependencyDeclaration { public final DependencyDeletePolicy dependencyDeletePolicy; // Dependency Supplier? -// private final Supplier supplierForAppId = null; -// private final Function, String> supplierForInstanceIdFromExisting = null; + // private final Supplier supplierForAppId = null; + // private final Function, String> + // supplierForInstanceIdFromExisting = null; public DependencyDeclaration(String key, String appId, String alias, CreatePolicy createPolicy, UpdatePolicy updatePolicy, DeletePolicy deletePolicy, DependencyUpdatePolicy dependencyUpdatePolicy, @@ -45,11 +46,6 @@ public DependencyDeclaration(String key, String appId, String alias, CreatePolic * app. */ public static enum CreatePolicy { - /** - * TODO - */ - HAS_TO_EXIST((t, u) -> false), // - /** * Always creates the dependent app except an {@link OpenemsAppInstance} is * already created and not a dependency of another app. @@ -76,10 +72,10 @@ private CreatePolicy(BiFunction, String, Boolean> isAll /** * Determines if the app of the given appId is allowed to create. This does not * mean an existing app can't be used as the dependency. - * + * * @param allInstances all app instances * @param appId the appId - * @returns true if the app is allowed to create + * @return s true if the app is allowed to create */ public final boolean isAllowedToCreate(List allInstances, String appId) { return this.isAllowedToCreateFunction.apply(allInstances, appId); @@ -103,6 +99,14 @@ private UpdatePolicy(Function isAllowedToUpdate) { this.isAllowedToUpdateFunction = isAllowedToUpdate; } + /** + * Determines if an {@link OpenemsAppInstance} is allowed to be updated. + * + * @param allInstances all {@link OpenemsAppInstance} + * @param parent the parent {@link OpenemsAppInstance} + * @param app2Update the {@link OpenemsAppInstance} to updated + * @return true if the instance is allowed to be updated else false + */ public final boolean isAllowedToUpdate(List allInstances, OpenemsAppInstance parent, OpenemsAppInstance app2Update) { return this.isAllowedToUpdateFunction.apply(new AllowedToValues(allInstances, parent, app2Update)); @@ -126,6 +130,14 @@ private DeletePolicy(Function isAllowedToDelete) { this.isAllowedToDeleteFunction = isAllowedToDelete; } + /** + * Determines if an {@link OpenemsAppInstance} is allowed to be deleted. + * + * @param allInstances all {@link OpenemsAppInstance} + * @param parent the parent {@link OpenemsAppInstance} + * @param app2Delete the {@link OpenemsAppInstance} to delete + * @return true if the instance is allowed to be deleted else false + */ public final boolean isAllowedToDelete(List allInstances, OpenemsAppInstance parent, OpenemsAppInstance app2Delete) { return this.isAllowedToDeleteFunction.apply(new AllowedToValues(allInstances, parent, app2Delete)); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java index 33d6bfda04b..2d794507ab3 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java @@ -10,12 +10,10 @@ public class ExistingDependencyConfig extends DependencyConfig { -// @Nullable + // @Nullable public final OpenemsAppInstance parentInstance; public final OpenemsAppInstance instance; -// public final List existingDependencies = null; - public ExistingDependencyConfig(OpenemsApp app, OpenemsApp parentApp, DependencyDeclaration sub, AppConfiguration config, String alias, JsonObject properties, List declarations, OpenemsAppInstance parent, OpenemsAppInstance instance) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTaskImpl.java similarity index 63% rename from io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java rename to io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTaskImpl.java index 9ab794aa327..d3642058500 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTask.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/SchedulerAggregateTaskImpl.java @@ -9,31 +9,30 @@ import org.osgi.service.component.annotations.Reference; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.types.EdgeConfig; import io.openems.edge.common.user.User; import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.ComponentUtil; -@Component(name = "AppManager.AggregateTask.SchedulerAggregateTask") -public class SchedulerAggregateTask implements AggregateTask { +@Component +public class SchedulerAggregateTaskImpl implements AggregateTask, AggregateTask.SchedulerAggregateTask { - private ComponentUtil componentUtil; + private final AggregateTask.ComponentAggregateTask aggregateTask; + private final ComponentUtil componentUtil; private List order; private List removeIds; - private List createdComponents; - private List deletedComponents; - @Activate - public SchedulerAggregateTask(@Reference ComponentUtil componentUtil) { + public SchedulerAggregateTaskImpl(@Reference AggregateTask.ComponentAggregateTask aggregateTask, + @Reference ComponentUtil componentUtil) { + this.aggregateTask = aggregateTask; this.componentUtil = componentUtil; } @Override public void reset() { - order = new LinkedList<>(); - removeIds = new LinkedList<>(); + this.order = new LinkedList<>(); + this.removeIds = new LinkedList<>(); } @Override @@ -52,24 +51,18 @@ public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) { @Override public void create(User user, List otherAppConfigurations) throws OpenemsNamedException { - this.order = componentUtil.insertSchedulerOrder(componentUtil.getSchedulerIds(), this.order); - this.componentUtil.updateScheduler(user, this.order, createdComponents); + this.order = this.componentUtil.insertSchedulerOrder(this.componentUtil.getSchedulerIds(), this.order); + this.componentUtil.updateScheduler(user, this.order, this.aggregateTask.getCreatedComponents()); + + this.delete(user, otherAppConfigurations); } @Override public void delete(User user, List otherAppConfigurations) throws OpenemsNamedException { - this.removeIds.addAll(deletedComponents); + this.removeIds.addAll(this.aggregateTask.getDeletedComponents()); this.removeIds.removeAll(AppManagerAppHelperImpl.getSchedulerIdsFromConfigs(otherAppConfigurations)); this.componentUtil.removeIdsInSchedulerIfExisting(user, this.removeIds); } - public final void setCreatedComponents(List createdComponents) { - this.createdComponents = createdComponents; - } - - public final void setDeletedComponents(List deletedComponents) { - this.deletedComponents = deletedComponents; - } - } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTask.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTaskImpl.java similarity index 76% rename from io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTask.java rename to io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTaskImpl.java index 27eeb1c0b25..6e208a1969b 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTask.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTaskImpl.java @@ -13,8 +13,8 @@ import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.ComponentUtil; -@Component(name = "AppManager.AggregateTask.StaticIpAggregateTask") -public class StaticIpAggregateTask implements AggregateTask { +@Component +public class StaticIpAggregateTaskImpl implements AggregateTask, AggregateTask.StaticIpAggregateTask { private final ComponentUtil componentUtil; @@ -22,7 +22,7 @@ public class StaticIpAggregateTask implements AggregateTask { private List ips2Delete; @Activate - public StaticIpAggregateTask(@Reference ComponentUtil componentUtil) { + public StaticIpAggregateTaskImpl(@Reference ComponentUtil componentUtil) { this.componentUtil = componentUtil; } @@ -39,14 +39,14 @@ public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) { return; } if (instance != null) { - ips.addAll(instance.ips); + this.ips.addAll(instance.ips); } if (oldConfig != null) { var diff = new ArrayList<>(oldConfig.ips); if (instance != null) { diff.removeAll(instance.ips); } - ips2Delete.addAll(diff); + this.ips2Delete.addAll(diff); } } @@ -56,7 +56,7 @@ public void create(User user, List otherAppConfigurations) thr return; } - this.componentUtil.updateHosts(user, ips, ips2Delete); + this.componentUtil.updateHosts(user, this.ips, this.ips2Delete); } @Override @@ -64,8 +64,8 @@ public void delete(User user, List otherAppConfigurations) thr if (System.getProperty("os.name").startsWith("Windows")) { return; } - ips.removeAll(AppManagerAppHelperImpl.getStaticIpsFromConfigs(otherAppConfigurations)); - this.componentUtil.updateHosts(user, null, ips2Delete); + this.ips.removeAll(AppManagerAppHelperImpl.getStaticIpsFromConfigs(otherAppConfigurations)); + this.componentUtil.updateHosts(user, null, this.ips2Delete); } } From 512bd28c57146bbd89b356dc6f0a700e463c7207 Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:16:50 +0200 Subject: [PATCH 11/30] seperated checkable config and validation; added language parameter to checkables --- .../edge/app/api/RestJsonApiReadOnly.java | 13 +- .../edge/app/api/RestJsonApiReadWrite.java | 11 +- .../edge/app/heat/CombinedHeatAndPower.java | 35 ++- .../io/openems/edge/app/heat/HeatPump.java | 29 +- .../openems/edge/app/heat/HeatingElement.java | 34 ++- .../app/loadcontrol/ManualRelayControl.java | 11 +- .../app/loadcontrol/ThresholdControl.java | 11 +- .../edge/app/meter/CarloGavazziMeter.java | 11 +- .../openems/edge/app/meter/JanitzaMeter.java | 11 +- .../openems/edge/app/meter/SocomecMeter.java | 11 +- .../GridOptimizedCharge.java | 12 +- .../core/appmanager/AbstractOpenemsApp.java | 10 +- .../edge/core/appmanager/AppManagerImpl.java | 63 ++-- .../edge/core/appmanager/OpenemsApp.java | 3 +- .../validator/AbstractCheckable.java | 19 ++ .../validator/CheckAppsNotInstalled.java | 27 +- .../validator/CheckCardinality.java | 9 +- .../core/appmanager/validator/CheckHome.java | 11 +- .../core/appmanager/validator/CheckHost.java | 10 +- .../CheckNoComponentInstalledOfFactoryId.java | 10 +- .../appmanager/validator/CheckRelayCount.java | 12 +- .../core/appmanager/validator/Checkable.java | 6 +- .../core/appmanager/validator/Validator.java | 283 +++--------------- .../appmanager/validator/ValidatorConfig.java | 152 ++++++++++ .../appmanager/validator/ValidatorImpl.java | 95 ++++++ 25 files changed, 521 insertions(+), 378 deletions(-) create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorConfig.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorImpl.java diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java index 6ea2422408d..fc012627909 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java @@ -29,8 +29,7 @@ import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.validator.CheckAppsNotInstalled; -import io.openems.edge.core.appmanager.validator.Validator; -import io.openems.edge.core.appmanager.validator.Validator.Builder; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** * Describes a App for ReadOnly Rest JSON Api. @@ -46,7 +45,7 @@ }, "appDescriptor": { "websiteUrl": https://docs.fenecon.de/de/_/latest/fems/apis.html#_fems_app_modbustcp_api_lesend +"https://fenecon.de/fems-2-2/fems-app-modbus-tcp-lesend-2/">https://fenecon.de/fems-2-2/fems-app-modbus-tcp-lesend-2/ } } * @@ -102,11 +101,11 @@ protected ThrowingTriFunction(new TreeMap()) // + new ValidatorConfig.CheckableConfig(CheckAppsNotInstalled.COMPONENT_NAME, + new ValidatorConfig.MapBuilder<>(new TreeMap()) // .put("appIds", new String[] { "App.Api.RestJson.ReadWrite" }) // .build()))); } diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java index decdf2e5cbd..c83651a6f61 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java @@ -32,8 +32,7 @@ import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.validator.CheckAppsNotInstalled; -import io.openems.edge.core.appmanager.validator.Validator; -import io.openems.edge.core.appmanager.validator.Validator.Builder; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** * Describes a App for ReadWrite Rest JSON Api. @@ -122,11 +121,11 @@ protected ThrowingTriFunction(new TreeMap()) // + new ValidatorConfig.CheckableConfig(CheckAppsNotInstalled.COMPONENT_NAME, + new ValidatorConfig.MapBuilder<>(new TreeMap()) // .put("appIds", new String[] { "App.Api.RestJson.ReadOnly" }) // .build()))); } diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java b/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java index 4f671b7764e..0d919e9b6c5 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java @@ -33,9 +33,10 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; +import io.openems.edge.core.appmanager.dependency.DependencyUtil; import io.openems.edge.core.appmanager.validator.CheckRelayCount; -import io.openems.edge.core.appmanager.validator.Validator; -import io.openems.edge.core.appmanager.validator.Validator.Builder; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** * Describes a App for a Heating Element. @@ -101,7 +102,27 @@ protected ThrowingTriFunction b.addProperty("highThreshold", 80)) // .build()));// - return new AppConfiguration(comp); + var componentIdOfRelay = outputChannelAddress.substring(0, outputChannelAddress.indexOf('/')); + + var appIdOfRelay = DependencyUtil.getInstanceIdOfAppWhichHasComponent(this.componentManager, + componentIdOfRelay, this.getAppId()); + + if (appIdOfRelay == null) { + // relay may be created but not as a app + return new AppConfiguration(comp); + } + + var dependencies = Lists.newArrayList(new DependencyDeclaration("RELAY", // + DependencyDeclaration.CreatePolicy.NEVER, // + DependencyDeclaration.UpdatePolicy.NEVER, // + DependencyDeclaration.DeletePolicy.NEVER, // + DependencyDeclaration.DependencyUpdatePolicy.ALLOW_ALL, // + DependencyDeclaration.DependencyDeletePolicy.NOT_ALLOWED, // + DependencyDeclaration.AppDependencyConfig.create() // + .setSpecificInstanceId(appIdOfRelay) // + .build())); + + return new AppConfiguration(comp, null, null, dependencies); }; } @@ -141,11 +162,11 @@ public OpenemsAppCategory[] getCategorys() { } @Override - public Builder getValidateBuilder() { - return Validator.create() // + public ValidatorConfig.Builder getValidateBuilder() { + return ValidatorConfig.create() // .setInstallableCheckableConfigs(Lists.newArrayList(// - new Validator.CheckableConfig(CheckRelayCount.COMPONENT_NAME, - new Validator.MapBuilder<>(new TreeMap()) // + new ValidatorConfig.CheckableConfig(CheckRelayCount.COMPONENT_NAME, + new ValidatorConfig.MapBuilder<>(new TreeMap()) // .put("count", 1) // .build()))); } diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java index 30d432f96d0..0b0ba0df472 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java @@ -31,9 +31,9 @@ import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; +import io.openems.edge.core.appmanager.dependency.DependencyUtil; import io.openems.edge.core.appmanager.validator.CheckRelayCount; -import io.openems.edge.core.appmanager.validator.Validator; -import io.openems.edge.core.appmanager.validator.Validator.Builder; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** * Describes a App for a Heat Pump. @@ -87,15 +87,24 @@ protected ThrowingTriFunction(new TreeMap()) // + new ValidatorConfig.CheckableConfig(CheckRelayCount.COMPONENT_NAME, + new ValidatorConfig.MapBuilder<>(new TreeMap()) // .put("count", 2) // .build()))); } diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java index f066a351d85..ff4b816406d 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java @@ -33,9 +33,10 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; +import io.openems.edge.core.appmanager.dependency.DependencyUtil; import io.openems.edge.core.appmanager.validator.CheckRelayCount; -import io.openems.edge.core.appmanager.validator.Validator; -import io.openems.edge.core.appmanager.validator.Validator.Builder; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** * Describes a App for a RTU Heating Element. @@ -109,7 +110,26 @@ protected ThrowingTriFunction(new TreeMap()) // + new ValidatorConfig.CheckableConfig(CheckRelayCount.COMPONENT_NAME, + new ValidatorConfig.MapBuilder<>(new TreeMap()) // .put("count", 3) // .build()))); } diff --git a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java index 82ebb9d0246..2496bd42086 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java +++ b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java @@ -34,8 +34,7 @@ import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.validator.CheckRelayCount; -import io.openems.edge.core.appmanager.validator.Validator; -import io.openems.edge.core.appmanager.validator.Validator.Builder; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** * Describes a App for a manual relay control. @@ -141,11 +140,11 @@ public OpenemsAppCategory[] getCategorys() { } @Override - public Builder getValidateBuilder() { - return Validator.create() // + public ValidatorConfig.Builder getValidateBuilder() { + return ValidatorConfig.create() // .setInstallableCheckableConfigs(Lists.newArrayList(// - new Validator.CheckableConfig(CheckRelayCount.COMPONENT_NAME, - new Validator.MapBuilder<>(new TreeMap()) // + new ValidatorConfig.CheckableConfig(CheckRelayCount.COMPONENT_NAME, + new ValidatorConfig.MapBuilder<>(new TreeMap()) // .put("count", 1) // .build()))); } diff --git a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java index 9a37903c739..8e3281813ff 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java +++ b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java @@ -35,8 +35,7 @@ import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.validator.CheckRelayCount; -import io.openems.edge.core.appmanager.validator.Validator; -import io.openems.edge.core.appmanager.validator.Validator.Builder; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** * Describes a App for a Threshold Controller. @@ -146,11 +145,11 @@ public OpenemsAppCategory[] getCategorys() { } @Override - public Builder getValidateBuilder() { - return Validator.create() // + public ValidatorConfig.Builder getValidateBuilder() { + return ValidatorConfig.create() // .setInstallableCheckableConfigs(Lists.newArrayList(// - new Validator.CheckableConfig(CheckRelayCount.COMPONENT_NAME, - new Validator.MapBuilder<>(new TreeMap()) // + new ValidatorConfig.CheckableConfig(CheckRelayCount.COMPONENT_NAME, + new ValidatorConfig.MapBuilder<>(new TreeMap()) // .put("count", 1) // .build()))); } diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java index 8c5a351b83e..3220e52d8a3 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java @@ -32,8 +32,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.validator.CheckHome; -import io.openems.edge.core.appmanager.validator.Validator; -import io.openems.edge.core.appmanager.validator.Validator.Builder; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** * Describes a app for a Carlo Gavazzi meter. @@ -129,11 +128,11 @@ public AppDescriptor getAppDescriptor() { } @Override - public Builder getValidateBuilder() { - return Validator.create() // + public ValidatorConfig.Builder getValidateBuilder() { + return ValidatorConfig.create() // .setCompatibleCheckableConfigs(Lists.newArrayList(// - new Validator.CheckableConfig(CheckHome.COMPONENT_NAME, - new Validator.MapBuilder<>(new TreeMap()) // + new ValidatorConfig.CheckableConfig(CheckHome.COMPONENT_NAME, + new ValidatorConfig.MapBuilder<>(new TreeMap()) // .build()))); } diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java index c00501ddfad..488385933e8 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java @@ -37,8 +37,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.validator.CheckHome; -import io.openems.edge.core.appmanager.validator.Validator; -import io.openems.edge.core.appmanager.validator.Validator.Builder; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** * Describes a App for a Janitza meter. @@ -164,11 +163,11 @@ public AppDescriptor getAppDescriptor() { } @Override - public Builder getValidateBuilder() { - return Validator.create() // + public ValidatorConfig.Builder getValidateBuilder() { + return ValidatorConfig.create() // .setCompatibleCheckableConfigs(Lists.newArrayList(// - new Validator.CheckableConfig(CheckHome.COMPONENT_NAME, - new Validator.MapBuilder<>(new TreeMap()) // + new ValidatorConfig.CheckableConfig(CheckHome.COMPONENT_NAME, + new ValidatorConfig.MapBuilder<>(new TreeMap()) // .build()))); } diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java index a33b5874f7a..1068271fadc 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java @@ -32,8 +32,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.validator.CheckHome; -import io.openems.edge.core.appmanager.validator.Validator; -import io.openems.edge.core.appmanager.validator.Validator.Builder; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** * Describes a App for a Socomec meter. @@ -128,11 +127,11 @@ public AppDescriptor getAppDescriptor() { } @Override - public Builder getValidateBuilder() { - return Validator.create() // + public ValidatorConfig.Builder getValidateBuilder() { + return ValidatorConfig.create() // .setCompatibleCheckableConfigs(Lists.newArrayList(// - new Validator.CheckableConfig(CheckHome.COMPONENT_NAME, - new Validator.MapBuilder<>(new TreeMap()) // + new ValidatorConfig.CheckableConfig(CheckHome.COMPONENT_NAME, + new ValidatorConfig.MapBuilder<>(new TreeMap()) // .build()))); } diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java index c8f07b2c21d..806be79139b 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java @@ -33,8 +33,7 @@ import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.validator.CheckHome; -import io.openems.edge.core.appmanager.validator.Validator; -import io.openems.edge.core.appmanager.validator.Validator.Builder; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** * Describes a App for a Grid Optimized Charge. @@ -141,15 +140,6 @@ public AppDescriptor getAppDescriptor() { .build(); } - @Override - public Builder getValidateBuilder() { - return Validator.create() // TODO remove later when home has dependency - .setCompatibleCheckableConfigs(Lists.newArrayList(// - new Validator.CheckableConfig(CheckHome.COMPONENT_NAME, true, - new Validator.MapBuilder<>(new TreeMap()) // - .build()))); - } - @Override public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.PV_SELF_CONSUMPTION }; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java index 760b44145a7..3436cff2d26 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java @@ -72,7 +72,7 @@ protected final void assertCheckables(ConfigurationTarget t, Checkable... checka final List errors = new ArrayList<>(); for (Checkable checkable : checkables) { if (!checkable.check()) { - errors.add(checkable.getErrorMessage()); + errors.add(checkable.getErrorMessage(Language.DEFAULT)); } } if (!errors.isEmpty()) { @@ -257,7 +257,7 @@ private List getValidationErrors(JsonObject jProperties, List properties = new TreeMap<>(); properties.put("openemsApp", this); @@ -265,7 +265,7 @@ public final Validator getValidator() { var validator = this.getValidateBuilder().build(); validator.getInstallableCheckableConfigs() - .add(new Validator.CheckableConfig(CheckCardinality.COMPONENT_NAME, properties)); + .add(new ValidatorConfig.CheckableConfig(CheckCardinality.COMPONENT_NAME, properties)); if (this.installationValidation() != null) { validator.setConfigurationValidation((t, u) -> { @@ -276,8 +276,8 @@ public final Validator getValidator() { return validator; } - protected Validator.Builder getValidateBuilder() { - return Validator.create(); + protected ValidatorConfig.Builder getValidateBuilder() { + return ValidatorConfig.create(); } /** diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java index f2994591f7a..c81d761d388 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java @@ -11,6 +11,7 @@ import java.util.NoSuchElementException; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.osgi.service.cm.ConfigurationAdmin; @@ -51,6 +52,7 @@ import io.openems.edge.core.appmanager.jsonrpc.GetAppInstances; import io.openems.edge.core.appmanager.jsonrpc.GetApps; import io.openems.edge.core.appmanager.jsonrpc.UpdateAppInstance; +import io.openems.edge.core.appmanager.validator.Validator; import io.openems.edge.core.componentmanager.ComponentManagerImpl; @Designate(ocd = Config.class, factory = false) @@ -81,6 +83,9 @@ public class AppManagerImpl extends AbstractOpenemsComponent @Reference protected ComponentUtil componentUtil; + @Reference + protected Validator validator; + protected final List instantiatedApps = new ArrayList<>(); public AppManagerImpl() { @@ -197,8 +202,13 @@ public void configurationEvent(ConfigurationEvent event) { * @param excludingInstanceIds the instance ids that that should be ignored * @return the {@link Iterable} */ - public Iterable> appConfigs(UUID... excludingInstanceIds) { - return this.appConfigs(this.instantiatedApps, excludingInstanceIds); + public Iterable> appConfigs( + Predicate filter) { + return this.appConfigs(this.instantiatedApps, filter); + } + + public static Predicate exludingInstanceIds(UUID... excludingInstanceIds) { + return i -> !Arrays.stream(excludingInstanceIds).anyMatch(id -> id.equals(i.instanceId)); } /** @@ -210,11 +220,11 @@ public Iterable> appConfigs(UUID... * @return the {@link Iterable} */ public Iterable> appConfigs(List instances, - UUID... excludingInstanceIds) { + Predicate filter) { return new Iterable<>() { @Override public Iterator> iterator() { - return AppManagerImpl.this.appConfigIterator(instances, excludingInstanceIds); + return AppManagerImpl.this.appConfigIterator(instances, filter); } }; } @@ -227,10 +237,9 @@ public Iterator> iterator() { * @param excludingInstanceIds the instance ids that that should be ignored * @return the {@link Iterator} */ - public Iterator> appConfigIterator(List instances, - UUID... excludingInstanceIds) { - List actualInstances = instances.stream() - .filter(i -> !Arrays.stream(excludingInstanceIds).anyMatch(id -> id.equals(i.instanceId))) + private Iterator> appConfigIterator(List instances, + Predicate filter) { + List actualInstances = instances.stream().filter(i -> filter == null || filter.test(i)) // .collect(Collectors.toList()); return new Iterator<>() { @@ -322,7 +331,7 @@ public final OpenemsAppInstance findInstaceById(UUID uuid) throws NoSuchElementE */ public final List getOtherAppConfigurations(UUID... ignoreIds) { List allOtherConfigs = new ArrayList<>(this.instantiatedApps.size()); - for (var entry : this.appConfigs(ignoreIds)) { + for (var entry : this.appConfigs(AppManagerImpl.exludingInstanceIds(ignoreIds))) { allOtherConfigs.add(entry.getValue()); } return allOtherConfigs; @@ -336,28 +345,27 @@ public final List getOtherAppConfigurations(UUID... ignoreIds) * @return the Future JSON-RPC Response * @throws OpenemsNamedException on error */ - protected CompletableFuture handleAddAppInstanceRequest(User user, + public CompletableFuture handleAddAppInstanceRequest(User user, AddAppInstance.Request request) throws OpenemsNamedException { - var instanceId = UUID.randomUUID(); - var openemsApp = this.findAppById(request.appId); synchronized (this.instantiatedApps) { - var installedApps = this.appHelper.installApp(user, request.properties, request.alias, openemsApp); + var installedValues = this.appHelper.installApp(user, request.properties, request.alias, openemsApp); // Update App-Manager configuration try { // replace old instances with new ones - this.instantiatedApps.removeAll(installedApps); - this.instantiatedApps.addAll(installedApps); + this.instantiatedApps.removeAll(installedValues.modifiedOrCreatedApps); + this.instantiatedApps.addAll(installedValues.modifiedOrCreatedApps); this.updateAppManagerConfiguration(user, this.instantiatedApps); } catch (OpenemsNamedException e) { throw new OpenemsException( "AddAppInstance: unable to update App-Manager configuration: " + e.getMessage()); } + return CompletableFuture.completedFuture( + new AddAppInstance.Response(request.id, installedValues.rootInstance, installedValues.warnings)); } - return CompletableFuture.completedFuture(new AddAppInstance.Response(request.id, instanceId)); } /** @@ -368,7 +376,7 @@ protected CompletableFuture handleAddAppInstanceRequest( * @return the request id * @throws OpenemsNamedException on error */ - private CompletableFuture handleDeleteAppInstanceRequest(User user, + public CompletableFuture handleDeleteAppInstanceRequest(User user, DeleteAppInstance.Request request) throws OpenemsNamedException { synchronized (this.instantiatedApps) { @@ -392,10 +400,8 @@ private CompletableFuture handleDeleteAppInsta throw new OpenemsException("Unable to update App-Manager configuration for ID [" + request.instanceId + "]: " + e.getMessage()); } - + return CompletableFuture.completedFuture(new DeleteAppInstance.Response(request.id, result.warnings)); } - - return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.id)); } /** @@ -464,7 +470,8 @@ private CompletableFuture handleGetAppRequest(User user, var app = this.availableApps.stream().filter(t -> t.getAppId().equals(request.appId)).findFirst().get(); var instances = this.instantiatedApps.stream().filter(t -> t.appId.equals(request.appId)) .collect(Collectors.toList()); - return CompletableFuture.completedFuture(new GetApp.Response(request.id, app, instances, user.getLanguage())); + return CompletableFuture + .completedFuture(new GetApp.Response(request.id, app, instances, user.getLanguage(), validator)); } /** @@ -477,8 +484,8 @@ private CompletableFuture handleGetAppRequest(User user, */ private CompletableFuture handleGetAppsRequest(User user, GetApps.Request request) throws OpenemsNamedException { - return CompletableFuture.completedFuture( - new GetApps.Response(request.id, this.availableApps, this.instantiatedApps, user.getLanguage())); + return CompletableFuture.completedFuture(new GetApps.Response(request.id, this.availableApps, + this.instantiatedApps, user.getLanguage(), validator)); } @Override @@ -552,8 +559,10 @@ private CompletableFuture handleUpdateAppInstanceRequest throw new OpenemsException("Unable to update App-Manager configuration for ID [" + request.instanceId + "]: " + e.getMessage()); } + var newInstance = this.findInstaceById(request.instanceId); + return CompletableFuture + .completedFuture(new UpdateAppInstance.Response(request.id, newInstance, result.warnings)); } - return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.id)); } @Modified @@ -574,6 +583,10 @@ private void updateAppManagerConfiguration(User user, List a var p = new Property("apps", getJsonAppsString(apps)); var updateRequest = new UpdateComponentConfigRequest(SINGLETON_COMPONENT_ID, Arrays.asList(p)); // user can be null using internal method - ((ComponentManagerImpl) this.componentManager).handleUpdateComponentConfigRequest(user, updateRequest); + if (user == null) { + ((ComponentManagerImpl) this.componentManager).handleUpdateComponentConfigRequest(user, updateRequest); + } else { + this.componentManager.handleJsonrpcRequest(user, updateRequest); + } } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java index 796258aea57..34e16a5a2dc 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java @@ -7,6 +7,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.session.Language; import io.openems.edge.core.appmanager.validator.Validator; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; public interface OpenemsApp { @@ -78,7 +79,7 @@ public AppConfiguration getAppConfiguration(ConfigurationTarget target, JsonObje * * @return the Validator */ - public Validator getValidator(); + public ValidatorConfig getValidatorConfig(); /** * Validate the {@link OpenemsApp}. diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java new file mode 100644 index 00000000000..1295ff2d640 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java @@ -0,0 +1,19 @@ +package io.openems.edge.core.appmanager.validator; + +import org.osgi.service.component.ComponentConstants; +import org.osgi.service.component.ComponentContext; + +public abstract class AbstractCheckable implements Checkable { + + private final ComponentContext componentContext; + + public AbstractCheckable(ComponentContext componentContext) { + this.componentContext = componentContext; + } + + @Override + public String getComponentName() { + return this.componentContext.getProperties().get(ComponentConstants.COMPONENT_NAME).toString(); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckAppsNotInstalled.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckAppsNotInstalled.java index bace5c7528a..e8f704b7f5e 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckAppsNotInstalled.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckAppsNotInstalled.java @@ -5,15 +5,17 @@ import java.util.Map; import java.util.stream.Collectors; +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.Reference; +import io.openems.common.session.Language; import io.openems.edge.core.appmanager.AppManager; import io.openems.edge.core.appmanager.AppManagerImpl; @Component(name = CheckAppsNotInstalled.COMPONENT_NAME) -public class CheckAppsNotInstalled implements Checkable { +public class CheckAppsNotInstalled extends AbstractCheckable implements Checkable { public static final String COMPONENT_NAME = "Validator.Checkable.CheckAppsNotInstalled"; @@ -23,7 +25,8 @@ public class CheckAppsNotInstalled implements Checkable { private List installedApps = new LinkedList<>(); @Activate - public CheckAppsNotInstalled(@Reference AppManager appManager) { + public CheckAppsNotInstalled(@Reference AppManager appManager, ComponentContext componentContext) { + super(componentContext); this.appManager = appManager; } @@ -35,14 +38,10 @@ public void setProperties(Map properties) { @Override public boolean check() { this.installedApps = new LinkedList<>(); - if (this.appManager == null) { - return false; - } - if (!(this.appManager instanceof AppManagerImpl)) { + var appManagerImpl = this.getAppManagerImpl(); + if (appManagerImpl == null) { return false; } - var appManagerImpl = (AppManagerImpl) this.appManager; - var instances = appManagerImpl.getInstantiatedApps(); for (String item : this.appIds) { if (instances.stream().anyMatch(t -> t.appId.equals(item))) { @@ -52,8 +51,18 @@ public boolean check() { return this.installedApps.isEmpty(); } + private AppManagerImpl getAppManagerImpl() { + if (this.appManager == null) { + return null; + } + if (!(this.appManager instanceof AppManagerImpl)) { + return null; + } + return (AppManagerImpl) this.appManager; + } + @Override - public String getErrorMessage() { + public String getErrorMessage(Language language) { return "Apps with ID[" + this.installedApps.stream().collect(Collectors.joining(", ")) + "] are installed!" + System.lineSeparator() + "Delete them to be able to install this App."; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java index f50282a3ea6..94a990d299a 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java @@ -4,10 +4,12 @@ import java.util.Map; import java.util.NoSuchElementException; +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.Reference; +import io.openems.common.session.Language; import io.openems.edge.core.appmanager.AppManager; import io.openems.edge.core.appmanager.AppManagerImpl; import io.openems.edge.core.appmanager.OpenemsApp; @@ -16,7 +18,7 @@ import io.openems.edge.core.appmanager.OpenemsAppInstance; @Component(name = CheckCardinality.COMPONENT_NAME) -public class CheckCardinality implements Checkable { +public class CheckCardinality extends AbstractCheckable implements Checkable { public static final String COMPONENT_NAME = "Validator.Checkable.CheckCardinality"; @@ -26,7 +28,8 @@ public class CheckCardinality implements Checkable { private String errorMessage = null; @Activate - public CheckCardinality(@Reference AppManager appManager) { + public CheckCardinality(@Reference AppManager appManager, ComponentContext componentContext) { + super(componentContext); this.appManager = appManager; } @@ -98,7 +101,7 @@ private OpenemsAppCategory getMatchingCategorie(AppManagerImpl appManager, } @Override - public String getErrorMessage() { + public String getErrorMessage(Language language) { return this.errorMessage; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java index ab45cefbfb3..b0065cda6da 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java @@ -2,15 +2,17 @@ import java.util.TreeMap; +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.Reference; import io.openems.common.OpenemsConstants; +import io.openems.common.session.Language; import io.openems.edge.common.component.ComponentManager; @Component(name = CheckHome.COMPONENT_NAME) -public class CheckHome implements Checkable { +public class CheckHome extends AbstractCheckable implements Checkable { public static final String COMPONENT_NAME = "Validator.Checkable.CheckHome"; @@ -18,9 +20,10 @@ public class CheckHome implements Checkable { private final Checkable checkAppsNotInstalled; @Activate - public CheckHome(@Reference ComponentManager componentManager, + public CheckHome(@Reference ComponentManager componentManager, ComponentContext componentContext, @Reference(target = "(" + OpenemsConstants.PROPERTY_OSGI_COMPONENT_NAME + "=" + CheckAppsNotInstalled.COMPONENT_NAME + ")") Checkable checkAppsNotInstalled) { + super(componentContext); this.componentManager = componentManager; this.checkAppsNotInstalled = checkAppsNotInstalled; } @@ -28,7 +31,7 @@ public CheckHome(@Reference ComponentManager componentManager, @Override public boolean check() { var batteries = this.componentManager.getEdgeConfig().getComponentsByFactory("Battery.Fenecon.Home"); - this.checkAppsNotInstalled.setProperties(new Validator.MapBuilder<>(new TreeMap()) // + this.checkAppsNotInstalled.setProperties(new ValidatorConfig.MapBuilder<>(new TreeMap()) // .put("appIds", new String[] { "App.FENECON.Home" }) // .build()); // TODO remove check for batteries @@ -41,7 +44,7 @@ public boolean check() { } @Override - public String getErrorMessage() { + public String getErrorMessage(Language language) { return "No Home installed"; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHost.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHost.java index 45cbacc4325..6f092912180 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHost.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHost.java @@ -6,11 +6,14 @@ import java.net.UnknownHostException; import java.util.Map; +import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import io.openems.common.session.Language; + @Component(name = CheckHost.COMPONENT_NAME) -public class CheckHost implements Checkable { +public class CheckHost extends AbstractCheckable implements Checkable { public static final String COMPONENT_NAME = "Validator.Checkable.CheckHost"; @@ -18,7 +21,8 @@ public class CheckHost implements Checkable { private Integer port; @Activate - public CheckHost() { + public CheckHost(ComponentContext componentContext) { + super(componentContext); } private void init(String host, Integer port) { @@ -64,7 +68,7 @@ public boolean check() { } @Override - public String getErrorMessage() { + public String getErrorMessage(Language language) { // TODO translation var portMsg = this.port != null ? " on Port " + this.port : ""; if (this.host == null) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckNoComponentInstalledOfFactoryId.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckNoComponentInstalledOfFactoryId.java index 9b771ad9011..8effcfbed48 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckNoComponentInstalledOfFactoryId.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckNoComponentInstalledOfFactoryId.java @@ -2,14 +2,16 @@ import java.util.Map; +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.Reference; +import io.openems.common.session.Language; import io.openems.edge.common.component.ComponentManager; @Component(name = CheckNoComponentInstalledOfFactoryId.COMPONENT_NAME) -public class CheckNoComponentInstalledOfFactoryId implements Checkable { +public class CheckNoComponentInstalledOfFactoryId extends AbstractCheckable implements Checkable { public static final String COMPONENT_NAME = "Validator.Checkable.CheckNoComponentInstalledOfFactorieId"; @@ -18,7 +20,9 @@ public class CheckNoComponentInstalledOfFactoryId implements Checkable { private String factorieId; @Activate - public CheckNoComponentInstalledOfFactoryId(@Reference ComponentManager componentManager) { + public CheckNoComponentInstalledOfFactoryId(@Reference ComponentManager componentManager, + ComponentContext componentContext) { + super(componentContext); this.componentManager = componentManager; } @@ -33,7 +37,7 @@ public boolean check() { } @Override - public String getErrorMessage() { + public String getErrorMessage(Language language) { return "Components with the FactorieID[" + this.factorieId + "] are installed!" + System.lineSeparator() + "Remove them to be able to install this app."; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckRelayCount.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckRelayCount.java index 665b60a2d7c..fd02635a4e7 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckRelayCount.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckRelayCount.java @@ -4,15 +4,17 @@ import java.util.List; import java.util.Map; +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.Reference; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.session.Language; import io.openems.edge.core.appmanager.ComponentUtil; @Component(name = CheckRelayCount.COMPONENT_NAME) -public class CheckRelayCount implements Checkable { +public class CheckRelayCount extends AbstractCheckable implements Checkable { public static final String COMPONENT_NAME = "Validator.Checkable.CheckRelayCount"; @@ -21,7 +23,8 @@ public class CheckRelayCount implements Checkable { private int count; @Activate - public CheckRelayCount(@Reference ComponentUtil openemsAppUtil) { + public CheckRelayCount(@Reference ComponentUtil openemsAppUtil, ComponentContext componentContext) { + super(componentContext); this.openemsAppUtil = openemsAppUtil; } @@ -58,9 +61,10 @@ public boolean check() { } @Override - public String getErrorMessage() { + public String getErrorMessage(Language language) { // TODO translation - return "There are not enough Relay ports available!"; + return "There are not enough Relay ports available!" + System.lineSeparator() + + "Install a Relay to be able to install this App."; } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkable.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkable.java index 312424e424a..d1b407601e1 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkable.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkable.java @@ -2,7 +2,11 @@ import java.util.Map; +import io.openems.common.session.Language; + public interface Checkable { + + public String getComponentName(); /** * Checks if the implemented task was successful or not. @@ -16,7 +20,7 @@ public interface Checkable { * * @return the message */ - public String getErrorMessage(); + public String getErrorMessage(Language language); /** * Sets the properties. diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java index 3cb8d8a5d36..8591f827e97 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java @@ -1,228 +1,57 @@ package io.openems.edge.core.appmanager.validator; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.Lists; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; -import io.openems.common.OpenemsConstants; -import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.function.ThrowingBiFunction; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.utils.JsonUtils; import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.validator.ValidatorConfig.CheckableConfig; -public class Validator { - - private static final Logger LOG = LoggerFactory.getLogger(Validator.class); - - private final List compatibleCheckableConfigs; - private final List installableCheckableConfigs; - - private ThrowingBiFunction>, // - OpenemsNamedException> // - configurationValidation; - - public static final class Builder { - - private List compatibleCheckableConfigs; - private List installableCheckableConfigs; - - protected Builder() { - - } - - public Builder setCompatibleCheckableConfigs(List compatibleCheckableConfigs) { - this.compatibleCheckableConfigs = compatibleCheckableConfigs; - return this; - } - - public Builder setInstallableCheckableConfigs(List installableCheckableConfigs) { - this.installableCheckableConfigs = installableCheckableConfigs; - return this; - } - - public Validator build() { - return new Validator(this.compatibleCheckableConfigs, this.installableCheckableConfigs); - } - - } - - // TODO convert to record in java 17. - public static final class CheckableConfig { - - private final String checkableComponentName; - private final boolean invertResult; - private final Map properties; - - public CheckableConfig(String checkableComponentName, boolean invertResult, Map properties) { - this.checkableComponentName = checkableComponentName; - this.invertResult = invertResult; - this.properties = properties; - } - - public CheckableConfig(String checkableComponentName, Map properties) { - this(checkableComponentName, false, properties); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (this.getClass() != obj.getClass()) { - return false; - } - var other = (CheckableConfig) obj; - return java.util.Objects.equals(this.checkableComponentName, other.checkableComponentName) - && this.invertResult == other.invertResult; - } - - } - - public static final class MapBuilder, K, V> { - - private final T map; - - public MapBuilder(T mapImpl) { - this.map = mapImpl; - } - - /** - * Does the exact same like {@link Map#put(Object, Object)}. - * - * @param key the key - * @param value the value - * @return this - */ - public MapBuilder put(K key, V value) { - this.map.put(key, value); - return this; - } - - public T build() { - return this.map; - } - } +public interface Validator { /** - * Creates a builder for an {@link Validator}. + * Gets the error messages for compatibility. * - * @return the builder + * @return the error messages */ - public static final Builder create() { - return new Builder(); - } - - protected Validator(List compatibleCheckableConfigs, - List installableCheckableConfigs) { - this.compatibleCheckableConfigs = compatibleCheckableConfigs != null // - ? compatibleCheckableConfigs - : Lists.newArrayList(); - this.installableCheckableConfigs = installableCheckableConfigs != null // - ? installableCheckableConfigs - : Lists.newArrayList(); + public default List getErrorCompatibleMessages(ValidatorConfig config) { + return getErrorMessages(config.getCompatibleCheckableConfigs(), false); } /** - * Gets the error messages for compatibility. + * Gets the error messages for installation. * * @return the error messages */ - public List getErrorCompatibleMessages() { - return getErrorMessages(this.compatibleCheckableConfigs, false); + public default List getErrorInstallableMessages(ValidatorConfig config) { + return getErrorMessages(config.getInstallableCheckableConfigs(), false); } /** - * Gets the error messages for the given {@link Checkable}. + * Validates the Configuration {@link Checkable}s. * - * @param checkableConfigs the {@link Checkable}s to be checked. - * @param returnImmediate after the first checkable who returns false - * @return a list of errors + * @param target the target of the configuration + * @param properties the configuration properties + * @throws OpenemsNamedException on validation error */ - private static List getErrorMessages(List checkableConfigs, boolean returnImmediate) { - if (checkableConfigs.isEmpty()) { - return new ArrayList<>(); - } - var errorMessages = new ArrayList(checkableConfigs.size()); - var bundleContext = FrameworkUtil.getBundle(Checkable.class).getBundleContext(); - // build filter - var filterBuilder = new StringBuilder(); - if (checkableConfigs.size() > 1) { - filterBuilder.append("(|"); + public default void validateConfiguration(ValidatorConfig config, ConfigurationTarget target, JsonObject properties) + throws OpenemsNamedException { + if (config.getConfigurationValidation() == null) { + return; } - checkableConfigs.forEach(t -> filterBuilder.append("(component.name=" + t.checkableComponentName + ")")); - if (checkableConfigs.size() > 1) { - filterBuilder.append(")"); + var checkables = config.getConfigurationValidation().apply(target, properties); + if (checkables == null) { + return; } - try { - // get all service references - Collection> serviceReferences = bundleContext - .getServiceReferences(Checkable.class, filterBuilder.toString()); - var noneExistingCheckables = Lists.newArrayList(); - checkableConfigs.forEach(c -> noneExistingCheckables.add(c)); - var isReturnedImmediate = false; - for (var reference : serviceReferences) { - var componentName = (String) reference.getProperty(OpenemsConstants.PROPERTY_OSGI_COMPONENT_NAME); - var checkableConfig = checkableConfigs.stream() - .filter(c -> c.checkableComponentName.equals(componentName)).findFirst().orElse(null); - var checkable = bundleContext.getService(reference); - if (checkableConfig.properties != null) { - checkable.setProperties(checkableConfig.properties); - } - noneExistingCheckables.removeIf(c -> c.equals(checkableConfig)); - var result = checkable.check(); - if (result == checkableConfig.invertResult) { - var errorMessage = checkable.getErrorMessage(); - if (checkableConfig.invertResult) { - errorMessage = "Invert[" + errorMessage + "]"; - } - errorMessages.add(errorMessage); - if (returnImmediate) { - isReturnedImmediate = true; - break; - } - } - } - - if (!noneExistingCheckables.isEmpty() && !isReturnedImmediate) { - LOG.warn("Checkables[" + noneExistingCheckables.stream().map(c -> c.checkableComponentName) - .collect(Collectors.joining(";")) + "] are not found!"); - } - - // free all service references - for (var reference : serviceReferences) { - bundleContext.ungetService(reference); - } - } catch (InvalidSyntaxException | IllegalStateException e) { - // Can not get service references - e.printStackTrace(); + var errors = getErrorMessages(config.getCompatibleCheckableConfigs(), false); + if (!errors.isEmpty()) { + throw new OpenemsException(errors.stream().collect(Collectors.joining(";"))); } - return errorMessages; - } - - /** - * Gets the error messages for installation. - * - * @return the error messages - */ - public List getErrorInstallableMessages() { - return getErrorMessages(this.installableCheckableConfigs, false); } /** @@ -230,70 +59,40 @@ public List getErrorInstallableMessages() { * * @return the Status */ - public OpenemsAppStatus getStatus() { - if (!getErrorMessages(this.compatibleCheckableConfigs, true).isEmpty()) { + public default OpenemsAppStatus getStatus(ValidatorConfig config) { + if (!this.getErrorMessages(config.getCompatibleCheckableConfigs(), true).isEmpty()) { return OpenemsAppStatus.INCOMPATIBLE; } - if (!getErrorMessages(this.installableCheckableConfigs, true).isEmpty()) { + if (!this.getErrorMessages(config.getInstallableCheckableConfigs(), true).isEmpty()) { return OpenemsAppStatus.COMPATIBLE; } return OpenemsAppStatus.INSTALLABLE; } - public void setConfigurationValidation(ThrowingBiFunction>, OpenemsNamedException> configurationValidation) { - this.configurationValidation = configurationValidation; - } - /** * Builds a {@link JsonObject} out of this {@link Validator}. * * @return the {@link JsonObject} */ - public JsonObject toJsonObject() { - var compatibleMessages = JsonUtils.buildJsonArray().build(); - for (var message : this.getErrorCompatibleMessages()) { - compatibleMessages.add(message); - } - var installableMessages = JsonUtils.buildJsonArray().build(); - for (var message : this.getErrorInstallableMessages()) { - installableMessages.add(message); - } + public default JsonObject toJsonObject(ValidatorConfig config) { return JsonUtils.buildJsonObject() // - .addProperty("name", this.getStatus().name()) // - .add("errorCompatibleMessages", compatibleMessages) // - .add("errorInstallableMessages", installableMessages) // + .addProperty("name", this.getStatus(config).name()) // + .add("errorCompatibleMessages", + this.getErrorCompatibleMessages(config).stream().map(s -> new JsonPrimitive(s)) + .collect(JsonUtils.toJsonArray())) // + .add("errorInstallableMessages", + this.getErrorInstallableMessages(config).stream().map(s -> new JsonPrimitive(s)) + .collect(JsonUtils.toJsonArray())) // .build(); } /** - * Validates the Configuration {@link Checkable}s. + * Gets the error messages for the given {@link Checkable}. * - * @param target the target of the configuration - * @param properties the configuration properties - * @throws OpenemsNamedException on validation error + * @param checkableConfigs the {@link Checkable}s to be checked. + * @param returnImmediate after the first checkable who returns false + * @return a list of errors */ - public void validateConfiguration(ConfigurationTarget target, JsonObject properties) throws OpenemsNamedException { - if (this.configurationValidation == null) { - return; - } - var checkables = this.configurationValidation.apply(target, properties); - if (checkables == null) { - return; - } - var errors = getErrorMessages(this.compatibleCheckableConfigs, false); - if (!errors.isEmpty()) { - throw new OpenemsException(errors.stream().collect(Collectors.joining(";"))); - } - } - - public List getCompatibleCheckableConfigs() { - return this.compatibleCheckableConfigs; - } - - public List getInstallableCheckableConfigs() { - return this.installableCheckableConfigs; - } + public abstract List getErrorMessages(List checkableConfigs, boolean returnImmediate); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorConfig.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorConfig.java new file mode 100644 index 00000000000..c79e6277732 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorConfig.java @@ -0,0 +1,152 @@ +package io.openems.edge.core.appmanager.validator; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Lists; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingBiFunction; +import io.openems.edge.core.appmanager.ConfigurationTarget; + +public class ValidatorConfig { + + private static final Logger LOG = LoggerFactory.getLogger(ValidatorConfig.class); + + // TODO + private final List preInstallCheckableConfigs = null; + private final List compatibleCheckableConfigs; + private final List installableCheckableConfigs; + + private ThrowingBiFunction>, // + OpenemsNamedException> // + configurationValidation; + + public static final class Builder { + + private List compatibleCheckableConfigs; + private List installableCheckableConfigs; + + protected Builder() { + + } + + public Builder setCompatibleCheckableConfigs(List compatibleCheckableConfigs) { + this.compatibleCheckableConfigs = compatibleCheckableConfigs; + return this; + } + + public Builder setInstallableCheckableConfigs(List installableCheckableConfigs) { + this.installableCheckableConfigs = installableCheckableConfigs; + return this; + } + + public ValidatorConfig build() { + return new ValidatorConfig(this.compatibleCheckableConfigs, this.installableCheckableConfigs); + } + + } + + // TODO convert to record in java 17. + public static final class CheckableConfig { + + public final String checkableComponentName; + public final boolean invertResult; + public final Map properties; + + public CheckableConfig(String checkableComponentName, boolean invertResult, Map properties) { + this.checkableComponentName = checkableComponentName; + this.invertResult = invertResult; + this.properties = properties; + } + + public CheckableConfig(String checkableComponentName, Map properties) { + this(checkableComponentName, false, properties); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (this.getClass() != obj.getClass()) { + return false; + } + var other = (CheckableConfig) obj; + return java.util.Objects.equals(this.checkableComponentName, other.checkableComponentName) + && this.invertResult == other.invertResult; + } + + } + + public static final class MapBuilder, K, V> { + + private final T map; + + public MapBuilder(T mapImpl) { + this.map = mapImpl; + } + + /** + * Does the exact same like {@link Map#put(Object, Object)}. + * + * @param key the key + * @param value the value + * @return this + */ + public MapBuilder put(K key, V value) { + this.map.put(key, value); + return this; + } + + public T build() { + return this.map; + } + } + + /** + * Creates a builder for an {@link ValidatorConfig}. + * + * @return the builder + */ + public static final Builder create() { + return new Builder(); + } + + protected ValidatorConfig(List compatibleCheckableConfigs, + List installableCheckableConfigs) { + this.compatibleCheckableConfigs = compatibleCheckableConfigs != null // + ? compatibleCheckableConfigs + : Lists.newArrayList(); + this.installableCheckableConfigs = installableCheckableConfigs != null // + ? installableCheckableConfigs + : Lists.newArrayList(); + } + + public void setConfigurationValidation( + ThrowingBiFunction>, OpenemsNamedException> configurationValidation) { + this.configurationValidation = configurationValidation; + } + + public ThrowingBiFunction>, OpenemsNamedException> getConfigurationValidation() { + return configurationValidation; + } + + public List getCompatibleCheckableConfigs() { + return this.compatibleCheckableConfigs; + } + + public List getInstallableCheckableConfigs() { + return this.installableCheckableConfigs; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorImpl.java new file mode 100644 index 00000000000..b63163284b4 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorImpl.java @@ -0,0 +1,95 @@ +package io.openems.edge.core.appmanager.validator; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Lists; + +import io.openems.common.OpenemsConstants; +import io.openems.common.session.Language; +import io.openems.edge.core.appmanager.validator.ValidatorConfig.CheckableConfig; + +@Component +public class ValidatorImpl implements Validator { + + private static final Logger LOG = LoggerFactory.getLogger(ValidatorImpl.class); + + @Activate + public ValidatorImpl() { + } + + @Override + public List getErrorMessages(List checkableConfigs, boolean returnImmediate) { + if (checkableConfigs.isEmpty()) { + return new ArrayList<>(); + } + var errorMessages = new ArrayList(checkableConfigs.size()); + var bundleContext = FrameworkUtil.getBundle(Checkable.class).getBundleContext(); + // build filter + var filterBuilder = new StringBuilder(); + if (checkableConfigs.size() > 1) { + filterBuilder.append("(|"); + } + checkableConfigs.forEach(t -> filterBuilder.append("(component.name=" + t.checkableComponentName + ")")); + if (checkableConfigs.size() > 1) { + filterBuilder.append(")"); + } + try { + // get all service references + Collection> serviceReferences = bundleContext + .getServiceReferences(Checkable.class, filterBuilder.toString()); + var noneExistingCheckables = Lists.newArrayList(); + checkableConfigs.forEach(c -> noneExistingCheckables.add(c)); + var isReturnedImmediate = false; + var usedReferencens = new ArrayList>(serviceReferences.size()); + for (var reference : serviceReferences) { + var componentName = (String) reference.getProperty(OpenemsConstants.PROPERTY_OSGI_COMPONENT_NAME); + var checkableConfig = checkableConfigs.stream() + .filter(c -> c.checkableComponentName.equals(componentName)).findFirst().orElse(null); + var checkable = bundleContext.getService(reference); + usedReferencens.add(reference); + if (checkableConfig.properties != null) { + checkable.setProperties(checkableConfig.properties); + } + noneExistingCheckables.removeIf(c -> c.equals(checkableConfig)); + var result = checkable.check(); + if (result == checkableConfig.invertResult) { + var errorMessage = checkable.getErrorMessage(Language.DEFAULT); + if (checkableConfig.invertResult) { + errorMessage = "Invert[" + errorMessage + "]"; + } + errorMessages.add(errorMessage); + if (returnImmediate) { + isReturnedImmediate = true; + break; + } + } + } + + if (!noneExistingCheckables.isEmpty() && !isReturnedImmediate) { + LOG.warn("Checkables[" + noneExistingCheckables.stream().map(c -> c.checkableComponentName) + .collect(Collectors.joining(";")) + "] are not found!"); + } + + // free all service references + for (var reference : usedReferencens) { + bundleContext.ungetService(reference); + } + } catch (InvalidSyntaxException | IllegalStateException e) { + // Can not get service references + e.printStackTrace(); + } + return errorMessages; + } + +} From 79881c958246ab7d2522e7415cb8ea42130ff7b7 Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:18:27 +0200 Subject: [PATCH 12/30] added pvSelfConsumtion as dependency --- .../app/integratedsystem/FeneconHome.java | 58 ++- .../SelfConsumptionOptimization.java | 428 ++++++++++++++++++ .../core/appmanager/translation_de.properties | 7 +- .../core/appmanager/translation_en.properties | 7 +- 4 files changed, 475 insertions(+), 25 deletions(-) create mode 100644 io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java index ccc0b2faa7e..39fe3e9c167 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java @@ -25,6 +25,7 @@ import io.openems.edge.app.integratedsystem.FeneconHome.Property; import io.openems.edge.app.meter.SocomecMeter; import io.openems.edge.app.pvselfconsumption.GridOptimizedCharge; +import io.openems.edge.app.pvselfconsumption.SelfConsumptionOptimization; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.AppAssistant; @@ -118,7 +119,8 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { var modbusIdExternal = "modbus1"; var emergencyReserveEnabled = EnumUtils.getAsBoolean(p, Property.EMERGENCY_RESERVE_ENABLED); - var rippleControlReceiverActive = EnumUtils.getAsBoolean(p, Property.RIPPLE_CONTROL_RECEIVER_ACTIV); + var rippleControlReceiverActive = EnumUtils.getAsOptionalBoolean(p, Property.RIPPLE_CONTROL_RECEIVER_ACTIV) + .orElse(false); // Battery-Inverter Settings var safetyCountry = EnumUtils.getAsString(p, Property.SAFETY_COUNTRY); @@ -213,16 +215,7 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { "Controller.Ess.Hybrid.Surplus-Feed-To-Grid", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("ess.id", essId) // - .build()), - new EdgeConfig.Component("ctrlBalancing0", - bundle.getString(this.getAppId() + ".ctrlBalancing0.alias"), - "Controller.Symmetric.Balancing", JsonUtils.buildJsonObject() // - .addProperty("enabled", true) // - .addProperty("ess.id", essId) // - .addProperty("meter.id", "meter0") // - .addProperty("targetGridSetpoint", 0) // - .build()) - + .build()) // ); if (EnumUtils.getAsOptionalBoolean(p, Property.HAS_DC_PV1).orElse(false)) { @@ -282,30 +275,47 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { schedulerExecutionOrder.add("ctrlBalancing0"); var dependencies = Lists.newArrayList(new DependencyDeclaration("GRID_OPTIMIZED_CHARGE", // - "App.PvSelfConsumption.GridOptimizedCharge", // - bundle.getString("App.PvSelfConsumption.GridOptimizedCharge.Name"), // DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING, // DependencyDeclaration.UpdatePolicy.ALWAYS, // DependencyDeclaration.DeletePolicy.IF_MINE, // DependencyDeclaration.DependencyUpdatePolicy.ALLOW_ONLY_UNCONFIGURED_PROPERTIES, // DependencyDeclaration.DependencyDeletePolicy.NOT_ALLOWED, // - JsonUtils.buildJsonObject() // - .addProperty(GridOptimizedCharge.Property.SELL_TO_GRID_LIMIT_ENABLED.name(), - !rippleControlReceiverActive) // - .addProperty(GridOptimizedCharge.Property.MAXIMUM_SELL_TO_GRID_POWER.name(), maxFeedInPower) // - .build())); + DependencyDeclaration.AppDependencyConfig.create() // + .setAppId("App.PvSelfConsumption.GridOptimizedCharge") // + .setProperties(JsonUtils.buildJsonObject() // + .addProperty(GridOptimizedCharge.Property.SELL_TO_GRID_LIMIT_ENABLED.name(), + !rippleControlReceiverActive) // + .addProperty(GridOptimizedCharge.Property.MAXIMUM_SELL_TO_GRID_POWER.name(), + maxFeedInPower) // + .build()) + .build()), + new DependencyDeclaration("SELF_CONSUMTION_OPTIMIZATION", // + DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING, // + DependencyDeclaration.UpdatePolicy.NEVER, // + DependencyDeclaration.DeletePolicy.IF_MINE, // + DependencyDeclaration.DependencyUpdatePolicy.ALLOW_ONLY_UNCONFIGURED_PROPERTIES, // + DependencyDeclaration.DependencyDeletePolicy.NOT_ALLOWED, // + DependencyDeclaration.AppDependencyConfig.create() // + .setAppId("App.PvSelfConsumption.SelfConsumptionOptimization") // + .setProperties(JsonUtils.buildJsonObject() // + .addProperty(SelfConsumptionOptimization.Property.ESS_ID.name(), essId) // + .addProperty(SelfConsumptionOptimization.Property.METER_ID.name(), "meter0") // + .build()) + .build()) // + ); if (EnumUtils.getAsOptionalBoolean(p, Property.HAS_AC_METER).orElse(false)) { dependencies.add(new DependencyDeclaration("AC_METER", // - "App.Meter.Socomec", // - bundle.getString("App.PvSelfConsumption.GridOptimizedCharge.Name"), // DependencyDeclaration.CreatePolicy.ALWAYS, // DependencyDeclaration.UpdatePolicy.ALWAYS, // DependencyDeclaration.DeletePolicy.IF_MINE, // DependencyDeclaration.DependencyUpdatePolicy.ALLOW_ONLY_UNCONFIGURED_PROPERTIES, // DependencyDeclaration.DependencyDeletePolicy.NOT_ALLOWED, // - JsonUtils.buildJsonObject() // - .addProperty(SocomecMeter.Property.MODBUS_UNIT_ID.name(), 6) // + DependencyDeclaration.AppDependencyConfig.create() // + .setAppId("App.Meter.Socomec") // + .setProperties(JsonUtils.buildJsonObject() // + .addProperty(SocomecMeter.Property.MODBUS_UNIT_ID.name(), 6) // + .build()) .build())); } @@ -346,7 +356,7 @@ public AppAssistant getAppAssistant(Language language) { .setLabel(bundle.getString(this.getAppId() + ".rippleControlReceiver.label")) .setDescription( bundle.getString(this.getAppId() + ".rippleControlReceiver.description")) - .setDefaultValue(true) // + .setDefaultValue(false) // .build()) .add(JsonFormlyUtil.buildInput(Property.MAX_FEED_IN_POWER) // .setLabel(bundle.getString(this.getAppId() + ".feedInLimit.label")) // @@ -428,6 +438,8 @@ public AppAssistant getAppAssistant(Language language) { .add(JsonFormlyUtil.buildInput(Property.EMERGENCY_RESERVE_SOC) // .setLabel(bundle.getString(this.getAppId() + ".reserveEnergy.label")) // .setInputType(Type.NUMBER) // + .setMin(0) // + .setMax(100) // .onlyShowIfChecked(Property.HAS_EMERGENCY_RESERVE) // .onlyIf(hasEmergencyReserve, f -> { f.setDefaultValue(this.componentManager.getEdgeConfig() diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java new file mode 100644 index 00000000000..8e1f47abebd --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java @@ -0,0 +1,428 @@ +package io.openems.edge.app.pvselfconsumption; + +import java.util.EnumMap; +import java.util.List; + +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.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; +import io.openems.common.types.EdgeConfig; +import io.openems.common.types.EdgeConfig.Component; +import io.openems.common.utils.EnumUtils; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.pvselfconsumption.SelfConsumptionOptimization.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.JsonFormlyUtil; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.ess.api.ManagedSymmetricEss; +import io.openems.edge.meter.api.SymmetricMeter; + +/** + * Describes a App for a Grid Optimized Charge. + * + *

+  {
+    "appId":"App.PvSelfConsumption.SelfConsumptionOptimization",
+    "alias":"Eigenverbrauchsoptimierung",
+    "instanceId": UUID,
+    "image": base64,
+    "properties":{
+    	"ESS_ID": "ess0",
+    	"METER_ID": "meter0"
+    },
+    "appDescriptor": {
+    	"websiteUrl": https://fenecon.de/fems-2-2/fems-app-eigenverbrauchsoptimierung-2//
+    }
+  }
+ * 
+ */ +@org.osgi.service.component.annotations.Component(name = "App.PvSelfConsumption.SelfConsumptionOptimization") +public class SelfConsumptionOptimization extends AbstractOpenemsApp implements OpenemsApp { + + public static enum Property { + // User values + ALIAS, // + ESS_ID, // + METER_ID, // + ; + } + + @Activate + public SelfConsumptionOptimization(@Reference ComponentManager componentManager, ComponentContext componentContext, + @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + super(componentManager, componentContext, cm, componentUtil); + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, l) -> { + + final var ctrlBalacingId = "ctrlBalancing0"; + + final var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); + final var essId = EnumUtils.getAsString(p, Property.ESS_ID); + final var meterId = EnumUtils.getAsString(p, Property.METER_ID); + + List comp = Lists.newArrayList(new EdgeConfig.Component(ctrlBalacingId, alias, + "Controller.Symmetric.Balancing", JsonUtils.buildJsonObject() // + .addProperty("enabled", true) // + .addProperty("ess.id", essId) // + .addProperty("meter.id", meterId) // + .addProperty("targetGridSetpoint", 0) // + .build()));// + + return new AppConfiguration(comp); + }; + } + + @Override + public AppAssistant getAppAssistant(Language language) { + var bundle = AbstractOpenemsApp.getTranslationBundle(language); + return AppAssistant.create(this.getName(language)) // + .fields(JsonUtils.buildJsonArray() // + .add(JsonFormlyUtil.buildSelect(Property.ESS_ID)// + .setLabel(bundle.getString(this.getAppId() + ".ess.label")) // + .setDescription(bundle.getString(this.getAppId() + ".ess.description")) // + .isRequired(true) // + .setOptions(this.componentManager.getEnabledComponentsOfType(ManagedSymmetricEss.class), + c -> c.id() + ": " + c.alias(), c -> c.id()) // + .build()) + .add(JsonFormlyUtil.buildSelect(Property.METER_ID)// + .setLabel(bundle.getString(this.getAppId() + ".meter.label")) // + .setDescription(bundle.getString(this.getAppId() + ".meter.description")) // + .isRequired(true) // + .setOptions(this.componentManager.getEnabledComponentsOfType(SymmetricMeter.class), + c -> c.id() + ": " + c.alias(), c -> c.id()) // + .build()) + .build()) + .build(); + } + + @Override + public AppDescriptor getAppDescriptor() { + return AppDescriptor.create() // + .setWebsiteUrl("https://fenecon.de/fems-2-2/fems-app-eigenverbrauchsoptimierung-2/") // + .build(); + } + + @Override + public OpenemsAppCategory[] getCategorys() { + return new OpenemsAppCategory[] { OpenemsAppCategory.PV_SELF_CONSUMPTION }; + } + + @Override + protected Class getPropertyClass() { + return Property.class; + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.SINGLE; + } + + @Override + public String getImage() { + return "data:image/png;base64,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"; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties index 67aa7aff168..7db987b2709 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties @@ -150,4 +150,9 @@ App.PvSelfConsumption.GridOptimizedCharge.Name = Netzdienliche Beladung App.PvSelfConsumption.GridOptimizedCharge.sellToGridLimitEnabled.label = Ist der maximal Stromverkauf an das Netz aktiviert? App.PvSelfConsumption.GridOptimizedCharge.sellToGridLimitEnabled.description = Ist die Logik für den maximal Stromverkauf an das Netz aktiviert? App.PvSelfConsumption.GridOptimizedCharge.maximumSellToGridPower.label = Maximal zulässiger Stromverkauf an das Netz -App.PvSelfConsumption.GridOptimizedCharge.maximumSellToGridPower.description = Die Zielgrenze für den Verkauf von Strom an das Netz. \ No newline at end of file +App.PvSelfConsumption.GridOptimizedCharge.maximumSellToGridPower.description = Die Zielgrenze für den Verkauf von Strom an das Netz. +App.PvSelfConsumption.SelfConsumptionOptimization.Name = Eigenverbrauchsoptimierung +App.PvSelfConsumption.SelfConsumptionOptimization.ess.label = Ess-ID +App.PvSelfConsumption.SelfConsumptionOptimization.ess.description = ID von den Ess Gerät. +App.PvSelfConsumption.SelfConsumptionOptimization.meter.label = Netzzähler-ID +App.PvSelfConsumption.SelfConsumptionOptimization.meter.description = ID von den Netzzähler. \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties index 67a49456990..8da830a98d0 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties @@ -150,4 +150,9 @@ App.PvSelfConsumption.GridOptimizedCharge.Name = Grid optimized charge App.PvSelfConsumption.GridOptimizedCharge.sellToGridLimitEnabled.label = Is Sell-To-Grid-Limit enabled? App.PvSelfConsumption.GridOptimizedCharge.sellToGridLimitEnabled.description = Is the sell to grid limit logic enabled? App.PvSelfConsumption.GridOptimizedCharge.maximumSellToGridPower.label = Maximum allowed Sell-To-Grid power -App.PvSelfConsumption.GridOptimizedCharge.maximumSellToGridPower.description = The target limit for sell-to-grid power. \ No newline at end of file +App.PvSelfConsumption.GridOptimizedCharge.maximumSellToGridPower.description = The target limit for sell-to-grid power. +App.PvSelfConsumption.SelfConsumptionOptimization.Name = self-consumption optimisation +App.PvSelfConsumption.SelfConsumptionOptimization.ess.label = Ess-ID +App.PvSelfConsumption.SelfConsumptionOptimization.ess.description = ID of Ess device. +App.PvSelfConsumption.SelfConsumptionOptimization.meter.label = Grid-Meter-ID +App.PvSelfConsumption.SelfConsumptionOptimization.meter.description = ID of the Grid-Meter. \ No newline at end of file From 9fb49a4dcdf23c6afa54f1769c6e2d906c752173 Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:20:11 +0200 Subject: [PATCH 13/30] improvements creating an instance --- .../io/openems/common/utils/EnumUtils.java | 18 + .../edge/core/appmanager/JsonFormlyUtil.java | 86 ++- .../dependency/AppManagerAppHelper.java | 4 +- .../dependency/AppManagerAppHelperImpl.java | 531 ++++++++++++------ .../ComponentAggregateTaskImpl.java | 4 + .../dependency/DependencyConfig.java | 10 +- .../dependency/DependencyDeclaration.java | 101 +++- .../appmanager/dependency/DependencyUtil.java | 60 ++ .../dependency/ExistingDependencyConfig.java | 8 +- .../dependency/StaticIpAggregateTaskImpl.java | 6 +- 10 files changed, 630 insertions(+), 198 deletions(-) create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java diff --git a/io.openems.common/src/io/openems/common/utils/EnumUtils.java b/io.openems.common/src/io/openems/common/utils/EnumUtils.java index cbbd7573574..fecb288370e 100644 --- a/io.openems.common/src/io/openems/common/utils/EnumUtils.java +++ b/io.openems.common/src/io/openems/common/utils/EnumUtils.java @@ -48,6 +48,24 @@ public static > Optional getAsOptionalString(Enu } } + /** + * Gets the member of the {@link EnumMap} as {@link Optional} {@link Integer}. + * + * @param the type of the EnumMap key + * @param map the {@link EnumMap} + * @param member the member + * @return the {@link Optional} {@link Integer} value + * @throws OpenemsNamedException on error + */ + public static > Optional getAsOptionalInt(EnumMap map, + ENUM member) { + try { + return Optional.of(getAsInt(map, member)); + } catch (OpenemsNamedException e) { + return Optional.empty(); + } + } + /** * Gets the member of the {@link EnumMap} as {@link JsonPrimitive}. * diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java index 39cc9285a5a..b4d6087f1af 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java @@ -58,6 +58,17 @@ public static > SelectBuilder buildSelect(T property) { return new SelectBuilder(property); } + /** + * Creates a JsonObject Formly Repeat Builder for the given enum. + * + * @param the type of the enum + * @param property the enum property + * @return a {@link RepeatBuilder} + */ + public static > RepeatBuilder buildRepeat(T property) { + return new RepeatBuilder(property); + } + /** * A Builder for a Formly field. * @@ -102,8 +113,12 @@ private final T setType(String type) { return this.getSelf(); } - private final T setKey(String key) { - this.jsonObject.addProperty("key", key); + public final T setKey(String key) { + if (key != null) { + this.jsonObject.addProperty("key", key); + } else if (this.jsonObject.has("key")) { + this.jsonObject.remove("key"); + } return this.getSelf(); } @@ -165,7 +180,11 @@ public final T isRequired(boolean isRequired) { } public final T setLabel(String label) { - this.templateOptions.addProperty("label", label); + if (label != null) { + this.templateOptions.addProperty("label", label); + } else if (this.templateOptions.has("label")) { + this.templateOptions.remove("label"); + } return this.getSelf(); } @@ -601,4 +620,65 @@ protected String getType() { } + /** + * A Builder for a Formly Checkbox. + * + *
+	 * {
+	 * 	"key": "key",
+	 * 	"type": "repeat",
+	 * 	"templateOptions": {
+	 * 		"label": "label",
+	 * 		"required": true
+	 * 	},
+	 * 	"expressionProperties": {
+	 * 		"templateOptions.required": "model.PROPERTY"
+	 * 	},
+	 * 	"hideExpression": "!model.PROPERTY",
+	 * 	"defaultValue": "defaultValue"
+	 * }
+	 * 
+ * + */ + public static final class RepeatBuilder extends FormlyBuilder { + + private JsonObject fieldArray; + + private > RepeatBuilder(PROPERTY property) { + super(property); + } + + private RepeatBuilder(DefaultEnum property) { + super(property); + } + + public RepeatBuilder setAddText(String addText) { + if (addText != null && addText.isBlank()) { + this.templateOptions.addProperty("addText", addText); + } else if (this.templateOptions.has("addText")) { + this.templateOptions.remove("addText"); + } + return this; + } + + public RepeatBuilder setFieldArray(JsonObject object) { + this.fieldArray = object; + return this; + } + + @Override + protected String getType() { + return "repeat"; + } + + @Override + public JsonObject build() { + if (fieldArray != null) { + this.jsonObject.add("fieldArray", this.fieldArray); + } + return super.build(); + } + + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java index e729e553932..d33a03647b7 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java @@ -1,7 +1,5 @@ package io.openems.edge.core.appmanager.dependency; -import java.util.List; - import com.google.gson.JsonObject; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; @@ -22,7 +20,7 @@ public interface AppManagerAppHelper { * @return s a list of the created {@link OpenemsAppInstance}s * @throws OpenemsNamedException on error */ - public List installApp(User user, JsonObject properties, String alias, OpenemsApp app) + public UpdateValues installApp(User user, JsonObject properties, String alias, OpenemsApp app) throws OpenemsNamedException; /** diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java index 0aac2febce9..770ebd84cd0 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java @@ -11,13 +11,15 @@ import java.util.Set; import java.util.TreeMap; import java.util.UUID; +import java.util.function.Function; import java.util.stream.Collectors; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.gson.JsonObject; @@ -36,17 +38,23 @@ import io.openems.edge.core.appmanager.ConfigurationTarget; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppInstance; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration.AppDependencyConfig; +import io.openems.edge.core.appmanager.validator.Validator; @Component public class AppManagerAppHelperImpl implements AppManagerAppHelper { + private final Logger log = LoggerFactory.getLogger(this.getClass()); + private final ComponentManager componentManager; private final ComponentUtil componentUtil; + private final Validator validator; + // tasks private final AggregateTask.ComponentAggregateTask componentsTask; private final AggregateTask.SchedulerAggregateTask schedulerTask; - private final AggregateTask.SchedulerAggregateTask staticIpTask; + private final AggregateTask.StaticIpAggregateTask staticIpTask; private final AggregateTask[] tasks; @@ -54,11 +62,12 @@ public class AppManagerAppHelperImpl implements AppManagerAppHelper { @Activate public AppManagerAppHelperImpl(@Reference ComponentManager componentManager, @Reference ComponentUtil componentUtil, - @Reference AggregateTask.ComponentAggregateTask componentsTask, + @Reference Validator validator, @Reference AggregateTask.ComponentAggregateTask componentsTask, @Reference AggregateTask.SchedulerAggregateTask schedulerTask, - @Reference AggregateTask.SchedulerAggregateTask staticIpTask) { + @Reference AggregateTask.StaticIpAggregateTask staticIpTask) { this.componentManager = componentManager; this.componentUtil = componentUtil; + this.validator = validator; this.componentsTask = componentsTask; this.schedulerTask = schedulerTask; this.staticIpTask = staticIpTask; @@ -66,9 +75,9 @@ public AppManagerAppHelperImpl(@Reference ComponentManager componentManager, @Re } @Override - public List installApp(User user, JsonObject properties, String alias, OpenemsApp app) + public UpdateValues installApp(User user, JsonObject properties, String alias, OpenemsApp app) throws OpenemsNamedException { - return this.updateApp(user, null, properties, alias, app).modifiedOrCreatedApps; + return this.updateApp(user, null, properties, alias, app); } @Override @@ -80,12 +89,13 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj // e. g. install HOME is requested it may have a dependency on a SOCOMEC Meter // but the meter has a checkable that there has to be a HOME installed // maybe add temporary apps in this component + final var warnings = new LinkedList(); if (oldInstance == null) { - AppManagerAppHelperImpl.checkStatus(app); + this.checkStatus(app); } else { // determine if properties are allowed to be updated var references = this.getAppsWithReferenceTo(oldInstance.instanceId); - for (var entry : this.getAppManagerImpl().appConfigs(references)) { + for (var entry : this.getAppManagerImpl().appConfigs(references, null)) { for (var dependencieDeclaration : entry.getValue().dependencies) { var dd = entry.getKey().dependencies.stream() @@ -96,6 +106,15 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj continue; } + var dependencyApp = this.getAppManagerImpl().findInstaceById(dd.get().instanceId); + var appConfig = dependencieDeclaration.appConfigs.stream() + .filter(d -> d.appId.equals(dependencyApp.appId)).findAny(); + + if (appConfig.isEmpty()) { + // TODO can not get app config + continue; + } + switch (dependencieDeclaration.dependencyUpdatePolicy) { case ALLOW_ALL: // everything can be changed @@ -106,9 +125,15 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj break; case ALLOW_ONLY_UNCONFIGURED_PROPERTIES: // override properties - // TODO maybe warning that these values are not set - for (var propEntry : dependencieDeclaration.properties.entrySet()) { - properties.add(propEntry.getKey(), propEntry.getValue()); + for (var propEntry : appConfig.get().properties.entrySet()) { + if (!properties.has(propEntry.getKey()) + || !properties.get(propEntry.getKey()).equals(propEntry.getValue())) { + // TODO translation + warnings.add("Can not change Property[" + propEntry.getKey() + + "] because of a Dependency constraint!"); + properties.add(propEntry.getKey(), propEntry.getValue()); + } + } break; } @@ -135,148 +160,210 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj var modifiedOrCreatedApps = new ArrayList(); var deletedApps = new ArrayList(); + final var lastCreatedOrModifiedApp = new MutableValue(); // update app and its dependencies - this.foreachDependency(app, alias, properties, ConfigurationTarget.UPDATE, language, dc -> { - // get old instance if existing - ExistingDependencyConfig oldAppConfig = null; - if (oldInstance != null) { - if (dc.isDependency()) { - oldAppConfig = oldInstances.remove(new AppIdKey(dc.parent.getAppId(), dc.sub.key)); - if (oldAppConfig != null) { - for (var entry : oldAppConfig.properties.entrySet()) { - // add old values which are not set by the DependecyDeclaration - if (!dc.properties.has(entry.getKey())) { - dc.properties.add(entry.getKey(), entry.getValue()); + this.foreachDependency(app, alias, properties, ConfigurationTarget.UPDATE, language, + this::determineDependencyConfig, dc -> { + // get old instance if existing + ExistingDependencyConfig oldAppConfig = null; + if (oldInstance != null) { + if (dc.isDependency()) { + oldAppConfig = oldInstances.remove(new AppIdKey(dc.parent.getAppId(), dc.sub.key)); + if (oldAppConfig != null) { + for (var entry : oldAppConfig.appDependencyConfig.properties.entrySet()) { + // add old values which are not set by the DependecyDeclaration + if (!dc.appDependencyConfig.properties.has(entry.getKey())) { + dc.appDependencyConfig.properties.add(entry.getKey(), entry.getValue()); + } + } + + } + } else { + AppConfiguration oldAppConfiguration = null; + try { + oldAppConfiguration = dc.app.getAppConfiguration(ConfigurationTarget.UPDATE, + oldInstance.properties, language); + + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); } - } + var appDependencyConfig = DependencyDeclaration.AppDependencyConfig.create() // + .setAppId(app.getAppId()) // + .setAlias(oldInstance.alias) // + .setProperties(oldInstance.properties) // + .build(); + oldAppConfig = new ExistingDependencyConfig(app, null, null, oldAppConfiguration, + appDependencyConfig, null, null, oldInstance); + } } - } else { - AppConfiguration oldAppConfiguration = null; - try { - oldAppConfiguration = dc.app.getAppConfiguration(ConfigurationTarget.UPDATE, - oldInstance.properties, language); - } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); + // map dependencies if this is the parent + var dependecies = new ArrayList(dependencieInstances.size()); + if (!dependencieInstances.isEmpty()) { + var isParent = !dc.isDependency(); + for (var dependency : dependencieInstances.entrySet()) { + if (!isParent && !dc.config.dependencies.stream() + .anyMatch(t -> t.equals(dependency.getKey().sub))) { + isParent = false; + break; + } + isParent = true; + dependecies + .add(new Dependency(dependency.getKey().sub.key, dependency.getValue().instanceId)); + } + if (isParent) { + dependencieInstances.clear(); + } } - oldAppConfig = new ExistingDependencyConfig(app, null, null, oldAppConfiguration, oldInstance.alias, - oldInstance.properties, null, null, oldInstance); - } - } - // map dependencies if this is the parent - var dependecies = new ArrayList(dependencieInstances.size()); - if (!dependencieInstances.isEmpty()) { - var isParent = !dc.isDependency(); - for (var dependency : dependencieInstances.entrySet()) { - if (!isParent - && !dc.config.dependencies.stream().anyMatch(t -> t.equals(dependency.getKey().sub))) { - isParent = false; - break; - } - isParent = true; - dependecies.add(new Dependency(dependency.getKey().sub.key, dependency.getValue().instanceId)); - } - if (isParent) { - dependencieInstances.clear(); - } - } + var otherAppConfigs = this.getAppManagerImpl().getOtherAppConfigurations( + modifiedOrCreatedApps.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + + // create app or get as dependency + if (oldAppConfig == null) { + var neededApp = this.findNeededApp(dc); + if (neededApp == null) { + return false; + } + AppConfiguration oldConfig = null; + UUID instanceId; + OpenemsAppInstance oldInstanceOfCurrentApp = null; + String aliasOfNewInstance = dc.appDependencyConfig.alias; + if (neededApp.isPresent()) { + instanceId = neededApp.get().instanceId; + oldInstanceOfCurrentApp = neededApp.get(); + if (dc.sub.updatePolicy.isAllowedToUpdate(this.getAppManagerImpl().getInstantiatedApps(), + null, neededApp.get())) { + try { + // update app + oldConfig = dc.app.getAppConfiguration(ConfigurationTarget.UPDATE, + neededApp.get().properties, language); + for (var entry : neededApp.get().properties.entrySet()) { + // add old values which are not set by the DependecyDeclaration + if (!dc.appDependencyConfig.properties.has(entry.getKey())) { + dc.appDependencyConfig.properties.add(entry.getKey(), entry.getValue()); + } + } + + if (aliasOfNewInstance == null) { + aliasOfNewInstance = oldInstanceOfCurrentApp.alias; + } + + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); + } + } + } else { + // create app + instanceId = UUID.randomUUID(); - var otherAppConfigs = this.getAppManagerImpl().getOtherAppConfigurations( - modifiedOrCreatedApps.stream().map(t -> t.instanceId).toArray(UUID[]::new)); + // use app name as default alias if not given + if (aliasOfNewInstance == null) { + aliasOfNewInstance = dc.app.getName(language); + } - // create app or get as dependency - if (oldAppConfig == null) { - var appId = dc.app.getAppId(); + // check if the created app can satisfy another app dependency + for (var instance : this.getAppManagerImpl().getInstantiatedApps()) { + var neededDependency = this.getNeededDependencyTo(instance, dc.app.getAppId()); + if (neededDependency == null) { + continue; + } + if (neededDependency.createPolicy == DependencyDeclaration.CreatePolicy.ALWAYS) { + continue; + } + // override properties if set by dependency + var config = determineDependencyConfig(neededDependency.appConfigs); + for (var entry : config.properties.entrySet()) { + if (!dc.appDependencyConfig.properties.has(entry.getKey()) + || !dc.appDependencyConfig.properties.get(entry.getKey()) + .equals(entry.getValue())) { + warnings.add("Override Property[" + entry.getKey() + + "] because of a Dependency constraint!"); + } + dc.appDependencyConfig.properties.add(entry.getKey(), entry.getValue()); + } - var neededApp = this.findNeededApp(dc, appId); - if (neededApp == null) { - return false; - } - AppConfiguration oldConfig = null; - UUID instanceId; - OpenemsAppInstance oldInstanceOfCurrentApp = null; - if (neededApp.isPresent()) { - instanceId = neededApp.get().instanceId; - oldInstanceOfCurrentApp = neededApp.get(); - if (dc.sub.updatePolicy.isAllowedToUpdate(this.getAppManagerImpl().getInstantiatedApps(), null, - neededApp.get())) { - try { - // update app - oldConfig = dc.app.getAppConfiguration(ConfigurationTarget.UPDATE, - neededApp.get().properties, language); - for (var entry : neededApp.get().properties.entrySet()) { - // add old values which are not set by the DependecyDeclaration - if (!dc.properties.has(entry.getKey())) { - dc.properties.add(entry.getKey(), entry.getValue()); + // update dependencies + var alreadyModifiedAppIndex = modifiedOrCreatedApps.indexOf(instance); + var replaceApp = instance; + if (alreadyModifiedAppIndex != -1) { + replaceApp = modifiedOrCreatedApps.get(alreadyModifiedAppIndex); + } + var newDependencies = new ArrayList(); + if (replaceApp.dependencies != null) { + newDependencies.addAll(replaceApp.dependencies); } + newDependencies.add(new Dependency(neededDependency.key, instanceId)); + modifiedOrCreatedApps.remove(replaceApp); + modifiedOrCreatedApps.add(new OpenemsAppInstance(replaceApp.appId, replaceApp.alias, + replaceApp.instanceId, replaceApp.properties, newDependencies)); } + } + + var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), aliasOfNewInstance, instanceId, + dc.appDependencyConfig.properties, dependecies); + lastCreatedOrModifiedApp.setValue(newAppInstance); + modifiedOrCreatedApps.add(newAppInstance); + dependencieInstances.put(dc, newAppInstance); + try { + var newConfig = this.getNewAppConfigWithReplacedIds(dc.app, oldInstanceOfCurrentApp, + newAppInstance, AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), + language); + + this.aggregateAllTasks(newConfig, oldConfig); } catch (OpenemsNamedException e) { errors.add(e.getMessage()); } + return true; } - } else { - // create app - instanceId = UUID.randomUUID(); - // check if the created app can satisfy another app dependency - for (var instance : this.getAppManagerImpl().getInstantiatedApps()) { - var neededDependency = this.getNeededDependencyTo(instance, dc.app.getAppId()); - if (neededDependency == null) { - continue; - } - if (neededDependency.createPolicy == DependencyDeclaration.CreatePolicy.ALWAYS) { - continue; - } - var alreadyModifiedAppIndex = modifiedOrCreatedApps.indexOf(instance); - var replaceApp = instance; - if (alreadyModifiedAppIndex != -1) { - replaceApp = modifiedOrCreatedApps.get(alreadyModifiedAppIndex); - } - var newDependencies = new ArrayList(); - if (replaceApp.dependencies != null) { - newDependencies.addAll(replaceApp.dependencies); + + // find parent + OpenemsAppInstance parent = null; + if (dc.isDependency()) { + if (dc.parent.getAppId().equals(oldInstance.appId)) { + parent = oldInstance; + } else { + for (var entry : oldInstances.entrySet()) { + if (entry.getValue().app.equals(dc.parent)) { + parent = entry.getValue().instance; + } + } } - newDependencies.add(new Dependency(neededDependency.key, instanceId)); - modifiedOrCreatedApps.remove(replaceApp); - modifiedOrCreatedApps.add(new OpenemsAppInstance(replaceApp.appId, replaceApp.alias, - replaceApp.instanceId, replaceApp.properties, newDependencies)); } - } - var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), dc.alias, instanceId, dc.properties, - dependecies); - modifiedOrCreatedApps.add(newAppInstance); - dependencieInstances.put(dc, newAppInstance); - try { - var newConfig = this.getNewAppConfigWithReplacedIds(dc.app, oldInstanceOfCurrentApp, newAppInstance, - AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), language); + // update existing app + if (dc.isDependency() + && !dc.sub.updatePolicy.isAllowedToUpdate(this.getAppManagerImpl().getInstantiatedApps(), + parent, oldAppConfig.instance)) { + // not allowed to update but still a dependency + return true; + } - this.aggregateAllTasks(newConfig, oldConfig); - } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); - } - return true; - } + var newInstanceAlias = dc.appDependencyConfig.alias; + if (newInstanceAlias == null) { + newInstanceAlias = oldAppConfig.instance.alias; + } - // update existing app - var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), dc.alias, oldAppConfig.instance.instanceId, - dc.properties, dependecies); - modifiedOrCreatedApps.add(newAppInstance); - dependencieInstances.put(dc, newAppInstance); + var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), newInstanceAlias, + oldAppConfig.instance.instanceId, dc.appDependencyConfig.properties, dependecies); + lastCreatedOrModifiedApp.setValue(newAppInstance); + modifiedOrCreatedApps.add(newAppInstance); + dependencieInstances.put(dc, newAppInstance); - try { - var newAppConfig = this.getNewAppConfigWithReplacedIds(dc.app, oldAppConfig.instance, newAppInstance, - AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), language); + try { + var newAppConfig = this.getNewAppConfigWithReplacedIds(dc.app, oldAppConfig.instance, + newAppInstance, AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), + language); - this.aggregateAllTasks(newAppConfig, oldAppConfig.config); + this.aggregateAllTasks(newAppConfig, oldAppConfig.config); - } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); - } + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); + } - return true; - }); + return true; + }); // add removed apps for deletion for (var entry : oldInstances.entrySet()) { @@ -300,28 +387,56 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj // create or delete unused components this.componentsTask.create(user, otherAppConfigs); } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); + log.error(e.getMessage()); + // TODO translation + errors.add("Can not update Components!"); } try { // update scheduler execute order this.schedulerTask.create(user, otherAppConfigs); } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); + log.error(e.getMessage()); + // TODO translation + errors.add("Can not update Scheduler!"); } try { // update static ips this.staticIpTask.create(user, otherAppConfigs); } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); + log.error(e.getMessage()); + // TODO translation + errors.add("Can not update static ips!"); } if (!errors.isEmpty()) { throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); } - return new UpdateValues(modifiedOrCreatedApps, deletedApps); + return new UpdateValues(lastCreatedOrModifiedApp.getValue(), modifiedOrCreatedApps, deletedApps, warnings); + } + + private static final class MutableValue { + + private T value; + + public MutableValue() { + this(null); + } + + public MutableValue(T value) { + this.setValue(value); + } + + public void setValue(T value) { + this.value = value; + } + + public T getValue() { + return value; + } + } private final DependencyDeclaration getNeededDependencyTo(OpenemsAppInstance instance, String appId) { @@ -338,9 +453,16 @@ private final DependencyDeclaration getNeededDependencyTo(OpenemsAppInstance ins && instance.dependencies.stream().anyMatch(d -> d.key.equals(neededDependency.key))) { continue; } - if (neededDependency.appId.equals(appId)) { + // TODO when adding an app the current app can't be referenced +// if (neededDependency.appConfigs.stream().filter(c -> c.specificInstanceId != null) +// .anyMatch(c -> c.specificInstanceId.equals(instanceId))) { +// return neededDependency; +// } + if (neededDependency.appConfigs.stream().filter(c -> c.appId != null) + .anyMatch(c -> c.appId.equals(appId))) { return neededDependency; } + } } catch (OpenemsNamedException e) { // can not get app configuration @@ -349,12 +471,23 @@ private final DependencyDeclaration getNeededDependencyTo(OpenemsAppInstance ins } public static final class UpdateValues { + public final OpenemsAppInstance rootInstance; public final List modifiedOrCreatedApps; public final List deletedApps; - public UpdateValues(List modifiedOrCreatedApps, List deletedApps) { + public final List warnings; + + public UpdateValues(OpenemsAppInstance rootInstance, List modifiedOrCreatedApps, + List deletedApps) { + this(rootInstance, modifiedOrCreatedApps, deletedApps, null); + } + + public UpdateValues(OpenemsAppInstance rootInstance, List modifiedOrCreatedApps, + List deletedApps, List warnings) { + this.rootInstance = rootInstance; this.modifiedOrCreatedApps = modifiedOrCreatedApps; this.deletedApps = deletedApps; + this.warnings = warnings; } } @@ -458,7 +591,7 @@ public UpdateValues deleteApp(User user, OpenemsAppInstance instance) throws Ope throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); } - return new UpdateValues(modifiedApps, deletedInstances); + return new UpdateValues(instance, modifiedApps, deletedInstances); } // private void getModifiedAppAssistant(User user, OpenemsApp app) { @@ -497,7 +630,7 @@ private void resetTasks() { private final boolean isAllowedToDelete(OpenemsAppInstance instance, UUID... ignoreIds) { // check if a parent does not allow deletion of this instance for (var entry : this.getAppManagerImpl().appConfigs(this.getAppsWithReferenceTo(instance.instanceId), - ignoreIds)) { + AppManagerImpl.exludingInstanceIds(ignoreIds))) { for (var dependency : entry.getKey().dependencies) { if (!dependency.instanceId.equals(instance.instanceId)) { continue; @@ -521,16 +654,16 @@ private final boolean isAllowedToDelete(OpenemsAppInstance instance, UUID... ign return true; } - protected static void checkStatus(OpenemsApp openemsApp) throws OpenemsNamedException { - var validator = openemsApp.getValidator(); - var status = validator.getStatus(); - switch (validator.getStatus()) { + protected void checkStatus(OpenemsApp openemsApp) throws OpenemsNamedException { + var validatorConfig = openemsApp.getValidatorConfig(); + var status = this.validator.getStatus(validatorConfig); + switch (status) { case INCOMPATIBLE: - throw new OpenemsException("App is not compatible! " - + validator.getErrorCompatibleMessages().stream().collect(Collectors.joining(";"))); + throw new OpenemsException("App is not compatible! " + this.validator + .getErrorCompatibleMessages(validatorConfig).stream().collect(Collectors.joining(";"))); case COMPATIBLE: - throw new OpenemsException("App can not be installed! " - + validator.getErrorCompatibleMessages().stream().collect(Collectors.joining(";"))); + throw new OpenemsException("App can not be installed! " + this.validator + .getErrorInstallableMessages(validatorConfig).stream().collect(Collectors.joining(";"))); case INSTALLABLE: // app can be installed return; @@ -571,18 +704,28 @@ protected static List getStaticIpsFromConfigs(List con * app needs to be created; the {@link OpenemsAppInstance} if an * existing app can be used */ - private Optional findNeededApp(DependencyConfig dc, String appId) { + private Optional findNeededApp(DependencyConfig dc) { if (!dc.isDependency()) { return Optional.absent(); } + if (dc.appDependencyConfig.specificInstanceId != null) { + try { + var appById = this.getAppManagerImpl().findInstaceById(dc.appDependencyConfig.specificInstanceId); + return Optional.of(appById); + } catch (NoSuchElementException e) { + return null; + } + } + var appId = dc.appDependencyConfig.appId; if (dc.sub.createPolicy == DependencyDeclaration.CreatePolicy.ALWAYS) { var neededApps = this.getAppManagerImpl().getInstantiatedApps().stream().filter(t -> t.appId.equals(appId)) .collect(Collectors.toList()); OpenemsAppInstance availableApp = null; for (var neededApp : neededApps) { - if (!this.getAppManagerImpl().getInstantiatedApps().stream() - .filter(t -> t.dependencies != null && !t.dependencies.isEmpty()).anyMatch(t -> t.dependencies - .stream().anyMatch(d -> d.instanceId.equals(neededApp.instanceId)))) { +// if (!this.getAppManagerImpl().getInstantiatedApps().stream() +// .filter(t -> t.dependencies != null && !t.dependencies.isEmpty()).anyMatch(t -> t.dependencies +// .stream().anyMatch(d -> d.instanceId.equals(neededApp.instanceId)))) { + if (this.getAppsWithDependencyTo(neededApp).isEmpty()) { availableApp = neededApp; break; } @@ -600,6 +743,13 @@ private Optional findNeededApp(DependencyConfig dc, String a return null; } + private List getAppsWithDependencyTo(OpenemsAppInstance instance) { + return this.getAppManagerImpl().getInstantiatedApps().stream() + .filter(t -> t.dependencies != null && !t.dependencies.isEmpty()) + .filter(t -> t.dependencies.stream().anyMatch(d -> d.instanceId.equals(instance.instanceId))) + .collect(Collectors.toList()); + } + /** * Recursively iterates over all dependencies and the given app. * @@ -621,25 +771,42 @@ private Optional findNeededApp(DependencyConfig dc, String a * @return s the last {@link DependencyConfig} * @throws OpenemsNamedException on error */ - private DependencyConfig foreachDependency(OpenemsApp app, String alias, JsonObject defaultProperties, + private DependencyConfig foreachDependency(OpenemsApp app, AppDependencyConfig appConfig, ConfigurationTarget target, Function function, DependencyDeclaration sub, - Language l, OpenemsApp parent, Set alreadyIteratedApps) throws OpenemsNamedException { + Language l, OpenemsApp parent, Set alreadyIteratedApps, + Function, AppDependencyConfig> determineDependencyConfig) + throws OpenemsNamedException { if (alreadyIteratedApps == null) { alreadyIteratedApps = new HashSet<>(); } alreadyIteratedApps.add(app); - defaultProperties.addProperty("ALIAS", alias); - var config = app.getAppConfiguration(target, defaultProperties, l); - defaultProperties.remove("ALIAS"); + if (appConfig.alias != null) { + appConfig.properties.addProperty("ALIAS", appConfig.alias); + } + var config = app.getAppConfiguration(target, appConfig.properties, l); + if (appConfig.alias != null) { + appConfig.properties.remove("ALIAS"); + } var dependencies = new LinkedList(); for (var dependency : config.dependencies) { + var nextAppConfig = determineDependencyConfig.apply(dependency.appConfigs); + if (nextAppConfig == null) { + // can not determine one out of many configs + continue; + } try { - var dependencyApp = this.getAppManagerImpl().findAppById(dependency.appId); + OpenemsApp dependencyApp; + if (nextAppConfig.appId != null) { + dependencyApp = this.getAppManagerImpl().findAppById(nextAppConfig.appId); + } else { + var specificApp = this.getAppManagerImpl().findInstaceById(nextAppConfig.specificInstanceId); + dependencyApp = this.getAppManagerImpl().findAppById(specificApp.appId); + } if (alreadyIteratedApps.contains(dependencyApp)) { continue; } - var addingConfig = this.foreachDependency(dependencyApp, dependency.alias, dependency.properties, - target, function, dependency, l, app, alreadyIteratedApps); + var addingConfig = this.foreachDependency(dependencyApp, nextAppConfig, target, function, dependency, l, + app, alreadyIteratedApps, determineDependencyConfig); if (addingConfig != null) { dependencies.add(addingConfig); } @@ -648,17 +815,45 @@ private DependencyConfig foreachDependency(OpenemsApp app, String alias, JsonObj } } - var newConfig = new DependencyConfig(app, parent, sub, config, alias, defaultProperties, dependencies); + var newConfig = new DependencyConfig(app, parent, sub, config, appConfig, dependencies); if (function.apply(newConfig)) { return newConfig; } return null; } + private DependencyDeclaration.AppDependencyConfig determineDependencyConfig(List configs) { + if (configs == null || configs.isEmpty()) { + return null; + } + if (configs.size() == 1) { + return configs.get(0); + } + // TODO multiple dependency configs + for (var config : configs) { + var instances = this.getAppManagerImpl().getInstantiatedApps().stream() + .filter(i -> i.appId.equals(config.appId)).collect(Collectors.toList()); + for (var instance : instances) { + var existingDependencies = this.getAppsWithDependencyTo(instance); + if (existingDependencies.isEmpty()) { + return config; + } + } + } + + return null; + } + private void foreachDependency(OpenemsApp app, String alias, JsonObject defaultProperties, - ConfigurationTarget target, Language l, Function consumer) - throws OpenemsNamedException { - this.foreachDependency(app, alias, defaultProperties, target, consumer, null, l, null, null); + ConfigurationTarget target, Language l, + Function, AppDependencyConfig> determineDependencyConfig, + Function consumer) throws OpenemsNamedException { + var appConfig = DependencyDeclaration.AppDependencyConfig.create() // + .setAppId(app.getAppId()) // + .setAlias(alias) // + .setProperties(defaultProperties) // + .build(); + this.foreachDependency(app, appConfig, target, consumer, null, l, null, null, determineDependencyConfig); } private void foreachExistingDependecy(OpenemsAppInstance instance, ConfigurationTarget target, Language l, @@ -717,8 +912,20 @@ private DependencyConfig foreachExistingDependency(OpenemsAppInstance instance, if (parent != null) { parentApp = this.getAppManagerImpl().findAppById(parent.appId); } - var newConfig = new ExistingDependencyConfig(app, parentApp, sub, config, instance.alias, instance.properties, - dependecies, parent, instance); + + DependencyDeclaration.AppDependencyConfig dependencyAppConfig; + if (sub == null) { + dependencyAppConfig = DependencyDeclaration.AppDependencyConfig.create() // + .setAppId(instance.appId) // + .setProperties(instance.properties) // + .setAlias(instance.alias) // + .build(); + } else { + dependencyAppConfig = sub.appConfigs.stream().filter(c -> c.appId.equals(instance.appId)).findAny().get(); + } + + var newConfig = new ExistingDependencyConfig(app, parentApp, sub, config, dependencyAppConfig, dependecies, + parent, instance); if (consumer.apply(newConfig)) { return newConfig; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java index f4c94caf877..05f7177cd57 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java @@ -77,6 +77,10 @@ public void create(User user, List otherAppConfigurations) thr var isSameConfigWithoutAlias = ComponentUtilImpl.isSameConfigurationWithoutAlias(null, comp, foundComponentWithSameId); + if(isSameConfigWithoutAlias && comp.getAlias() == null) { + // alias == null => no update + continue; + } var isSameConfig = isSameConfigWithoutAlias && comp.getAlias().equals(foundComponentWithSameId.getAlias()); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java index 34d820c56a8..800886edc38 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyConfig.java @@ -2,8 +2,6 @@ import java.util.List; -import com.google.gson.JsonObject; - import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.OpenemsApp; @@ -17,19 +15,17 @@ public class DependencyConfig { public final DependencyDeclaration sub; public final AppConfiguration config; - public final String alias; - public final JsonObject properties; + public final DependencyDeclaration.AppDependencyConfig appDependencyConfig; public final List declarations; public DependencyConfig(OpenemsApp app, OpenemsApp parent, DependencyDeclaration sub, AppConfiguration config, - String alias, JsonObject properties, List declarations) { + DependencyDeclaration.AppDependencyConfig appDependencyConfig, List declarations) { this.app = app; this.parent = parent; this.sub = sub; this.config = config; - this.alias = alias; - this.properties = properties; + this.appDependencyConfig = appDependencyConfig; this.declarations = declarations; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java index 76cedda5e1a..9c3487e4510 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java @@ -1,7 +1,11 @@ package io.openems.edge.core.appmanager.dependency; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.UUID; import java.util.function.BiFunction; +import java.util.function.Consumer; import com.google.common.base.Function; import com.google.gson.JsonObject; @@ -11,9 +15,9 @@ public class DependencyDeclaration { public final String key; - public final String appId; - public final String alias; - public final JsonObject properties; + + // unmodifiableList + public final List appConfigs; public final CreatePolicy createPolicy; public final UpdatePolicy updatePolicy; @@ -22,18 +26,17 @@ public class DependencyDeclaration { public final DependencyUpdatePolicy dependencyUpdatePolicy; public final DependencyDeletePolicy dependencyDeletePolicy; - // Dependency Supplier? - // private final Supplier supplierForAppId = null; - // private final Function, String> - // supplierForInstanceIdFromExisting = null; - - public DependencyDeclaration(String key, String appId, String alias, CreatePolicy createPolicy, - UpdatePolicy updatePolicy, DeletePolicy deletePolicy, DependencyUpdatePolicy dependencyUpdatePolicy, - DependencyDeletePolicy dependencyDeletePolicy, JsonObject properties) { + public DependencyDeclaration(String key, CreatePolicy createPolicy, UpdatePolicy updatePolicy, + DeletePolicy deletePolicy, DependencyUpdatePolicy dependencyUpdatePolicy, + DependencyDeletePolicy dependencyDeletePolicy, AppDependencyConfig... appConfigs) { this.key = key; - this.appId = appId; - this.alias = alias; - this.properties = properties == null ? new JsonObject() : properties; + + if (appConfigs.length == 0) { + throw new IllegalArgumentException("There has to be atleast one 'appConfig'!"); + } + // TODO check for duplicated appIds + this.appConfigs = Collections.unmodifiableList(Arrays.asList(appConfigs)); + this.createPolicy = createPolicy; this.updatePolicy = updatePolicy; this.deletePolicy = deletePolicy; @@ -41,6 +44,71 @@ public DependencyDeclaration(String key, String appId, String alias, CreatePolic this.dependencyDeletePolicy = dependencyDeletePolicy; } + public static class AppDependencyConfig { + + // NOTE: must have either appId or specificInstanceId + public final String appId; + public final UUID specificInstanceId; + public final String alias; + public final JsonObject properties; + + private AppDependencyConfig(String appId, UUID specificInstanceId, String alias, JsonObject properties) { + if (appId == null) { + throw new NullPointerException("'appId' of a AppDependencyConfig can't be null!"); + } + this.appId = appId; + this.specificInstanceId = specificInstanceId; + this.alias = alias; + this.properties = properties == null ? new JsonObject() : properties; + } + + public static Builder create() { + return new Builder(); + } + + public static final class Builder { + private String appId; + private UUID specificInstanceId; + private String alias; + private JsonObject properties; + + public Builder() { + } + + public Builder setAppId(String appId) { + this.appId = appId; + return this; + } + + public Builder setSpecificInstanceId(UUID specificInstanceId) { + this.specificInstanceId = specificInstanceId; + return this; + } + + public Builder setAlias(String alias) { + this.alias = alias; + return this; + } + + public Builder setProperties(JsonObject properties) { + this.properties = properties; + return this; + } + + public Builder onlyIf(boolean expression, Consumer consumer) { + if (expression) { + consumer.accept(this); + } + return this; + } + + public AppDependencyConfig build() { + return new AppDependencyConfig(this.appId, this.specificInstanceId, this.alias, this.properties); + } + } + + } + /** * Defines if the dependency app should get created when creating the parent * app. @@ -87,8 +155,9 @@ public final boolean isAllowedToCreate(List allInstances, St */ public static enum UpdatePolicy { ALWAYS(v -> true), // - IF_MINE(v -> v.allInstances.stream() - .anyMatch(a -> !a.equals(v.parent) && a.dependencies != null + IF_MINE(v -> !v.allInstances.stream() // + .filter(i -> !i.equals(v.parent)) // + .anyMatch(a -> a.dependencies != null && a.dependencies.stream().anyMatch(d -> d.instanceId.equals(v.app2Update.instanceId)))), // NEVER(v -> false), // ; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java new file mode 100644 index 00000000000..9ecb8e2bd51 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java @@ -0,0 +1,60 @@ +package io.openems.edge.core.appmanager.dependency; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppManager; +import io.openems.edge.core.appmanager.AppManagerImpl; + +public class DependencyUtil { + + /** + * Temporary field to avoid endless loop. + */ + private static final Set CURRENTLY_CALLING_APP_IDS = new HashSet(); + + /** + * Gets the instanceId of the first found app that has the given componentId in + * its {@link AppConfiguration}. + * + *

+ * WARN: when calling this inside an app configuration it can lead to an endless + * loop + * + * @param componentManager a componentManager to get the appManager + * @param componentId the component id that the app should have + * @return the found instanceId or null if no app has this component + */ + public static final UUID getInstanceIdOfAppWhichHasComponent(ComponentManager componentManager, String componentId, + String currentlyCallingApp) { + var appManagerImpl = DependencyUtil.getAppManagerImpl(componentManager); + if (appManagerImpl == null) { + return null; + } + CURRENTLY_CALLING_APP_IDS.add(currentlyCallingApp); + for (var entry : appManagerImpl.appConfigs(appManagerImpl.getInstantiatedApps(), + i -> !CURRENTLY_CALLING_APP_IDS.contains(i.appId))) { + if (entry.getValue().components.stream().anyMatch(c -> c.getId().equals(componentId))) { + CURRENTLY_CALLING_APP_IDS.remove(currentlyCallingApp); + return entry.getKey().instanceId; + } + } + CURRENTLY_CALLING_APP_IDS.remove(currentlyCallingApp); + return null; + } + + private static final AppManagerImpl getAppManagerImpl(ComponentManager componentManager) { + var appManager = componentManager.getEnabledComponentsOfType(AppManager.class); + if (appManager.size() != 1) { + return null; + } + if (!(appManager.get(0) instanceof AppManagerImpl)) { + return null; + } + return (AppManagerImpl) appManager.get(0); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java index 2d794507ab3..20964573368 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ExistingDependencyConfig.java @@ -2,8 +2,6 @@ import java.util.List; -import com.google.gson.JsonObject; - import io.openems.edge.core.appmanager.AppConfiguration; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppInstance; @@ -15,9 +13,9 @@ public class ExistingDependencyConfig extends DependencyConfig { public final OpenemsAppInstance instance; public ExistingDependencyConfig(OpenemsApp app, OpenemsApp parentApp, DependencyDeclaration sub, - AppConfiguration config, String alias, JsonObject properties, List declarations, - OpenemsAppInstance parent, OpenemsAppInstance instance) { - super(app, parentApp, sub, config, alias, properties, declarations); + AppConfiguration config, DependencyDeclaration.AppDependencyConfig appDependencyConfig, + List declarations, OpenemsAppInstance parent, OpenemsAppInstance instance) { + super(app, parentApp, sub, config, appDependencyConfig, declarations); this.parentInstance = parent; this.instance = instance; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTaskImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTaskImpl.java index 6e208a1969b..fc048b7dd29 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTaskImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/StaticIpAggregateTaskImpl.java @@ -16,6 +16,8 @@ @Component public class StaticIpAggregateTaskImpl implements AggregateTask, AggregateTask.StaticIpAggregateTask { + private final boolean isWindows = System.getProperty("os.name").startsWith("Windows"); + private final ComponentUtil componentUtil; private List ips; @@ -35,7 +37,7 @@ public void reset() { @Override public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) { - if (System.getProperty("os.name").startsWith("Windows")) { + if (this.isWindows) { return; } if (instance != null) { @@ -52,7 +54,7 @@ public void aggregate(AppConfiguration instance, AppConfiguration oldConfig) { @Override public void create(User user, List otherAppConfigurations) throws OpenemsNamedException { - if (System.getProperty("os.name").startsWith("Windows")) { + if (this.isWindows) { return; } From e6c2c54d71cc8e9442b812f9b75145a164a63dd3 Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:28:21 +0200 Subject: [PATCH 14/30] removed FENECON website url from javadoc --- .../src/io/openems/edge/app/api/ModbusTcpApiReadOnly.java | 3 +-- .../src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java | 3 +-- io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java | 4 +++- .../src/io/openems/edge/app/api/RestJsonApiReadOnly.java | 3 +-- .../src/io/openems/edge/app/api/RestJsonApiReadWrite.java | 3 +-- .../src/io/openems/edge/app/evcs/EvcsCluster.java | 3 +-- .../src/io/openems/edge/app/evcs/HardyBarthEvcs.java | 3 +-- .../src/io/openems/edge/app/evcs/IesKeywattEvcs.java | 3 +-- .../src/io/openems/edge/app/evcs/KebaEvcs.java | 3 +-- .../src/io/openems/edge/app/hardware/KMtronic8Channel.java | 4 +++- .../src/io/openems/edge/app/heat/CombinedHeatAndPower.java | 1 + .../src/io/openems/edge/app/heat/HeatPump.java | 3 +-- .../src/io/openems/edge/app/heat/HeatingElement.java | 3 +-- .../src/io/openems/edge/app/integratedsystem/FeneconHome.java | 4 +++- .../io/openems/edge/app/loadcontrol/ManualRelayControl.java | 3 +-- .../src/io/openems/edge/app/loadcontrol/ThresholdControl.java | 3 +-- .../src/io/openems/edge/app/meter/CarloGavazziMeter.java | 3 +-- .../src/io/openems/edge/app/meter/JanitzaMeter.java | 3 +-- .../src/io/openems/edge/app/meter/SocomecMeter.java | 3 +-- .../src/io/openems/edge/app/pvinverter/KacoPvInverter.java | 3 +-- .../src/io/openems/edge/app/pvinverter/KostalPvInverter.java | 3 +-- .../src/io/openems/edge/app/pvinverter/SmaPvInverter.java | 3 +-- .../io/openems/edge/app/pvinverter/SolarEdgePvInverter.java | 3 +-- .../edge/app/pvselfconsumption/GridOptimizedCharge.java | 3 +-- .../app/pvselfconsumption/SelfConsumptionOptimization.java | 3 +-- .../io/openems/edge/app/timeofusetariff/AwattarHourly.java | 1 + .../openems/edge/app/timeofusetariff/StromdaoCorrently.java | 1 + .../src/io/openems/edge/app/timeofusetariff/Tibber.java | 1 + 28 files changed, 34 insertions(+), 45 deletions(-) diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadOnly.java b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadOnly.java index 472dc88aa09..eeb6ef4b103 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadOnly.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadOnly.java @@ -45,8 +45,7 @@ "CONTROLLER_ID": "ctrlApiModbusTcp0" }, "appDescriptor": { - "websiteUrl": https://docs.fenecon.de/de/_/latest/fems/apis.html#_fems_app_modbustcp_api_lesend + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java index 65e2094db1e..47b94add635 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java @@ -51,8 +51,7 @@ "COMPONENT_IDS": ["_sum", ...] }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-modbus-tcp-schreibzugriff-2/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java b/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java index 9654c3d8776..c5207ed0971 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java @@ -46,7 +46,9 @@ "CLIENT_ID": "edge0", "URI": "tcp://localhost:1883" }, - "appDescriptor": {} + "appDescriptor": { + "websiteUrl": URL + } } * */ diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java index fc012627909..f7feaca51f4 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadOnly.java @@ -44,8 +44,7 @@ "CONTROLLER_ID": "ctrlApiRest0" }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-modbus-tcp-lesend-2/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java index c83651a6f61..da7a8dde510 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java @@ -48,8 +48,7 @@ "API_TIMEOUT": 60 }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-rest-json-schreibzugriff-2/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java index 9ac81a72847..4b3e2dc2024 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java @@ -45,8 +45,7 @@ "EVCS_IDS": [ "evcs0", "evcs1", ...] }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-multiladepunkt-eigenverbrauch-2/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java index a302712bdcb..c7a2ccdb442 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java @@ -45,8 +45,7 @@ "IP":"192.168.25.30" }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-app-echarge-hardy-barth-ladestation/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java index 5a03f34b045..971d60dfec7 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java @@ -47,8 +47,7 @@ "OCCP_CONNECTOR_IDENTIFIER": "1" }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-ies-keywatt-ladestation-2// + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java index 1fb0a83ecb7..5a06296bdd3 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java @@ -44,8 +44,7 @@ "IP":"192.168.25.11" }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-keba-ladestation/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java b/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java index a9bc63b37ec..f702598d86c 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java +++ b/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java @@ -45,7 +45,9 @@ "MODBUS_ID": "modbus10", "IP": "192.168.1.199" }, - "appDescriptor": {} + "appDescriptor": { + "websiteUrl": URL + } } * */ diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java b/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java index 0d919e9b6c5..40aba05be1d 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java @@ -52,6 +52,7 @@ "OUTPUT_CHANNEL": "io0/Relay1" }, "appDescriptor": { + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java index 0b0ba0df472..20c4308d813 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java @@ -50,8 +50,7 @@ "OUTPUT_CHANNEL_2": "io0/Relay3" }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-sg-ready-waermepumpe-2/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java index ff4b816406d..fa3626f54ce 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java @@ -54,8 +54,7 @@ "OUTPUT_CHANNEL_PHASE_L3": "io0/Relay3" }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-heizstab/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java index 39fe3e9c167..40e911fa901 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java @@ -63,7 +63,9 @@ "EMERGENCY_RESERVE_ENABLED":true, "EMERGENCY_RESERVE_SOC":20 }, - "appDescriptor": {} + "appDescriptor": { + "websiteUrl": URL + } } * */ diff --git a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java index 2496bd42086..fb5c2244bcb 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java +++ b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java @@ -50,8 +50,7 @@ "OUTPUT_CHANNEL": "io1/Relay1" }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-manuelle-relaissteuerung/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java index 8e3281813ff..4274440c9fb 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java +++ b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java @@ -51,8 +51,7 @@ "OUTPUT_CHANNELS":['io1/Relay1', 'io1/Relay2'] }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-schwellwert-steuerung/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java index 3220e52d8a3..d84bc413afc 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java @@ -49,8 +49,7 @@ "MODBUS_UNIT_ID": 6 }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-carlo-gavazzi-zaehler-2/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java index 488385933e8..679611531e3 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java @@ -57,8 +57,7 @@ "MODBUS_UNIT_ID": 1 }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-janitza-zaehler-2/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java index 1068271fadc..29878e96846 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java @@ -49,8 +49,7 @@ "MODBUS_UNIT_ID": 6 }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems/fems-app-socomec-zaehler-2 + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KacoPvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KacoPvInverter.java index 0e9c74df2c2..c08112650f8 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KacoPvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KacoPvInverter.java @@ -44,8 +44,7 @@ "PORT": "502" }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-kaco-pv-wechselrichter/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KostalPvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KostalPvInverter.java index 36af7e7c2d7..eb89ce1bf81 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KostalPvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KostalPvInverter.java @@ -45,8 +45,7 @@ "MODBUS_UNIT_ID": "71" }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-kostal-pv-wechselrichter/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java index 8ba0c913b15..1a834c3e6eb 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java @@ -45,8 +45,7 @@ "MODBUS_UNIT_ID": "126" }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-sma-pv-wechselrichter/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SolarEdgePvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SolarEdgePvInverter.java index 445517b5002..46d6664f7fc 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SolarEdgePvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SolarEdgePvInverter.java @@ -44,8 +44,7 @@ "PORT": "502" }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-solaredge-pv-wechselrichter/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java index 806be79139b..242e9ac0d14 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java @@ -50,8 +50,7 @@ "MAXIMUM_SELL_TO_GRID_POWER": 10000 }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-netzdienliche-beladung/ + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java index 8e1f47abebd..8e4a3161c00 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java @@ -47,8 +47,7 @@ "METER_ID": "meter0" }, "appDescriptor": { - "websiteUrl": https://fenecon.de/fems-2-2/fems-app-eigenverbrauchsoptimierung-2// + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java index fb1cefa4078..51b28d70ef0 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java @@ -43,6 +43,7 @@ "TIME_OF_USE_TARIF_ID": "timeOfUseTariff0" }, "appDescriptor": { + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java index e81b9ab5c03..0568cfffb05 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java @@ -46,6 +46,7 @@ "ZIP_CODE": "12345678" }, "appDescriptor": { + "websiteUrl": URL } } * diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java index 1025fa72aeb..9ebe21c916b 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java @@ -46,6 +46,7 @@ "ACCESS_TOKEN": {token} }, "appDescriptor": { + "websiteUrl": URL } } * From 7b0f17e42a2ae7cf306c42b2166ce739356f5e45 Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:28:55 +0200 Subject: [PATCH 15/30] improved validation --- .../core/appmanager/AbstractOpenemsApp.java | 66 +++++++++++++++---- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java index 3436cff2d26..925364b6d86 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java @@ -33,6 +33,7 @@ import io.openems.edge.core.appmanager.validator.CheckCardinality; import io.openems.edge.core.appmanager.validator.Checkable; import io.openems.edge.core.appmanager.validator.Validator; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; import io.openems.edge.core.host.NetworkInterface.Inet4AddressWithNetmask; public abstract class AbstractOpenemsApp> implements OpenemsApp { @@ -474,13 +475,34 @@ private void validateDependecies(List errors, List configDep // check if exactly one app is available of the needed appId for (var dependency : notRegisteredDependencies) { - var list = appManager.getInstantiatedApps().stream().filter(t -> t.appId.equals(dependency.appId)) - .collect(Collectors.toList()); - if (list.size() != 1) { - errors.add("Missing dependency with Key[" + dependency.key + "] needed App[" + dependency.appId + "]"); - } else { - checkProperties(errors, list.get(0).properties, neededDependencies, dependency.key); + List minErrors = null; + for (var appConfig : dependency.appConfigs) { + var appConfigErrors = new LinkedList(); + if (appConfig.specificInstanceId != null) { + try { + var instance = appManager.findInstaceById(appConfig.specificInstanceId); + checkProperties(errors, instance.properties, appConfig, dependency.key); + } catch (NoSuchElementException e) { + appConfigErrors.add("Specific InstanceId[" + appConfig.specificInstanceId + "] not found!"); + } + } else { + + var list = appManager.getInstantiatedApps().stream().filter(t -> t.appId.equals(appConfig.appId)) + .collect(Collectors.toList()); + if (list.size() != 1) { + errors.add("Missing dependency with Key[" + dependency.key + "] needed App[" + appConfig.appId + + "]"); + } else { + checkProperties(errors, list.get(0).properties, appConfig, dependency.key); + } + } + + if (minErrors == null || minErrors.size() > appConfigErrors.size()) { + minErrors = appConfigErrors; + } } + + errors.addAll(minErrors); } if (configDependencies == null) { @@ -490,8 +512,30 @@ private void validateDependecies(List errors, List configDep for (var dependency : configDependencies) { try { var appInstance = appManager.findInstaceById(dependency.instanceId); + var dd = neededDependencies.stream().filter(d -> d.key.equals(dependency.key)).findAny(); + if (dd.isEmpty()) { + errors.add("Can not get DependencyDeclaration of Dependency[" + dependency.key + "]"); + continue; + } + + // get app config + var appConfig = dd.get().appConfigs.stream() // + .filter(c -> c.specificInstanceId != null) // + .filter(c -> c.specificInstanceId.equals(appInstance.instanceId)).findAny(); + + if (appConfig.isEmpty()) { + appConfig = dd.get().appConfigs.stream() // + .filter(c -> c.appId != null) // + .filter(c -> c.appId.equals(appInstance.appId)).findAny(); + + if (appConfig.isEmpty()) { + errors.add("Can not get DependencyAppConfig of Dependency[" + dependency.key + "]"); + continue; + } + } + // when available check properties - checkProperties(errors, appInstance.properties, neededDependencies, dependency.key); + checkProperties(errors, appInstance.properties, appConfig.get(), dependency.key); } catch (NoSuchElementException e) { errors.add("App with instance[" + dependency.instanceId + "] not available!"); } @@ -499,13 +543,13 @@ private void validateDependecies(List errors, List configDep } private static final void checkProperties(List errors, JsonObject actualAppProperties, - List neededDependencies, String dependecyKey) { - var subApp = neededDependencies.stream().filter(t -> t.key.equals(dependecyKey)).findFirst().orElse(null); - if (subApp == null) { + DependencyDeclaration.AppDependencyConfig appDependencyConfig, String dependecyKey) { + if (appDependencyConfig == null) { errors.add("SubApp with Key[" + dependecyKey + "] not found!"); return; } - for (var property : subApp.properties.entrySet()) { + + for (var property : appDependencyConfig.properties.entrySet()) { var actualValue = actualAppProperties.get(property.getKey()); if (actualValue == null) { errors.add("Value for Key[" + property.getKey() + "] not found!"); From b69919205256ac493f471492c491d22c927351fd Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:29:27 +0200 Subject: [PATCH 16/30] added warnings for UI --- .../core/appmanager/AppConfiguration.java | 6 +++- .../core/appmanager/OpenemsAppInstance.java | 2 +- .../appmanager/jsonrpc/AddAppInstance.java | 15 +++++++--- .../appmanager/jsonrpc/DeleteAppInstance.java | 22 ++++++++++++++ .../edge/core/appmanager/jsonrpc/GetApp.java | 10 ++++--- .../appmanager/jsonrpc/GetAppInstances.java | 6 +--- .../edge/core/appmanager/jsonrpc/GetApps.java | 9 +++--- .../appmanager/jsonrpc/UpdateAppInstance.java | 29 ++++++++++++++++++- .../edge/settings/app/install.component.ts | 14 ++++++++- .../settings/app/jsonrpc/addAppInstance.ts | 5 +++- .../settings/app/jsonrpc/updateAppInstance.ts | 18 +++++++++++- .../app/edge/settings/app/update.component.ts | 15 +++++++--- 12 files changed, 124 insertions(+), 27 deletions(-) diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppConfiguration.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppConfiguration.java index 204520a8221..1274e394c56 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppConfiguration.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppConfiguration.java @@ -16,6 +16,10 @@ public class AppConfiguration { public final List dependencies; + public AppConfiguration() { + this(null); + } + public AppConfiguration(List components) { this(components, null); } @@ -30,7 +34,7 @@ public AppConfiguration(List components, List schedulerExecut public AppConfiguration(List components, List schedulerExecutionOrder, List ips, List dependencies) { - this.components = components; + this.components = components != null ? components : new ArrayList<>(); this.schedulerExecutionOrder = schedulerExecutionOrder != null ? schedulerExecutionOrder : new ArrayList<>(); this.ips = ips != null ? ips : new ArrayList<>(); this.dependencies = dependencies != null ? dependencies : new ArrayList<>(); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java index d369a9dac01..92905193689 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java @@ -55,7 +55,7 @@ public int hashCode() { public JsonObject toJsonObject() { return JsonUtils.buildJsonObject() // .addProperty("appId", this.appId) // - .addProperty("alias", this.alias) // + .addProperty("alias", this.alias != null ? this.alias : "") // .addProperty("instanceId", this.instanceId.toString()) // .add("properties", this.properties) // TODO define if the field is editable .onlyIf(this.dependencies != null && !this.dependencies.isEmpty(), j -> j.add("dependencies", // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/AddAppInstance.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/AddAppInstance.java index cd582efd6fc..7cc1718cc3b 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/AddAppInstance.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/AddAppInstance.java @@ -1,8 +1,11 @@ package io.openems.edge.core.appmanager.jsonrpc; +import java.util.List; import java.util.UUID; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.jsonrpc.base.JsonrpcRequest; @@ -92,17 +95,21 @@ public JsonObject getParams() { public static class Response extends JsonrpcResponseSuccess { - private final UUID instanceId; + private final OpenemsAppInstance instance; + private final JsonArray warnings; - public Response(UUID id, UUID instanceId) { + public Response(UUID id, OpenemsAppInstance instance, List warnings) { super(id); - this.instanceId = instanceId; + this.instance = instance; + this.warnings = warnings == null ? new JsonArray() + : warnings.stream().map(t -> new JsonPrimitive(t)).collect(JsonUtils.toJsonArray()); } @Override public JsonObject getResult() { return JsonUtils.buildJsonObject() // - .addProperty("instanceId", this.instanceId.toString()) // + .add("instance", instance.toJsonObject()) // + .add("warnings", warnings) // .build(); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/DeleteAppInstance.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/DeleteAppInstance.java index 1c26278d423..b9e9c9d596d 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/DeleteAppInstance.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/DeleteAppInstance.java @@ -1,11 +1,15 @@ package io.openems.edge.core.appmanager.jsonrpc; +import java.util.List; import java.util.UUID; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; import io.openems.common.utils.JsonUtils; import io.openems.edge.core.appmanager.OpenemsAppInstance; @@ -76,4 +80,22 @@ public JsonObject getParams() { } } + public static class Response extends JsonrpcResponseSuccess { + + private final JsonArray warnings; + + public Response(UUID id, List warnings) { + super(id); + this.warnings = warnings == null ? new JsonArray() + : warnings.stream().map(t -> new JsonPrimitive(t)).collect(JsonUtils.toJsonArray()); + } + + @Override + public JsonObject getResult() { + return JsonUtils.buildJsonObject() // + .add("warnings", warnings) // + .build(); + } + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java index 1d7ae4b0cc1..c41284bd3c1 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java @@ -12,6 +12,7 @@ import io.openems.common.utils.JsonUtils; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppInstance; +import io.openems.edge.core.appmanager.validator.Validator; /** * Gets the available {@link OpenemsApp}. @@ -97,7 +98,7 @@ public JsonObject getParams() { public static class Response extends JsonrpcResponseSuccess { private static JsonObject createAppObject(OpenemsApp app, List instantiatedApps, - Language language) { + Language language, Validator validator) { var instanceIds = JsonUtils.buildJsonArray(); for (var instantiatedApp : instantiatedApps) { @@ -113,16 +114,17 @@ private static JsonObject createAppObject(OpenemsApp app, List instantiatedApps, Language language) { + public Response(UUID id, OpenemsApp app, List instantiatedApps, Language language, + Validator validator) { super(id); - this.app = createAppObject(app, instantiatedApps, language); + this.app = createAppObject(app, instantiatedApps, language, validator); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppInstances.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppInstances.java index 1595c3489b4..25e64b0cd48 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppInstances.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppInstances.java @@ -89,11 +89,7 @@ public static class Response extends JsonrpcResponseSuccess { public Response(UUID id, List instances) { super(id); - var result = JsonUtils.buildJsonArray(); // - for (var instance : instances) { - result.add(instance.toJsonObject()); - } - this.instances = result.build(); + this.instances = instances.stream().map(i -> i.toJsonObject()).collect(JsonUtils.toJsonArray()); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java index bd2ca085db4..fbe0f32a0cc 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java @@ -14,6 +14,7 @@ import io.openems.common.utils.JsonUtils; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppInstance; +import io.openems.edge.core.appmanager.validator.Validator; /** * Gets the available {@link OpenemsApp}s. @@ -93,7 +94,7 @@ public JsonObject getParams() { public static class Response extends JsonrpcResponseSuccess { private static JsonArray createAppsArray(List availableApps, - List instantiatedApps, Language language) { + List instantiatedApps, Language language, Validator validator) { var result = JsonUtils.buildJsonArray(); for (var app : availableApps) { // TODO don't show integrated systems for normal users @@ -117,7 +118,7 @@ private static JsonArray createAppsArray(List availableApps, .addProperty("appId", app.getAppId()) // .addProperty("name", app.getName(language)) // .addProperty("image", app.getImage()) // - .add("status", app.getValidator().toJsonObject()) // + .add("status", validator.toJsonObject(app.getValidatorConfig())) // .add("instanceIds", instanceIds.build()) // .build()); } @@ -127,9 +128,9 @@ private static JsonArray createAppsArray(List availableApps, private final JsonArray apps; public Response(UUID id, List availableApps, List instantiatedApps, - Language language) { + Language language, Validator validator) { super(id); - this.apps = createAppsArray(availableApps, instantiatedApps, language); + this.apps = createAppsArray(availableApps, instantiatedApps, language, validator); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppInstance.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppInstance.java index a63e7522783..bf6ba2df25c 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppInstance.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppInstance.java @@ -1,11 +1,15 @@ package io.openems.edge.core.appmanager.jsonrpc; +import java.util.List; import java.util.UUID; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; import io.openems.common.utils.JsonUtils; import io.openems.edge.core.appmanager.OpenemsAppInstance; @@ -70,9 +74,10 @@ private Request(JsonrpcRequest request, UUID instanceId, String alias, JsonObjec this.properties = properties; } - public Request(UUID instanceId, JsonObject properties) { + public Request(UUID instanceId, String alias, JsonObject properties) { super(METHOD); this.instanceId = instanceId; + this.alias = alias; this.properties = properties; } @@ -80,9 +85,31 @@ public Request(UUID instanceId, JsonObject properties) { public JsonObject getParams() { return JsonUtils.buildJsonObject() // .addProperty("instanceId", this.instanceId.toString()) // + .addProperty("alias", this.alias) // .add("properties", this.properties) // .build(); } } + public static class Response extends JsonrpcResponseSuccess { + + private final OpenemsAppInstance instance; + private final JsonArray warnings; + + public Response(UUID id, OpenemsAppInstance instance, List warnings) { + super(id); + this.instance = instance; + this.warnings = warnings == null ? new JsonArray() + : warnings.stream().map(t -> new JsonPrimitive(t)).collect(JsonUtils.toJsonArray()); + } + + @Override + public JsonObject getResult() { + return JsonUtils.buildJsonObject() // + .add("instance", instance.toJsonObject()) // + .add("warnings", warnings) // + .build(); + } + } + } diff --git a/ui/src/app/edge/settings/app/install.component.ts b/ui/src/app/edge/settings/app/install.component.ts index 80a29d9b4ca..346d39de3cf 100644 --- a/ui/src/app/edge/settings/app/install.component.ts +++ b/ui/src/app/edge/settings/app/install.component.ts @@ -83,8 +83,20 @@ export class InstallAppComponent implements OnInit { properties: clonedFields }) })).then(response => { + let result = (response as AddAppInstance.Response).result + + if (result.instance) { + result.instanceId = result.instance.instanceId + this.model = result.instance.properties + } + if (result.warnings && result.warnings.length > 0) { + this.service.toast(result.warnings.join(";"), 'warning') + } else { + this.service.toast("Successfully installed App", 'success'); + } + + this.form.markAsPristine(); - this.service.toast("Successfully installed App", 'success'); }).catch(reason => { this.service.toast("Error installing App:" + reason.error.message, 'danger'); }).finally(() => { diff --git a/ui/src/app/edge/settings/app/jsonrpc/addAppInstance.ts b/ui/src/app/edge/settings/app/jsonrpc/addAppInstance.ts index e272cedda5e..74cef3fd4f6 100644 --- a/ui/src/app/edge/settings/app/jsonrpc/addAppInstance.ts +++ b/ui/src/app/edge/settings/app/jsonrpc/addAppInstance.ts @@ -1,4 +1,5 @@ import { JsonrpcRequest, JsonrpcResponseSuccess } from "../../../../shared/jsonrpc/base"; +import { GetAppInstances } from "./getAppInstances"; /** * Adds an OpenemsAppInstance. @@ -54,7 +55,9 @@ export namespace AddAppInstance { public constructor( public readonly id: string, public readonly result: { - instanceId: string + instanceId: string, + instance: GetAppInstances.AppInstance, + warnings: String[] } ) { super(id, result); diff --git a/ui/src/app/edge/settings/app/jsonrpc/updateAppInstance.ts b/ui/src/app/edge/settings/app/jsonrpc/updateAppInstance.ts index 7fc81036238..9c45d1a0863 100644 --- a/ui/src/app/edge/settings/app/jsonrpc/updateAppInstance.ts +++ b/ui/src/app/edge/settings/app/jsonrpc/updateAppInstance.ts @@ -1,4 +1,6 @@ -import { JsonrpcRequest } from "../../../../shared/jsonrpc/base"; +import { JsonrpcRequest, JsonrpcResponseSuccess } from "../../../../shared/jsonrpc/base"; +import { GetAppInstances } from "./getAppInstances"; +import { GetApps } from "./getApps"; /** * Updates an instance of an {@link OpenemsApp}. @@ -47,4 +49,18 @@ export namespace UpdateAppInstance { } } + + export class Response extends JsonrpcResponseSuccess { + + public constructor( + public readonly id: string, + public readonly result: { + instance: GetAppInstances.AppInstance, + warnings: String[] + } + ) { + super(id, result); + } + } + } \ No newline at end of file diff --git a/ui/src/app/edge/settings/app/update.component.ts b/ui/src/app/edge/settings/app/update.component.ts index afc77070c1d..d6da6d1e125 100644 --- a/ui/src/app/edge/settings/app/update.component.ts +++ b/ui/src/app/edge/settings/app/update.component.ts @@ -114,7 +114,15 @@ export class UpdateAppComponent implements OnInit { properties: clonedFields }) })).then(response => { - this.service.toast("Successfully updated App", 'success'); + var res = (response as UpdateAppInstance.Response); + + if (res.result.warnings && res.result.warnings.length > 0) { + this.service.toast(res.result.warnings.join(";"), 'warning'); + } else { + this.service.toast("Successfully updated App", 'success'); + } + instance.properties = res.result.instance.properties + instance.properties["ALIAS"] = res.result.instance.alias instance.isUpdateting = false }).catch(reason => { this.service.toast("Error updating App:" + reason.error.message, 'danger'); @@ -131,11 +139,10 @@ export class UpdateAppComponent implements OnInit { instanceId: instance.instanceId }) })).then(response => { + this.instances.splice(this.instances.indexOf(instance), 1) this.service.toast("Successfully deleted App", 'success'); }).catch(reason => { this.service.toast("Error deleting App:" + reason.error.message, 'danger'); - }).finally(() => { - this.instances.splice(this.instances.indexOf(instance), 1) - }); + }) } } \ No newline at end of file From 376b239545c710061c184ad74ff02da54cd26aa4 Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:30:30 +0200 Subject: [PATCH 17/30] format & checkstyle --- .../GridOptimizedCharge.java | 3 - .../SelfConsumptionOptimization.java | 4 +- .../core/appmanager/AbstractOpenemsApp.java | 11 +--- .../edge/core/appmanager/AppManagerImpl.java | 36 +++++++----- .../edge/core/appmanager/JsonFormlyUtil.java | 2 +- .../ComponentAggregateTaskImpl.java | 2 +- .../dependency/DependencyDeclaration.java | 22 +++---- .../appmanager/dependency/DependencyUtil.java | 13 +++-- .../appmanager/jsonrpc/AddAppInstance.java | 9 +-- .../appmanager/jsonrpc/DeleteAppInstance.java | 8 ++- .../edge/core/appmanager/jsonrpc/GetApp.java | 2 +- .../appmanager/jsonrpc/GetAppInstances.java | 2 +- .../edge/core/appmanager/jsonrpc/GetApps.java | 2 +- .../appmanager/jsonrpc/UpdateAppInstance.java | 11 ++-- .../validator/AbstractCheckable.java | 6 +- .../core/appmanager/validator/Checkable.java | 8 ++- .../core/appmanager/validator/Validator.java | 58 +++++++------------ .../appmanager/validator/ValidatorConfig.java | 27 --------- .../appmanager/validator/ValidatorImpl.java | 5 +- 19 files changed, 99 insertions(+), 132 deletions(-) diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java index 242e9ac0d14..4b7d75a73ad 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java @@ -2,7 +2,6 @@ import java.util.EnumMap; import java.util.List; -import java.util.TreeMap; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -32,8 +31,6 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; -import io.openems.edge.core.appmanager.validator.CheckHome; -import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** * Describes a App for a Grid Optimized Charge. diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java index 8e4a3161c00..547830e8b9b 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java @@ -101,14 +101,14 @@ public AppAssistant getAppAssistant(Language language) { .setDescription(bundle.getString(this.getAppId() + ".ess.description")) // .isRequired(true) // .setOptions(this.componentManager.getEnabledComponentsOfType(ManagedSymmetricEss.class), - c -> c.id() + ": " + c.alias(), c -> c.id()) // + c -> c.id() + ": " + c.alias(), ManagedSymmetricEss::id) // .build()) .add(JsonFormlyUtil.buildSelect(Property.METER_ID)// .setLabel(bundle.getString(this.getAppId() + ".meter.label")) // .setDescription(bundle.getString(this.getAppId() + ".meter.description")) // .isRequired(true) // .setOptions(this.componentManager.getEnabledComponentsOfType(SymmetricMeter.class), - c -> c.id() + ": " + c.alias(), c -> c.id()) // + c -> c.id() + ": " + c.alias(), SymmetricMeter::id) // .build()) .build()) .build(); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java index 925364b6d86..b0f660236e1 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java @@ -32,7 +32,6 @@ import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; import io.openems.edge.core.appmanager.validator.CheckCardinality; import io.openems.edge.core.appmanager.validator.Checkable; -import io.openems.edge.core.appmanager.validator.Validator; import io.openems.edge.core.appmanager.validator.ValidatorConfig; import io.openems.edge.core.host.NetworkInterface.Inet4AddressWithNetmask; @@ -268,12 +267,6 @@ public final ValidatorConfig getValidatorConfig() { validator.getInstallableCheckableConfigs() .add(new ValidatorConfig.CheckableConfig(CheckCardinality.COMPONENT_NAME, properties)); - if (this.installationValidation() != null) { - validator.setConfigurationValidation((t, u) -> { - var p = this.convertToEnumMap(new ArrayList<>(), u); - return this.installationValidation().apply(t, p); - }); - } return validator; } @@ -480,7 +473,7 @@ private void validateDependecies(List errors, List configDep var appConfigErrors = new LinkedList(); if (appConfig.specificInstanceId != null) { try { - var instance = appManager.findInstaceById(appConfig.specificInstanceId); + var instance = appManager.findInstanceById(appConfig.specificInstanceId); checkProperties(errors, instance.properties, appConfig, dependency.key); } catch (NoSuchElementException e) { appConfigErrors.add("Specific InstanceId[" + appConfig.specificInstanceId + "] not found!"); @@ -511,7 +504,7 @@ private void validateDependecies(List errors, List configDep // check if dependency apps are available for (var dependency : configDependencies) { try { - var appInstance = appManager.findInstaceById(dependency.instanceId); + var appInstance = appManager.findInstanceById(dependency.instanceId); var dd = neededDependencies.stream().filter(d -> d.key.equals(dependency.key)).findAny(); if (dd.isEmpty()) { errors.add("Can not get DependencyDeclaration of Dependency[" + dependency.key + "]"); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java index c81d761d388..c67ba442517 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java @@ -195,11 +195,21 @@ public void configurationEvent(ConfigurationEvent event) { this.worker.configurationEvent(event); } + /** + * Gets a filter for excluding instances. + * + * @param excludingInstanceIds the instances that should be excluded + * @return the filter + */ + public static Predicate exludingInstanceIds(UUID... excludingInstanceIds) { + return i -> !Arrays.stream(excludingInstanceIds).anyMatch(id -> id.equals(i.instanceId)); + } + /** * Gets an {@link Iterable} that loops thru every existing app instance and its * configuration. * - * @param excludingInstanceIds the instance ids that that should be ignored + * @param filter the filter that gets applied to the instances * @return the {@link Iterable} */ public Iterable> appConfigs( @@ -207,16 +217,12 @@ public Iterable> appConfigs( return this.appConfigs(this.instantiatedApps, filter); } - public static Predicate exludingInstanceIds(UUID... excludingInstanceIds) { - return i -> !Arrays.stream(excludingInstanceIds).anyMatch(id -> id.equals(i.instanceId)); - } - /** * Gets an {@link Iterable} that loops thru every instance and its * configuration. * - * @param instances the instances - * @param excludingInstanceIds the instance ids that that should be ignored + * @param instances the instances + * @param filter the filter that gets applied to the instances * @return the {@link Iterable} */ public Iterable> appConfigs(List instances, @@ -233,8 +239,8 @@ public Iterator> iterator() { * Gets an {@link Iterator} that loops thru every instance and its * configuration. * - * @param instances the instances - * @param excludingInstanceIds the instance ids that that should be ignored + * @param instances the instances + * @param filter the filter that gets applied to the instances * @return the {@link Iterator} */ private Iterator> appConfigIterator(List instances, @@ -315,7 +321,7 @@ public final OpenemsApp findAppById(String id) throws NoSuchElementException { * @return s the instance * @throws NoSuchElementException if no instance is present */ - public final OpenemsAppInstance findInstaceById(UUID uuid) throws NoSuchElementException { + public final OpenemsAppInstance findInstanceById(UUID uuid) throws NoSuchElementException { return this.instantiatedApps.stream() // .filter(t -> t.instanceId.equals(uuid)) // .findFirst() // @@ -383,7 +389,7 @@ public CompletableFuture handleDeleteAppInstan final OpenemsAppInstance instance; try { - instance = this.findInstaceById(request.instanceId); + instance = this.findInstanceById(request.instanceId); } catch (NoSuchElementException e) { return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.id)); } @@ -471,7 +477,7 @@ private CompletableFuture handleGetAppRequest(User user, var instances = this.instantiatedApps.stream().filter(t -> t.appId.equals(request.appId)) .collect(Collectors.toList()); return CompletableFuture - .completedFuture(new GetApp.Response(request.id, app, instances, user.getLanguage(), validator)); + .completedFuture(new GetApp.Response(request.id, app, instances, user.getLanguage(), this.validator)); } /** @@ -485,7 +491,7 @@ private CompletableFuture handleGetAppRequest(User user, private CompletableFuture handleGetAppsRequest(User user, GetApps.Request request) throws OpenemsNamedException { return CompletableFuture.completedFuture(new GetApps.Response(request.id, this.availableApps, - this.instantiatedApps, user.getLanguage(), validator)); + this.instantiatedApps, user.getLanguage(), this.validator)); } @Override @@ -540,7 +546,7 @@ private CompletableFuture handleUpdateAppInstanceRequest OpenemsApp app = null; try { - oldApp = this.findInstaceById(request.instanceId); + oldApp = this.findInstanceById(request.instanceId); app = this.findAppById(oldApp.appId); } catch (NoSuchElementException e) { throw new OpenemsException("App-Instance-ID [" + request.instanceId + "] is unknown."); @@ -559,7 +565,7 @@ private CompletableFuture handleUpdateAppInstanceRequest throw new OpenemsException("Unable to update App-Manager configuration for ID [" + request.instanceId + "]: " + e.getMessage()); } - var newInstance = this.findInstaceById(request.instanceId); + var newInstance = this.findInstanceById(request.instanceId); return CompletableFuture .completedFuture(new UpdateAppInstance.Response(request.id, newInstance, result.warnings)); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java index b4d6087f1af..56d76619ad4 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/JsonFormlyUtil.java @@ -673,7 +673,7 @@ protected String getType() { @Override public JsonObject build() { - if (fieldArray != null) { + if (this.fieldArray != null) { this.jsonObject.add("fieldArray", this.fieldArray); } return super.build(); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java index 05f7177cd57..773911f56ea 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/ComponentAggregateTaskImpl.java @@ -77,7 +77,7 @@ public void create(User user, List otherAppConfigurations) thr var isSameConfigWithoutAlias = ComponentUtilImpl.isSameConfigurationWithoutAlias(null, comp, foundComponentWithSameId); - if(isSameConfigWithoutAlias && comp.getAlias() == null) { + if (isSameConfigWithoutAlias && comp.getAlias() == null) { // alias == null => no update continue; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java index 9c3487e4510..c2588d970e0 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyDeclaration.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.UUID; import java.util.function.BiFunction; -import java.util.function.Consumer; import com.google.common.base.Function; import com.google.gson.JsonObject; @@ -53,8 +52,9 @@ public static class AppDependencyConfig { public final JsonObject properties; private AppDependencyConfig(String appId, UUID specificInstanceId, String alias, JsonObject properties) { - if (appId == null) { - throw new NullPointerException("'appId' of a AppDependencyConfig can't be null!"); + if (appId == null && specificInstanceId == null) { + throw new NullPointerException( + "'appId' and 'specificInstanceId' of a AppDependencyConfig can't be both null!"); } this.appId = appId; this.specificInstanceId = specificInstanceId; @@ -62,6 +62,11 @@ private AppDependencyConfig(String appId, UUID specificInstanceId, String alias, this.properties = properties == null ? new JsonObject() : properties; } + /** + * Gets a {@link Builder} for an {@link AppDependencyConfig}. + * + * @return the builder + */ public static Builder create() { return new Builder(); } @@ -95,13 +100,6 @@ public Builder setProperties(JsonObject properties) { return this; } - public Builder onlyIf(boolean expression, Consumer consumer) { - if (expression) { - consumer.accept(this); - } - return this; - } - public AppDependencyConfig build() { return new AppDependencyConfig(this.appId, this.specificInstanceId, this.alias, this.properties); } @@ -226,7 +224,9 @@ public AllowedToValues(List allInstances, OpenemsAppInstance } } - // TODO + /** + * Defines if the user can change properties of the dependency app. + */ public static enum DependencyUpdatePolicy { ALLOW_ALL, // ALLOW_ONLY_UNCONFIGURED_PROPERTIES, // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java index 9ecb8e2bd51..ec84844fc1f 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java @@ -14,18 +14,19 @@ public class DependencyUtil { /** * Temporary field to avoid endless loop. */ - private static final Set CURRENTLY_CALLING_APP_IDS = new HashSet(); + private static final Set CURRENTLY_CALLING_APP_IDS = new HashSet<>(); /** * Gets the instanceId of the first found app that has the given componentId in * its {@link AppConfiguration}. - * + * *

- * WARN: when calling this inside an app configuration it can lead to an endless + * WARNING: when calling this inside an app configuration it can lead to an endless * loop - * - * @param componentManager a componentManager to get the appManager - * @param componentId the component id that the app should have + * + * @param componentManager a componentManager to get the appManager + * @param componentId the component id that the app should have + * @param currentlyCallingApp the app that is currently calling this methode * @return the found instanceId or null if no app has this component */ public static final UUID getInstanceIdOfAppWhichHasComponent(ComponentManager componentManager, String componentId, diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/AddAppInstance.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/AddAppInstance.java index 7cc1718cc3b..e84b46efc74 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/AddAppInstance.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/AddAppInstance.java @@ -39,7 +39,8 @@ * "jsonrpc": "2.0", * "id": "UUID", * "result": { - * "instanceId": string (uuid) + * "instance": {@link OpenemsAppInstance#toJsonObject()} + * "warnings": string[] * } * } * @@ -102,14 +103,14 @@ public Response(UUID id, OpenemsAppInstance instance, List warnings) { super(id); this.instance = instance; this.warnings = warnings == null ? new JsonArray() - : warnings.stream().map(t -> new JsonPrimitive(t)).collect(JsonUtils.toJsonArray()); + : warnings.stream().map(JsonPrimitive::new).collect(JsonUtils.toJsonArray()); } @Override public JsonObject getResult() { return JsonUtils.buildJsonObject() // - .add("instance", instance.toJsonObject()) // - .add("warnings", warnings) // + .add("instance", this.instance.toJsonObject()) // + .add("warnings", this.warnings) // .build(); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/DeleteAppInstance.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/DeleteAppInstance.java index b9e9c9d596d..3806cd6339a 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/DeleteAppInstance.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/DeleteAppInstance.java @@ -37,7 +37,9 @@ * { * "jsonrpc": "2.0", * "id": "UUID", - * "result": {} + * "result": { + * "warnings": string[] + * } * } * */ @@ -87,13 +89,13 @@ public static class Response extends JsonrpcResponseSuccess { public Response(UUID id, List warnings) { super(id); this.warnings = warnings == null ? new JsonArray() - : warnings.stream().map(t -> new JsonPrimitive(t)).collect(JsonUtils.toJsonArray()); + : warnings.stream().map(JsonPrimitive::new).collect(JsonUtils.toJsonArray()); } @Override public JsonObject getResult() { return JsonUtils.buildJsonObject() // - .add("warnings", warnings) // + .add("warnings", this.warnings) // .build(); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java index c41284bd3c1..0f40a78ed63 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java @@ -114,7 +114,7 @@ private static JsonObject createAppObject(OpenemsApp app, List instances) { super(id); - this.instances = instances.stream().map(i -> i.toJsonObject()).collect(JsonUtils.toJsonArray()); + this.instances = instances.stream().map(OpenemsAppInstance::toJsonObject).collect(JsonUtils.toJsonArray()); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java index fbe0f32a0cc..b18b418c3d2 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java @@ -118,7 +118,7 @@ private static JsonArray createAppsArray(List availableApps, .addProperty("appId", app.getAppId()) // .addProperty("name", app.getName(language)) // .addProperty("image", app.getImage()) // - .add("status", validator.toJsonObject(app.getValidatorConfig())) // + .add("status", validator.toJsonObject(app.getValidatorConfig(), language)) // .add("instanceIds", instanceIds.build()) // .build()); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppInstance.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppInstance.java index bf6ba2df25c..8b0c531dca9 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppInstance.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppInstance.java @@ -38,7 +38,10 @@ * { * "jsonrpc": "2.0", * "id": "UUID", - * "result": {} + * "result": { + * "instance": {@link OpenemsAppInstance#toJsonObject()} + * "warnings": string[] + * } * } * */ @@ -100,14 +103,14 @@ public Response(UUID id, OpenemsAppInstance instance, List warnings) { super(id); this.instance = instance; this.warnings = warnings == null ? new JsonArray() - : warnings.stream().map(t -> new JsonPrimitive(t)).collect(JsonUtils.toJsonArray()); + : warnings.stream().map(JsonPrimitive::new).collect(JsonUtils.toJsonArray()); } @Override public JsonObject getResult() { return JsonUtils.buildJsonObject() // - .add("instance", instance.toJsonObject()) // - .add("warnings", warnings) // + .add("instance", this.instance.toJsonObject()) // + .add("warnings", this.warnings) // .build(); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java index 1295ff2d640..2d58f9b5ef5 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java @@ -4,13 +4,13 @@ import org.osgi.service.component.ComponentContext; public abstract class AbstractCheckable implements Checkable { - + private final ComponentContext componentContext; - + public AbstractCheckable(ComponentContext componentContext) { this.componentContext = componentContext; } - + @Override public String getComponentName() { return this.componentContext.getProperties().get(ComponentConstants.COMPONENT_NAME).toString(); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkable.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkable.java index d1b407601e1..883dde7d8c7 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkable.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkable.java @@ -5,7 +5,12 @@ import io.openems.common.session.Language; public interface Checkable { - + + /** + * Gets the Component Name of the {@link Checkable}. + * + * @return the component name + */ public String getComponentName(); /** @@ -18,6 +23,7 @@ public interface Checkable { /** * Gets the error message if the check was incorrect completed. * + * @param language the language of the message * @return the message */ public String getErrorMessage(Language language); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java index 8591f827e97..9161fede5e3 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java @@ -1,15 +1,12 @@ package io.openems.edge.core.appmanager.validator; import java.util.List; -import java.util.stream.Collectors; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; -import io.openems.common.exceptions.OpenemsException; -import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.session.Language; import io.openems.common.utils.JsonUtils; -import io.openems.edge.core.appmanager.ConfigurationTarget; import io.openems.edge.core.appmanager.validator.ValidatorConfig.CheckableConfig; public interface Validator { @@ -17,71 +14,56 @@ public interface Validator { /** * Gets the error messages for compatibility. * + * @param config the config that gets validated + * @param language the language of the errors * @return the error messages */ - public default List getErrorCompatibleMessages(ValidatorConfig config) { - return getErrorMessages(config.getCompatibleCheckableConfigs(), false); + public default List getErrorCompatibleMessages(ValidatorConfig config, Language language) { + return this.getErrorMessages(config.getCompatibleCheckableConfigs(), language, false); } /** * Gets the error messages for installation. * + * @param config the config that gets validated + * @param language the language of the errors * @return the error messages */ - public default List getErrorInstallableMessages(ValidatorConfig config) { - return getErrorMessages(config.getInstallableCheckableConfigs(), false); - } - - /** - * Validates the Configuration {@link Checkable}s. - * - * @param target the target of the configuration - * @param properties the configuration properties - * @throws OpenemsNamedException on validation error - */ - public default void validateConfiguration(ValidatorConfig config, ConfigurationTarget target, JsonObject properties) - throws OpenemsNamedException { - if (config.getConfigurationValidation() == null) { - return; - } - var checkables = config.getConfigurationValidation().apply(target, properties); - if (checkables == null) { - return; - } - var errors = getErrorMessages(config.getCompatibleCheckableConfigs(), false); - if (!errors.isEmpty()) { - throw new OpenemsException(errors.stream().collect(Collectors.joining(";"))); - } + public default List getErrorInstallableMessages(ValidatorConfig config, Language language) { + return this.getErrorMessages(config.getInstallableCheckableConfigs(), language, false); } /** * Validates the {@link Checkable}s and gets the Status. * + * @param config the config that gets validated * @return the Status */ public default OpenemsAppStatus getStatus(ValidatorConfig config) { - if (!this.getErrorMessages(config.getCompatibleCheckableConfigs(), true).isEmpty()) { + if (!this.getErrorMessages(config.getCompatibleCheckableConfigs(), null, true).isEmpty()) { return OpenemsAppStatus.INCOMPATIBLE; } - if (!this.getErrorMessages(config.getInstallableCheckableConfigs(), true).isEmpty()) { + if (!this.getErrorMessages(config.getInstallableCheckableConfigs(), null, true).isEmpty()) { return OpenemsAppStatus.COMPATIBLE; } return OpenemsAppStatus.INSTALLABLE; } /** - * Builds a {@link JsonObject} out of this {@link Validator}. + * Builds a {@link JsonObject} out of the given {@link ValidatorConfig}. * + * @param config the config that gets validated + * @param language the language of the errors * @return the {@link JsonObject} */ - public default JsonObject toJsonObject(ValidatorConfig config) { + public default JsonObject toJsonObject(ValidatorConfig config, Language language) { return JsonUtils.buildJsonObject() // .addProperty("name", this.getStatus(config).name()) // .add("errorCompatibleMessages", - this.getErrorCompatibleMessages(config).stream().map(s -> new JsonPrimitive(s)) + this.getErrorCompatibleMessages(config, language).stream().map(JsonPrimitive::new) .collect(JsonUtils.toJsonArray())) // .add("errorInstallableMessages", - this.getErrorInstallableMessages(config).stream().map(s -> new JsonPrimitive(s)) + this.getErrorInstallableMessages(config, language).stream().map(JsonPrimitive::new) .collect(JsonUtils.toJsonArray())) // .build(); } @@ -90,9 +72,11 @@ public default JsonObject toJsonObject(ValidatorConfig config) { * Gets the error messages for the given {@link Checkable}. * * @param checkableConfigs the {@link Checkable}s to be checked. + * @param language the language of the errors * @param returnImmediate after the first checkable who returns false * @return a list of errors */ - public abstract List getErrorMessages(List checkableConfigs, boolean returnImmediate); + public abstract List getErrorMessages(List checkableConfigs, Language language, + boolean returnImmediate); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorConfig.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorConfig.java index c79e6277732..159fbaec4a6 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorConfig.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorConfig.java @@ -3,31 +3,13 @@ import java.util.List; import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.google.common.collect.Lists; -import com.google.gson.JsonObject; - -import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.function.ThrowingBiFunction; -import io.openems.edge.core.appmanager.ConfigurationTarget; public class ValidatorConfig { - private static final Logger LOG = LoggerFactory.getLogger(ValidatorConfig.class); - - // TODO - private final List preInstallCheckableConfigs = null; private final List compatibleCheckableConfigs; private final List installableCheckableConfigs; - private ThrowingBiFunction>, // - OpenemsNamedException> // - configurationValidation; - public static final class Builder { private List compatibleCheckableConfigs; @@ -132,15 +114,6 @@ protected ValidatorConfig(List compatibleCheckableConfigs, : Lists.newArrayList(); } - public void setConfigurationValidation( - ThrowingBiFunction>, OpenemsNamedException> configurationValidation) { - this.configurationValidation = configurationValidation; - } - - public ThrowingBiFunction>, OpenemsNamedException> getConfigurationValidation() { - return configurationValidation; - } - public List getCompatibleCheckableConfigs() { return this.compatibleCheckableConfigs; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorImpl.java index b63163284b4..89ed92d038d 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/ValidatorImpl.java @@ -29,7 +29,8 @@ public ValidatorImpl() { } @Override - public List getErrorMessages(List checkableConfigs, boolean returnImmediate) { + public List getErrorMessages(List checkableConfigs, Language language, + boolean returnImmediate) { if (checkableConfigs.isEmpty()) { return new ArrayList<>(); } @@ -64,7 +65,7 @@ public List getErrorMessages(List checkableConfigs, boo noneExistingCheckables.removeIf(c -> c.equals(checkableConfig)); var result = checkable.check(); if (result == checkableConfig.invertResult) { - var errorMessage = checkable.getErrorMessage(Language.DEFAULT); + var errorMessage = checkable.getErrorMessage(language); if (checkableConfig.invertResult) { errorMessage = "Invert[" + errorMessage + "]"; } From 1c8295743d3c29584b6218460190bbd6abcf3f91 Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:30:59 +0200 Subject: [PATCH 18/30] added Unit-Test --- .../common/test/DummyComponentManager.java | 38 +- .../openems/edge/app/TestADependencyToC.java | 129 +++++ .../openems/edge/app/TestBDependencyToC.java | 105 ++++ .../test/io/openems/edge/app/TestC.java | 85 ++++ .../AppManagerAppHelperImplTest.java | 471 ++++++++++++++++++ .../edge/core/appmanager/DummyValidator.java | 43 ++ 6 files changed, 870 insertions(+), 1 deletion(-) create mode 100644 io.openems.edge.core/test/io/openems/edge/app/TestADependencyToC.java create mode 100644 io.openems.edge.core/test/io/openems/edge/app/TestBDependencyToC.java create mode 100644 io.openems.edge.core/test/io/openems/edge/app/TestC.java create mode 100644 io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerAppHelperImplTest.java create mode 100644 io.openems.edge.core/test/io/openems/edge/core/appmanager/DummyValidator.java diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java b/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java index 2a7c8c16f14..bca4521ec06 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java +++ b/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java @@ -1,21 +1,29 @@ package io.openems.edge.common.test; +import java.io.IOException; import java.time.Clock; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Hashtable; import java.util.List; import java.util.concurrent.CompletableFuture; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import io.openems.common.exceptions.OpenemsError; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.base.GenericJsonrpcResponseSuccess; import io.openems.common.jsonrpc.base.JsonrpcRequest; import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; import io.openems.common.jsonrpc.request.GetEdgeConfigRequest; +import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; import io.openems.common.jsonrpc.response.GetEdgeConfigResponse; import io.openems.common.session.Role; import io.openems.common.types.EdgeConfig; @@ -33,6 +41,8 @@ public class DummyComponentManager implements ComponentManager { private final Clock clock; private JsonObject edgeConfigJson; + private ConfigurationAdmin configurationAdmin = null; + public DummyComponentManager() { this(Clock.systemDefaultZone()); } @@ -56,7 +66,7 @@ public List getAllComponents() { public List getEnabledComponentsOfType(Class clazz) { List result = new ArrayList<>(); for (OpenemsComponent component : this.components) { - if (component.getClass().isInstance(clazz)) { + if (clazz.isInstance(component)) { result.add((T) component); } } @@ -167,6 +177,8 @@ public CompletableFuture handleJsonrpcRequest(User user, case GetEdgeConfigRequest.METHOD: return this.handleGetEdgeConfigRequest(user, GetEdgeConfigRequest.from(request)); + case UpdateComponentConfigRequest.METHOD: + return this.handleUpdateComponentConfigRequest(user, UpdateComponentConfigRequest.from(request)); default: throw OpenemsError.JSONRPC_UNHANDLED_METHOD.exception(request.getMethod()); @@ -188,9 +200,33 @@ private CompletableFuture handleGetEdgeConfigRequest(Use return CompletableFuture.completedFuture(response); } + private CompletableFuture handleUpdateComponentConfigRequest(User user, + UpdateComponentConfigRequest request) throws OpenemsNamedException { + if (this.configurationAdmin == null) { + throw new OpenemsException("Can not update Component Config. ConfigurationAdmin is null!"); + } + try { + for (var configuration : this.configurationAdmin.listConfigurations(request.getComponentId())) { + var properties = new Hashtable(); + for (var property : request.getProperties()) { + properties.put(property.getName(), property.getValue()); + } + configuration.update(properties); + break; + } + return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.getId())); + } catch (IOException | InvalidSyntaxException e) { + throw new OpenemsException("Can not update Component Config."); + } + } + @Override public Clock getClock() { return this.clock; } + public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) { + this.configurationAdmin = configurationAdmin; + } + } \ No newline at end of file diff --git a/io.openems.edge.core/test/io/openems/edge/app/TestADependencyToC.java b/io.openems.edge.core/test/io/openems/edge/app/TestADependencyToC.java new file mode 100644 index 00000000000..bde7ebbf6c1 --- /dev/null +++ b/io.openems.edge.core/test/io/openems/edge/app/TestADependencyToC.java @@ -0,0 +1,129 @@ +package io.openems.edge.app; + +import java.util.EnumMap; + +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.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; +import io.openems.common.utils.EnumUtils; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.TestADependencyToC.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration.AppDependencyConfig; + +@Component(name = "App.Test.TestADependencyToC") +public class TestADependencyToC extends AbstractOpenemsApp implements OpenemsApp { + + public static enum Property { + CREATE_POLICY, // + UPDATE_POLICY, // + DELETE_POLICY, // + DEPENDENCY_UPDATE_POLICY, // + DEPENDENCY_DELETE_POLICY, // + NUMBER + } + + @Activate + public TestADependencyToC(@Reference ComponentManager componentManager, ComponentContext context, + @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + super(componentManager, context, cm, componentUtil); + } + + @Override + public AppAssistant getAppAssistant(Language language) { + return AppAssistant.create(this.getName(language)) // + .build(); + } + + @Override + public AppDescriptor getAppDescriptor() { + return AppDescriptor.create() // + .build(); + } + + @Override + public OpenemsAppCategory[] getCategorys() { + return new OpenemsAppCategory[] { OpenemsAppCategory.TEST }; + } + + @Override + public String getImage() { + return OpenemsApp.FALLBACK_IMAGE; + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.MULTIPLE; + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, s) -> { + + var createPolicy = Enum.valueOf(DependencyDeclaration.CreatePolicy.class, + EnumUtils.getAsOptionalString(p, Property.CREATE_POLICY) + .orElse(DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING.name())); + + var updatePolicy = Enum.valueOf(DependencyDeclaration.UpdatePolicy.class, + EnumUtils.getAsOptionalString(p, Property.UPDATE_POLICY) + .orElse(DependencyDeclaration.UpdatePolicy.ALWAYS.name())); + + var deletePolicy = Enum.valueOf(DependencyDeclaration.DeletePolicy.class, + EnumUtils.getAsOptionalString(p, Property.DELETE_POLICY) + .orElse(DependencyDeclaration.DeletePolicy.IF_MINE.name())); + + var dependencyUpdatePolicy = Enum.valueOf(DependencyDeclaration.DependencyUpdatePolicy.class, + EnumUtils.getAsOptionalString(p, Property.DEPENDENCY_UPDATE_POLICY) + .orElse(DependencyDeclaration.DependencyUpdatePolicy.ALLOW_ALL.name())); + + var dependencyDeletePolicy = Enum.valueOf(DependencyDeclaration.DependencyDeletePolicy.class, + EnumUtils.getAsOptionalString(p, Property.DEPENDENCY_DELETE_POLICY) + .orElse(DependencyDeclaration.DependencyDeletePolicy.ALLOWED.name())); + + var number = EnumUtils.getAsOptionalInt(p, Property.NUMBER).orElse(0); + + var dependencies = Lists.newArrayList(new DependencyDeclaration("C", // + createPolicy, updatePolicy, deletePolicy, // + dependencyUpdatePolicy, dependencyDeletePolicy, // + AppDependencyConfig.create() // + .setAppId("App.Test.TestC") // + .setProperties(JsonUtils.buildJsonObject() // + .addProperty(TestC.Property.NUMBER.name(), number) // + .build()) + .build()) // + ); + + return new AppConfiguration(null, null, null, dependencies); + }; + } + + @Override + protected Class getPropertyClass() { + return Property.class; + } + + @Override + public String getName(Language language) { + return this.getAppId(); + } + +} diff --git a/io.openems.edge.core/test/io/openems/edge/app/TestBDependencyToC.java b/io.openems.edge.core/test/io/openems/edge/app/TestBDependencyToC.java new file mode 100644 index 00000000000..2ada6b498c2 --- /dev/null +++ b/io.openems.edge.core/test/io/openems/edge/app/TestBDependencyToC.java @@ -0,0 +1,105 @@ +package io.openems.edge.app; + +import java.util.EnumMap; + +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.Reference; + +import com.google.common.collect.Lists; +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; +import io.openems.common.utils.EnumUtils; +import io.openems.edge.app.TestBDependencyToC.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration.AppDependencyConfig; + +@Component(name = "App.Test.TestBDependencyToC") +public class TestBDependencyToC extends AbstractOpenemsApp implements OpenemsApp { + + public static enum Property { + CREATE_POLICY + } + + @Activate + public TestBDependencyToC(@Reference ComponentManager componentManager, ComponentContext context, + @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + super(componentManager, context, cm, componentUtil); + } + + @Override + public AppAssistant getAppAssistant(Language language) { + return AppAssistant.create(this.getName(language)) // + .build(); + } + + @Override + public AppDescriptor getAppDescriptor() { + return AppDescriptor.create() // + .build(); + } + + @Override + public OpenemsAppCategory[] getCategorys() { + return new OpenemsAppCategory[] { OpenemsAppCategory.TEST }; + } + + @Override + public String getImage() { + return OpenemsApp.FALLBACK_IMAGE; + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.MULTIPLE; + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, p, s) -> { + + var createPolicy = Enum.valueOf(DependencyDeclaration.CreatePolicy.class, + EnumUtils.getAsOptionalString(p, Property.CREATE_POLICY) + .orElse(DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING.name())); + + var dependencies = Lists.newArrayList(new DependencyDeclaration("c", // + createPolicy, // + DependencyDeclaration.UpdatePolicy.ALWAYS, // + DependencyDeclaration.DeletePolicy.IF_MINE, // + DependencyDeclaration.DependencyUpdatePolicy.ALLOW_ALL, // + DependencyDeclaration.DependencyDeletePolicy.NOT_ALLOWED, // + AppDependencyConfig.create() // + .setAppId("App.Test.TestC") // + .build()) // + ); + + return new AppConfiguration(null, null, null, dependencies); + }; + } + + @Override + protected Class getPropertyClass() { + return Property.class; + } + + @Override + public String getName(Language language) { + return this.getAppId(); + } + +} diff --git a/io.openems.edge.core/test/io/openems/edge/app/TestC.java b/io.openems.edge.core/test/io/openems/edge/app/TestC.java new file mode 100644 index 00000000000..240dc31dc47 --- /dev/null +++ b/io.openems.edge.core/test/io/openems/edge/app/TestC.java @@ -0,0 +1,85 @@ +package io.openems.edge.app; + +import java.util.EnumMap; + +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.Reference; + +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.function.ThrowingTriFunction; +import io.openems.common.session.Language; +import io.openems.edge.app.TestC.Property; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; +import io.openems.edge.core.appmanager.AppAssistant; +import io.openems.edge.core.appmanager.AppConfiguration; +import io.openems.edge.core.appmanager.AppDescriptor; +import io.openems.edge.core.appmanager.ComponentUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.OpenemsAppCategory; + +@Component(name = "App.Test.TestC") +public class TestC extends AbstractOpenemsApp implements OpenemsApp { + + public static enum Property { + NUMBER + } + + @Activate + public TestC(@Reference ComponentManager componentManager, ComponentContext componentContext, + @Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) { + super(componentManager, componentContext, cm, componentUtil); + } + + @Override + public AppAssistant getAppAssistant(Language language) { + return AppAssistant.create(this.getName(language)) // + .build(); + } + + @Override + public AppDescriptor getAppDescriptor() { + return AppDescriptor.create() // + .build(); + } + + @Override + public OpenemsAppCategory[] getCategorys() { + return new OpenemsAppCategory[] { OpenemsAppCategory.TEST }; + } + + @Override + public String getImage() { + return OpenemsApp.FALLBACK_IMAGE; + } + + @Override + public OpenemsAppCardinality getCardinality() { + return OpenemsAppCardinality.MULTIPLE; + } + + @Override + protected ThrowingTriFunction, Language, AppConfiguration, OpenemsNamedException> appConfigurationFactory() { + return (t, u, s) -> { + return new AppConfiguration(); + }; + } + + @Override + protected Class getPropertyClass() { + return Property.class; + } + + @Override + public String getName(Language language) { + return this.getAppId(); + } + +} diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerAppHelperImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerAppHelperImplTest.java new file mode 100644 index 00000000000..221055dce6c --- /dev/null +++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerAppHelperImplTest.java @@ -0,0 +1,471 @@ +package io.openems.edge.core.appmanager; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.concurrent.ExecutionException; + +import org.junit.Before; +import org.junit.Test; +import org.osgi.service.component.ComponentConstants; +import org.osgi.service.component.ComponentContext; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.session.Language; +import io.openems.common.session.Role; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.app.TestADependencyToC; +import io.openems.edge.app.TestBDependencyToC; +import io.openems.edge.app.TestC; +import io.openems.edge.app.evcs.KebaEvcs; +import io.openems.edge.app.integratedsystem.FeneconHome; +import io.openems.edge.app.timeofusetariff.AwattarHourly; +import io.openems.edge.app.timeofusetariff.StromdaoCorrently; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyComponentContext; +import io.openems.edge.common.test.DummyComponentManager; +import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.common.test.DummyUser; +import io.openems.edge.common.user.User; +import io.openems.edge.core.appmanager.dependency.AppManagerAppHelperImpl; +import io.openems.edge.core.appmanager.dependency.ComponentAggregateTaskImpl; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; +import io.openems.edge.core.appmanager.dependency.SchedulerAggregateTaskImpl; +import io.openems.edge.core.appmanager.dependency.StaticIpAggregateTaskImpl; +import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance; +import io.openems.edge.core.appmanager.jsonrpc.DeleteAppInstance; +import io.openems.edge.core.appmanager.jsonrpc.UpdateAppInstance; +import io.openems.edge.core.appmanager.validator.CheckCardinality; +import io.openems.edge.core.appmanager.validator.Validator; + +public class AppManagerAppHelperImplTest { + + private final User user = new DummyUser("1", "password", Language.DEFAULT, Role.ADMIN); + + private DummyConfigurationAdmin cm; + private DummyComponentManager componentManger; + private ComponentUtil componentUtil; + private Validator validator; + + private FeneconHome homeApp; + private KebaEvcs kebaEvcsApp; + private AwattarHourly awattarApp; + private StromdaoCorrently stromdao; + + private TestADependencyToC testAApp; + private TestBDependencyToC testBApp; + private TestC testCApp; + + private AppManagerImpl sut; + + @Before + public void beforeEach() throws Exception { + + this.cm = new DummyConfigurationAdmin(); + this.cm.getOrCreateEmptyConfiguration(AppManager.SINGLETON_SERVICE_PID); + + this.componentManger = new DummyComponentManager(); + this.componentManger.setConfigJson(JsonUtils.buildJsonObject() // + .add("components", JsonUtils.buildJsonObject() // + .add("scheduler0", JsonUtils.buildJsonObject() // + .addProperty("factoryId", "Scheduler.AllAlphabetically") // + .add("properties", JsonUtils.buildJsonObject() // + .addProperty("enabled", true) // + .add("controllers.ids", JsonUtils.buildJsonArray() // + .add("ctrlGridOptimizedCharge0") // + .add("ctrlEssSurplusFeedToGrid0") // + .add("ctrlBalancing0") // + .build()) // + .build()) // + .build()) // + .build()) // + .add("factories", JsonUtils.buildJsonObject() // + .build()) // + .build() // + ); + this.componentUtil = new ComponentUtilImpl(this.componentManger, this.cm); + + this.homeApp = new FeneconHome(this.componentManger, getComponentContext("App.FENECON.Home"), this.cm, + this.componentUtil); + this.kebaEvcsApp = new KebaEvcs(this.componentManger, getComponentContext("App.Evcs.Keba"), this.cm, + this.componentUtil); + this.awattarApp = new AwattarHourly(this.componentManger, getComponentContext("App.TimeVariablePrice.Awattar"), + this.cm, this.componentUtil); + this.stromdao = new StromdaoCorrently(this.componentManger, + getComponentContext("App.TimeVariablePrice.Stromdao"), this.cm, this.componentUtil); + + this.testAApp = new TestADependencyToC(this.componentManger, getComponentContext("App.Test.TestADependencyToC"), + this.cm, this.componentUtil); + + this.testBApp = new TestBDependencyToC(this.componentManger, getComponentContext("App.Test.TestBDependencyToC"), + this.cm, this.componentUtil); + + this.testCApp = new TestC(this.componentManger, getComponentContext("App.Test.TestC"), this.cm, + this.componentUtil); + + final var componentTask = new ComponentAggregateTaskImpl(this.componentManger); + final var schedulerTask = new SchedulerAggregateTaskImpl(componentTask, this.componentUtil); + final var staticIpTask = new StaticIpAggregateTaskImpl(this.componentUtil); + + this.sut = new AppManagerImpl(); + this.componentManger.addComponent(this.sut); + this.componentManger.setConfigurationAdmin(this.cm); + + var dummyValidator = new DummyValidator(); + dummyValidator.setCheckables(Lists + .newArrayList(new CheckCardinality(this.sut, getComponentContext(CheckCardinality.COMPONENT_NAME)))); + this.validator = dummyValidator; + + var appManagerAppHelper = new AppManagerAppHelperImpl(this.componentManger, this.componentUtil, this.validator, + componentTask, schedulerTask, staticIpTask); + + new ComponentTest(this.sut) // + .addReference("cm", this.cm) // + .addReference("componentManager", this.componentManger) // + .addReference("appHelper", appManagerAppHelper) // + .addReference("validator", this.validator) // + .addReference("availableApps", + ImmutableList.of(this.homeApp, this.kebaEvcsApp, this.awattarApp, this.stromdao, this.testAApp, + this.testBApp, this.testCApp)) // + .activate(MyConfig.create() // + .setApps(JsonUtils.buildJsonArray() // + .build().toString()) // + .build()); + + } + + @Test + public void testCreatePolicyIfNotExisting() throws OpenemsNamedException { + assertEquals(0, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testAApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("CREATE_POLICY", DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING.name()) + .build())); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testBApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("CREATE_POLICY", DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING.name()) + .build())); + + assertEquals(3, this.sut.getInstantiatedApps().size()); + } + + @Test + public void testCreatePolicyAlways() throws OpenemsNamedException { + assertEquals(0, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testAApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("CREATE_POLICY", DependencyDeclaration.CreatePolicy.ALWAYS.name()).build())); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testBApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("CREATE_POLICY", DependencyDeclaration.CreatePolicy.ALWAYS.name()).build())); + + assertEquals(4, this.sut.getInstantiatedApps().size()); + } + + @Test + public void testCreatePolicyNever() throws OpenemsNamedException { + assertEquals(0, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testAApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("CREATE_POLICY", DependencyDeclaration.CreatePolicy.NEVER.name()).build())); + + assertEquals(1, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testBApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("CREATE_POLICY", DependencyDeclaration.CreatePolicy.NEVER.name()).build())); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + } + + @Test + public void testUpdatePolicyNever() throws OpenemsNamedException { + assertEquals(0, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testAApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("UPDATE_POLICY", DependencyDeclaration.UpdatePolicy.NEVER.name()) // + .addProperty("NUMBER", 1) // + .build())); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + + this.sut.handleJsonrpcRequest(this.user, + new UpdateAppInstance.Request(this.getAppByAppId(this.testAApp.getAppId()).instanceId, "", + JsonUtils.buildJsonObject() // + .addProperty("UPDATE_POLICY", DependencyDeclaration.UpdatePolicy.NEVER.name()) // + .addProperty("NUMBER", 2) // + .build())); + + var instance = this.getAppByAppId(this.testCApp.getAppId()); + assertEquals(1, instance.properties.get(TestC.Property.NUMBER.name()).getAsInt()); + } + + @Test + public void testUpdatePolicyAlways() throws OpenemsNamedException { + assertEquals(0, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testAApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("UPDATE_POLICY", DependencyDeclaration.UpdatePolicy.ALWAYS.name()) // + .addProperty("NUMBER", 1) // + .build())); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testBApp.getAppId(), "", // + JsonUtils.buildJsonObject().build())); + + assertEquals(3, this.sut.getInstantiatedApps().size()); + + this.sut.handleJsonrpcRequest(this.user, + new UpdateAppInstance.Request(this.getAppByAppId(this.testAApp.getAppId()).instanceId, "", + JsonUtils.buildJsonObject() // + .addProperty("UPDATE_POLICY", DependencyDeclaration.UpdatePolicy.ALWAYS.name()) // + .addProperty("NUMBER", 2) // + .build())); + + var instance = this.getAppByAppId(this.testCApp.getAppId()); + assertEquals(2, instance.properties.get(TestC.Property.NUMBER.name()).getAsInt()); + } + + @Test + public void testUpdatePolicyIfMine() throws OpenemsNamedException { + assertEquals(0, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testAApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("UPDATE_POLICY", DependencyDeclaration.UpdatePolicy.IF_MINE.name()) // + .addProperty("NUMBER", 1) // + .build())); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + + this.sut.handleJsonrpcRequest(this.user, + new UpdateAppInstance.Request(this.getAppByAppId(this.testAApp.getAppId()).instanceId, "", + JsonUtils.buildJsonObject() // + .addProperty("UPDATE_POLICY", DependencyDeclaration.UpdatePolicy.IF_MINE.name()) // + .addProperty("NUMBER", 2) // + .build())); + + var instance = this.getAppByAppId(this.testCApp.getAppId()); + assertEquals(2, instance.properties.get(TestC.Property.NUMBER.name()).getAsInt()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testBApp.getAppId(), "", // + JsonUtils.buildJsonObject().build())); + + assertEquals(3, this.sut.getInstantiatedApps().size()); + + this.sut.handleJsonrpcRequest(this.user, + new UpdateAppInstance.Request(this.getAppByAppId(this.testAApp.getAppId()).instanceId, "", + JsonUtils.buildJsonObject() // + .addProperty("UPDATE_POLICY", DependencyDeclaration.UpdatePolicy.IF_MINE.name()) // + .addProperty("NUMBER", 3) // + .build())); + + instance = this.getAppByAppId(this.testCApp.getAppId()); + assertEquals(2, instance.properties.get(TestC.Property.NUMBER.name()).getAsInt()); + } + + @Test + public void testDeletePolicyNever() throws OpenemsNamedException { + assertEquals(0, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testAApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("DELETE_POLICY", DependencyDeclaration.DeletePolicy.NEVER.name()) // + .build())); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + + var instance = this.getAppByAppId(this.testAApp.getAppId()); + this.sut.handleDeleteAppInstanceRequest(this.user, new DeleteAppInstance.Request(instance.instanceId)); + + assertEquals(1, this.sut.getInstantiatedApps().size()); + } + + @Test + public void testDeletePolicyAlways() throws OpenemsNamedException { + assertEquals(0, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testAApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("DELETE_POLICY", DependencyDeclaration.DeletePolicy.ALWAYS.name()) // + .build())); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testBApp.getAppId(), "", // + JsonUtils.buildJsonObject().build())); + + assertEquals(3, this.sut.getInstantiatedApps().size()); + + var instance = this.getAppByAppId(this.testAApp.getAppId()); + this.sut.handleDeleteAppInstanceRequest(this.user, new DeleteAppInstance.Request(instance.instanceId)); + + assertEquals(1, this.sut.getInstantiatedApps().size()); + } + + @Test + public void testDeletePolicyIfMine() throws OpenemsNamedException { + assertEquals(0, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testAApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("DELETE_POLICY", DependencyDeclaration.DeletePolicy.IF_MINE.name()) // + .build())); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testBApp.getAppId(), "", // + JsonUtils.buildJsonObject().build())); + + assertEquals(3, this.sut.getInstantiatedApps().size()); + + var instance = this.getAppByAppId(this.testAApp.getAppId()); + this.sut.handleDeleteAppInstanceRequest(this.user, new DeleteAppInstance.Request(instance.instanceId)); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + } + + @Test + public void testDependencyDeletePolicyAllowed() throws OpenemsNamedException { + assertEquals(0, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testAApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("DEPENDENCY_DELETE_POLICY", + DependencyDeclaration.DependencyDeletePolicy.ALLOWED.name()) // + .build())); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + + var instance = this.getAppByAppId(this.testCApp.getAppId()); + this.sut.handleDeleteAppInstanceRequest(this.user, new DeleteAppInstance.Request(instance.instanceId)); + + assertEquals(1, this.sut.getInstantiatedApps().size()); + } + + @Test(expected = OpenemsNamedException.class) + public void testDependencyDeletePolicyNotAllowed() throws OpenemsNamedException { + assertEquals(0, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testAApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("DEPENDENCY_DELETE_POLICY", + DependencyDeclaration.DependencyDeletePolicy.NOT_ALLOWED.name()) // + .build())); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + + var instance = this.getAppByAppId(this.testCApp.getAppId()); + this.sut.handleDeleteAppInstanceRequest(this.user, new DeleteAppInstance.Request(instance.instanceId)); + } + + @Test + public void testDependencyUpdatePolicyAllowAll() + throws OpenemsNamedException, InterruptedException, ExecutionException { + assertEquals(0, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testAApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("DEPENDENCY_UPDATE_POLICY", + DependencyDeclaration.DependencyUpdatePolicy.ALLOW_ALL.name()) // + .addProperty("NUMBER", 1) // + .build())); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + + var newAlias = "newAppAlias"; + var completable = this.sut.handleJsonrpcRequest(this.user, + new UpdateAppInstance.Request(this.getAppByAppId(this.testCApp.getAppId()).instanceId, newAlias, + JsonUtils.buildJsonObject() // + .addProperty("NUMBER", 2) // + .build())); + + var result = completable.get().getResult(); + assertTrue(!result.has("warnings") || (result.get("warnings").getAsJsonArray().size() == 0)); + + var instance = this.getAppByAppId(this.testCApp.getAppId()); + assertEquals(newAlias, instance.alias); + assertEquals(2, instance.properties.get("NUMBER").getAsInt()); + } + + @Test(expected = OpenemsNamedException.class) + public void testDependencyUpdatePolicyAllowNone() + throws OpenemsNamedException, InterruptedException, ExecutionException { + assertEquals(0, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testAApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("DEPENDENCY_UPDATE_POLICY", + DependencyDeclaration.DependencyUpdatePolicy.ALLOW_NONE.name()) // + .addProperty("NUMBER", 1) // + .build())); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + + var newAlias = "newAppAlias"; + this.sut.handleJsonrpcRequest(this.user, + new UpdateAppInstance.Request(this.getAppByAppId(this.testCApp.getAppId()).instanceId, newAlias, + JsonUtils.buildJsonObject() // + .addProperty("NUMBER", 2) // + .build())); + } + + @Test + public void testDependencyUpdatePolicyAllowOnlyUnconfiguredProperties() + throws OpenemsNamedException, InterruptedException, ExecutionException { + assertEquals(0, this.sut.getInstantiatedApps().size()); + + this.sut.handleAddAppInstanceRequest(this.user, new AddAppInstance.Request(this.testAApp.getAppId(), "", // + JsonUtils.buildJsonObject() // + .addProperty("DEPENDENCY_UPDATE_POLICY", + DependencyDeclaration.DependencyUpdatePolicy.ALLOW_ONLY_UNCONFIGURED_PROPERTIES.name()) // + .addProperty("NUMBER", 1) // + .build())); + + assertEquals(2, this.sut.getInstantiatedApps().size()); + + var newAlias = "newAppAlias"; + var completable = this.sut.handleJsonrpcRequest(this.user, + new UpdateAppInstance.Request(this.getAppByAppId(this.testCApp.getAppId()).instanceId, newAlias, + JsonUtils.buildJsonObject() // + .addProperty("NUMBER", 2) // + .build())); + + var result = completable.get().getResult(); + assertTrue(result.has("warnings")); + assertTrue(result.get("warnings").getAsJsonArray().size() > 0); + + var instance = this.getAppByAppId(this.testCApp.getAppId()); + assertEquals(newAlias, instance.alias); + assertEquals(1, instance.properties.get("NUMBER").getAsInt()); + + } + + private OpenemsAppInstance getAppByAppId(String appId) { + return this.sut.getInstantiatedApps().stream().filter(i -> i.appId.equals(appId)).findAny().get(); + } + + private static ComponentContext getComponentContext(String appId) { + Dictionary properties = new Hashtable<>(); + properties.put(ComponentConstants.COMPONENT_NAME, appId); + return new DummyComponentContext(properties); + } + +} diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/DummyValidator.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/DummyValidator.java new file mode 100644 index 00000000000..65e59feed2f --- /dev/null +++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/DummyValidator.java @@ -0,0 +1,43 @@ +package io.openems.edge.core.appmanager; + +import java.util.ArrayList; +import java.util.List; + +import io.openems.common.session.Language; +import io.openems.edge.core.appmanager.validator.Checkable; +import io.openems.edge.core.appmanager.validator.Validator; +import io.openems.edge.core.appmanager.validator.ValidatorConfig.CheckableConfig; + +public class DummyValidator implements Validator { + + private List checkables; + + @Override + public List getErrorMessages(List checkableConfigs, Language language, + boolean returnImmediate) { + var errors = new ArrayList(); + for (var check : checkableConfigs) { + var checkable = this.findCheckableByName(check.checkableComponentName); + checkable.setProperties(check.properties); + if (!checkable.check()) { + errors.add(checkable.getErrorMessage(language)); + return errors; + } + + } + return errors; + } + + private Checkable findCheckableByName(String name) { + return this.checkables.stream().filter(c -> c.getComponentName().equals(name)).findAny().get(); + } + + public void setCheckables(List checkables) { + this.checkables = checkables; + } + + public List getCheckables() { + return this.checkables; + } + +} From 0ad236fd55f886506b4d4734c5f292742be304ed Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:32:31 +0200 Subject: [PATCH 19/30] minor test changes --- .../openems/edge/app/meter/SocomecMeter.java | 1 + .../dependency/AppManagerAppHelperImpl.java | 188 ++++++++++-------- .../core/appmanager/AppManagerImplTest.java | 94 ++++++--- 3 files changed, 178 insertions(+), 105 deletions(-) diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java index 29878e96846..b5d3f99ba6a 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java @@ -106,6 +106,7 @@ public AppAssistant getAppAssistant(Language language) { .add(JsonFormlyUtil.buildSelect(Property.TYPE) // .setLabel(bundle.getString("App.Meter.mountType.label")) // .setOptions(this.buildMeterOptions(language)) // + .setDefaultValue("PRODUCTION") // .build()) // .add(JsonFormlyUtil.buildInput(Property.MODBUS_UNIT_ID) // .setLabel(bundle.getString("modbusUnitId")) // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java index 770ebd84cd0..b41f15a679b 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java @@ -11,6 +11,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.UUID; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; @@ -90,8 +91,9 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj // but the meter has a checkable that there has to be a HOME installed // maybe add temporary apps in this component final var warnings = new LinkedList(); + final var language = user == null ? null : user.getLanguage(); if (oldInstance == null) { - this.checkStatus(app); + this.checkStatus(app, language); } else { // determine if properties are allowed to be updated var references = this.getAppsWithReferenceTo(oldInstance.instanceId); @@ -106,7 +108,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj continue; } - var dependencyApp = this.getAppManagerImpl().findInstaceById(dd.get().instanceId); + var dependencyApp = this.getAppManagerImpl().findInstanceById(dd.get().instanceId); var appConfig = dependencieDeclaration.appConfigs.stream() .filter(d -> d.appId.equals(dependencyApp.appId)).findAny(); @@ -120,9 +122,8 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj // everything can be changed break; case ALLOW_NONE: - // reset to old config - properties = oldInstance.properties; - break; + throw new OpenemsException( + "This app is not allowed to be updated because of a dependency constraint!"); case ALLOW_ONLY_UNCONFIGURED_PROPERTIES: // override properties for (var propEntry : appConfig.get().properties.entrySet()) { @@ -135,19 +136,21 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj } } + if (appConfig.get().alias != null && !alias.equals(appConfig.get().alias)) { + warnings.add("Can not change Alias because of a Dependency constraint!"); + alias = appConfig.get().alias; + } break; } } } } - final var language = user == null ? null : user.getLanguage(); - var errors = new LinkedList(); var oldInstances = new TreeMap(); var dependencieInstances = new HashMap(); - // all existing app dependencies + // get all existing app dependencies if (oldInstance != null) { this.foreachExistingDependecy(oldInstance, ConfigurationTarget.UPDATE, language, dc -> { if (!dc.isDependency()) { @@ -158,12 +161,31 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj }); } + BiFunction includeDependency = (a, d) -> { + ExistingDependencyConfig oldAppConfig = oldInstances.get(new AppIdKey(a.getAppId(), d.key)); + + if (oldAppConfig == null) { + switch (d.createPolicy) { + case ALWAYS: + case IF_NOT_EXISTING: + return true; + case NEVER: + var possibleInstance = this.findNeededApp(d, this.determineDependencyConfig(d.appConfigs)); + if(possibleInstance != null) { + return true; + } + return false; + } + } + return true; + }; + var modifiedOrCreatedApps = new ArrayList(); var deletedApps = new ArrayList(); final var lastCreatedOrModifiedApp = new MutableValue(); // update app and its dependencies this.foreachDependency(app, alias, properties, ConfigurationTarget.UPDATE, language, - this::determineDependencyConfig, dc -> { + this::determineDependencyConfig, includeDependency, dc -> { // get old instance if existing ExistingDependencyConfig oldAppConfig = null; if (oldInstance != null) { @@ -222,14 +244,14 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj // create app or get as dependency if (oldAppConfig == null) { - var neededApp = this.findNeededApp(dc); + var neededApp = this.findNeededApp(dc.sub, dc.appDependencyConfig); if (neededApp == null) { return false; } AppConfiguration oldConfig = null; UUID instanceId; OpenemsAppInstance oldInstanceOfCurrentApp = null; - String aliasOfNewInstance = dc.appDependencyConfig.alias; + var aliasOfNewInstance = dc.appDependencyConfig.alias; if (neededApp.isPresent()) { instanceId = neededApp.get().instanceId; oldInstanceOfCurrentApp = neededApp.get(); @@ -273,7 +295,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj continue; } // override properties if set by dependency - var config = determineDependencyConfig(neededDependency.appConfigs); + var config = this.determineDependencyConfig(neededDependency.appConfigs); for (var entry : config.properties.entrySet()) { if (!dc.appDependencyConfig.properties.has(entry.getKey()) || !dc.appDependencyConfig.properties.get(entry.getKey()) @@ -327,30 +349,40 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj for (var entry : oldInstances.entrySet()) { if (entry.getValue().app.equals(dc.parent)) { parent = entry.getValue().instance; + break; } } } } // update existing app - if (dc.isDependency() + var isNotAllowedToUpdate = dc.isDependency() && !dc.sub.updatePolicy.isAllowedToUpdate(this.getAppManagerImpl().getInstantiatedApps(), - parent, oldAppConfig.instance)) { - // not allowed to update but still a dependency - return true; - } + parent, oldAppConfig.instance); var newInstanceAlias = dc.appDependencyConfig.alias; if (newInstanceAlias == null) { newInstanceAlias = oldAppConfig.instance.alias; } - var newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), newInstanceAlias, - oldAppConfig.instance.instanceId, dc.appDependencyConfig.properties, dependecies); + OpenemsAppInstance newAppInstance; + + if (isNotAllowedToUpdate) { + newAppInstance = oldAppConfig.instance; + } else { + newAppInstance = new OpenemsAppInstance(dc.app.getAppId(), newInstanceAlias, + oldAppConfig.instance.instanceId, dc.appDependencyConfig.properties, dependecies); + } + lastCreatedOrModifiedApp.setValue(newAppInstance); - modifiedOrCreatedApps.add(newAppInstance); dependencieInstances.put(dc, newAppInstance); + if (isNotAllowedToUpdate) { + // not allowed to update but still a dependency + return true; + } + modifiedOrCreatedApps.add(newAppInstance); + try { var newAppConfig = this.getNewAppConfigWithReplacedIds(dc.app, oldAppConfig.instance, newAppInstance, AppManagerAppHelperImpl.getComponentsFromConfigs(otherAppConfigs), @@ -387,7 +419,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj // create or delete unused components this.componentsTask.create(user, otherAppConfigs); } catch (OpenemsNamedException e) { - log.error(e.getMessage()); + this.log.error(e.getMessage()); // TODO translation errors.add("Can not update Components!"); } @@ -396,7 +428,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj // update scheduler execute order this.schedulerTask.create(user, otherAppConfigs); } catch (OpenemsNamedException e) { - log.error(e.getMessage()); + this.log.error(e.getMessage()); // TODO translation errors.add("Can not update Scheduler!"); } @@ -405,7 +437,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj // update static ips this.staticIpTask.create(user, otherAppConfigs); } catch (OpenemsNamedException e) { - log.error(e.getMessage()); + this.log.error(e.getMessage()); // TODO translation errors.add("Can not update static ips!"); } @@ -434,7 +466,7 @@ public void setValue(T value) { } public T getValue() { - return value; + return this.value; } } @@ -454,10 +486,6 @@ private final DependencyDeclaration getNeededDependencyTo(OpenemsAppInstance ins continue; } // TODO when adding an app the current app can't be referenced -// if (neededDependency.appConfigs.stream().filter(c -> c.specificInstanceId != null) -// .anyMatch(c -> c.specificInstanceId.equals(instanceId))) { -// return neededDependency; -// } if (neededDependency.appConfigs.stream().filter(c -> c.appId != null) .anyMatch(c -> c.appId.equals(appId))) { return neededDependency; @@ -594,10 +622,6 @@ public UpdateValues deleteApp(User user, OpenemsAppInstance instance) throws Ope return new UpdateValues(instance, modifiedApps, deletedInstances); } - // private void getModifiedAppAssistant(User user, OpenemsApp app) { - // TODO does not work when having multiple instances of the same app and only - // one has properties that cant be edited - private List getAppsWithReferenceTo(UUID... instanceIds) { return this.getAppManagerImpl().getInstantiatedApps() // .stream() // @@ -654,16 +678,16 @@ private final boolean isAllowedToDelete(OpenemsAppInstance instance, UUID... ign return true; } - protected void checkStatus(OpenemsApp openemsApp) throws OpenemsNamedException { + protected void checkStatus(OpenemsApp openemsApp, Language language) throws OpenemsNamedException { var validatorConfig = openemsApp.getValidatorConfig(); var status = this.validator.getStatus(validatorConfig); switch (status) { case INCOMPATIBLE: throw new OpenemsException("App is not compatible! " + this.validator - .getErrorCompatibleMessages(validatorConfig).stream().collect(Collectors.joining(";"))); + .getErrorCompatibleMessages(validatorConfig, language).stream().collect(Collectors.joining(";"))); case COMPATIBLE: throw new OpenemsException("App can not be installed! " + this.validator - .getErrorInstallableMessages(validatorConfig).stream().collect(Collectors.joining(";"))); + .getErrorInstallableMessages(validatorConfig, language).stream().collect(Collectors.joining(";"))); case INSTALLABLE: // app can be installed return; @@ -698,33 +722,30 @@ protected static List getStaticIpsFromConfigs(List con /** * Finds the needed app for a {@link DependencyDeclaration}. * - * @param dc the current {@link DependencyConfig} - * @param appId the appId of the needed {@link Dependency} + * @param dc the current {@link DependencyConfig} * @return s null if the app can not be added; {@link Optional#absent()} if the * app needs to be created; the {@link OpenemsAppInstance} if an * existing app can be used */ - private Optional findNeededApp(DependencyConfig dc) { - if (!dc.isDependency()) { + private Optional findNeededApp(DependencyDeclaration declaration, + DependencyDeclaration.AppDependencyConfig config) { + if (declaration == null) { return Optional.absent(); } - if (dc.appDependencyConfig.specificInstanceId != null) { + if (config.specificInstanceId != null) { try { - var appById = this.getAppManagerImpl().findInstaceById(dc.appDependencyConfig.specificInstanceId); + var appById = this.getAppManagerImpl().findInstanceById(config.specificInstanceId); return Optional.of(appById); } catch (NoSuchElementException e) { return null; } } - var appId = dc.appDependencyConfig.appId; - if (dc.sub.createPolicy == DependencyDeclaration.CreatePolicy.ALWAYS) { + var appId = config.appId; + if (declaration.createPolicy == DependencyDeclaration.CreatePolicy.ALWAYS) { var neededApps = this.getAppManagerImpl().getInstantiatedApps().stream().filter(t -> t.appId.equals(appId)) .collect(Collectors.toList()); OpenemsAppInstance availableApp = null; for (var neededApp : neededApps) { -// if (!this.getAppManagerImpl().getInstantiatedApps().stream() -// .filter(t -> t.dependencies != null && !t.dependencies.isEmpty()).anyMatch(t -> t.dependencies -// .stream().anyMatch(d -> d.instanceId.equals(neededApp.instanceId)))) { if (this.getAppsWithDependencyTo(neededApp).isEmpty()) { availableApp = neededApp; break; @@ -737,7 +758,7 @@ private Optional findNeededApp(DependencyConfig dc) { if (!neededApp.isEmpty()) { return Optional.of(neededApp.get(0)); } - if (dc.sub.createPolicy == DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING) { + if (declaration.createPolicy == DependencyDeclaration.CreatePolicy.IF_NOT_EXISTING) { return Optional.absent(); } return null; @@ -756,26 +777,28 @@ private List getAppsWithDependencyTo(OpenemsAppInstance inst *

* Order bottom -> top. * - * @param app the app to be installed - * @param alias the alias of the current instance - * @param defaultProperties the properties of the current instane - * @param target the {@link ConfigurationTarget} - * @param function returns true if the instance gets created or - * already exists - * @param sub the {@link DependencyDeclaration} - * @param l the {@link Language} - * @param parent the parent app - * @param alreadyIteratedApps the apps that already got iterated thru to avoid - * endless loop. e. g. if two apps have each other as - * a dependency + * @param app the app to be installed + * @param appConfig the {@link AppDependencyConfig} of the + * current app + * @param target the {@link ConfigurationTarget} + * @param function returns true if the instance gets created or + * already exists + * @param sub the {@link DependencyDeclaration} + * @param l the {@link Language} + * @param parent the parent app + * @param alreadyIteratedApps the apps that already got iterated thru to + * avoid endless loop. e. g. if two apps have + * each other as a dependency + * @param determineDependencyConfig the function to determine the + * {@link AppDependencyConfig} * @return s the last {@link DependencyConfig} * @throws OpenemsNamedException on error */ private DependencyConfig foreachDependency(OpenemsApp app, AppDependencyConfig appConfig, ConfigurationTarget target, Function function, DependencyDeclaration sub, Language l, OpenemsApp parent, Set alreadyIteratedApps, - Function, AppDependencyConfig> determineDependencyConfig) - throws OpenemsNamedException { + Function, AppDependencyConfig> determineDependencyConfig, + BiFunction includeDependency) throws OpenemsNamedException { if (alreadyIteratedApps == null) { alreadyIteratedApps = new HashSet<>(); } @@ -799,19 +822,24 @@ private DependencyConfig foreachDependency(OpenemsApp app, AppDependencyConfig a if (nextAppConfig.appId != null) { dependencyApp = this.getAppManagerImpl().findAppById(nextAppConfig.appId); } else { - var specificApp = this.getAppManagerImpl().findInstaceById(nextAppConfig.specificInstanceId); + var specificApp = this.getAppManagerImpl().findInstanceById(nextAppConfig.specificInstanceId); dependencyApp = this.getAppManagerImpl().findAppById(specificApp.appId); } if (alreadyIteratedApps.contains(dependencyApp)) { continue; } + if (!includeDependency.apply(app, dependency)) { + continue; + } + var addingConfig = this.foreachDependency(dependencyApp, nextAppConfig, target, function, dependency, l, - app, alreadyIteratedApps, determineDependencyConfig); + app, alreadyIteratedApps, determineDependencyConfig, includeDependency); if (addingConfig != null) { dependencies.add(addingConfig); } } catch (NoSuchElementException e) { - // TODO can not find app + // can not find app + e.printStackTrace(); } } @@ -822,6 +850,20 @@ private DependencyConfig foreachDependency(OpenemsApp app, AppDependencyConfig a return null; } + private void foreachDependency(OpenemsApp app, String alias, JsonObject defaultProperties, + ConfigurationTarget target, Language l, + Function, AppDependencyConfig> determineDependencyConfig, + BiFunction includeDependency, + Function consumer) throws OpenemsNamedException { + var appConfig = DependencyDeclaration.AppDependencyConfig.create() // + .setAppId(app.getAppId()) // + .setAlias(alias) // + .setProperties(defaultProperties) // + .build(); + this.foreachDependency(app, appConfig, target, consumer, null, l, null, null, determineDependencyConfig, + includeDependency); + } + private DependencyDeclaration.AppDependencyConfig determineDependencyConfig(List configs) { if (configs == null || configs.isEmpty()) { return null; @@ -829,7 +871,7 @@ private DependencyDeclaration.AppDependencyConfig determineDependencyConfig(List if (configs.size() == 1) { return configs.get(0); } - // TODO multiple dependency configs + for (var config : configs) { var instances = this.getAppManagerImpl().getInstantiatedApps().stream() .filter(i -> i.appId.equals(config.appId)).collect(Collectors.toList()); @@ -841,19 +883,7 @@ private DependencyDeclaration.AppDependencyConfig determineDependencyConfig(List } } - return null; - } - - private void foreachDependency(OpenemsApp app, String alias, JsonObject defaultProperties, - ConfigurationTarget target, Language l, - Function, AppDependencyConfig> determineDependencyConfig, - Function consumer) throws OpenemsNamedException { - var appConfig = DependencyDeclaration.AppDependencyConfig.create() // - .setAppId(app.getAppId()) // - .setAlias(alias) // - .setProperties(defaultProperties) // - .build(); - this.foreachDependency(app, appConfig, target, consumer, null, l, null, null, determineDependencyConfig); + return configs.get(0); } private void foreachExistingDependecy(OpenemsAppInstance instance, ConfigurationTarget target, Language l, @@ -894,7 +924,7 @@ private DependencyConfig foreachExistingDependency(OpenemsAppInstance instance, dependecies = new ArrayList<>(instance.dependencies.size()); for (var dependency : instance.dependencies) { try { - var dependencyApp = this.getAppManagerImpl().findInstaceById(dependency.instanceId); + var dependencyApp = this.getAppManagerImpl().findInstanceById(dependency.instanceId); if (alreadyIteratedApps.contains(dependencyApp)) { continue; } diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java index 4478166b760..d15276c8ae6 100644 --- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java +++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerImplTest.java @@ -8,9 +8,9 @@ import java.util.Dictionary; import java.util.Hashtable; -import java.util.Map.Entry; import java.util.TreeMap; import java.util.UUID; +import java.util.Map.Entry; import org.junit.Before; import org.junit.Test; @@ -20,9 +20,12 @@ import com.google.common.collect.ImmutableList; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.session.Language; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.evcs.KebaEvcs; import io.openems.edge.app.integratedsystem.FeneconHome; +import io.openems.edge.app.pvselfconsumption.GridOptimizedCharge; +import io.openems.edge.app.pvselfconsumption.SelfConsumptionOptimization; import io.openems.edge.app.timeofusetariff.AwattarHourly; import io.openems.edge.app.timeofusetariff.StromdaoCorrently; import io.openems.edge.common.host.Host; @@ -30,8 +33,14 @@ import io.openems.edge.common.test.DummyComponentContext; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.core.appmanager.dependency.AggregateTask; +import io.openems.edge.core.appmanager.dependency.AppManagerAppHelperImpl; +import io.openems.edge.core.appmanager.dependency.ComponentAggregateTaskImpl; +import io.openems.edge.core.appmanager.dependency.SchedulerAggregateTaskImpl; +import io.openems.edge.core.appmanager.dependency.StaticIpAggregateTaskImpl; import io.openems.edge.core.appmanager.validator.CheckCardinality; import io.openems.edge.core.appmanager.validator.Validator; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; public class AppManagerImplTest { @@ -39,7 +48,12 @@ public class AppManagerImplTest { private DummyComponentManager componentManger; private ComponentUtil componentUtil; + private Validator validator = new DummyValidator(); + private FeneconHome homeApp; + private GridOptimizedCharge gridOptimizedCharge; + private SelfConsumptionOptimization selfConsumptionOptimization; + private KebaEvcs kebaEvcsApp; private AwattarHourly awattarApp; private StromdaoCorrently stromdao; @@ -49,13 +63,12 @@ public class AppManagerImplTest { @Before public void beforeEach() throws Exception { - this.cm = new DummyConfigurationAdmin(); - this.cm.getOrCreateEmptyConfiguration(AppManager.SINGLETON_SERVICE_PID); - final var essId = "ess0"; final var modbusIdInternal = "modbus0"; final var modbusIdExternal = "modbus1"; + final var meterId = "meter0"; + final var emergencyReserveEnabled = false; // Battery-Inverter Settings @@ -63,6 +76,9 @@ public void beforeEach() throws Exception { final var maxFeedInPower = 10000; final var feedInSetting = "LAGGING_0_95"; + this.cm = new DummyConfigurationAdmin(); + this.cm.getOrCreateEmptyConfiguration(AppManager.SINGLETON_SERVICE_PID); + this.componentManger = new DummyComponentManager(); this.componentManger.setConfigJson(JsonUtils.buildJsonObject() // .add("components", JsonUtils.buildJsonObject() // @@ -94,7 +110,7 @@ public void beforeEach() throws Exception { .addProperty("invalidateElementsAfterReadErrors", 1) // .build()) // .build()) // - .add("meter0", JsonUtils.buildJsonObject() // + .add(meterId, JsonUtils.buildJsonObject() // .addProperty("factoryId", "GoodWe.Grid-Meter") // .addProperty("alias", "Netzzähler") // .add("properties", JsonUtils.buildJsonObject() // @@ -230,6 +246,13 @@ public void beforeEach() throws Exception { this.homeApp = new FeneconHome(this.componentManger, getComponentContext("App.FENECON.Home"), this.cm, this.componentUtil); + + this.gridOptimizedCharge = new GridOptimizedCharge(this.componentManger, + getComponentContext("App.PvSelfConsumption.GridOptimizedCharge"), this.cm, this.componentUtil); + + this.selfConsumptionOptimization = new SelfConsumptionOptimization(this.componentManger, + getComponentContext("App.PvSelfConsumption.SelfConsumptionOptimization"), this.cm, this.componentUtil); + this.kebaEvcsApp = new KebaEvcs(this.componentManger, getComponentContext("App.Evcs.Keba"), this.cm, this.componentUtil); this.awattarApp = new AwattarHourly(this.componentManger, getComponentContext("App.TimeVariablePrice.Awattar"), @@ -237,18 +260,28 @@ public void beforeEach() throws Exception { this.stromdao = new StromdaoCorrently(this.componentManger, getComponentContext("App.TimeVariablePrice.Stromdao"), this.cm, this.componentUtil); + AggregateTask.ComponentAggregateTask componentTask = new ComponentAggregateTaskImpl(this.componentManger); + AggregateTask.SchedulerAggregateTask schedulerTask = new SchedulerAggregateTaskImpl(componentTask, + this.componentUtil); + AggregateTask.StaticIpAggregateTask staticIpTask = new StaticIpAggregateTaskImpl(this.componentUtil); + + var appManagerAppHelper = new AppManagerAppHelperImpl(this.componentManger, this.componentUtil, this.validator, + componentTask, schedulerTask, staticIpTask); + this.sut = new AppManagerImpl(); new ComponentTest(this.sut) // .addReference("cm", this.cm) // .addReference("componentManager", this.componentManger) // + .addReference("appHelper", appManagerAppHelper) // .addReference("availableApps", - ImmutableList.of(this.homeApp, this.kebaEvcsApp, this.awattarApp, this.stromdao)) // + ImmutableList.of(this.homeApp, this.gridOptimizedCharge, this.selfConsumptionOptimization, + this.kebaEvcsApp, this.awattarApp, this.stromdao)) // .activate(MyConfig.create() // .setApps(JsonUtils.buildJsonArray() // .add(JsonUtils.buildJsonObject() // .addProperty("appId", "App.FENECON.Home") // .addProperty("alias", "FENECON Home") // - .addProperty("instanceId", "ef13f394-1a3c-43ed-b726-ef1efaf23fdf") // + .addProperty("instanceId", UUID.randomUUID().toString()) // .add("properties", JsonUtils.buildJsonObject() // .addProperty("SAFETY_COUNTRY", safetyCountry) // .addProperty("MAX_FEED_IN_POWER", maxFeedInPower) // @@ -259,6 +292,24 @@ public void beforeEach() throws Exception { .addProperty("EMERGENCY_RESERVE_ENABLED", emergencyReserveEnabled) // .build()) // .build()) // + .add(JsonUtils.buildJsonObject() // + .addProperty("appId", "App.PvSelfConsumption.GridOptimizedCharge") // + .addProperty("alias", "") // + .addProperty("instanceId", UUID.randomUUID().toString()) // + .add("properties", JsonUtils.buildJsonObject() // + .addProperty("SELL_TO_GRID_LIMIT_ENABLED", true) // + .addProperty("MAXIMUM_SELL_TO_GRID_POWER", maxFeedInPower) // + .build()) // + .build()) + .add(JsonUtils.buildJsonObject() // + .addProperty("appId", "App.PvSelfConsumption.SelfConsumptionOptimization") // + .addProperty("alias", "") // + .addProperty("instanceId", UUID.randomUUID().toString()) // + .add("properties", JsonUtils.buildJsonObject() // + .addProperty("ESS_ID", essId) // + .addProperty("METER_ID", meterId) // + .build()) // + .build()) .build().toString()) // .build()); } @@ -268,7 +319,7 @@ public void testAppValidateWorker() throws OpenemsException, Exception { var worker = new AppValidateWorker(this.sut); worker.validateApps(); - assertEquals(this.sut.instantiatedApps.size(), 1); + assertEquals(this.sut.instantiatedApps.size(), 3); // should not have found defective Apps for (Entry entry : worker.defectiveApps.entrySet()) { @@ -281,15 +332,6 @@ public void testGetInstantiatedApps() { this.sut.getInstantiatedApps().add(null); } -// @Test TODO -// public void testGetReplaceableComponentIds() throws Exception { -// var replaceableIds = this.sut.getReplaceableComponentIds(this.kebaEvcsApp, JsonUtils.buildJsonObject().build()); -// -// assertEquals(replaceableIds.size(), 2); -// assertEquals("EVCS_ID", replaceableIds.get("evcs0")); -// assertEquals("CTRL_EVCS_ID", replaceableIds.get("ctrlEvcs0")); -// } - @Test public void testFindAppById() { assertEquals(this.homeApp, this.sut.findAppById("App.FENECON.Home")); @@ -297,12 +339,12 @@ public void testFindAppById() { @Test public void testCheckCardinalitySingle() throws Exception { - var checkable = new CheckCardinality(this.sut); - checkable.setProperties(new Validator.MapBuilder<>(new TreeMap()) // + var checkable = new CheckCardinality(this.sut, getComponentContext(CheckCardinality.COMPONENT_NAME)); + checkable.setProperties(new ValidatorConfig.MapBuilder<>(new TreeMap()) // .put("openemsApp", this.homeApp) // .build()); assertFalse(checkable.check()); - assertNotNull(checkable.getErrorMessage()); + assertNotNull(checkable.getErrorMessage(Language.DEFAULT)); } @Test @@ -311,24 +353,24 @@ public void testCheckCardinalityMultiple() throws Exception { JsonUtils.buildJsonObject().build(), null)); this.sut.instantiatedApps.add(new OpenemsAppInstance(this.kebaEvcsApp.getAppId(), "alias", UUID.randomUUID(), JsonUtils.buildJsonObject().build(), null)); - var checkable = new CheckCardinality(this.sut); - checkable.setProperties(new Validator.MapBuilder<>(new TreeMap()) // + var checkable = new CheckCardinality(this.sut, getComponentContext(CheckCardinality.COMPONENT_NAME)); + checkable.setProperties(new ValidatorConfig.MapBuilder<>(new TreeMap()) // .put("openemsApp", this.kebaEvcsApp) // .build()); assertTrue(checkable.check()); - assertNull(checkable.getErrorMessage()); + assertNull(checkable.getErrorMessage(Language.DEFAULT)); } @Test public void testCheckCardinalitySingleInCategorie() throws Exception { this.sut.instantiatedApps.add(new OpenemsAppInstance(this.awattarApp.getAppId(), "alias", UUID.randomUUID(), JsonUtils.buildJsonObject().build(), null)); - var checkable = new CheckCardinality(this.sut); - checkable.setProperties(new Validator.MapBuilder<>(new TreeMap()) // + var checkable = new CheckCardinality(this.sut, getComponentContext(CheckCardinality.COMPONENT_NAME)); + checkable.setProperties(new ValidatorConfig.MapBuilder<>(new TreeMap()) // .put("openemsApp", this.stromdao) // .build()); assertFalse(checkable.check()); - assertNotNull(checkable.getErrorMessage()); + assertNotNull(checkable.getErrorMessage(Language.DEFAULT)); } private static ComponentContext getComponentContext(String appId) { From 7dc6882d3ecb870063e1895289733428a63f2f4f Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:33:02 +0200 Subject: [PATCH 20/30] renamed Apps to App Center --- ui/src/app/edge/settings/settings.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/edge/settings/settings.component.html b/ui/src/app/edge/settings/settings.component.html index 5c67be5084f..42b7b06a13f 100644 --- a/ui/src/app/edge/settings/settings.component.html +++ b/ui/src/app/edge/settings/settings.component.html @@ -123,7 +123,7 @@ - {{ environment.edgeShortName }} Apps (Beta-Test) + {{ environment.edgeShortName }} App Center From 6700372fd6ffadb78587c583328c54b5ee654460 Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:33:52 +0200 Subject: [PATCH 21/30] added translation for checkables --- .../edge/core/appmanager/OpenemsApp.java | 5 +-- .../validator/AbstractCheckable.java | 26 ++++++++++++ .../validator/CheckAppsNotInstalled.java | 4 +- .../validator/CheckCardinality.java | 40 +++++++++++++++---- .../core/appmanager/validator/CheckHome.java | 2 +- .../core/appmanager/validator/CheckHost.java | 10 +++-- .../CheckNoComponentInstalledOfFactoryId.java | 4 +- .../appmanager/validator/CheckRelayCount.java | 4 +- .../core/appmanager/validator/Validator.java | 7 ++-- .../validator/translation_de.properties | 13 ++++++ .../validator/translation_en.properties | 13 ++++++ .../app/edge/settings/app/update.component.ts | 2 + ui/src/app/shared/translate/de.ts | 2 +- 13 files changed, 105 insertions(+), 27 deletions(-) create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/translation_de.properties create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/translation_en.properties diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java index 34e16a5a2dc..79e141a1ac6 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java @@ -6,7 +6,6 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.session.Language; -import io.openems.edge.core.appmanager.validator.Validator; import io.openems.edge.core.appmanager.validator.ValidatorConfig; public interface OpenemsApp { @@ -75,9 +74,9 @@ public AppConfiguration getAppConfiguration(ConfigurationTarget target, JsonObje public OpenemsAppCardinality getCardinality(); /** - * Gets the {@link Validator} of this {@link OpenemsApp}. + * Gets the {@link ValidatorConfig} of this {@link OpenemsApp}. * - * @return the Validator + * @return the ValidatorConfig */ public ValidatorConfig getValidatorConfig(); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java index 2d58f9b5ef5..0124f372fb0 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java @@ -1,8 +1,13 @@ package io.openems.edge.core.appmanager.validator; +import java.text.MessageFormat; +import java.util.ResourceBundle; + import org.osgi.service.component.ComponentConstants; import org.osgi.service.component.ComponentContext; +import io.openems.common.session.Language; + public abstract class AbstractCheckable implements Checkable { private final ComponentContext componentContext; @@ -16,4 +21,25 @@ public String getComponentName() { return this.componentContext.getProperties().get(ComponentConstants.COMPONENT_NAME).toString(); } + protected static String getTranslation(Language language, String key, Object... params) { + // TODO translation + switch (language) { + case CZ: + case ES: + case FR: + case NL: + language = Language.EN; + break; + case DE: + case EN: + break; + } + + var translationBundle = ResourceBundle.getBundle("io.openems.edge.core.appmanager.validator.translation", + language.getLocal()); + var errorMessage = translationBundle.getString(key); + + return MessageFormat.format(errorMessage, params); + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckAppsNotInstalled.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckAppsNotInstalled.java index e8f704b7f5e..89914e4b73f 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckAppsNotInstalled.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckAppsNotInstalled.java @@ -63,8 +63,8 @@ private AppManagerImpl getAppManagerImpl() { @Override public String getErrorMessage(Language language) { - return "Apps with ID[" + this.installedApps.stream().collect(Collectors.joining(", ")) + "] are installed!" - + System.lineSeparator() + "Delete them to be able to install this App."; + return AbstractCheckable.getTranslation(language, "Validator.Checkable.CheckAppsNotInstalled.Message", + this.installedApps.stream().collect(Collectors.joining(", "))); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java index 94a990d299a..1c32a4308b7 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java @@ -25,7 +25,17 @@ public class CheckCardinality extends AbstractCheckable implements Checkable { private final AppManager appManager; private OpenemsApp openemsApp; - private String errorMessage = null; + private ErrorType errorType = ErrorType.NONE; + private String errorMessage; + private OpenemsAppCategory matchingCategory; + + private static enum ErrorType { + SAME_CATEGORIE, // + SAME_APP, // + NONE, // + OTHER, // + ; + } @Activate public CheckCardinality(@Reference AppManager appManager, ComponentContext componentContext) { @@ -40,13 +50,16 @@ public void setProperties(Map properties) { @Override public boolean check() { + this.errorType = ErrorType.NONE; this.errorMessage = null; if (this.appManager == null) { this.errorMessage = "App Manager not available!"; + this.errorType = ErrorType.OTHER; return false; } if (!(this.appManager instanceof AppManagerImpl)) { this.errorMessage = "Wrong AppManager active!"; + this.errorType = ErrorType.OTHER; return false; } var appManagerImpl = (AppManagerImpl) this.appManager; @@ -56,25 +69,23 @@ public boolean check() { case SINGLE: if (instantiatedApps.stream().anyMatch(t -> t.appId.equals(this.openemsApp.getAppId()))) { // only create one instance of this app - this.errorMessage = "An instance of the app[" + this.openemsApp.getAppId() + "] is already created!"; + this.errorType = ErrorType.SAME_APP; } break; case SINGLE_IN_CATEGORY: var matchedCategorie = this.getMatchingCategorie(appManagerImpl, instantiatedApps); if (matchedCategorie != null) { // only create one instance with the same category of this app - this.errorMessage = "An instance of an app with the same category[" + matchedCategorie.name() - + "] is already created!"; + this.matchingCategory = matchedCategorie; + this.errorType = ErrorType.SAME_CATEGORIE; } break; case MULTIPLE: // any number of this app can be instantiated break; - default: - this.errorMessage = "Usage '" + this.openemsApp.getCardinality().name() + "' is not implemented."; } - return this.errorMessage == null; + return this.errorType == ErrorType.NONE; } private OpenemsAppCategory getMatchingCategorie(AppManagerImpl appManager, @@ -102,7 +113,20 @@ private OpenemsAppCategory getMatchingCategorie(AppManagerImpl appManager, @Override public String getErrorMessage(Language language) { - return this.errorMessage; + switch (errorType) { + case SAME_APP: + return AbstractCheckable.getTranslation(language, "Validator.Checkable.CheckCardinality.Message.Single", + this.openemsApp.getAppId()); + case SAME_CATEGORIE: + return AbstractCheckable.getTranslation(language, + "Validator.Checkable.CheckCardinality.Message.SingleInCategorie", + this.matchingCategory.getReadableName(language)); + case OTHER: + return this.errorMessage; + case NONE: + return null; + } + return null; } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java index b0065cda6da..e8b53cc5280 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHome.java @@ -45,7 +45,7 @@ public boolean check() { @Override public String getErrorMessage(Language language) { - return "No Home installed"; + return AbstractCheckable.getTranslation(language, "Validator.Checkable.CheckHome.Message"); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHost.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHost.java index 6f092912180..8d88c319866 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHost.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckHost.java @@ -69,12 +69,14 @@ public boolean check() { @Override public String getErrorMessage(Language language) { - // TODO translation - var portMsg = this.port != null ? " on Port " + this.port : ""; + var address = this.host.getHostAddress(); + if (this.port != null) { + address += ":" + this.port; + } if (this.host == null) { - return "IP '" + this.host.getHostAddress() + "'" + portMsg + " is not a valid IP-Address"; + return AbstractCheckable.getTranslation(language, "Validator.Checkable.CheckHost.WrongIp", address); } - return "Device with IP '" + this.host.getHostAddress() + "'" + portMsg + " is not reachable!"; + return AbstractCheckable.getTranslation(language, "Validator.Checkable.CheckHost.NotReachable", address); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckNoComponentInstalledOfFactoryId.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckNoComponentInstalledOfFactoryId.java index 8effcfbed48..42739394733 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckNoComponentInstalledOfFactoryId.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckNoComponentInstalledOfFactoryId.java @@ -38,8 +38,8 @@ public boolean check() { @Override public String getErrorMessage(Language language) { - return "Components with the FactorieID[" + this.factorieId + "] are installed!" + System.lineSeparator() - + "Remove them to be able to install this app."; + return AbstractCheckable.getTranslation(language, + "Validator.Checkable.CheckNoComponentInstalledOfFactorieId.Message", this.factorieId); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckRelayCount.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckRelayCount.java index fd02635a4e7..c5566b7ad39 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckRelayCount.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckRelayCount.java @@ -62,9 +62,7 @@ public boolean check() { @Override public String getErrorMessage(Language language) { - // TODO translation - return "There are not enough Relay ports available!" + System.lineSeparator() - + "Install a Relay to be able to install this App."; + return AbstractCheckable.getTranslation(language, "Validator.Checkable.CheckRelayCount.Message"); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java index 9161fede5e3..3f541095ff0 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Validator.java @@ -40,10 +40,11 @@ public default List getErrorInstallableMessages(ValidatorConfig config, * @return the Status */ public default OpenemsAppStatus getStatus(ValidatorConfig config) { - if (!this.getErrorMessages(config.getCompatibleCheckableConfigs(), null, true).isEmpty()) { + // language not need for status + if (!this.getErrorMessages(config.getCompatibleCheckableConfigs(), Language.DEFAULT, true).isEmpty()) { return OpenemsAppStatus.INCOMPATIBLE; } - if (!this.getErrorMessages(config.getInstallableCheckableConfigs(), null, true).isEmpty()) { + if (!this.getErrorMessages(config.getInstallableCheckableConfigs(), Language.DEFAULT, true).isEmpty()) { return OpenemsAppStatus.COMPATIBLE; } return OpenemsAppStatus.INSTALLABLE; @@ -76,7 +77,7 @@ public default JsonObject toJsonObject(ValidatorConfig config, Language language * @param returnImmediate after the first checkable who returns false * @return a list of errors */ - public abstract List getErrorMessages(List checkableConfigs, Language language, + public List getErrorMessages(List checkableConfigs, Language language, boolean returnImmediate); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/translation_de.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/translation_de.properties new file mode 100644 index 00000000000..03f60419265 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/translation_de.properties @@ -0,0 +1,13 @@ +Validator.Checkable.CheckAppsNotInstalled.Message = Apps mit der ID[{0}] sind installiert! Deinstalliere diese um diese App installieren zu k�nnen. + +Validator.Checkable.CheckCardinality.Message.SingleInCategorie = Eine Instanz einer App mit der selben Kategorie "{0}" ist bereits erstellt worden! +Validator.Checkable.CheckCardinality.Message.Single = Eine Instanz der App[{0}] ist bereits erstellt worden! + +Validator.Checkable.CheckHome.Message = Kein Home System installiert! + +Validator.Checkable.CheckHost.NotReachable = Ger�t mit der IP "{0}" ist nicht erreichbar! +Validator.Checkable.CheckHost.WrongIp = IP "{0}" ist keine valide IP-Adresse! + +Validator.Checkable.CheckNoComponentInstalledOfFactorieId.Message = Komponenten mit der FaktorieID[{0}] sind installiert! Entferne diese um diese App installieren zu k�nnen. + +Validator.Checkable.CheckRelayCount.Message = Es sind nicht genug Relais ports verf�gbar! Installiere ein Relais um diese App installieren zu k�nnen. diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/translation_en.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/translation_en.properties new file mode 100644 index 00000000000..23147b16cc1 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/translation_en.properties @@ -0,0 +1,13 @@ +Validator.Checkable.CheckAppsNotInstalled.Message = Apps with ID[{0}] are installed! Delete them to be able to install this App. + +Validator.Checkable.CheckCardinality.Message.SingleInCategorie = An instance of an app with the same category '{0}' is already created! +Validator.Checkable.CheckCardinality.Message.Single = An instance of the app[{0}] is already created! + +Validator.Checkable.CheckHome.Message = No Home system installed! + +Validator.Checkable.CheckHost.NotReachable = Device with IP "{0}" is not reachable! +Validator.Checkable.CheckHost.WrongIp = IP "{0}" is not a valid IP-Address! + +Validator.Checkable.CheckNoComponentInstalledOfFactorieId.Message = Components with the FactorieID[{0}] are installed! Remove them to be able to install this app. + +Validator.Checkable.CheckRelayCount.Message = There are not enough Relay ports available! Install a Relay to be able to install this App. diff --git a/ui/src/app/edge/settings/app/update.component.ts b/ui/src/app/edge/settings/app/update.component.ts index d6da6d1e125..9250662c213 100644 --- a/ui/src/app/edge/settings/app/update.component.ts +++ b/ui/src/app/edge/settings/app/update.component.ts @@ -143,6 +143,8 @@ export class UpdateAppComponent implements OnInit { this.service.toast("Successfully deleted App", 'success'); }).catch(reason => { this.service.toast("Error deleting App:" + reason.error.message, 'danger'); + }).finally(() => { + instance.isDeleting = false }) } } \ No newline at end of file diff --git a/ui/src/app/shared/translate/de.ts b/ui/src/app/shared/translate/de.ts index 36f905adfb9..3e06df6e1c0 100644 --- a/ui/src/app/shared/translate/de.ts +++ b/ui/src/app/shared/translate/de.ts @@ -476,7 +476,7 @@ export const TRANSLATION = { header: 'Der App-Manager befindet sich aktuell in einer ersten Testversion. Falls nicht alle Apps angezeigt werden, muss evtl. die FEMS Version geupdatet werden.', installed: 'Installiert', available: 'Verfügbar', - incompatible: 'Incompatible', + incompatible: 'Inkompatibel', buyApp: 'App kaufen', modifyApp: 'App bearbeiten', createApp: 'App anlegen', From aa4a19044e2fd5fee8b2e640c8e7505f0e740e7a Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:34:15 +0200 Subject: [PATCH 22/30] renamed counter to meter --- .../openems/edge/core/appmanager/translation_en.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties index 8da830a98d0..b508bb6c13e 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties @@ -121,10 +121,10 @@ App.Meter.production = Production App.Meter.gridMeter = Grid-Meter App.Meter.consumtionMeter = Consumption-Meter -App.Meter.CarloGavazzi.Name = CARLO GAVAZZI Counter -App.Meter.Janitza.Name = Janitza Counter +App.Meter.CarloGavazzi.Name = CARLO GAVAZZI Meter +App.Meter.Janitza.Name = Janitza Meter App.Meter.Janitza.productModel = Product Model -App.Meter.Socomec.Name = SOCOMEC Counter +App.Meter.Socomec.Name = SOCOMEC Meter # PV-Inverter App.PvInverter.ip.description = The IP address of the PV-Inverter. From 4f9c2e61ed3792195a855d02dc1d03484472f57c Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:35:38 +0200 Subject: [PATCH 23/30] added util class for translation to avoid exceptions --- .../edge/app/api/RestJsonApiReadWrite.java | 6 +- .../io/openems/edge/app/evcs/EvcsCluster.java | 6 +- .../openems/edge/app/evcs/HardyBarthEvcs.java | 6 +- .../openems/edge/app/evcs/IesKeywattEvcs.java | 12 ++- .../io/openems/edge/app/evcs/KebaEvcs.java | 6 +- .../edge/app/hardware/KMtronic8Channel.java | 6 +- .../edge/app/heat/CombinedHeatAndPower.java | 7 +- .../io/openems/edge/app/heat/HeatPump.java | 13 +++- .../openems/edge/app/heat/HeatingElement.java | 19 +++-- .../app/integratedsystem/FeneconHome.java | 73 ++++++++++++------- .../app/loadcontrol/ManualRelayControl.java | 7 +- .../app/loadcontrol/ThresholdControl.java | 7 +- .../edge/app/meter/AbstractMeterApp.java | 7 +- .../edge/app/meter/CarloGavazziMeter.java | 7 +- .../openems/edge/app/meter/JanitzaMeter.java | 13 ++-- .../openems/edge/app/meter/SocomecMeter.java | 7 +- .../edge/app/pvinverter/KacoPvInverter.java | 10 ++- .../edge/app/pvinverter/KostalPvInverter.java | 14 ++-- .../edge/app/pvinverter/SmaPvInverter.java | 14 ++-- .../app/pvinverter/SolarEdgePvInverter.java | 10 ++- .../GridOptimizedCharge.java | 15 ++-- .../SelfConsumptionOptimization.java | 11 ++- .../timeofusetariff/StromdaoCorrently.java | 6 +- .../edge/app/timeofusetariff/Tibber.java | 7 +- .../core/appmanager/AbstractOpenemsApp.java | 5 +- .../edge/core/appmanager/TranslationUtil.java | 28 +++++++ 26 files changed, 215 insertions(+), 107 deletions(-) create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/TranslationUtil.java diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java index da7a8dde510..4dcbc5fb4ab 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/RestJsonApiReadWrite.java @@ -31,6 +31,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.validator.CheckAppsNotInstalled; import io.openems.edge.core.appmanager.validator.ValidatorConfig; @@ -75,8 +76,9 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.API_TIMEOUT) // - .setLabel(bundle.getString("App.Api.apiTimeout.label")) // - .setDescription(bundle.getString("App.Api.apiTimeout.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "App.Api.apiTimeout.label")) // + .setDescription( + TranslationUtil.getTranslation(bundle, "App.Api.apiTimeout.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(60) // .setMin(30) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java index 4b3e2dc2024..cdc75cf54f6 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java @@ -30,6 +30,9 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; +import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; +import io.openems.edge.core.appmanager.dependency.DependencyUtil; /** * Describes a evcs cluster. @@ -92,7 +95,8 @@ public AppAssistant getAppAssistant(Language language) { .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildSelect(Property.EVCS_IDS) // .setLabel("EVCS-IDs") // - .setDescription(bundle.getString(this.getAppId() + ".evcsIds.description")) // + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".evcsIds.description")) // .setOptions(this.componentUtil.getEnabledComponentsOfStartingId("evcs").stream() .filter(t -> !t.id().startsWith("evcsCluster")).collect(Collectors.toList()), t -> t.alias() == null || t.alias().isEmpty() ? t.id() diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java index c7a2ccdb442..e0a36d5abe0 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java @@ -29,6 +29,7 @@ import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Validation; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.TranslationUtil; /** * Describes a Hardy Barth evcs App. @@ -102,8 +103,9 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel(bundle.getString("ipAddress")) // - .setDescription(bundle.getString(this.getAppId() + ".Ip.description")) + .setLabel(TranslationUtil.getTranslation(bundle, "ipAddress")) // + .setDescription( + TranslationUtil.getTranslation(bundle, this.getAppId() + ".Ip.description")) .setDefaultValue(Property.IP.getDefaultValue()) // .isRequired(true) // .setValidation(Validation.IP) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java index 971d60dfec7..6c89cfcd3e7 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/IesKeywattEvcs.java @@ -30,6 +30,7 @@ import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.TranslationUtil; /** * Describes a IES Keywatt evcs App. @@ -111,14 +112,17 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.OCCP_CHARGE_POINT_IDENTIFIER) // - .setLabel(bundle.getString(this.getAppId() + ".chargepoint.label")) // - .setDescription(bundle.getString(this.getAppId() + ".chargepoint.description")) // + .setLabel( + TranslationUtil.getTranslation(bundle, this.getAppId() + ".chargepoint.label")) // + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".chargepoint.description")) // .setDefaultValue(Property.OCCP_CHARGE_POINT_IDENTIFIER.getDefaultValue()) // .isRequired(true) // .build()) // .add(JsonFormlyUtil.buildInput(Property.OCCP_CONNECTOR_IDENTIFIER) // - .setLabel(bundle.getString(this.getAppId() + ".connector.label")) // - .setDescription(bundle.getString(this.getAppId() + ".connector.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".connector.label")) // + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".connector.description")) // .setDefaultValue(Property.OCCP_CONNECTOR_IDENTIFIER.getDefaultValue()) // .isRequired(true) // .setInputType(Type.NUMBER) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java index 5a06296bdd3..d94bc8cb53a 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java @@ -28,6 +28,7 @@ import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Validation; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.TranslationUtil; /** * Describes a Keba evcs App. @@ -101,8 +102,9 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel(bundle.getString("ipAddress")) // - .setDescription(bundle.getString(this.getAppId() + ".Ip.description")) + .setLabel(TranslationUtil.getTranslation(bundle, "ipAddress")) // + .setDescription( + TranslationUtil.getTranslation(bundle, this.getAppId() + ".Ip.description")) .setDefaultValue(Property.IP.getDefaultValue()) // .isRequired(true) // .setValidation(Validation.IP) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java b/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java index f702598d86c..744c03f7132 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java +++ b/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java @@ -30,6 +30,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; /** * Describes a App for KMtronic 8-Channel Relay. @@ -99,8 +100,9 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel(bundle.getString("ipAddress")) // - .setDescription(bundle.getString(this.getAppId() + ".Ip.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "ipAddress")) // + .setDescription( + TranslationUtil.getTranslation(bundle, this.getAppId() + ".Ip.description")) // .setDefaultValue("192.168.1.199") // .isRequired(true) // .setValidation(Validation.IP) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java b/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java index 40aba05be1d..d65353ed625 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java @@ -33,6 +33,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; import io.openems.edge.core.appmanager.dependency.DependencyUtil; import io.openems.edge.core.appmanager.validator.CheckRelayCount; @@ -144,8 +145,10 @@ public AppAssistant getAppAssistant(Language language) { } return relays[0]; }) // - .setLabel(bundle.getString(this.getAppId() + ".outputChannel.label")) // - .setDescription(bundle.getString(this.getAppId() + ".outputChannel.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannel.label")) // + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannel.description")) // .build()) .build()) .build(); diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java index 20c4308d813..f4074e0f672 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java @@ -30,6 +30,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; import io.openems.edge.core.appmanager.dependency.DependencyUtil; import io.openems.edge.core.appmanager.validator.CheckRelayCount; @@ -122,14 +123,18 @@ public AppAssistant getAppAssistant(Language language) { .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_1) // .setOptions(options) // .onlyIf(relays != null, t -> t.setDefaultValue(relays[0])) // - .setLabel(bundle.getString(this.getAppId() + ".outputChannel1.label")) - .setDescription(bundle.getString(this.getAppId() + ".outputChannel1.description")) + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannel1.label")) + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannel1.description")) .build()) .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_2) // .setOptions(options) // .onlyIf(relays != null, t -> t.setDefaultValue(relays[1])) // - .setLabel(bundle.getString(this.getAppId() + ".outputChannel2.label")) - .setDescription(bundle.getString(this.getAppId() + ".outputChannel2.description")) + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannel2.label")) + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannel2.description")) .build()) .build()) .build(); diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java index fa3626f54ce..523e226dd50 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java @@ -33,6 +33,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; import io.openems.edge.core.appmanager.dependency.DependencyUtil; import io.openems.edge.core.appmanager.validator.CheckRelayCount; @@ -145,20 +146,26 @@ public AppAssistant getAppAssistant(Language language) { .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_PHASE_L1) // .setOptions(options) // .onlyIf(relays != null, t -> t.setDefaultValue(relays[0])) // - .setLabel(bundle.getString(this.getAppId() + ".outputChannelPhaseL1.label")) - .setDescription(bundle.getString(this.getAppId() + ".outputChannelPhaseL1.description")) + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannelPhaseL1.label")) + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannelPhaseL1.description")) .build()) .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_PHASE_L2) // .setOptions(options) // .onlyIf(relays != null, t -> t.setDefaultValue(relays[1])) // - .setLabel(bundle.getString(this.getAppId() + ".outputChannelPhaseL2.label")) - .setDescription(bundle.getString(this.getAppId() + ".outputChannelPhaseL2.description")) + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannelPhaseL2.label")) + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannelPhaseL2.description")) .build()) .add(JsonFormlyUtil.buildSelect(Property.OUTPUT_CHANNEL_PHASE_L3) // .setOptions(options) // .onlyIf(relays != null, t -> t.setDefaultValue(relays[2])) // - .setLabel(bundle.getString(this.getAppId() + ".outputChannelPhaseL3.label")) - .setDescription(bundle.getString(this.getAppId() + ".outputChannelPhaseL3.description")) + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannelPhaseL3.label")) + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannelPhaseL3.description")) .build()) .build()) .build(); diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java index 40e911fa901..6ae24b62dc5 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java @@ -38,6 +38,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; /** @@ -137,7 +138,7 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { var bundle = AbstractOpenemsApp.getTranslationBundle(l); var components = Lists.newArrayList(// new EdgeConfig.Component(modbusIdInternal, - bundle.getString(this.getAppId() + "." + modbusIdInternal + ".alias"), + TranslationUtil.getTranslation(bundle, this.getAppId() + "." + modbusIdInternal + ".alias"), "Bridge.Modbus.Serial", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("portName", "/dev/busUSB1") // @@ -150,7 +151,7 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { j -> j.addProperty("invalidateElementsAfterReadErrors", 1) // ).build()), new EdgeConfig.Component(modbusIdExternal, - bundle.getString(this.getAppId() + "." + modbusIdExternal + ".alias"), + TranslationUtil.getTranslation(bundle, this.getAppId() + "." + modbusIdExternal + ".alias"), "Bridge.Modbus.Serial", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("portName", "/dev/busUSB2") // @@ -161,21 +162,23 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { .addProperty("logVerbosity", "NONE") // .addProperty("invalidateElementsAfterReadErrors", 1) // .build()), - new EdgeConfig.Component("meter0", bundle.getString(this.getAppId() + ".meter0.alias"), + new EdgeConfig.Component("meter0", + TranslationUtil.getTranslation(bundle, this.getAppId() + ".meter0.alias"), "GoodWe.Grid-Meter", // JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("modbus.id", modbusIdExternal) // .addProperty("modbusUnitId", 247) // .build()), - new EdgeConfig.Component("io0", bundle.getString(this.getAppId() + ".io0.alias"), - "IO.KMtronic.4Port", // + new EdgeConfig.Component("io0", + TranslationUtil.getTranslation(bundle, this.getAppId() + ".io0.alias"), "IO.KMtronic.4Port", // JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("modbus.id", modbusIdInternal) // .addProperty("modbusUnitId", 2) // .build()), - new EdgeConfig.Component("battery0", bundle.getString(this.getAppId() + ".battery0.alias"), + new EdgeConfig.Component("battery0", + TranslationUtil.getTranslation(bundle, this.getAppId() + ".battery0.alias"), "Battery.Fenecon.Home", // JsonUtils.buildJsonObject() // .addProperty("enabled", true) // @@ -185,7 +188,7 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { .addProperty("batteryStartUpRelay", "io0/Relay4") // .build()), new EdgeConfig.Component("batteryInverter0", - bundle.getString(this.getAppId() + ".batteryInverter0.alias"), // + TranslationUtil.getTranslation(bundle, this.getAppId() + ".batteryInverter0.alias"), "GoodWe.BatteryInverter", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("modbus.id", modbusIdExternal) // @@ -197,14 +200,16 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { .addProperty("feedPowerPara", maxFeedInPower) // .addProperty("setfeedInPowerSettings", feedInSetting) // .build()), - new EdgeConfig.Component(essId, bundle.getString(this.getAppId() + "." + essId + ".alias"), + new EdgeConfig.Component(essId, + TranslationUtil.getTranslation(bundle, this.getAppId() + "." + essId + ".alias"), "Ess.Generic.ManagedSymmetric", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("startStop", "START") // .addProperty("batteryInverter.id", "batteryInverter0") // .addProperty("battery.id", "battery0") // .build()), - new EdgeConfig.Component("predictor0", bundle.getString(this.getAppId() + ".predictor0.alias"), + new EdgeConfig.Component("predictor0", + TranslationUtil.getTranslation(bundle, this.getAppId() + ".predictor0.alias"), "Predictor.PersistenceModel", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .add("channelAddresses", JsonUtils.buildJsonArray() // @@ -213,7 +218,8 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { .build()) // .build()), new EdgeConfig.Component("ctrlEssSurplusFeedToGrid0", - bundle.getString(this.getAppId() + ".ctrlEssSurplusFeedToGrid0.alias"), + TranslationUtil.getTranslation(bundle, + this.getAppId() + ".ctrlEssSurplusFeedToGrid0.alias"), "Controller.Ess.Hybrid.Surplus-Feed-To-Grid", JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("ess.id", essId) // @@ -244,7 +250,8 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { var hasEmergencyReserve = EnumUtils.getAsOptionalBoolean(p, Property.HAS_EMERGENCY_RESERVE).orElse(false); if (hasEmergencyReserve) { - components.add(new EdgeConfig.Component("meter2", bundle.getString(this.getAppId() + ".meter2.alias"), + components.add(new EdgeConfig.Component("meter2", + TranslationUtil.getTranslation(bundle, this.getAppId() + ".meter2.alias"), "GoodWe.EmergencyPowerMeter", // JsonUtils.buildJsonObject() // .addProperty("enabled", true) // @@ -254,7 +261,8 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { var emergencyReserveSoc = EnumUtils.getAsInt(p, Property.EMERGENCY_RESERVE_SOC); components.add(new EdgeConfig.Component("ctrlEmergencyCapacityReserve0", - bundle.getString(this.getAppId() + ".ctrlEmergencyCapacityReserve0.alias"), + TranslationUtil.getTranslation(bundle, + this.getAppId() + ".ctrlEmergencyCapacityReserve0.alias"), "Controller.Ess.EmergencyCapacityReserve", // JsonUtils.buildJsonObject() // .addProperty("enabled", true) // @@ -334,19 +342,23 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildSelect(Property.SAFETY_COUNTRY) // - .setLabel(bundle.getString(this.getAppId() + ".safetyCountry.label")) // + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".safetyCountry.label")) // .isRequired(true) // .setOptions(JsonUtils.buildJsonArray() // .add(JsonUtils.buildJsonObject() // - .addProperty("label", bundle.getString("germany")) // + .addProperty("label", // + TranslationUtil.getTranslation(bundle, "germany")) // .addProperty("value", "GERMANY") // .build()) // .add(JsonUtils.buildJsonObject() // - .addProperty("label", bundle.getString("austria")) // + .addProperty("label", // + TranslationUtil.getTranslation(bundle, "austria")) // .addProperty("value", "AUSTRIA") // .build()) // .add(JsonUtils.buildJsonObject() // - .addProperty("label", bundle.getString("switzerland")) // + .addProperty("label", // + TranslationUtil.getTranslation(bundle, "switzerland")) // .addProperty("value", "SWITZERLAND") // .build()) // .build()) // @@ -355,13 +367,15 @@ public AppAssistant getAppAssistant(Language language) { .getProperty("safetyCountry").get().getAsString()); }).build()) .add(JsonFormlyUtil.buildCheckbox(Property.RIPPLE_CONTROL_RECEIVER_ACTIV) // - .setLabel(bundle.getString(this.getAppId() + ".rippleControlReceiver.label")) - .setDescription( - bundle.getString(this.getAppId() + ".rippleControlReceiver.description")) + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".rippleControlReceiver.label")) + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".rippleControlReceiver.description")) .setDefaultValue(false) // .build()) .add(JsonFormlyUtil.buildInput(Property.MAX_FEED_IN_POWER) // - .setLabel(bundle.getString(this.getAppId() + ".feedInLimit.label")) // + .setLabel( + TranslationUtil.getTranslation(bundle, this.getAppId() + ".feedInLimit.label")) // .isRequired(true) // .onlyShowIfNotChecked(Property.RIPPLE_CONTROL_RECEIVER_ACTIV) // .setInputType(Type.NUMBER) // @@ -370,7 +384,8 @@ public AppAssistant getAppAssistant(Language language) { .getProperty("feedPowerPara").get()); }).build()) .add(JsonFormlyUtil.buildSelect(Property.FEED_IN_SETTING) // - .setLabel(bundle.getString(this.getAppId() + ".feedInSettings.label")) // + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".feedInSettings.label")) // .isRequired(true) // .setOptions(this.getFeedInSettingsOptions(), t -> t, t -> t) // .onlyIf(batteryInverter.isPresent(), f -> { @@ -379,14 +394,15 @@ public AppAssistant getAppAssistant(Language language) { .get().getAsString()); }).build()) .add(JsonFormlyUtil.buildCheckbox(Property.HAS_AC_METER) // - .setLabel(bundle.getString(this.getAppId() + ".hasAcMeterSocomec.label")) // + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".hasAcMeterSocomec.label")) // .isRequired(true) // .setDefaultValue(this.componentUtil // .getComponent("meter1", "Meter.Socomec.Threephase") // .isPresent()) // .build()) .add(JsonFormlyUtil.buildCheckbox(Property.HAS_DC_PV1) // - .setLabel(bundle.getString(this.getAppId() + ".hasDcPV1.label")) // + .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".hasDcPV1.label")) // .isRequired(true) // .setDefaultValue(this.componentUtil // .getComponent("charger0", "GoodWe.Charger-PV1").isPresent()) @@ -406,7 +422,7 @@ public AppAssistant getAppAssistant(Language language) { })) .build()) .add(JsonFormlyUtil.buildCheckbox(Property.HAS_DC_PV2) // - .setLabel(bundle.getString(this.getAppId() + ".hasDcPV2.label")) // + .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".hasDcPV2.label")) // .isRequired(true) // .setDefaultValue(this.componentUtil // .getComponent("charger1", "GoodWe.Charger-PV2").isPresent()) @@ -426,19 +442,22 @@ public AppAssistant getAppAssistant(Language language) { })) .build()) .add(JsonFormlyUtil.buildCheckbox(Property.EMERGENCY_RESERVE_ENABLED) // - .setLabel(bundle.getString(this.getAppId() + ".emergencyPowerSupply.label")) // + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".emergencyPowerSupply.label")) // .isRequired(true) // .onlyIf(batteryInverter.isPresent(), f -> { f.setDefaultValue(batteryInverter.get().getProperty("backupEnable").get() .getAsString().equals("ENABLE")); }).build()) .add(JsonFormlyUtil.buildCheckbox(Property.HAS_EMERGENCY_RESERVE) // - .setLabel(bundle.getString(this.getAppId() + ".emergencyPowerEnergy.label")) // + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".emergencyPowerEnergy.label")) // .setDefaultValue(hasEmergencyReserve) // .onlyShowIfChecked(Property.EMERGENCY_RESERVE_ENABLED) // .build()) .add(JsonFormlyUtil.buildInput(Property.EMERGENCY_RESERVE_SOC) // - .setLabel(bundle.getString(this.getAppId() + ".reserveEnergy.label")) // + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".reserveEnergy.label")) // .setInputType(Type.NUMBER) // .setMin(0) // .setMax(100) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java index fb5c2244bcb..ae048b0d887 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java +++ b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java @@ -33,6 +33,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.validator.CheckRelayCount; import io.openems.edge.core.appmanager.validator.ValidatorConfig; @@ -120,8 +121,10 @@ public AppAssistant getAppAssistant(Language language) { return relays == null ? null : relays[0]; }) // .isRequired(true) // - .setLabel(bundle.getString(this.getAppId() + ".outputChannel.label")) // - .setDescription(bundle.getString(this.getAppId() + ".outputChannel.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannel.label")) // + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannel.description")) // .build()) .build()) .build(); diff --git a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java index 4274440c9fb..2007e9e4363 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java +++ b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java @@ -34,6 +34,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.validator.CheckRelayCount; import io.openems.edge.core.appmanager.validator.ValidatorConfig; @@ -125,8 +126,10 @@ public AppAssistant getAppAssistant(Language language) { return relays == null ? null : relays[0]; }) // .isRequired(true) // - .setLabel(bundle.getString(this.getAppId() + ".outputChannels.label")) // - .setDescription(bundle.getString(this.getAppId() + ".outputChannels.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannels.label")) // + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".outputChannels.description")) // .build()) .build()) .build(); diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/AbstractMeterApp.java b/io.openems.edge.core/src/io/openems/edge/app/meter/AbstractMeterApp.java index 96ef28c8fbe..dfb4b45b3a9 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/AbstractMeterApp.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/AbstractMeterApp.java @@ -11,6 +11,7 @@ import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.ComponentUtil; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; public abstract class AbstractMeterApp> extends AbstractOpenemsApp { @@ -28,15 +29,15 @@ protected final JsonArray buildMeterOptions(Language language) { var bundle = AbstractOpenemsApp.getTranslationBundle(language); return JsonUtils.buildJsonArray() // .add(JsonUtils.buildJsonObject() // - .addProperty("label", bundle.getString("App.Meter.production")) // + .addProperty("label", TranslationUtil.getTranslation(bundle, "App.Meter.production")) // .addProperty("value", "PRODUCTION") // .build()) .add(JsonUtils.buildJsonObject() // - .addProperty("label", bundle.getString("App.Meter.gridMeter")) // + .addProperty("label", TranslationUtil.getTranslation(bundle, "App.Meter.gridMeter")) // .addProperty("value", "GRID") // .build()) .add(JsonUtils.buildJsonObject() // - .addProperty("label", bundle.getString("App.Meter.consumtionMeter")) // + .addProperty("label", TranslationUtil.getTranslation(bundle, "App.Meter.consumtionMeter")) // .addProperty("value", "CONSUMPTION_METERED") // .build()) .build(); diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java index d84bc413afc..d85512f9a9c 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/CarloGavazziMeter.java @@ -31,6 +31,7 @@ import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.validator.CheckHome; import io.openems.edge.core.appmanager.validator.ValidatorConfig; @@ -105,12 +106,12 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildSelect(Property.TYPE) // - .setLabel(bundle.getString("App.Meter.mountType.label")) // + .setLabel(TranslationUtil.getTranslation(bundle, "App.Meter.mountType.label")) // .setOptions(this.buildMeterOptions(language)) // .build()) // .add(JsonFormlyUtil.buildInput(Property.MODBUS_UNIT_ID) // - .setLabel(bundle.getString("modbusUnitId")) // - .setDescription(bundle.getString("modbusUnitId.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "modbusUnitId")) // + .setDescription(TranslationUtil.getTranslation(bundle, "modbusUnitId.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(6) // .setMin(0) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java index 679611531e3..b0b395759cd 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/JanitzaMeter.java @@ -36,6 +36,7 @@ import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Validation; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.validator.CheckHome; import io.openems.edge.core.appmanager.validator.ValidatorConfig; @@ -127,25 +128,25 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildSelect(Property.MODEL) // - .setLabel(bundle.getString(this.getAppId() + ".productModel")) // + .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".productModel")) // .isRequired(true) // .setOptions(this.buildFactorieIdOptions()) // .build()) // .add(JsonFormlyUtil.buildSelect(Property.TYPE) // - .setLabel(bundle.getString("App.Meter.mountType.label")) // + .setLabel(TranslationUtil.getTranslation(bundle, "App.Meter.mountType.label")) // .isRequired(true) // .setOptions(this.buildMeterOptions(language)) // .build()) // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel(bundle.getString("ipAddress")) // - .setDescription(bundle.getString("App.Meter.ip.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "ipAddress")) // + .setDescription(TranslationUtil.getTranslation(bundle, "App.Meter.ip.description")) // .isRequired(true) // .setDefaultValue("10.4.0.12") // .setValidation(Validation.IP) // .build()) .add(JsonFormlyUtil.buildInput(Property.MODBUS_UNIT_ID) // - .setLabel(bundle.getString("modbusUnitId")) // - .setDescription(bundle.getString("modbusUnitId.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "modbusUnitId")) // + .setDescription(TranslationUtil.getTranslation(bundle, "modbusUnitId.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(1) // .setMin(0) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java b/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java index b5d3f99ba6a..34faaf73e33 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/meter/SocomecMeter.java @@ -31,6 +31,7 @@ import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.validator.CheckHome; import io.openems.edge.core.appmanager.validator.ValidatorConfig; @@ -104,13 +105,13 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildSelect(Property.TYPE) // - .setLabel(bundle.getString("App.Meter.mountType.label")) // + .setLabel(TranslationUtil.getTranslation(bundle, "App.Meter.mountType.label")) // .setOptions(this.buildMeterOptions(language)) // .setDefaultValue("PRODUCTION") // .build()) // .add(JsonFormlyUtil.buildInput(Property.MODBUS_UNIT_ID) // - .setLabel(bundle.getString("modbusUnitId")) // - .setDescription(bundle.getString("modbusUnitId.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "modbusUnitId")) // + .setDescription(TranslationUtil.getTranslation(bundle, "modbusUnitId.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(6) // .setMin(0) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KacoPvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KacoPvInverter.java index c08112650f8..5ddb758061a 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KacoPvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KacoPvInverter.java @@ -27,6 +27,7 @@ import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Validation; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.TranslationUtil; /** * Describes a App for Kaco PV-Inverter. @@ -92,15 +93,16 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel(bundle.getString("ipAddress")) // - .setDescription(bundle.getString("App.PvInverter.ip.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "ipAddress")) // + .setDescription(TranslationUtil.getTranslation(bundle, "App.PvInverter.ip.description")) // .setDefaultValue("192.168.178.85") // .isRequired(true) // .setValidation(Validation.IP) // .build()) // .add(JsonFormlyUtil.buildInput(Property.PORT) // - .setLabel(bundle.getString("port")) // - .setDescription(bundle.getString("App.PvInverter.port.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "port")) // + .setDescription( + TranslationUtil.getTranslation(bundle, "App.PvInverter.port.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(502) // .setMin(0) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KostalPvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KostalPvInverter.java index eb89ce1bf81..0657d764f64 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KostalPvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/KostalPvInverter.java @@ -27,6 +27,7 @@ import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Validation; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.TranslationUtil; /** * Describes a App for Kostal PV-Inverter. @@ -98,23 +99,24 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel(bundle.getString("ipAddress")) // - .setDescription(bundle.getString("App.PvInverter.ip.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "ipAddress")) // + .setDescription(TranslationUtil.getTranslation(bundle, "App.PvInverter.ip.description")) // .setDefaultValue("192.168.178.85") // .isRequired(true) // .setValidation(Validation.IP) // .build()) // .add(JsonFormlyUtil.buildInput(Property.PORT) // - .setLabel(bundle.getString("port")) // - .setDescription(bundle.getString("App.PvInverter.port.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "port")) // + .setDescription( + TranslationUtil.getTranslation(bundle, "App.PvInverter.port.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(502) // .setMin(0) // .isRequired(true) // .build()) // .add(JsonFormlyUtil.buildInput(Property.MODBUS_UNIT_ID) // - .setLabel(bundle.getString("modbusUnitId")) // - .setDescription(bundle.getString("modbusUnitId.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "modbusUnitId")) // + .setDescription(TranslationUtil.getTranslation(bundle, "modbusUnitId.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(71) // .setMin(0) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java index 1a834c3e6eb..32ad2026428 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SmaPvInverter.java @@ -27,6 +27,7 @@ import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Validation; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.TranslationUtil; /** * Describes a App for SMA PV-Inverter. @@ -97,23 +98,24 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel(bundle.getString("ipAddress")) // - .setDescription(bundle.getString("App.PvInverter.ip.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "ipAddress")) // + .setDescription(TranslationUtil.getTranslation(bundle, "App.PvInverter.ip.description")) // .setDefaultValue("192.168.178.85") // .isRequired(true) // .setValidation(Validation.IP) // .build()) // .add(JsonFormlyUtil.buildInput(Property.PORT) // - .setLabel(bundle.getString("port")) // - .setDescription(bundle.getString("App.PvInverter.port.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "port")) // + .setDescription( + TranslationUtil.getTranslation(bundle, "App.PvInverter.port.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(502) // .setMin(0) // .isRequired(true) // .build()) // .add(JsonFormlyUtil.buildInput(Property.MODBUS_UNIT_ID) // - .setLabel(bundle.getString("modbusUnitId")) // - .setDescription(bundle.getString(this.getAppId() + ".modbusUnitId.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "modbusUnitId")) // + .setDescription(TranslationUtil.getTranslation(bundle, "modbusUnitId.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(126) // .setMin(0) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SolarEdgePvInverter.java b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SolarEdgePvInverter.java index 46d6664f7fc..34b5f1b5043 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SolarEdgePvInverter.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvinverter/SolarEdgePvInverter.java @@ -27,6 +27,7 @@ import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Validation; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; +import io.openems.edge.core.appmanager.TranslationUtil; /** * Describes a App for SolarEdge PV-Inverter. @@ -93,15 +94,16 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.IP) // - .setLabel(bundle.getString("ipAddress")) // - .setDescription(bundle.getString("App.PvInverter.ip.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "ipAddress")) // + .setDescription(TranslationUtil.getTranslation(bundle, "App.PvInverter.ip.description")) // .setDefaultValue("192.168.178.85") // .isRequired(true) // .setValidation(Validation.IP) // .build()) // .add(JsonFormlyUtil.buildInput(Property.PORT) // - .setLabel(bundle.getString("port")) // - .setDescription(bundle.getString("App.PvInverter.port.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "port")) // + .setDescription( + TranslationUtil.getTranslation(bundle, "App.PvInverter.port.description")) // .setInputType(Type.NUMBER) // .setDefaultValue(502) // .setMin(0) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java index 4b7d75a73ad..c2a9fd01328 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/GridOptimizedCharge.java @@ -31,6 +31,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; /** * Describes a App for a Grid Optimized Charge. @@ -113,18 +114,20 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildCheckbox(Property.SELL_TO_GRID_LIMIT_ENABLED) // - .setLabel(bundle.getString(this.getAppId() + ".sellToGridLimitEnabled.label")) // - .setDescription( - bundle.getString(this.getAppId() + ".sellToGridLimitEnabled.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".sellToGridLimitEnabled.label")) // + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".sellToGridLimitEnabled.description")) // .build()) .add(JsonFormlyUtil.buildInput(Property.MAXIMUM_SELL_TO_GRID_POWER) // .setInputType(Type.NUMBER) // .isRequired(true) // .setMin(0) // .onlyShowIfChecked(Property.SELL_TO_GRID_LIMIT_ENABLED) // - .setLabel(bundle.getString(this.getAppId() + ".maximumSellToGridPower.label")) // - .setDescription( - bundle.getString(this.getAppId() + ".maximumSellToGridPower.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".maximumSellToGridPower.label")) // + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".maximumSellToGridPower.description")) // .build()) .build()) .build(); diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java index 547830e8b9b..58f4842056c 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java @@ -30,6 +30,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.meter.api.SymmetricMeter; @@ -97,15 +98,17 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildSelect(Property.ESS_ID)// - .setLabel(bundle.getString(this.getAppId() + ".ess.label")) // - .setDescription(bundle.getString(this.getAppId() + ".ess.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".ess.label")) // + .setDescription( + TranslationUtil.getTranslation(bundle, this.getAppId() + ".ess.description")) // .isRequired(true) // .setOptions(this.componentManager.getEnabledComponentsOfType(ManagedSymmetricEss.class), c -> c.id() + ": " + c.alias(), ManagedSymmetricEss::id) // .build()) .add(JsonFormlyUtil.buildSelect(Property.METER_ID)// - .setLabel(bundle.getString(this.getAppId() + ".meter.label")) // - .setDescription(bundle.getString(this.getAppId() + ".meter.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".meter.label")) // + .setDescription( + TranslationUtil.getTranslation(bundle, this.getAppId() + ".meter.description")) // .isRequired(true) // .setOptions(this.componentManager.getEnabledComponentsOfType(SymmetricMeter.class), c -> c.id() + ": " + c.alias(), SymmetricMeter::id) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java index 0568cfffb05..607de7a4d52 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java @@ -30,6 +30,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; /** * Describes a App for StromdaoCorrently. @@ -97,8 +98,9 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.ZIP_CODE) // - .setLabel(bundle.getString(this.getAppId() + ".zipCode.label")) // - .setDescription(bundle.getString(this.getAppId() + ".zipCode.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".zipCode.label")) // + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".zipCode.description")) // .isRequired(true) // .build()) // .build()) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java index 9ebe21c916b..7c33e84c406 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java @@ -30,6 +30,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; /** * Describes a App for Tibber. @@ -102,8 +103,10 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)).fields(// JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.ACCESS_TOKEN) // - .setLabel(bundle.getString(this.getAppId() + ".accessToken.label")) // - .setDescription(bundle.getString(this.getAppId() + ".accessToken.description")) // + .setLabel( + TranslationUtil.getTranslation(bundle, this.getAppId() + ".accessToken.label")) // + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".accessToken.description")) // .setInputType(Type.PASSWORD) // .isRequired(true) // .build()) // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java index b0f660236e1..7edf1e1fb7b 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java @@ -27,6 +27,7 @@ import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.types.EdgeConfig.Component; +import io.openems.common.utils.JsonUtils; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.core.appmanager.dependency.Dependency; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; @@ -299,7 +300,7 @@ protected String getValueOrDefault(EnumMap map, DefaultEn protected String getValueOrDefault(EnumMap map, PROPERTY property, String defaultValue) { var element = map.get(property); if (element != null) { - return element.getAsString(); + return JsonUtils.getAsOptionalString(element).orElse(defaultValue); } return defaultValue; } @@ -568,7 +569,7 @@ public String getImage() { } protected static String getTranslation(Language language, String key) { - return getTranslationBundle(language).getString(key); + return TranslationUtil.getTranslation(getTranslationBundle(language), key); } protected static ResourceBundle getTranslationBundle(Language language) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/TranslationUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/TranslationUtil.java new file mode 100644 index 00000000000..cb3b80c885b --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/TranslationUtil.java @@ -0,0 +1,28 @@ +package io.openems.edge.core.appmanager; + +import java.text.MessageFormat; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +public class TranslationUtil { + + /** + * Gets the value for the given key from the translationBundle. + * + * @param translationBundle the translation bundle + * @param key the key of the translation + * @param params the parameter of the translation + * @return the translated string or the key if the translation was not found or + * the format is invalid + */ + public static String getTranslation(ResourceBundle translationBundle, String key, Object... params) { + try { + var string = translationBundle.getString(key); + return MessageFormat.format(string, params); + } catch (MissingResourceException | IllegalArgumentException e) { + e.printStackTrace(); + return key; + } + } + +} From 260110fa3f9f5ddc566e832b8320d07ebfb24dda Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:52:17 +0200 Subject: [PATCH 24/30] translation; clean up --- .../org.eclipse.core.resources.prefs | 1 + .../edge/app/heat/CombinedHeatAndPower.java | 6 + .../io/openems/edge/app/heat/HeatPump.java | 6 + .../openems/edge/app/heat/HeatingElement.java | 6 + .../app/integratedsystem/FeneconHome.java | 32 +++- .../core/appmanager/AppInstallWorker.java | 47 ++--- .../edge/core/appmanager/AppManagerImpl.java | 10 + .../edge/core/appmanager/TranslationUtil.java | 2 +- .../dependency/AppManagerAppHelper.java | 1 - .../dependency/AppManagerAppHelperImpl.java | 173 +++++++++++------- .../appmanager/dependency/DependencyUtil.java | 19 +- .../appmanager/dependency/UpdateValues.java | 28 +++ .../dependency/translation_de.properties | 8 + .../dependency/translation_en.properties | 8 + .../validator/AbstractCheckable.java | 9 +- .../validator/CheckCardinality.java | 2 +- .../validator/translation_de.properties | 8 +- .../edge/settings/app/index.component.html | 8 +- .../edge/settings/app/single.component.html | 10 +- ui/src/app/shared/translate/cz.ts | 2 + ui/src/app/shared/translate/de.ts | 2 + ui/src/app/shared/translate/en.ts | 2 + ui/src/app/shared/translate/es.ts | 2 + ui/src/app/shared/translate/fr.ts | 2 + ui/src/app/shared/translate/nl.ts | 2 + 25 files changed, 268 insertions(+), 128 deletions(-) create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/UpdateValues.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_de.properties create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_en.properties diff --git a/io.openems.edge.core/.settings/org.eclipse.core.resources.prefs b/io.openems.edge.core/.settings/org.eclipse.core.resources.prefs index 99f26c0203a..0e5ea801f51 100644 --- a/io.openems.edge.core/.settings/org.eclipse.core.resources.prefs +++ b/io.openems.edge.core/.settings/org.eclipse.core.resources.prefs @@ -1,2 +1,3 @@ eclipse.preferences.version=1 +encoding//src/io/openems/edge/core/appmanager/dependency/translation_de.properties=UTF-8 encoding/=UTF-8 diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java b/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java index d65353ed625..635f626cf8c 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java @@ -52,6 +52,12 @@ "CTRL_CHP_SOC_ID": "ctrlChpSoc0", "OUTPUT_CHANNEL": "io0/Relay1" }, + "dependencies": [ + { + "key": "RELAY", + "instanceId": UUID + } + ], "appDescriptor": { "websiteUrl": URL } diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java index f4074e0f672..d496e952285 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java @@ -50,6 +50,12 @@ "OUTPUT_CHANNEL_1": "io0/Relay2", "OUTPUT_CHANNEL_2": "io0/Relay3" }, + "dependencies": [ + { + "key": "RELAY", + "instanceId": UUID + } + ], "appDescriptor": { "websiteUrl": URL } diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java index 523e226dd50..cd11f37bc5b 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java @@ -54,6 +54,12 @@ "OUTPUT_CHANNEL_PHASE_L2": "io0/Relay2", "OUTPUT_CHANNEL_PHASE_L3": "io0/Relay3" }, + "dependencies": [ + { + "key": "RELAY", + "instanceId": UUID + } + ], "appDescriptor": { "websiteUrl": URL } diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java index 6ae24b62dc5..fe0b79f05ec 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHome.java @@ -52,7 +52,7 @@ "image": base64, "properties":{ "SAFETY_COUNTRY":"AUSTRIA", - "RIPPLE_CONTROL_RECEIVER_AKTIV":false, + "RIPPLE_CONTROL_RECEIVER_ACTIV":false, "MAX_FEED_IN_POWER":5000, "FEED_IN_SETTING":"PU_ENABLE_CURVE", "HAS_AC_METER":true, @@ -64,6 +64,20 @@ "EMERGENCY_RESERVE_ENABLED":true, "EMERGENCY_RESERVE_SOC":20 }, + "dependencies": [ + { + "key": "GRID_OPTIMIZED_CHARGE", + "instanceId": UUID + }, + { + "key": "AC_METER", + "instanceId": UUID + }, + { + "key": "SELF_CONSUMTION_OPTIMIZATION", + "instanceId": UUID + } + ], "appDescriptor": { "websiteUrl": URL } @@ -273,6 +287,20 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { .build())); } + var hasAcMeter = EnumUtils.getAsOptionalBoolean(p, Property.HAS_AC_METER).orElse(false); + + // remove components that were in the old configuration but now are a dependency + if (t == ConfigurationTarget.DELETE) { + components.add(new EdgeConfig.Component("ctrlGridOptimizedCharge0", "", + "Controller.Ess.GridOptimizedCharge", JsonUtils.buildJsonObject().build())); + components.add(new EdgeConfig.Component("ctrlBalancing0", "", "Controller.Symmetric.Balancing", + JsonUtils.buildJsonObject().build())); + if (hasAcMeter) { + components.add(new EdgeConfig.Component("meter1", "", "Meter.Socomec.Threephase", + JsonUtils.buildJsonObject().build())); + } + } + /* * Set Execution Order for Scheduler. */ @@ -314,7 +342,7 @@ AppConfiguration, OpenemsNamedException> appConfigurationFactory() { .build()) // ); - if (EnumUtils.getAsOptionalBoolean(p, Property.HAS_AC_METER).orElse(false)) { + if (hasAcMeter) { dependencies.add(new DependencyDeclaration("AC_METER", // DependencyDeclaration.CreatePolicy.ALWAYS, // DependencyDeclaration.UpdatePolicy.ALWAYS, // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppInstallWorker.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppInstallWorker.java index 2767c6c5469..1e9d7d6cf21 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppInstallWorker.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppInstallWorker.java @@ -13,7 +13,7 @@ public class AppInstallWorker extends AbstractWorker { /** * Time to wait before doing the check. This allows the system to completely * boot and read configurations. And enough time to allow the user to delete the - * ReadOnly App and let him install the ReadWrite one. + * ReadOnly App and let him install the ReadWrite ones. */ private static final int INITIAL_WAIT_TIME = 60_000; // in ms @@ -26,48 +26,31 @@ public AppInstallWorker(AppManagerImpl parent) { } private void installFreeApps() { + this.installReadOnlyApi("App.Api.ModbusTcp.ReadOnly", "App.Api.ModbusTcp.ReadWrite", + "Controller.Api.ModbusTcp.ReadWrite"); + this.installReadOnlyApi("App.Api.RestJson.ReadOnly", "App.Api.RestJson.ReadWrite", + "Controller.Api.Rest.ReadWrite"); + } - // install ModbusTcp.ReadOnly - if (this.parent.getInstantiatedApps().stream().noneMatch( - t -> t.appId.equals("App.Api.ModbusTcp.ReadOnly") || t.appId.equals("App.Api.ModbusTcp.ReadWrite"))) { - - // TODO this is only required if the ReadWrite controller exists without an App - if (this.parent.componentManager.getEdgeConfig() - .getComponentIdsByFactory("Controller.Api.ModbusTcp.ReadWrite").size() == 0) { - - try { - this.parent.handleAddAppInstanceRequest(null, new AddAppInstance.Request( - "App.Api.ModbusTcp.ReadOnly", "", JsonUtils.buildJsonObject().build())); - } catch (OpenemsNamedException e) { - this.log.info("Unable to install free App[ModbusTcp.ReadOnly]"); - } - - } else { - this.log.warn("Unable to create App[App.Api.ModbusTcp.ReadOnly] because a " - + "Component with the FactoryId[Controller.Api.ModbusTcp.ReadWrite] exists!"); - } - } - - // install RestJson.ReadOnly - if (this.parent.getInstantiatedApps().stream().noneMatch( - t -> t.appId.equals("App.Api.RestJson.ReadOnly") || t.appId.equals("App.Api.RestJson.ReadWrite"))) { + private final void installReadOnlyApi(String readOnly, String readWrite, String readWriteController) { + if (this.parent.getInstantiatedApps().stream() + .noneMatch(t -> t.appId.equals(readOnly) || t.appId.equals(readWrite))) { // TODO this is only required if the ReadWrite controller exists without an App - if (this.parent.componentManager.getEdgeConfig().getComponentIdsByFactory("Controller.Api.Rest.ReadWrite") + if (this.parent.componentManager.getEdgeConfig().getComponentIdsByFactory(readWriteController) .size() == 0) { try { - this.parent.handleAddAppInstanceRequest(null, new AddAppInstance.Request( - "App.Api.RestJson.ReadOnly", "", JsonUtils.buildJsonObject().build())); + this.parent.handleAddAppInstanceRequest(null, + new AddAppInstance.Request(readOnly, "", JsonUtils.buildJsonObject().build())); } catch (OpenemsNamedException e) { - this.log.info("Unable to install free App[RestJson.ReadOnly]"); + this.log.info("Unable to install free App[" + readOnly + "]"); } } else { - this.log.warn("Unable to create App[App.Api.RestJson.ReadOnly] because a " - + "Component with the FactoryId[Controller.Api.Rest.ReadWrite] exists!"); + this.log.warn("Unable to create App[" + readOnly + "] because a " + "Component with the FactoryId[" + + readWrite + "] exists!"); } } - } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java index c67ba442517..3bd332584d2 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java @@ -205,6 +205,16 @@ public static Predicate exludingInstanceIds(UUID... return i -> !Arrays.stream(excludingInstanceIds).anyMatch(id -> id.equals(i.instanceId)); } + /** + * Gets an {@link Iterable} that loops thru every existing app instance and its + * configuration. + * + * @return the {@link Iterable} + */ + public Iterable> appConfigs() { + return this.appConfigs(null); + } + /** * Gets an {@link Iterable} that loops thru every existing app instance and its * configuration. diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/TranslationUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/TranslationUtil.java index cb3b80c885b..a1a2f128088 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/TranslationUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/TranslationUtil.java @@ -8,7 +8,7 @@ public class TranslationUtil { /** * Gets the value for the given key from the translationBundle. - * + * * @param translationBundle the translation bundle * @param key the key of the translation * @param params the parameter of the translation diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java index d33a03647b7..55d591f9559 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelper.java @@ -6,7 +6,6 @@ import io.openems.edge.common.user.User; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppInstance; -import io.openems.edge.core.appmanager.dependency.AppManagerAppHelperImpl.UpdateValues; public interface AppManagerAppHelper { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java index b41f15a679b..512236ed5d1 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.ResourceBundle; import java.util.Set; import java.util.TreeMap; import java.util.UUID; @@ -39,6 +40,7 @@ import io.openems.edge.core.appmanager.ConfigurationTarget; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppInstance; +import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration.AppDependencyConfig; import io.openems.edge.core.appmanager.validator.Validator; @@ -92,6 +94,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj // maybe add temporary apps in this component final var warnings = new LinkedList(); final var language = user == null ? null : user.getLanguage(); + final var bundle = getTranslationBundle(language); if (oldInstance == null) { this.checkStatus(app, language); } else { @@ -109,10 +112,10 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj } var dependencyApp = this.getAppManagerImpl().findInstanceById(dd.get().instanceId); - var appConfig = dependencieDeclaration.appConfigs.stream() - .filter(d -> d.appId.equals(dependencyApp.appId)).findAny(); - if (appConfig.isEmpty()) { + var appConfig = this.getAppDependencyConfig(dependencyApp, dependencieDeclaration.appConfigs); + + if (appConfig == null) { // TODO can not get app config continue; } @@ -122,23 +125,24 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj // everything can be changed break; case ALLOW_NONE: - throw new OpenemsException( - "This app is not allowed to be updated because of a dependency constraint!"); + throw new OpenemsException(TranslationUtil.getTranslation(bundle, "appNotAllowedToBeUpdated")); case ALLOW_ONLY_UNCONFIGURED_PROPERTIES: // override properties - for (var propEntry : appConfig.get().properties.entrySet()) { + for (var propEntry : appConfig.properties.entrySet()) { if (!properties.has(propEntry.getKey()) || !properties.get(propEntry.getKey()).equals(propEntry.getValue())) { - // TODO translation - warnings.add("Can not change Property[" + propEntry.getKey() - + "] because of a Dependency constraint!"); + + warnings.add(TranslationUtil.getTranslation(bundle, "canNotChangeProperty", + propEntry.getKey())); + properties.add(propEntry.getKey(), propEntry.getValue()); } } - if (appConfig.get().alias != null && !alias.equals(appConfig.get().alias)) { - warnings.add("Can not change Alias because of a Dependency constraint!"); - alias = appConfig.get().alias; + // override alias if set + if (appConfig.alias != null && !alias.equals(appConfig.alias)) { + warnings.add(TranslationUtil.getTranslation(bundle, "canNotChangeAlias")); + alias = appConfig.alias; } break; } @@ -152,7 +156,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj var dependencieInstances = new HashMap(); // get all existing app dependencies if (oldInstance != null) { - this.foreachExistingDependecy(oldInstance, ConfigurationTarget.UPDATE, language, dc -> { + this.foreachExistingDependency(oldInstance, ConfigurationTarget.UPDATE, language, dc -> { if (!dc.isDependency()) { return true; } @@ -162,7 +166,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj } BiFunction includeDependency = (a, d) -> { - ExistingDependencyConfig oldAppConfig = oldInstances.get(new AppIdKey(a.getAppId(), d.key)); + var oldAppConfig = oldInstances.get(new AppIdKey(a.getAppId(), d.key)); if (oldAppConfig == null) { switch (d.createPolicy) { @@ -171,7 +175,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj return true; case NEVER: var possibleInstance = this.findNeededApp(d, this.determineDependencyConfig(d.appConfigs)); - if(possibleInstance != null) { + if (possibleInstance != null) { return true; } return false; @@ -207,7 +211,8 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj oldInstance.properties, language); } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); + this.log.error(e.getMessage()); + errors.add(TranslationUtil.getTranslation(bundle, "canNotGetAppConfiguration")); } var appDependencyConfig = DependencyDeclaration.AppDependencyConfig.create() // @@ -273,7 +278,8 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj } } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); + this.log.error(e.getMessage()); + errors.add(TranslationUtil.getTranslation(bundle, "canNotGetAppConfiguration")); } } } else { @@ -286,24 +292,41 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj } // check if the created app can satisfy another app dependency - for (var instance : this.getAppManagerImpl().getInstantiatedApps()) { + final var fallBackAlwaysCreateApp = new MutableValue(); + + var apps2UpdateDependency = this.getAppManagerImpl().getInstantiatedApps().stream() // + .filter(i -> { + var neededDependency = this.getNeededDependencyTo(i, dc.app.getAppId()); + if (neededDependency == null) { + return false; + } + if (neededDependency.createPolicy == DependencyDeclaration.CreatePolicy.ALWAYS) { + // only set the dependency to one app which has the always create policy + fallBackAlwaysCreateApp.setValue(i); + return false; + } + return true; + }) // + .collect(Collectors.toList()); + + if (apps2UpdateDependency.isEmpty() && fallBackAlwaysCreateApp.getValue() != null) { + apps2UpdateDependency.add(fallBackAlwaysCreateApp.getValue()); + } + + for (var instance : apps2UpdateDependency) { var neededDependency = this.getNeededDependencyTo(instance, dc.app.getAppId()); - if (neededDependency == null) { - continue; - } - if (neededDependency.createPolicy == DependencyDeclaration.CreatePolicy.ALWAYS) { - continue; - } // override properties if set by dependency - var config = this.determineDependencyConfig(neededDependency.appConfigs); - for (var entry : config.properties.entrySet()) { - if (!dc.appDependencyConfig.properties.has(entry.getKey()) - || !dc.appDependencyConfig.properties.get(entry.getKey()) - .equals(entry.getValue())) { - warnings.add("Override Property[" + entry.getKey() - + "] because of a Dependency constraint!"); + if (neededDependency.dependencyUpdatePolicy != DependencyDeclaration.DependencyUpdatePolicy.ALLOW_ALL) { + var config = this.determineDependencyConfig(neededDependency.appConfigs); + for (var entry : config.properties.entrySet()) { + if (!dc.appDependencyConfig.properties.has(entry.getKey()) + || !dc.appDependencyConfig.properties.get(entry.getKey()) + .equals(entry.getValue())) { + warnings.add(TranslationUtil.getTranslation(bundle, "overrideProperty", + entry.getKey())); + } + dc.appDependencyConfig.properties.add(entry.getKey(), entry.getValue()); } - dc.appDependencyConfig.properties.add(entry.getKey(), entry.getValue()); } // update dependencies @@ -335,7 +358,8 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj this.aggregateAllTasks(newConfig, oldConfig); } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); + this.log.error(e.getMessage()); + errors.add(TranslationUtil.getTranslation(bundle, "canNotGetAppConfiguration")); } return true; } @@ -391,7 +415,8 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj this.aggregateAllTasks(newAppConfig, oldAppConfig.config); } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); + this.log.error(e.getMessage()); + errors.add(TranslationUtil.getTranslation(bundle, "canNotGetAppConfiguration")); } return true; @@ -420,8 +445,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj this.componentsTask.create(user, otherAppConfigs); } catch (OpenemsNamedException e) { this.log.error(e.getMessage()); - // TODO translation - errors.add("Can not update Components!"); + errors.add(TranslationUtil.getTranslation(bundle, "canNotUpdateComponents")); } try { @@ -429,8 +453,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj this.schedulerTask.create(user, otherAppConfigs); } catch (OpenemsNamedException e) { this.log.error(e.getMessage()); - // TODO translation - errors.add("Can not update Scheduler!"); + errors.add(TranslationUtil.getTranslation(bundle, "canNotUpdateScheduler")); } try { @@ -438,8 +461,7 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj this.staticIpTask.create(user, otherAppConfigs); } catch (OpenemsNamedException e) { this.log.error(e.getMessage()); - // TODO translation - errors.add("Can not update static ips!"); + errors.add(TranslationUtil.getTranslation(bundle, "canNotUpdateStaticIps")); } if (!errors.isEmpty()) { @@ -449,6 +471,19 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj return new UpdateValues(lastCreatedOrModifiedApp.getValue(), modifiedOrCreatedApps, deletedApps, warnings); } + private DependencyDeclaration.AppDependencyConfig getAppDependencyConfig(OpenemsAppInstance instance, + List appDependencyConfigs) { + for (var config : appDependencyConfigs) { + if (config.appId != null && config.appId.equals(instance.appId)) { + return config; + } + if (config.specificInstanceId.equals(instance.instanceId)) { + return config; + } + } + return null; + } + private static final class MutableValue { private T value; @@ -490,6 +525,10 @@ private final DependencyDeclaration getNeededDependencyTo(OpenemsAppInstance ins .anyMatch(c -> c.appId.equals(appId))) { return neededDependency; } + if (neededDependency.appConfigs.stream().filter(c -> c.specificInstanceId != null) + .anyMatch(c -> c.specificInstanceId.equals(instance.instanceId))) { + return neededDependency; + } } } catch (OpenemsNamedException e) { @@ -498,28 +537,6 @@ private final DependencyDeclaration getNeededDependencyTo(OpenemsAppInstance ins return null; } - public static final class UpdateValues { - public final OpenemsAppInstance rootInstance; - public final List modifiedOrCreatedApps; - public final List deletedApps; - - public final List warnings; - - public UpdateValues(OpenemsAppInstance rootInstance, List modifiedOrCreatedApps, - List deletedApps) { - this(rootInstance, modifiedOrCreatedApps, deletedApps, null); - } - - public UpdateValues(OpenemsAppInstance rootInstance, List modifiedOrCreatedApps, - List deletedApps, List warnings) { - this.rootInstance = rootInstance; - this.modifiedOrCreatedApps = modifiedOrCreatedApps; - this.deletedApps = deletedApps; - this.warnings = warnings; - } - - } - private static class AppIdKey implements Comparable { public final String appId; public final String key; @@ -553,7 +570,7 @@ public UpdateValues deleteApp(User user, OpenemsAppInstance instance) throws Ope final var language = user == null ? null : user.getLanguage(); final var errors = new LinkedList(); - this.foreachExistingDependecy(instance, ConfigurationTarget.DELETE, language, dc -> { + this.foreachExistingDependency(instance, ConfigurationTarget.DELETE, language, dc -> { // check if dependency is allowed to be deleted by its parent if (dc.isDependency()) { @@ -722,7 +739,9 @@ protected static List getStaticIpsFromConfigs(List con /** * Finds the needed app for a {@link DependencyDeclaration}. * - * @param dc the current {@link DependencyConfig} + * @param declaration the current {@link DependencyConfig} + * @param config the current + * {@link DependencyDeclaration.AppDependencyConfig} * @return s null if the app can not be added; {@link Optional#absent()} if the * app needs to be created; the {@link OpenemsAppInstance} if an * existing app can be used @@ -791,6 +810,8 @@ private List getAppsWithDependencyTo(OpenemsAppInstance inst * each other as a dependency * @param determineDependencyConfig the function to determine the * {@link AppDependencyConfig} + * @param includeDependency a {@link BiFunction} to determine if a + * dependency should get included * @return s the last {@link DependencyConfig} * @throws OpenemsNamedException on error */ @@ -886,7 +907,7 @@ private DependencyDeclaration.AppDependencyConfig determineDependencyConfig(List return configs.get(0); } - private void foreachExistingDependecy(OpenemsAppInstance instance, ConfigurationTarget target, Language l, + private void foreachExistingDependency(OpenemsAppInstance instance, ConfigurationTarget target, Language l, Function consumer) throws OpenemsNamedException { this.foreachExistingDependency(instance, target, consumer, null, null, l, null); } @@ -951,7 +972,7 @@ private DependencyConfig foreachExistingDependency(OpenemsAppInstance instance, .setAlias(instance.alias) // .build(); } else { - dependencyAppConfig = sub.appConfigs.stream().filter(c -> c.appId.equals(instance.appId)).findAny().get(); + dependencyAppConfig = this.getAppDependencyConfig(instance, sub.appConfigs); } var newConfig = new ExistingDependencyConfig(app, parentApp, sub, config, dependencyAppConfig, dependecies, @@ -1094,4 +1115,24 @@ private final AppManagerImpl getAppManagerImpl() { return (AppManagerImpl) this.componentManager.getEnabledComponentsOfType(AppManager.class).get(0); } + private static ResourceBundle getTranslationBundle(Language language) { + if (language == null) { + language = Language.DEFAULT; + } + // TODO translation + switch (language) { + case CZ: + case ES: + case FR: + case NL: + language = Language.EN; + break; + case DE: + case EN: + break; + } + + return ResourceBundle.getBundle("io.openems.edge.core.appmanager.dependency.translation", language.getLocal()); + } + } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java index ec84844fc1f..d1a8fb51186 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/DependencyUtil.java @@ -1,7 +1,5 @@ package io.openems.edge.core.appmanager.dependency; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; import io.openems.edge.common.component.ComponentManager; @@ -14,14 +12,14 @@ public class DependencyUtil { /** * Temporary field to avoid endless loop. */ - private static final Set CURRENTLY_CALLING_APP_IDS = new HashSet<>(); + private static boolean isCurrentlyRunning = false; /** * Gets the instanceId of the first found app that has the given componentId in * its {@link AppConfiguration}. * *

- * WARNING: when calling this inside an app configuration it can lead to an endless + * NOTE: when calling this inside an app configuration it can lead to an endless * loop * * @param componentManager a componentManager to get the appManager @@ -31,19 +29,22 @@ public class DependencyUtil { */ public static final UUID getInstanceIdOfAppWhichHasComponent(ComponentManager componentManager, String componentId, String currentlyCallingApp) { + if (isCurrentlyRunning) { + return null; + } + isCurrentlyRunning = true; var appManagerImpl = DependencyUtil.getAppManagerImpl(componentManager); if (appManagerImpl == null) { + isCurrentlyRunning = false; return null; } - CURRENTLY_CALLING_APP_IDS.add(currentlyCallingApp); - for (var entry : appManagerImpl.appConfigs(appManagerImpl.getInstantiatedApps(), - i -> !CURRENTLY_CALLING_APP_IDS.contains(i.appId))) { + for (var entry : appManagerImpl.appConfigs()) { if (entry.getValue().components.stream().anyMatch(c -> c.getId().equals(componentId))) { - CURRENTLY_CALLING_APP_IDS.remove(currentlyCallingApp); + isCurrentlyRunning = false; return entry.getKey().instanceId; } } - CURRENTLY_CALLING_APP_IDS.remove(currentlyCallingApp); + isCurrentlyRunning = false; return null; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/UpdateValues.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/UpdateValues.java new file mode 100644 index 00000000000..57ae5f41f12 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/UpdateValues.java @@ -0,0 +1,28 @@ +package io.openems.edge.core.appmanager.dependency; + +import java.util.List; + +import io.openems.edge.core.appmanager.OpenemsAppInstance; + +public class UpdateValues { + + public final OpenemsAppInstance rootInstance; + public final List modifiedOrCreatedApps; + public final List deletedApps; + + public final List warnings; + + public UpdateValues(OpenemsAppInstance rootInstance, List modifiedOrCreatedApps, + List deletedApps) { + this(rootInstance, modifiedOrCreatedApps, deletedApps, null); + } + + public UpdateValues(OpenemsAppInstance rootInstance, List modifiedOrCreatedApps, + List deletedApps, List warnings) { + this.rootInstance = rootInstance; + this.modifiedOrCreatedApps = modifiedOrCreatedApps; + this.deletedApps = deletedApps; + this.warnings = warnings; + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_de.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_de.properties new file mode 100644 index 00000000000..e02882ea795 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_de.properties @@ -0,0 +1,8 @@ +appNotAllowedToBeUpdated = Diese App darf nicht geupdated werden wegen einer Abhängigkeit einer anderen App! +canNotChangeProperty = Feld[{0}] darf nicht geändert werden wegen einer Abhängigkeit einer anderen App! +canNotChangeAlias = Alias darf nicht geändert werden wegen einer Abhängigkeit einer anderen App! +overrideProperty = Feld[{0}] wird überschrieben wegen einer Abhängigkeit einer anderen App! +canNotUpdateComponents = Komponenten konnten nicht geupdated werden! +canNotUpdateScheduler = Ausführungsreihenfolge im Scheduler konnte nicht geupdated werden! +canNotUpdateStaticIps = Statische IPs konnte nicht geupdated werden! +canNotGetAppConfiguration = AppConfiguration konnte nicht geholt werden! \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_en.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_en.properties new file mode 100644 index 00000000000..a635512823b --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_en.properties @@ -0,0 +1,8 @@ +appNotAllowedToBeUpdated = This app is not allowed to be updated because of a dependency constraint! +canNotChangeProperty = Can not change Property[{0}] because of a Dependency constraint! +canNotChangeAlias = Can not change Alias because of a Dependency constraint! +overrideProperty = Override Property[{0}] because of a Dependency constraint! +canNotUpdateComponents = Can not update Components! +canNotUpdateScheduler = Can not update Scheduler! +canNotUpdateStaticIps = Can not update static ips! +canNotGetAppConfiguration = Can not get AppConfiguration! diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java index 0124f372fb0..88b50059db5 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/AbstractCheckable.java @@ -1,12 +1,12 @@ package io.openems.edge.core.appmanager.validator; -import java.text.MessageFormat; import java.util.ResourceBundle; import org.osgi.service.component.ComponentConstants; import org.osgi.service.component.ComponentContext; import io.openems.common.session.Language; +import io.openems.edge.core.appmanager.TranslationUtil; public abstract class AbstractCheckable implements Checkable { @@ -22,6 +22,9 @@ public String getComponentName() { } protected static String getTranslation(Language language, String key, Object... params) { + if (language == null) { + language = Language.DEFAULT; + } // TODO translation switch (language) { case CZ: @@ -37,9 +40,7 @@ protected static String getTranslation(Language language, String key, Object... var translationBundle = ResourceBundle.getBundle("io.openems.edge.core.appmanager.validator.translation", language.getLocal()); - var errorMessage = translationBundle.getString(key); - - return MessageFormat.format(errorMessage, params); + return TranslationUtil.getTranslation(translationBundle, key, params); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java index 1c32a4308b7..ad0d6d9069d 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/CheckCardinality.java @@ -113,7 +113,7 @@ private OpenemsAppCategory getMatchingCategorie(AppManagerImpl appManager, @Override public String getErrorMessage(Language language) { - switch (errorType) { + switch (this.errorType) { case SAME_APP: return AbstractCheckable.getTranslation(language, "Validator.Checkable.CheckCardinality.Message.Single", this.openemsApp.getAppId()); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/translation_de.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/translation_de.properties index 03f60419265..9b38445dec1 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/translation_de.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/translation_de.properties @@ -1,13 +1,13 @@ -Validator.Checkable.CheckAppsNotInstalled.Message = Apps mit der ID[{0}] sind installiert! Deinstalliere diese um diese App installieren zu k�nnen. +Validator.Checkable.CheckAppsNotInstalled.Message = Apps mit der ID[{0}] sind installiert! Deinstalliere diese um diese App installieren zu können. Validator.Checkable.CheckCardinality.Message.SingleInCategorie = Eine Instanz einer App mit der selben Kategorie "{0}" ist bereits erstellt worden! Validator.Checkable.CheckCardinality.Message.Single = Eine Instanz der App[{0}] ist bereits erstellt worden! Validator.Checkable.CheckHome.Message = Kein Home System installiert! -Validator.Checkable.CheckHost.NotReachable = Ger�t mit der IP "{0}" ist nicht erreichbar! +Validator.Checkable.CheckHost.NotReachable = Gerät mit der IP "{0}" ist nicht erreichbar! Validator.Checkable.CheckHost.WrongIp = IP "{0}" ist keine valide IP-Adresse! -Validator.Checkable.CheckNoComponentInstalledOfFactorieId.Message = Komponenten mit der FaktorieID[{0}] sind installiert! Entferne diese um diese App installieren zu k�nnen. +Validator.Checkable.CheckNoComponentInstalledOfFactorieId.Message = Komponenten mit der FaktorieID[{0}] sind installiert! Entferne diese um diese App installieren zu können. -Validator.Checkable.CheckRelayCount.Message = Es sind nicht genug Relais ports verf�gbar! Installiere ein Relais um diese App installieren zu k�nnen. +Validator.Checkable.CheckRelayCount.Message = Es sind nicht genug Relais ports verfügbar! Installiere ein Relais um diese App installieren zu können. diff --git a/ui/src/app/edge/settings/app/index.component.html b/ui/src/app/edge/settings/app/index.component.html index 6afbfa049bc..6164e16233f 100644 --- a/ui/src/app/edge/settings/app/index.component.html +++ b/ui/src/app/edge/settings/app/index.component.html @@ -86,12 +86,12 @@

- errorCompatibleMessages:
+ Edge.Config.App.errorCompatible
- {{ message }}
- errorInstallableMessages:
+ Edge.Config.App.errorInstallable
- {{ message }}
@@ -132,12 +132,12 @@

- errorCompatibleMessages:
+ Edge.Config.App.errorCompatible
- {{ message }}
- errorInstallableMessages:
+ Edge.Config.App.errorInstallable
- {{ message }}
diff --git a/ui/src/app/edge/settings/app/single.component.html b/ui/src/app/edge/settings/app/single.component.html index 15010836378..d6d148de60c 100644 --- a/ui/src/app/edge/settings/app/single.component.html +++ b/ui/src/app/edge/settings/app/single.component.html @@ -33,12 +33,14 @@
- errorCompatibleMessages:
- - {{ message }}
+ Edge.Config.App.errorCompatible
+ - {{ message + }}
- errorInstallableMessages:
- - {{ message }}
+ Edge.Config.App.errorInstallable
+ - {{ message + }}
diff --git a/ui/src/app/shared/translate/cz.ts b/ui/src/app/shared/translate/cz.ts index 945f5d65aff..c416b7d0414 100644 --- a/ui/src/app/shared/translate/cz.ts +++ b/ui/src/app/shared/translate/cz.ts @@ -467,6 +467,8 @@ export const TRANSLATION = { createApp: 'vytvořit aplikaci', deleteApp: 'odstranit aplikaci', updateApp: 'aktualizace aplikace', + errorInstallable: 'Chyby pÅ™i instalaci', + errorCompatible: 'Chyby kompatibility', }, }, About: { diff --git a/ui/src/app/shared/translate/de.ts b/ui/src/app/shared/translate/de.ts index 3e06df6e1c0..22ce7ec07bd 100644 --- a/ui/src/app/shared/translate/de.ts +++ b/ui/src/app/shared/translate/de.ts @@ -482,6 +482,8 @@ export const TRANSLATION = { createApp: 'App anlegen', deleteApp: 'App entfernen', updateApp: 'App aktualisieren', + errorInstallable: 'Installierungs fehler', + errorCompatible: 'Kompatibilitäts fehler', }, } }, diff --git a/ui/src/app/shared/translate/en.ts b/ui/src/app/shared/translate/en.ts index 34da873a172..914e0848652 100644 --- a/ui/src/app/shared/translate/en.ts +++ b/ui/src/app/shared/translate/en.ts @@ -482,6 +482,8 @@ export const TRANSLATION = { createApp: 'Create app', deleteApp: 'Delete app', updateApp: 'Update app', + errorInstallable: 'Installation errors', + errorCompatible: 'Compatibility errors', }, } }, diff --git a/ui/src/app/shared/translate/es.ts b/ui/src/app/shared/translate/es.ts index 5e1f4e9a212..9919e5f6077 100644 --- a/ui/src/app/shared/translate/es.ts +++ b/ui/src/app/shared/translate/es.ts @@ -451,6 +451,8 @@ export const TRANSLATION = { createApp: 'Crear aplicación', deleteApp: 'Eliminar la aplicación', updateApp: 'Actualizar la aplicación', + errorInstallable: 'Errores de instalación', + errorCompatible: 'Errores de compatibilidad', }, } }, diff --git a/ui/src/app/shared/translate/fr.ts b/ui/src/app/shared/translate/fr.ts index 4289f80c14b..f056853a7e9 100644 --- a/ui/src/app/shared/translate/fr.ts +++ b/ui/src/app/shared/translate/fr.ts @@ -453,6 +453,8 @@ export const TRANSLATION = { createApp: 'Créer une application', deleteApp: 'Supprimer l\'application', updateApp: 'Mise à jour de l\'application', + errorInstallable: 'Erreurs d\'installation', + errorCompatible: 'Erreurs de compatibilité', }, } }, diff --git a/ui/src/app/shared/translate/nl.ts b/ui/src/app/shared/translate/nl.ts index e05b05524ce..fb2bdef5c32 100644 --- a/ui/src/app/shared/translate/nl.ts +++ b/ui/src/app/shared/translate/nl.ts @@ -450,6 +450,8 @@ export const TRANSLATION = { createApp: 'App maken', deleteApp: 'App verwijderen', updateApp: 'App bijwerken', + errorInstallable: 'Installatiefouten', + errorCompatible: 'Compatibiliteitsfouten', }, } }, From caddacfa435070a32ea6f1c2a419ed213cfd94a8 Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 11:59:58 +0200 Subject: [PATCH 25/30] small translation changes; doc --- .../dependency/AppManagerAppHelperImpl.java | 1 - .../io/openems/edge/app/TestADependencyToC.java | 8 +++----- .../io/openems/edge/app/TestBDependencyToC.java | 8 +++----- .../test/io/openems/edge/app/TestC.java | 8 +++----- ui/src/app/edge/settings/app/index.component.ts | 15 +++++++++------ .../app/edge/settings/app/install.component.html | 3 ++- ui/src/app/edge/settings/app/install.component.ts | 5 ++--- ui/src/app/edge/settings/app/single.component.ts | 6 +++--- ui/src/app/edge/settings/app/update.component.ts | 4 ++-- ui/src/app/shared/translate/cz.ts | 2 +- ui/src/app/shared/translate/de.ts | 2 +- ui/src/app/shared/translate/en.ts | 2 +- ui/src/app/shared/translate/es.ts | 2 +- ui/src/app/shared/translate/fr.ts | 2 +- ui/src/app/shared/translate/nl.ts | 2 +- 15 files changed, 33 insertions(+), 37 deletions(-) diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java index 512236ed5d1..a48f2db91af 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java @@ -116,7 +116,6 @@ public UpdateValues updateApp(User user, OpenemsAppInstance oldInstance, JsonObj var appConfig = this.getAppDependencyConfig(dependencyApp, dependencieDeclaration.appConfigs); if (appConfig == null) { - // TODO can not get app config continue; } diff --git a/io.openems.edge.core/test/io/openems/edge/app/TestADependencyToC.java b/io.openems.edge.core/test/io/openems/edge/app/TestADependencyToC.java index bde7ebbf6c1..d1cfffdc6d3 100644 --- a/io.openems.edge.core/test/io/openems/edge/app/TestADependencyToC.java +++ b/io.openems.edge.core/test/io/openems/edge/app/TestADependencyToC.java @@ -30,6 +30,9 @@ import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration.AppDependencyConfig; +/** + * Test app for testing dependencies. + */ @Component(name = "App.Test.TestADependencyToC") public class TestADependencyToC extends AbstractOpenemsApp implements OpenemsApp { @@ -65,11 +68,6 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.TEST }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - @Override public OpenemsAppCardinality getCardinality() { return OpenemsAppCardinality.MULTIPLE; diff --git a/io.openems.edge.core/test/io/openems/edge/app/TestBDependencyToC.java b/io.openems.edge.core/test/io/openems/edge/app/TestBDependencyToC.java index 2ada6b498c2..4233da8514d 100644 --- a/io.openems.edge.core/test/io/openems/edge/app/TestBDependencyToC.java +++ b/io.openems.edge.core/test/io/openems/edge/app/TestBDependencyToC.java @@ -29,6 +29,9 @@ import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; import io.openems.edge.core.appmanager.dependency.DependencyDeclaration.AppDependencyConfig; +/** + * Test app for testing dependencies. + */ @Component(name = "App.Test.TestBDependencyToC") public class TestBDependencyToC extends AbstractOpenemsApp implements OpenemsApp { @@ -59,11 +62,6 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.TEST }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - @Override public OpenemsAppCardinality getCardinality() { return OpenemsAppCardinality.MULTIPLE; diff --git a/io.openems.edge.core/test/io/openems/edge/app/TestC.java b/io.openems.edge.core/test/io/openems/edge/app/TestC.java index 240dc31dc47..edb968ee7bc 100644 --- a/io.openems.edge.core/test/io/openems/edge/app/TestC.java +++ b/io.openems.edge.core/test/io/openems/edge/app/TestC.java @@ -25,6 +25,9 @@ import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +/** + * Test app for testing dependencies. + */ @Component(name = "App.Test.TestC") public class TestC extends AbstractOpenemsApp implements OpenemsApp { @@ -55,11 +58,6 @@ public OpenemsAppCategory[] getCategorys() { return new OpenemsAppCategory[] { OpenemsAppCategory.TEST }; } - @Override - public String getImage() { - return OpenemsApp.FALLBACK_IMAGE; - } - @Override public OpenemsAppCardinality getCardinality() { return OpenemsAppCardinality.MULTIPLE; diff --git a/ui/src/app/edge/settings/app/index.component.ts b/ui/src/app/edge/settings/app/index.component.ts index ddade25ab69..ce41050417d 100644 --- a/ui/src/app/edge/settings/app/index.component.ts +++ b/ui/src/app/edge/settings/app/index.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; +import { timeout } from 'rxjs/operators'; import { ComponentJsonApiRequest } from 'src/app/shared/jsonrpc/request/componentJsonApiRequest'; import { environment } from 'src/environments'; import { Service, Websocket } from '../../../shared/shared'; @@ -16,7 +17,7 @@ export class IndexComponent { public readonly spinnerId: string = IndexComponent.SELECTOR; /** - * e. g. if more than 4 apps are in a list the categories are displayed + * e. g. if more than 4 apps are in a list the apps are displayed in their categories */ private static readonly MAX_APPS_IN_LIST = 4; @@ -31,7 +32,7 @@ export class IndexComponent { public categories = []; - constructor( + public constructor( private route: ActivatedRoute, private service: Service, private websocket: Websocket, @@ -39,7 +40,7 @@ export class IndexComponent { ) { } - ionViewWillEnter() { + private ionViewWillEnter() { // gets always called when entering the page this.init() } @@ -69,7 +70,6 @@ export class IndexComponent { this.categories.push({ val: category, isChecked: true }) } }); - }) this.updateSelection(null) @@ -81,7 +81,11 @@ export class IndexComponent { }); } - public updateSelection(event) { + /** + * Updates the slected categories. + * @param event the event of a click on a 'ion-fab-list' to stop it from closing + */ + private updateSelection(event) { if (event != null) { event.stopPropagation() } @@ -98,7 +102,6 @@ export class IndexComponent { } return true }) - }) sortedApps.forEach(a => { diff --git a/ui/src/app/edge/settings/app/install.component.html b/ui/src/app/edge/settings/app/install.component.html index f0dc9f0f4e0..3508efec352 100644 --- a/ui/src/app/edge/settings/app/install.component.html +++ b/ui/src/app/edge/settings/app/install.component.html @@ -13,7 +13,8 @@ - App installieren + + Edge.Config.App.createApp diff --git a/ui/src/app/edge/settings/app/install.component.ts b/ui/src/app/edge/settings/app/install.component.ts index 346d39de3cf..f48cea893af 100644 --- a/ui/src/app/edge/settings/app/install.component.ts +++ b/ui/src/app/edge/settings/app/install.component.ts @@ -25,7 +25,7 @@ export class InstallAppComponent implements OnInit { private edge: Edge = null; private isInstalling: boolean; - constructor( + public constructor( private route: ActivatedRoute, protected utils: Utils, private websocket: Websocket, @@ -33,7 +33,7 @@ export class InstallAppComponent implements OnInit { ) { } - ngOnInit() { + public ngOnInit() { this.service.startSpinner(this.spinnerId); let appId = this.route.snapshot.params["appId"]; this.appId = appId; @@ -95,7 +95,6 @@ export class InstallAppComponent implements OnInit { this.service.toast("Successfully installed App", 'success'); } - this.form.markAsPristine(); }).catch(reason => { this.service.toast("Error installing App:" + reason.error.message, 'danger'); diff --git a/ui/src/app/edge/settings/app/single.component.ts b/ui/src/app/edge/settings/app/single.component.ts index b4af1833140..0eafc6c4961 100644 --- a/ui/src/app/edge/settings/app/single.component.ts +++ b/ui/src/app/edge/settings/app/single.component.ts @@ -31,7 +31,7 @@ export class SingleAppComponent implements OnInit { private edge: Edge = null; - constructor( + public constructor( private route: ActivatedRoute, protected utils: Utils, private websocket: Websocket, @@ -40,7 +40,7 @@ export class SingleAppComponent implements OnInit { ) { } - ngOnInit() { + public ngOnInit() { this.service.startSpinner(this.spinnerId); this.updateIsXL(); this.appId = this.route.snapshot.params["appId"]; @@ -82,7 +82,7 @@ export class SingleAppComponent implements OnInit { } @HostListener('window:resize', ['$event']) - onResize(event) { + private onResize(event) { this.updateIsXL(); } diff --git a/ui/src/app/edge/settings/app/update.component.ts b/ui/src/app/edge/settings/app/update.component.ts index 9250662c213..970439079c6 100644 --- a/ui/src/app/edge/settings/app/update.component.ts +++ b/ui/src/app/edge/settings/app/update.component.ts @@ -34,7 +34,7 @@ export class UpdateAppComponent implements OnInit { private appName: string; - constructor( + public constructor( private route: ActivatedRoute, protected utils: Utils, private websocket: Websocket, @@ -42,7 +42,7 @@ export class UpdateAppComponent implements OnInit { ) { } - ngOnInit() { + public ngOnInit() { this.service.startSpinner(this.spinnerId); let appId = this.route.snapshot.params["appId"]; this.service.setCurrentComponent("App " + appId, this.route).then(edge => { diff --git a/ui/src/app/shared/translate/cz.ts b/ui/src/app/shared/translate/cz.ts index c416b7d0414..660ca6b69ab 100644 --- a/ui/src/app/shared/translate/cz.ts +++ b/ui/src/app/shared/translate/cz.ts @@ -464,7 +464,7 @@ export const TRANSLATION = { incompatible: 'Nekompatibilní', buyApp: 'koupit aplikaci', modifyApp: 'upravit aplikaci', - createApp: 'vytvořit aplikaci', + createApp: 'Instalace aplikace', deleteApp: 'odstranit aplikaci', updateApp: 'aktualizace aplikace', errorInstallable: 'Chyby pÅ™i instalaci', diff --git a/ui/src/app/shared/translate/de.ts b/ui/src/app/shared/translate/de.ts index 22ce7ec07bd..c96ab7f602d 100644 --- a/ui/src/app/shared/translate/de.ts +++ b/ui/src/app/shared/translate/de.ts @@ -479,7 +479,7 @@ export const TRANSLATION = { incompatible: 'Inkompatibel', buyApp: 'App kaufen', modifyApp: 'App bearbeiten', - createApp: 'App anlegen', + createApp: 'App installieren', deleteApp: 'App entfernen', updateApp: 'App aktualisieren', errorInstallable: 'Installierungs fehler', diff --git a/ui/src/app/shared/translate/en.ts b/ui/src/app/shared/translate/en.ts index 914e0848652..9060b378d89 100644 --- a/ui/src/app/shared/translate/en.ts +++ b/ui/src/app/shared/translate/en.ts @@ -479,7 +479,7 @@ export const TRANSLATION = { incompatible: 'Incompatible', buyApp: 'Buy app', modifyApp: 'Modify app', - createApp: 'Create app', + createApp: 'Install app', deleteApp: 'Delete app', updateApp: 'Update app', errorInstallable: 'Installation errors', diff --git a/ui/src/app/shared/translate/es.ts b/ui/src/app/shared/translate/es.ts index 9919e5f6077..c94e0050ac5 100644 --- a/ui/src/app/shared/translate/es.ts +++ b/ui/src/app/shared/translate/es.ts @@ -448,7 +448,7 @@ export const TRANSLATION = { incompatible: 'Incompatible', buyApp: 'Comprar aplicación', modifyApp: 'Modificar la aplicación', - createApp: 'Crear aplicación', + createApp: 'Instalar la aplicación', deleteApp: 'Eliminar la aplicación', updateApp: 'Actualizar la aplicación', errorInstallable: 'Errores de instalación', diff --git a/ui/src/app/shared/translate/fr.ts b/ui/src/app/shared/translate/fr.ts index f056853a7e9..dbab824ff88 100644 --- a/ui/src/app/shared/translate/fr.ts +++ b/ui/src/app/shared/translate/fr.ts @@ -450,7 +450,7 @@ export const TRANSLATION = { incompatible: 'Incompatibilité', buyApp: 'Acheter l\'application', modifyApp: 'Modifier l\'application', - createApp: 'Créer une application', + createApp: 'Installer l\'application', deleteApp: 'Supprimer l\'application', updateApp: 'Mise à jour de l\'application', errorInstallable: 'Erreurs d\'installation', diff --git a/ui/src/app/shared/translate/nl.ts b/ui/src/app/shared/translate/nl.ts index fb2bdef5c32..d0854b7cccd 100644 --- a/ui/src/app/shared/translate/nl.ts +++ b/ui/src/app/shared/translate/nl.ts @@ -447,7 +447,7 @@ export const TRANSLATION = { incompatible: 'Onverenigbaar', buyApp: 'App kopen', modifyApp: 'App wijzigen', - createApp: 'App maken', + createApp: 'App installeren', deleteApp: 'App verwijderen', updateApp: 'App bijwerken', errorInstallable: 'Installatiefouten', From 804827b5fffa498255cc7a15501cf14388a031ff Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 12:11:52 +0200 Subject: [PATCH 26/30] fix apis; clean up --- .../openems/edge/app/api/ModbusTcpApiReadOnly.java | 13 ++++++------- .../openems/edge/app/api/ModbusTcpApiReadWrite.java | 13 ++++++------- .../src/io/openems/edge/app/evcs/EvcsCluster.java | 2 -- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadOnly.java b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadOnly.java index eeb6ef4b103..c3b40ce4144 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadOnly.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadOnly.java @@ -29,8 +29,7 @@ import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.validator.CheckAppsNotInstalled; -import io.openems.edge.core.appmanager.validator.Validator; -import io.openems.edge.core.appmanager.validator.Validator.Builder; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** * Describes a App for ReadOnly Modbus/TCP Api. @@ -102,11 +101,11 @@ protected ThrowingTriFunction(new TreeMap()) // + protected io.openems.edge.core.appmanager.validator.ValidatorConfig.Builder getValidateBuilder() { + return ValidatorConfig.create() // + .setInstallableCheckableConfigs( + Lists.newArrayList(new ValidatorConfig.CheckableConfig(CheckAppsNotInstalled.COMPONENT_NAME, + new ValidatorConfig.MapBuilder<>(new TreeMap()) // .put("appIds", new String[] { "App.Api.ModbusTcp.ReadWrite" }) // .build()))); } diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java index 47b94add635..89e213ce64e 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java @@ -33,8 +33,7 @@ import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.validator.CheckAppsNotInstalled; -import io.openems.edge.core.appmanager.validator.Validator; -import io.openems.edge.core.appmanager.validator.Validator.Builder; +import io.openems.edge.core.appmanager.validator.ValidatorConfig; /** * Describes a App for ReadWrite Modbus/TCP Api. @@ -143,11 +142,11 @@ protected ThrowingTriFunction(new TreeMap()) // + protected io.openems.edge.core.appmanager.validator.ValidatorConfig.Builder getValidateBuilder() { + return ValidatorConfig.create() // + .setInstallableCheckableConfigs( + Lists.newArrayList(new ValidatorConfig.CheckableConfig(CheckAppsNotInstalled.COMPONENT_NAME, + new ValidatorConfig.MapBuilder<>(new TreeMap()) // .put("appIds", new String[] { "App.Api.ModbusTcp.ReadOnly" }) // .build()))); } diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java index cdc75cf54f6..c383fc966f1 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsCluster.java @@ -31,8 +31,6 @@ import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; import io.openems.edge.core.appmanager.TranslationUtil; -import io.openems.edge.core.appmanager.dependency.DependencyDeclaration; -import io.openems.edge.core.appmanager.dependency.DependencyUtil; /** * Describes a evcs cluster. From 6be4ab7bad4d54bc06cb5bed8c9ca294479cb9a3 Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 14:19:03 +0200 Subject: [PATCH 27/30] javadoc; translation --- .../io/openems/common/utils/JsonUtils.java | 7 +- .../org.eclipse.core.resources.prefs | 1 + .../SelfConsumptionOptimization.java | 288 ------------------ .../core/appmanager/OpenemsAppCategory.java | 12 +- .../dependency/AppManagerAppHelperImpl.java | 14 +- .../dependency/translation_de.properties | 1 + .../dependency/translation_en.properties | 3 +- 7 files changed, 29 insertions(+), 297 deletions(-) diff --git a/io.openems.common/src/io/openems/common/utils/JsonUtils.java b/io.openems.common/src/io/openems/common/utils/JsonUtils.java index 2cac52b61eb..21cbaec8005 100644 --- a/io.openems.common/src/io/openems/common/utils/JsonUtils.java +++ b/io.openems.common/src/io/openems/common/utils/JsonUtils.java @@ -302,7 +302,7 @@ public static class JsonArrayCollector implements Collector characteristics() { - return Sets.immutableEnumSet(Characteristics.UNORDERED); + return Sets.newHashSet().stream().collect(Sets.toImmutableEnumSet()); } @Override @@ -332,6 +332,11 @@ public Function finisher() { private static final Logger LOG = LoggerFactory.getLogger(JsonUtils.class); + /** + * Returns a Collector that accumulates the input elements into a new JsonArray. + * + * @return a Collector which collects all the input elements into a JsonArray + */ public static Collector toJsonArray() { return new JsonUtils.JsonArrayCollector(); } diff --git a/io.openems.edge.core/.settings/org.eclipse.core.resources.prefs b/io.openems.edge.core/.settings/org.eclipse.core.resources.prefs index 0e5ea801f51..2b7c020688d 100644 --- a/io.openems.edge.core/.settings/org.eclipse.core.resources.prefs +++ b/io.openems.edge.core/.settings/org.eclipse.core.resources.prefs @@ -1,3 +1,4 @@ eclipse.preferences.version=1 encoding//src/io/openems/edge/core/appmanager/dependency/translation_de.properties=UTF-8 +encoding//src/io/openems/edge/core/appmanager/translation_de.properties=UTF-8 encoding/=UTF-8 diff --git a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java index 58f4842056c..27fa8c84da1 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java +++ b/io.openems.edge.core/src/io/openems/edge/app/pvselfconsumption/SelfConsumptionOptimization.java @@ -139,292 +139,4 @@ public OpenemsAppCardinality getCardinality() { return OpenemsAppCardinality.SINGLE; } - @Override - public String getImage() { - return "data:image/png;base64,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"; - } - } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java index 2c55047ae9a..4f239238411 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java @@ -57,7 +57,15 @@ public enum OpenemsAppCategory { /** * Apis. */ - API("api"); + API("api"), + + /** + * Category for test apps. + * + *

+ * NOTE: Do not use this category for normal apps! + */ + TEST("test"); private String readableNameKey; @@ -74,7 +82,7 @@ private OpenemsAppCategory(String readableNameKey) { public String getReadableName(Language language) { var translationBundle = ResourceBundle.getBundle("io.openems.edge.core.appmanager.translation", language.getLocal()); - return translationBundle.getString(this.readableNameKey); + return TranslationUtil.getTranslation(translationBundle, this.readableNameKey); } /** diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java index a48f2db91af..0962c2fc02f 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/AppManagerAppHelperImpl.java @@ -560,13 +560,14 @@ public String toString() { public UpdateValues deleteApp(User user, OpenemsAppInstance instance) throws OpenemsNamedException { this.resetTasks(); + final var language = user == null ? null : user.getLanguage(); + final var bundle = getTranslationBundle(language); // check if the app is allowed to be delete if (!this.isAllowedToDelete(instance)) { - throw new OpenemsException("App is not allowed to be deleted because of a dependency constraint!"); + throw new OpenemsException(TranslationUtil.getTranslation(bundle, "appNotAllowedToBeDeleted")); } var deletedInstances = new LinkedList(); - final var language = user == null ? null : user.getLanguage(); final var errors = new LinkedList(); this.foreachExistingDependency(instance, ConfigurationTarget.DELETE, language, dc -> { @@ -614,21 +615,24 @@ public UpdateValues deleteApp(User user, OpenemsAppInstance instance) throws Ope // delete components this.componentsTask.delete(user, otherAppConfigs); } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); + this.log.error(e.getMessage()); + errors.add(TranslationUtil.getTranslation(bundle, "canNotUpdateComponents")); } try { // remove ids in scheduler this.schedulerTask.delete(user, otherAppConfigs); } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); + this.log.error(e.getMessage()); + errors.add(TranslationUtil.getTranslation(bundle, "canNotUpdateScheduler")); } try { // remove static ips this.staticIpTask.delete(user, otherAppConfigs); } catch (OpenemsNamedException e) { - errors.add(e.getMessage()); + this.log.error(e.getMessage()); + errors.add(TranslationUtil.getTranslation(bundle, "canNotUpdateStaticIps")); } if (!errors.isEmpty()) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_de.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_de.properties index e02882ea795..dd1c1846fc5 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_de.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_de.properties @@ -1,4 +1,5 @@ appNotAllowedToBeUpdated = Diese App darf nicht geupdated werden wegen einer Abhängigkeit einer anderen App! +appNotAllowedToBeDeleted = Diese App darf nicht gelöscht werden wegen einer Abhängigkeit einer anderen App! canNotChangeProperty = Feld[{0}] darf nicht geändert werden wegen einer Abhängigkeit einer anderen App! canNotChangeAlias = Alias darf nicht geändert werden wegen einer Abhängigkeit einer anderen App! overrideProperty = Feld[{0}] wird überschrieben wegen einer Abhängigkeit einer anderen App! diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_en.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_en.properties index a635512823b..b900ca96b80 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_en.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/translation_en.properties @@ -1,8 +1,9 @@ appNotAllowedToBeUpdated = This app is not allowed to be updated because of a dependency constraint! +appNotAllowedToBeDeleted = App is not allowed to be deleted because of a dependency constraint! canNotChangeProperty = Can not change Property[{0}] because of a Dependency constraint! canNotChangeAlias = Can not change Alias because of a Dependency constraint! overrideProperty = Override Property[{0}] because of a Dependency constraint! canNotUpdateComponents = Can not update Components! canNotUpdateScheduler = Can not update Scheduler! canNotUpdateStaticIps = Can not update static ips! -canNotGetAppConfiguration = Can not get AppConfiguration! +canNotGetAppConfiguration = Can not get AppConfiguration! \ No newline at end of file From 4b8dd8b1a70d1cbb12b346180fc0df11fee7368f Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 14:52:31 +0200 Subject: [PATCH 28/30] using TranslationUtil --- .../edge/app/api/ModbusTcpApiReadWrite.java | 12 ++++++++---- .../src/io/openems/edge/app/api/MqttApi.java | 19 ++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java index 89e213ce64e..bc7ed3c9175 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/ModbusTcpApiReadWrite.java @@ -32,6 +32,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.validator.CheckAppsNotInstalled; import io.openems.edge.core.appmanager.validator.ValidatorConfig; @@ -78,8 +79,9 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.API_TIMEOUT) // - .setLabel(bundle.getString("App.Api.apiTimeout.label")) // - .setDescription(bundle.getString("App.Api.apiTimeout.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "App.Api.apiTimeout.label")) // + .setDescription( + TranslationUtil.getTranslation(bundle, "App.Api.apiTimeout.description")) // .setDefaultValue(60) // .isRequired(true) // .setInputType(Type.NUMBER) // @@ -89,8 +91,10 @@ public AppAssistant getAppAssistant(Language language) { .add(JsonFormlyUtil.buildSelect(Property.COMPONENT_IDS) // .isMulti(true) // .isRequired(true) // - .setLabel(bundle.getString(this.getAppId() + ".componentIds.label")) // - .setDescription(bundle.getString(this.getAppId() + ".componentIds.description")) + .setLabel( + TranslationUtil.getTranslation(bundle, this.getAppId() + ".componentIds.label")) // + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".componentIds.description")) // .setOptions(this.componentManager.getAllComponents(), t -> t.id() + ": " + t.alias(), OpenemsComponent::id) .setDefaultValue(JsonUtils.buildJsonArray().add("_sum").build()) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java b/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java index c5207ed0971..becd05b419c 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java +++ b/io.openems.edge.core/src/io/openems/edge/app/api/MqttApi.java @@ -29,6 +29,7 @@ import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppCardinality; import io.openems.edge.core.appmanager.OpenemsAppCategory; +import io.openems.edge.core.appmanager.TranslationUtil; /** * Describes a App for MQTT Api. @@ -77,27 +78,31 @@ public AppAssistant getAppAssistant(Language language) { return AppAssistant.create(this.getName(language)) // .fields(JsonUtils.buildJsonArray() // .add(JsonFormlyUtil.buildInput(Property.USERNAME) // - .setLabel(bundle.getString("username")) // - .setDescription(bundle.getString(this.getAppId() + ".Username.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "username")) // + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".Username.description")) // .isRequired(true) // .setMinLenght(3) // .setMaxLenght(18) // .build()) // .add(JsonFormlyUtil.buildInput(Property.PASSWORD) // - .setLabel(bundle.getString("password")) // - .setDescription(bundle.getString(this.getAppId() + ".Password.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, "password")) // + .setDescription(TranslationUtil.getTranslation(bundle, + this.getAppId() + ".Password.description")) // .isRequired(true) // .setInputType(Type.PASSWORD) // .build()) // .add(JsonFormlyUtil.buildInput(Property.CLIENT_ID) // - .setLabel(bundle.getString(this.getAppId() + ".EdgeId.label")) // - .setDescription(bundle.getString(this.getAppId() + ".EdgeId.description")) // + .setLabel(TranslationUtil.getTranslation(bundle, this.getAppId() + ".EdgeId.label")) // + .setDescription( + TranslationUtil.getTranslation(bundle, this.getAppId() + ".EdgeId.description")) // .setDefaultValue("edge0") // .isRequired(true) // .build()) .add(JsonFormlyUtil.buildInput(Property.URI) // .setLabel("Uri") // - .setDescription(bundle.getString(this.getAppId() + ".Uri.description")) // + .setDescription( + TranslationUtil.getTranslation(bundle, this.getAppId() + ".Uri.description")) // .setDefaultValue("tcp://localhost:1883") // .isRequired(true) // .build()) // From c4767c1d68faebe098f75bbca721991d48bfdaed Mon Sep 17 00:00:00 2001 From: Michael Grill Date: Tue, 21 Jun 2022 15:08:42 +0200 Subject: [PATCH 29/30] added fix for app routing --- ui/src/app/shared/header/header.component.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/src/app/shared/header/header.component.ts b/ui/src/app/shared/header/header.component.ts index 9e114d77fda..809baa445d1 100644 --- a/ui/src/app/shared/header/header.component.ts +++ b/ui/src/app/shared/header/header.component.ts @@ -113,6 +113,12 @@ export class HeaderComponent { if (file === 'live') { urlArray.pop(); } + + // fix url for App "settings/app/install" and "settings/app/update" + if (urlArray.slice(-3, -1).join('/') === "settings/app") { + urlArray.pop(); + } + // re-join the url backUrl = urlArray.join('/') || '/'; From e09e1129ddd6e533c3ecaf1a429d22d557bd8da3 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Tue, 21 Jun 2022 22:23:51 +0200 Subject: [PATCH 30/30] Fix typo --- .../src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java index 7edf1e1fb7b..2b98a582b7e 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java @@ -245,7 +245,7 @@ private List getValidationErrors(JsonObject jProperties, List