From ff274850f3e82265ee445eeabdaf6e86c4fdc68e Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Mon, 31 Jan 2022 22:23:47 +0100 Subject: [PATCH] FEMS Backports (#1721) - Continuous improvement of Odoo Backend and FENECON Home implementation - Cleanup JsonUtils and EdgeConfig - Add EnumUtils - JsonrpcRequest: improve log messages on error - JUnit tests - Timeslot-Peakshaving ("Hochlastzeitfenster") - Draft for App-Manager - EssDcCharger and PV-Inverters: add modbus slave definition - GoodWe: - add config setting to disable MPP Tracking with external optimizers - improvements to "SMART"-Mode --- .../openems/backend/metadata/odoo/Field.java | 3 +- .../metadata/odoo/odoo/OdooHandler.java | 39 ++- .../backend/metadata/odoo/odoo/OdooUtils.java | 28 ++ .../odoo/postgres/task/UpdateEdgeConfig.java | 3 +- .../io/openems/common/OpenemsConstants.java | 4 +- .../common/exceptions/OpenemsError.java | 2 - .../common/jsonrpc/base/JsonrpcRequest.java | 5 +- .../io/openems/common/types/EdgeConfig.java | 68 +++- .../io/openems/common/utils/EnumUtils.java | 124 +++++++ .../io/openems/common/utils/JsonUtils.java | 299 +++++++++------- .../common/websocket/OnRequestHandler.java | 58 +++- .../websocket/OnRequestHandlerTest.java | 42 +++ io.openems.edge.application/EdgeApp.bndrun | 2 +- .../fenecon/home/FeneconHomeBattery.java | 177 +++++----- .../fenecon/home/FeneconHomeBatteryImpl.java | 137 ++++---- .../fenecon/home/enums/BmsControl.java | 35 -- .../fenecon/home/statemachine/Context.java | 12 +- .../fenecon/home/FeneconHomeBatteryTest.java | 19 +- .../batteryinverter/sinexcel/Sinexcel.java | 2 +- .../common/test/DummyComponentManager.java | 23 +- .../edge/controller/api/rest/RestHandler.java | 2 +- .../readwrite/RestApiReadWriteImplTest.java | 11 +- .../TimeslotPeakshaving.java | 1 - .../core/appmanager/AbstractOpenemsApp.java | 286 ++++++++++++++++ .../edge/core/appmanager/AppAssistant.java | 68 ++++ .../edge/core/appmanager/AppManager.java | 94 +++++ .../edge/core/appmanager/AppManagerImpl.java | 322 ++++++++++++++++++ .../core/appmanager/AppValidateWorker.java | 140 ++++++++ .../openems/edge/core/appmanager/Config.java | 16 + .../edge/core/appmanager/OpenemsApp.java | 54 +++ .../core/appmanager/OpenemsAppCategory.java | 10 + .../core/appmanager/OpenemsAppInstance.java | 38 +++ .../appmanager/jsonrpc/AddAppInstance.java | 105 ++++++ .../appmanager/jsonrpc/GetAppAssistant.java | 98 ++++++ .../appmanager/jsonrpc/GetAppInstances.java | 108 ++++++ .../edge/core/appmanager/jsonrpc/GetApps.java | 124 +++++++ .../appmanager/jsonrpc/UpdateAppInstance.java | 85 +++++ .../core/host/NetworkConfigurationWorker.java | 5 +- .../edge/core/host/SystemUpdateHandler.java | 6 +- .../ComponentManagerImplTest.java | 22 ++ .../edge/core/componentmanager/MyConfig.java | 35 ++ .../edge/ess/dccharger/api/EssDcCharger.java | 10 + ...stractEssDcChargerFeneconCommercial40.java | 15 +- .../charger/AbstractFeneconDessCharger.java | 17 +- .../edge/goodwe/batteryinverter/Config.java | 23 +- .../GoodWeBatteryInverterImpl.java | 34 +- .../charger/AbstractGoodWeEtCharger.java | 17 +- .../edge/goodwe/common/AbstractGoodWe.java | 4 +- .../edge/goodwe/common/ApplyPowerHandler.java | 2 +- .../io/openems/edge/goodwe/common/GoodWe.java | 13 +- .../goodwe/common/enums/AutoStartBackup.java | 32 -- .../goodwe/common/enums/BackupEnable.java | 32 -- .../goodwe/common/enums/EnableDisable.java | 17 + .../goodwe/common/enums/FeedPowerEnable.java | 35 -- .../common/enums/MeterReverseEnable.java | 32 -- .../GoodWeBatteryInverterImplTest.java | 53 +-- .../edge/goodwe/batteryinverter/MyConfig.java | 26 +- .../piko/charger/KostalPikoCharger.java | 15 +- .../api/ManagedSymmetricPvInverter.java | 6 + .../pvinverter/cluster/PvInverterCluster.java | 16 +- .../kaco/blueplanet/KacoBlueplanet.java | 16 +- .../edge/pvinverter/sma/SmaPvInverter.java | 16 +- .../edge/pvinverter/solarlog/SolarLog.java | 4 +- .../pvinverter/solarlog/SolarLogImpl.java | 16 +- .../ess/symmetric/reacting/EssSymmetric.java | 14 +- .../timeofusetariff/awattar/AwattarImpl.java | 2 +- .../timeofusetariff/tibber/TibberImpl.java | 2 +- .../tibber/TibberProviderTest.java | 2 +- .../shared/influxdb/InfluxConnector.java | 8 +- .../chart.component.ts | 5 +- ui/src/app/index/index.component.html | 4 +- .../request/submitSetupProtocolRequest.ts | 4 + ui/src/app/shared/service/utils.ts | 8 +- 73 files changed, 2622 insertions(+), 590 deletions(-) create mode 100644 io.openems.common/src/io/openems/common/utils/EnumUtils.java create mode 100644 io.openems.common/test/io/openems/common/websocket/OnRequestHandlerTest.java delete mode 100644 io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/enums/BmsControl.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/AppAssistant.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManager.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/AppValidateWorker.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/Config.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/AddAppInstance.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppAssistant.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppInstances.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java create mode 100644 io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppInstance.java create mode 100644 io.openems.edge.core/test/io/openems/edge/core/componentmanager/ComponentManagerImplTest.java create mode 100644 io.openems.edge.core/test/io/openems/edge/core/componentmanager/MyConfig.java delete mode 100644 io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/AutoStartBackup.java delete mode 100644 io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/BackupEnable.java create mode 100644 io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/EnableDisable.java delete mode 100644 io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/FeedPowerEnable.java delete mode 100644 io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/MeterReverseEnable.java diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/Field.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/Field.java index 50e2f16ac1c..b6819b6d78e 100644 --- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/Field.java +++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/Field.java @@ -327,7 +327,8 @@ public enum Partner implements Field { CITY("city", true), // COUNTRY("country_id", true), // ADDRESS_TYPE("type", true), // - LANGUAGE("lang", true); + LANGUAGE("lang", true), // + CATEGORY_ID("category_id", true); public static final String ODOO_MODEL = "res.partner"; public static final String ODOO_TABLE = Partner.ODOO_MODEL.replace(".", "_"); diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooHandler.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooHandler.java index 5c66193001d..00126b4983b 100644 --- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooHandler.java +++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooHandler.java @@ -408,6 +408,7 @@ public byte[] getOdooSetupProtocolReport(int setupProtocolId) throws OpenemsName public int submitSetupProtocol(MyUser user, JsonObject setupProtocolJson) throws OpenemsNamedException { var userJson = JsonUtils.getAsJsonObject(setupProtocolJson, "customer"); var edgeJson = JsonUtils.getAsJsonObject(setupProtocolJson, "edge"); + var installerJson = JsonUtils.getAsJsonObject(setupProtocolJson, "installer"); var edgeId = JsonUtils.getAsString(edgeJson, "id"); int[] foundEdge = OdooUtils.search(this.credentials, Field.EdgeDevice.ODOO_MODEL, @@ -416,7 +417,7 @@ public int submitSetupProtocol(MyUser user, JsonObject setupProtocolJson) throws throw new OpenemsException("Edge not found for id [" + edgeId + "]"); } - var password = PasswordUtils.generateRandomPassword(24); + var password = PasswordUtils.generateRandomPassword(8); var odooUserId = this.createOdooUser(userJson, password); var customerId = this.getOdooPartnerId(odooUserId); @@ -425,6 +426,20 @@ public int submitSetupProtocol(MyUser user, JsonObject setupProtocolJson) throws var protocolId = this.createSetupProtocol(setupProtocolJson, foundEdge[0], customerId, installerId); + var installer = OdooUtils.readOne(credentials, Field.Partner.ODOO_MODEL, installerId, Field.Partner.IS_COMPANY); + boolean isCompany = (boolean) installer.get("is_company"); + if (!isCompany) { + Map fieldsToUpdate = new HashMap<>(); + JsonUtils.getAsOptionalString(installerJson, "firstname") // + .ifPresent(firstname -> fieldsToUpdate.put(Field.Partner.FIRSTNAME.id(), firstname)); + JsonUtils.getAsOptionalString(installerJson, "lastname") // + .ifPresent(lastname -> fieldsToUpdate.put(Field.Partner.LASTNAME.id(), lastname)); + + if (!fieldsToUpdate.isEmpty()) { + OdooUtils.write(credentials, Field.Partner.ODOO_MODEL, new Integer[] { installerId }, fieldsToUpdate); + } + } + try { this.sendSetupProtocolMail(user, protocolId, edgeId); } catch (OpenemsNamedException ex) { @@ -481,20 +496,42 @@ private int createOdooUser(JsonObject userJson, String password) throws OpenemsN new Domain(Field.User.LOGIN, "=", email)); if (userFound.length == 1) { + // update existing user var userId = userFound[0]; OdooUtils.write(this.credentials, Field.User.ODOO_MODEL, new Integer[] { userId }, customerFields); return userId; } + customerFields.put(Field.User.LOGIN.id(), email); customerFields.put(Field.User.PASSWORD.id(), password); customerFields.put(Field.User.GLOBAL_ROLE.id(), OdooUserRole.OWNER.getOdooRole()); customerFields.put(Field.User.GROUPS.id(), OdooUserRole.OWNER.toOdooIds()); var createdUserId = OdooUtils.create(this.credentials, Field.User.ODOO_MODEL, customerFields); + try { + this.addTagToPartner(createdUserId); + } catch (OpenemsException e) { + this.log.warn("Unable to add tag for Odoo user id [" + createdUserId + "]", e); + } + this.sendRegistrationMail(createdUserId, password); return createdUserId; } + /** + * Add the "Created via IBN" tag to the referenced partner for given user id. + * + * @param userId to get Odoo partner + * @throws OpenemsException on error + */ + private void addTagToPartner(int userId) throws OpenemsException { + var tagId = OdooUtils.getObjectReference(credentials, "edge", "res_partner_category_created_via_ibn"); + var partnerId = this.getOdooPartnerId(userId); + + OdooUtils.write(this.credentials, Field.Partner.ODOO_MODEL, new Integer[] { partnerId }, + new FieldValue<>(Field.Partner.CATEGORY_ID, new Integer[] { tagId })); + } + /** * Create a setup protocol in Odoo. * diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUtils.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUtils.java index 62e7b451a1a..32b32f313fe 100644 --- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUtils.java +++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUtils.java @@ -434,6 +434,34 @@ protected static Map[] searchRead(Credentials credentials, Strin } } + /** + * Executes a get object reference from Odoo. + * + * @param credentials the Odoo credentials + * @param module the Odoo module + * @param name the external identifier + * @return internal id of external identifier + * @throws OpenemsException on error + */ + protected static int getObjectReference(Credentials credentials, String module, String name) + throws OpenemsException { + // Create request params + Object[] params = { credentials.getDatabase(), credentials.getUid(), credentials.getPassword(), "ir.model.data", + "get_object_reference", new Object[] { module, name } }; + try { + // Execute XML request + var resultObj = (Object[]) executeKw(credentials.getUrl(), params); + if (resultObj == null) { + throw new OpenemsException( + "No matching entry found for module [" + module + "] and name [" + name + "]"); + } + + return (int) resultObj[1]; + } catch (Throwable e) { + throw new OpenemsException("Unable to read from Odoo: " + e.getMessage()); + } + } + /** * Adds a message in Odoo Chatter ('mail.thread'). * diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/postgres/task/UpdateEdgeConfig.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/postgres/task/UpdateEdgeConfig.java index 84daf22d53e..00e7c5e8797 100644 --- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/postgres/task/UpdateEdgeConfig.java +++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/postgres/task/UpdateEdgeConfig.java @@ -9,6 +9,7 @@ import io.openems.backend.metadata.odoo.Field.EdgeDevice; import io.openems.common.types.EdgeConfig; import io.openems.common.types.EdgeConfig.Component.JsonFormat; +import io.openems.common.utils.JsonUtils; import io.openems.common.utils.StringUtils; public class UpdateEdgeConfig extends DatabaseTask { @@ -19,7 +20,7 @@ public class UpdateEdgeConfig extends DatabaseTask { public UpdateEdgeConfig(int odooId, EdgeConfig config) { this.odooId = odooId; - this.fullConfig = new GsonBuilder().setPrettyPrinting().create().toJson(config.toJson()); + this.fullConfig = JsonUtils.prettyToString(config.toJson()); this.componentsConfig = new GsonBuilder().setPrettyPrinting().create() .toJson(config.componentsToJson(JsonFormat.WITHOUT_CHANNELS)); } diff --git a/io.openems.common/src/io/openems/common/OpenemsConstants.java b/io.openems.common/src/io/openems/common/OpenemsConstants.java index 4f737f1c2d1..6c041975a4d 100644 --- a/io.openems.common/src/io/openems/common/OpenemsConstants.java +++ b/io.openems.common/src/io/openems/common/OpenemsConstants.java @@ -22,7 +22,7 @@ public class OpenemsConstants { *

* This is the month of the release. */ - public static final short VERSION_MINOR = 2; + public final static short VERSION_MINOR = 2; /** * The patch version of OpenEMS. @@ -31,7 +31,7 @@ public class OpenemsConstants { * This is always `0` for OpenEMS open source releases and reserved for private * distributions. */ - public static final short VERSION_PATCH = 0; + public final static short VERSION_PATCH = 0; /** * The additional version string. diff --git a/io.openems.common/src/io/openems/common/exceptions/OpenemsError.java b/io.openems.common/src/io/openems/common/exceptions/OpenemsError.java index 739b3847741..b5ab6f7fb2c 100644 --- a/io.openems.common/src/io/openems/common/exceptions/OpenemsError.java +++ b/io.openems.common/src/io/openems/common/exceptions/OpenemsError.java @@ -63,12 +63,10 @@ public enum OpenemsError { JSON_NO_STRING(5009, "JSON [%s] is not a String"), // JSON_NO_STRING_MEMBER(5010, "JSON [%s:%s] is not a String"), // JSON_NO_BOOLEAN(5011, "JSON [%s] is not a Boolean"), // - JSON_NO_BOOLEAN_MEMBER(5012, "JSON [%s:%s] is not a Boolean"), // JSON_NO_NUMBER(5013, "JSON [%s] is not a Number"), // JSON_NO_NUMBER_MEMBER(5014, "JSON [%s:%s] is not a Number"), // JSON_PARSE_ELEMENT_FAILED(5015, "JSON failed to parse [%s]. %s: %s"), // JSON_PARSE_FAILED(5016, "JSON failed to parse [%s]: %s"), // - JSON_NO_FLOAT_MEMBER(5017, "JSON [%s:%s] is not a Float"), // JSON_NO_ENUM_MEMBER(5018, "JSON [%s:%s] is not an Enum"), // JSON_NO_INET4ADDRESS(5020, "JSON [%s] is not an IPv4 address"), // JSON_NO_ENUM(5021, "JSON [%s] is not an Enum"), // diff --git a/io.openems.common/src/io/openems/common/jsonrpc/base/JsonrpcRequest.java b/io.openems.common/src/io/openems/common/jsonrpc/base/JsonrpcRequest.java index dcc8ac12daa..5f48be201e6 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/base/JsonrpcRequest.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/base/JsonrpcRequest.java @@ -28,8 +28,8 @@ public abstract class JsonrpcRequest extends AbstractJsonrpcRequest { public static final int DEFAULT_TIMEOUT_SECONDS = 60; public static final int NO_TIMEOUT = -1; - private final UUID id; - private final Optional timeoutOpt; + public final UUID id; + public final Optional timeoutOpt; /** * Creates a {@link JsonrpcRequest} with random {@link UUID} as id and @@ -115,6 +115,7 @@ public JsonrpcRequest(UUID id, String method, int timeout) { * * @return the {@link UUID} id */ + // TODO remove this method and directly use the public final 'id'. public UUID getId() { return this.id; } diff --git a/io.openems.common/src/io/openems/common/types/EdgeConfig.java b/io.openems.common/src/io/openems/common/types/EdgeConfig.java index 3ada947b7fe..9c18c72948b 100644 --- a/io.openems.common/src/io/openems/common/types/EdgeConfig.java +++ b/io.openems.common/src/io/openems/common/types/EdgeConfig.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.SortedMap; import java.util.TreeMap; import java.util.stream.Collectors; @@ -26,6 +27,7 @@ import io.openems.common.channel.ChannelCategory; import io.openems.common.channel.Level; import io.openems.common.channel.Unit; +import io.openems.common.exceptions.InvalidValueException; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.types.EdgeConfig.Component.JsonFormat; @@ -43,6 +45,8 @@ public class EdgeConfig { */ public static class Component { + public static final String NO_SERVICE_PID = "NO_SERVICE_PID"; + /** * Represents a Channel of an OpenEMS Component. */ @@ -300,11 +304,11 @@ public JsonObject toJson() { private final String id; private final String alias; private final String factoryId; - private final TreeMap properties; - private final TreeMap channels; + private final SortedMap properties; + private final SortedMap channels; public Component(String servicePid, String id, String alias, String factoryId, - TreeMap properties, TreeMap channels) { + SortedMap properties, SortedMap channels) { this.servicePid = servicePid; this.id = id; this.alias = alias; @@ -313,6 +317,34 @@ public Component(String servicePid, String id, String alias, String factoryId, this.channels = channels; } + /** + * Constructor with NO_SERVICE_PID. + * + * @param id the Component-ID + * @param alias the Alias + * @param factoryId the Factory-ID + * @param properties the configuration properties + * @param channels the channels + */ + public Component(String id, String alias, String factoryId, SortedMap properties, + SortedMap channels) { + this(NO_SERVICE_PID, id, alias, factoryId, properties, channels); + } + + /** + * Constructor with NO_SERVICE_PID, properties as JsonObject and no channels + * + * @param id the Component-ID + * @param factoryId the Factory-ID + * @param properties the configuration properties + */ + public Component(String id, String alias, String factoryId, JsonObject properties) { + this(NO_SERVICE_PID, id, alias, factoryId, + properties.entrySet().stream() + .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (o1, o2) -> o1, TreeMap::new)), + new TreeMap<>()); + } + /** * Gets the PID of the {@link Component}. * @@ -368,6 +400,21 @@ public Optional getProperty(String propertyId) { return Optional.ofNullable(this.properties.get(propertyId)); } + /** + * Gets the Property with the given ID or throws an Exception if it does not + * exist. + * + * @param propertyId the Property-ID + * @return the Property + */ + public JsonElement getPropertyOrError(String propertyId) throws InvalidValueException { + JsonElement property = this.properties.get(propertyId); + if (property != null) { + return property; + } + throw new InvalidValueException("Property with ID [" + propertyId + "] does not exist."); + } + /** * Gets a map of {@link Channel}s of the {@link Component}. * @@ -509,7 +556,6 @@ public static Component fromJson(String componentId, JsonElement json) throws Op } } return new Component(// - "NO_SERVICE_PID", // componentId, // alias, // factoryId, // @@ -1055,6 +1101,20 @@ public Optional getComponent(String componentId) { return Optional.ofNullable(this.components.get(componentId)); } + /** + * Gets the {@link Component} or throws an Exception if it does not exist. + * + * @param componentId the Component-ID + * @return the {@link Component} + */ + public Component getComponentOrError(String componentId) throws InvalidValueException { + Component component = this.components.get(componentId); + if (component != null) { + return component; + } + throw new InvalidValueException("Component with ID [" + componentId + "] does not exist."); + } + /** * Add a Factory. * diff --git a/io.openems.common/src/io/openems/common/utils/EnumUtils.java b/io.openems.common/src/io/openems/common/utils/EnumUtils.java new file mode 100644 index 00000000000..b96337222dd --- /dev/null +++ b/io.openems.common/src/io/openems/common/utils/EnumUtils.java @@ -0,0 +1,124 @@ +package io.openems.common.utils; + +import java.util.EnumMap; +import java.util.Optional; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +import io.openems.common.exceptions.OpenemsError; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; + +public class EnumUtils { + + /** + * Gets the member of the {@link EnumMap} as {@link Optional} {@link Boolean}. + * + * @param the type of the EnumMap key + * @param map the {@link EnumMap} + * @param member the member + * @return the {@link Optional} {@link Boolean} value + * @throws OpenemsNamedException on error + */ + public static > Optional getAsOptionalBoolean(EnumMap map, + ENUM member) { + try { + return Optional.of(getAsBoolean(map, member)); + } catch (OpenemsNamedException e) { + return Optional.empty(); + } + } + + /** + * Gets the member of the {@link EnumMap} as {@link Optional} {@link String}. + * + * @param the type of the EnumMap key + * @param map the {@link EnumMap} + * @param member the member + * @return the {@link Optional} {@link String} value + * @throws OpenemsNamedException on error + */ + public static > Optional getAsOptionalString(EnumMap map, + ENUM member) { + try { + return Optional.of(getAsString(map, member)); + } catch (OpenemsNamedException e) { + return Optional.empty(); + } + } + + /** + * Gets the member of the {@link EnumMap} as {@link JsonPrimitive}. + * + * @param the type of the EnumMap key + * @param map the {@link EnumMap} + * @param member the member + * @return the {@link JsonPrimitive} value + * @throws OpenemsNamedException on error + */ + public static > JsonPrimitive getAsPrimitive(EnumMap map, ENUM member) + throws OpenemsNamedException { + JsonElement jSubElement = getSubElement(map, member); + return JsonUtils.getAsPrimitive(jSubElement); + } + + /** + * Gets the member of the {@link EnumMap} as {@link Boolean}. + * + * @param the type of the EnumMap key + * @param map the {@link EnumMap} + * @param member the member + * @return the {@link Boolean} value + * @throws OpenemsNamedException on error + */ + public static > Boolean getAsBoolean(EnumMap map, ENUM member) + throws OpenemsNamedException { + return JsonUtils.getAsBoolean(getAsPrimitive(map, member)); + } + + /** + * Gets the member of the {@link EnumMap} as {@link String}. + * + * @param the type of the EnumMap key + * @param map the {@link EnumMap} + * @param member the member + * @return the {@link String} value + * @throws OpenemsNamedException on error + */ + public static > String getAsString(EnumMap map, ENUM member) + throws OpenemsNamedException { + return JsonUtils.getAsString(getAsPrimitive(map, member)); + } + + /** + * Gets the member of the {@link EnumMap} as {@link JsonElement}. + * + * @param the type of the EnumMap key + * @param map the {@link EnumMap} + * @param member the member + * @return the {@link JsonElement} value + * @throws OpenemsNamedException on error + */ + public static > JsonElement getSubElement(EnumMap map, ENUM member) + throws OpenemsNamedException { + if (!map.containsKey(member)) { + throw OpenemsError.JSON_HAS_NO_MEMBER.exception(member, + StringUtils.toShortString(map.toString(), 100).replaceAll("%", "%%")); + } + return map.get(member); + } + + /** + * Gets the member of the {@link EnumMap} as int. + * + * @param the type of the EnumMap key + * @param map the {@link EnumMap} + * @param member the member + * @return the int value + * @throws OpenemsNamedException on error + */ + public static > int getAsInt(EnumMap map, ENUM member) + throws OpenemsNamedException { + return JsonUtils.getAsInt(getAsPrimitive(map, member)); + } +} 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 295f2c8d841..0cefd17859a 100644 --- a/io.openems.common/src/io/openems/common/utils/JsonUtils.java +++ b/io.openems.common/src/io/openems/common/utils/JsonUtils.java @@ -1,7 +1,6 @@ package io.openems.common.utils; import java.net.Inet4Address; -import java.net.InetAddress; import java.net.UnknownHostException; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -341,8 +340,12 @@ public static JsonPrimitive getAsPrimitive(JsonElement jElement) throws OpenemsN * @throws OpenemsNamedException on error */ public static JsonPrimitive getAsPrimitive(JsonElement jElement, String memberName) throws OpenemsNamedException { - var jSubElement = JsonUtils.getSubElement(jElement, memberName); - return JsonUtils.getAsPrimitive(jSubElement); + var jSubElement = getSubElement(jElement, memberName); + if (!jSubElement.isJsonPrimitive()) { + throw OpenemsError.JSON_NO_PRIMITIVE_MEMBER.exception(memberName, + jElement.toString().replaceAll("%", "%%")); + } + return jSubElement.getAsJsonPrimitive(); } /** @@ -356,7 +359,7 @@ public static JsonPrimitive getAsPrimitive(JsonElement jElement, String memberNa */ public static Optional getOptionalSubElement(JsonElement jElement, String memberName) { try { - return Optional.of(JsonUtils.getSubElement(jElement, memberName)); + return Optional.of(getSubElement(jElement, memberName)); } catch (OpenemsNamedException e) { return Optional.empty(); } @@ -371,7 +374,7 @@ public static Optional getOptionalSubElement(JsonElement jElement, * @throws OpenemsNamedException on error */ public static JsonElement getSubElement(JsonElement jElement, String memberName) throws OpenemsNamedException { - var jObject = JsonUtils.getAsJsonObject(jElement); + var jObject = getAsJsonObject(jElement); if (!jObject.has(memberName)) { throw OpenemsError.JSON_HAS_NO_MEMBER.exception(memberName, StringUtils.toShortString(jElement, 100).replace("%", "%%")); @@ -402,7 +405,7 @@ public static JsonObject getAsJsonObject(JsonElement jElement) throws OpenemsNam * @throws OpenemsNamedException on error */ public static JsonObject getAsJsonObject(JsonElement jElement, String memberName) throws OpenemsNamedException { - var subElement = JsonUtils.getSubElement(jElement, memberName); + var subElement = getSubElement(jElement, memberName); if (!subElement.isJsonObject()) { throw OpenemsError.JSON_NO_OBJECT_MEMBER.exception(memberName, StringUtils.toShortString(subElement, 100).replace("%", "%%")); @@ -419,7 +422,7 @@ public static JsonObject getAsJsonObject(JsonElement jElement, String memberName */ public static Optional getAsOptionalJsonObject(JsonElement jElement) { try { - return Optional.of(JsonUtils.getAsJsonObject(jElement)); + return Optional.of(getAsJsonObject(jElement)); } catch (OpenemsNamedException e) { return Optional.empty(); } @@ -436,7 +439,7 @@ public static Optional getAsOptionalJsonObject(JsonElement jElement) */ public static Optional getAsOptionalJsonObject(JsonElement jElement, String memberName) { try { - return Optional.of(JsonUtils.getAsJsonObject(jElement, memberName)); + return Optional.of(getAsJsonObject(jElement, memberName)); } catch (OpenemsNamedException e) { return Optional.empty(); } @@ -465,7 +468,7 @@ public static JsonArray getAsJsonArray(JsonElement jElement) throws OpenemsNamed * @throws OpenemsNamedException on error */ public static JsonArray getAsJsonArray(JsonElement jElement, String memberName) throws OpenemsNamedException { - var jSubElement = JsonUtils.getSubElement(jElement, memberName); + var jSubElement = getSubElement(jElement, memberName); if (!jSubElement.isJsonArray()) { throw OpenemsError.JSON_NO_ARRAY_MEMBER.exception(memberName, jSubElement.toString().replace("%", "%%")); } @@ -483,7 +486,7 @@ public static JsonArray getAsJsonArray(JsonElement jElement, String memberName) */ public static Optional getAsOptionalJsonArray(JsonElement jElement, String memberName) { try { - return Optional.of(JsonUtils.getAsJsonArray(jElement, memberName)); + return Optional.of(getAsJsonArray(jElement, memberName)); } catch (OpenemsNamedException e) { return Optional.empty(); } @@ -498,7 +501,18 @@ public static Optional getAsOptionalJsonArray(JsonElement jElement, S */ public static String getAsString(JsonElement jElement) throws OpenemsNamedException { - var jPrimitive = JsonUtils.getAsPrimitive(jElement); + return getAsString(getAsPrimitive(jElement)); + } + + /** + * Gets the {@link JsonPrimitive} as {@link String}. + * + * @param jPrimitive the {@link JsonPrimitive} + * @return the {@link String} value + * @throws OpenemsNamedException on error + */ + + public static String getAsString(JsonPrimitive jPrimitive) throws OpenemsNamedException { if (!jPrimitive.isString()) { throw OpenemsError.JSON_NO_STRING.exception(jPrimitive.toString().replace("%", "%%")); } @@ -514,7 +528,7 @@ public static String getAsString(JsonElement jElement) throws OpenemsNamedExcept * @throws OpenemsNamedException on error */ public static String getAsString(JsonElement jElement, String memberName) throws OpenemsNamedException { - var jPrimitive = JsonUtils.getAsPrimitive(jElement, memberName); + var jPrimitive = getAsPrimitive(jElement, memberName); if (!jPrimitive.isString()) { throw OpenemsError.JSON_NO_STRING_MEMBER.exception(memberName, jPrimitive.toString().replace("%", "%%")); } @@ -530,7 +544,7 @@ public static String getAsString(JsonElement jElement, String memberName) throws */ public static Optional getAsOptionalString(JsonElement jElement) { try { - return Optional.of(JsonUtils.getAsString(jElement)); + return Optional.of(getAsString(jElement)); } catch (OpenemsNamedException e) { return Optional.empty(); } @@ -547,7 +561,7 @@ public static Optional getAsOptionalString(JsonElement jElement) { */ public static Optional getAsOptionalString(JsonElement jElement, String memberName) { try { - return Optional.of(JsonUtils.getAsString(jElement, memberName)); + return Optional.of(getAsString(jElement, memberName)); } catch (OpenemsNamedException e) { return Optional.empty(); } @@ -570,14 +584,13 @@ public static String[] getAsStringArray(JsonArray json) throws OpenemsNamedExcep } /** - * Gets the {@link JsonElement} as {@link Boolean}. - * - * @param jElement the {@link JsonElement} + * Gets the {@link JsonPrimitive} as {@link Boolean}. + * + * @param jPrimitive the {@link JsonPrimitive} * @return the {@link Boolean} value * @throws OpenemsNamedException on error */ - public static boolean getAsBoolean(JsonElement jElement) throws OpenemsNamedException { - var jPrimitive = JsonUtils.getAsPrimitive(jElement); + public static boolean getAsBoolean(JsonPrimitive jPrimitive) throws OpenemsNamedException { if (jPrimitive.isBoolean()) { return jPrimitive.getAsBoolean(); } @@ -585,14 +598,24 @@ public static boolean getAsBoolean(JsonElement jElement) throws OpenemsNamedExce var element = jPrimitive.getAsString(); if (element.equalsIgnoreCase("false")) { return false; - } - if (element.equalsIgnoreCase("true")) { + } else if (element.equalsIgnoreCase("true")) { return true; } } throw OpenemsError.JSON_NO_BOOLEAN.exception(jPrimitive.toString().replace("%", "%%")); } + /** + * Gets the {@link JsonElement} as {@link Boolean}. + * + * @param jElement the {@link JsonElement} + * @return the {@link Boolean} value + * @throws OpenemsNamedException on error + */ + public static boolean getAsBoolean(JsonElement jElement) throws OpenemsNamedException { + return getAsBoolean(getAsPrimitive(jElement)); + } + /** * Gets the member of the {@link JsonElement} as {@link Boolean}. * @@ -602,11 +625,8 @@ public static boolean getAsBoolean(JsonElement jElement) throws OpenemsNamedExce * @throws OpenemsNamedException on error */ public static boolean getAsBoolean(JsonElement jElement, String memberName) throws OpenemsNamedException { - var jPrimitive = JsonUtils.getAsPrimitive(jElement, memberName); - if (!jPrimitive.isBoolean()) { - throw OpenemsError.JSON_NO_BOOLEAN_MEMBER.exception(memberName, jPrimitive.toString().replace("%", "%%")); - } - return jPrimitive.getAsBoolean(); + var jPrimitive = getAsPrimitive(jElement, memberName); + return getAsBoolean(jPrimitive); } /** @@ -620,31 +640,40 @@ public static boolean getAsBoolean(JsonElement jElement, String memberName) thro */ public static Optional getAsOptionalBoolean(JsonElement jElement, String memberName) { try { - return Optional.of(JsonUtils.getAsBoolean(jElement, memberName)); + return Optional.of(getAsBoolean(jElement, memberName)); } catch (OpenemsNamedException e) { return Optional.empty(); } } /** - * Gets the {@link JsonElement} as short. - * - * @param jElement the {@link JsonElement} + * Gets the {@link JsonPrimitive} as short. + * + * @param jPrimitive the {@link JsonPrimitive} * @return the short value * @throws OpenemsNamedException on error */ - public static short getAsShort(JsonElement jElement) throws OpenemsNamedException { - var jPrimitive = JsonUtils.getAsPrimitive(jElement); + public static short getAsShort(JsonPrimitive jPrimitive) throws OpenemsNamedException { if (jPrimitive.isNumber()) { return jPrimitive.getAsShort(); - } - if (jPrimitive.isString()) { + } else if (jPrimitive.isString()) { var string = jPrimitive.getAsString(); return Short.parseShort(string); } throw OpenemsError.JSON_NO_INTEGER.exception(jPrimitive.toString().replace("%", "%%")); } + /** + * Gets the {@link JsonElement} as short. + * + * @param jElement the {@link JsonElement} + * @return the short value + * @throws OpenemsNamedException on error + */ + public static short getAsShort(JsonElement jElement) throws OpenemsNamedException { + return getAsShort(getAsPrimitive(jElement)); + } + /** * Gets the member of the {@link JsonElement} as short. * @@ -654,36 +683,37 @@ public static short getAsShort(JsonElement jElement) throws OpenemsNamedExceptio * @throws OpenemsNamedException on error */ public static short getAsShort(JsonElement jElement, String memberName) throws OpenemsNamedException { - var jPrimitive = JsonUtils.getAsPrimitive(jElement, memberName); - if (jPrimitive.isNumber()) { - return jPrimitive.getAsShort(); - } - if (jPrimitive.isString()) { - var string = jPrimitive.getAsString(); - return Short.parseShort(string); - } - throw OpenemsError.JSON_NO_INTEGER_MEMBER.exception(memberName, jPrimitive.toString().replace("%", "%%")); + return getAsShort(getAsPrimitive(jElement, memberName)); } /** - * Gets the {@link JsonElement} as int. - * - * @param jElement the {@link JsonElement} + * Gets the {@link JsonPrimitive} as int. + * + * @param jPrimitive the {@link JsonPrimitive} * @return the int value * @throws OpenemsNamedException on error */ - public static int getAsInt(JsonElement jElement) throws OpenemsNamedException { - var jPrimitive = JsonUtils.getAsPrimitive(jElement); + public static int getAsInt(JsonPrimitive jPrimitive) throws OpenemsNamedException { if (jPrimitive.isNumber()) { return jPrimitive.getAsInt(); - } - if (jPrimitive.isString()) { + } else if (jPrimitive.isString()) { var string = jPrimitive.getAsString(); return Integer.parseInt(string); } throw OpenemsError.JSON_NO_INTEGER.exception(jPrimitive.toString().replace("%", "%%")); } + /** + * Gets the {@link JsonElement} as int. + * + * @param jElement the {@link JsonElement} + * @return the int value + * @throws OpenemsNamedException on error + */ + public static int getAsInt(JsonElement jElement) throws OpenemsNamedException { + return getAsInt(getAsPrimitive(jElement)); + } + /** * Gets the member of the {@link JsonElement} as int. * @@ -693,15 +723,7 @@ public static int getAsInt(JsonElement jElement) throws OpenemsNamedException { * @throws OpenemsNamedException on error */ public static int getAsInt(JsonElement jElement, String memberName) throws OpenemsNamedException { - var jPrimitive = JsonUtils.getAsPrimitive(jElement, memberName); - if (jPrimitive.isNumber()) { - return jPrimitive.getAsInt(); - } - if (jPrimitive.isString()) { - var string = jPrimitive.getAsString(); - return Integer.parseInt(string); - } - throw OpenemsError.JSON_NO_INTEGER_MEMBER.exception(memberName, jPrimitive.toString().replace("%", "%%")); + return getAsInt(getAsPrimitive(jElement, memberName)); } /** @@ -728,7 +750,7 @@ public static int getAsInt(JsonArray jArray, int index) throws OpenemsNamedExcep */ public static Optional getAsOptionalInt(JsonElement jElement) { try { - return Optional.of(JsonUtils.getAsInt(jElement)); + return Optional.of(getAsInt(jElement)); } catch (OpenemsNamedException e) { return Optional.empty(); } @@ -745,31 +767,40 @@ public static Optional getAsOptionalInt(JsonElement jElement) { */ public static Optional getAsOptionalInt(JsonElement jElement, String memberName) { try { - return Optional.of(JsonUtils.getAsInt(jElement, memberName)); + return Optional.of(getAsInt(jElement, memberName)); } catch (OpenemsNamedException e) { return Optional.empty(); } } /** - * Gets the {@link JsonElement} as long. - * - * @param jElement the {@link JsonElement} + * Gets the {@link JsonPrimitive} as long. + * + * @param jPrimitive the {@link JsonPrimitive} * @return the long value * @throws OpenemsNamedException on error */ - public static long getAsLong(JsonElement jElement) throws OpenemsNamedException { - var jPrimitive = JsonUtils.getAsPrimitive(jElement); + public static long getAsLong(JsonPrimitive jPrimitive) throws OpenemsNamedException { if (jPrimitive.isNumber()) { return jPrimitive.getAsLong(); - } - if (jPrimitive.isString()) { + } else if (jPrimitive.isString()) { var string = jPrimitive.getAsString(); return Integer.parseInt(string); } throw OpenemsError.JSON_NO_NUMBER.exception(jPrimitive.toString().replace("%", "%%")); } + /** + * Gets the {@link JsonElement} as long. + * + * @param jElement the {@link JsonElement} + * @return the long value + * @throws OpenemsNamedException on error + */ + public static long getAsLong(JsonElement jElement) throws OpenemsNamedException { + return getAsLong(getAsPrimitive(jElement)); + } + /** * Gets the member of the {@link JsonElement} as long. * @@ -779,15 +810,7 @@ public static long getAsLong(JsonElement jElement) throws OpenemsNamedException * @throws OpenemsNamedException on error */ public static long getAsLong(JsonElement jElement, String memberName) throws OpenemsNamedException { - var jPrimitive = JsonUtils.getAsPrimitive(jElement, memberName); - if (jPrimitive.isNumber()) { - return jPrimitive.getAsLong(); - } - if (jPrimitive.isString()) { - var string = jPrimitive.getAsString(); - return Long.parseLong(string); - } - throw OpenemsError.JSON_NO_NUMBER.exception(jPrimitive.toString().replace("%", "%%")); + return getAsLong(getAsPrimitive(jElement, memberName)); } /** @@ -800,31 +823,40 @@ public static long getAsLong(JsonElement jElement, String memberName) throws Ope */ public static Optional getAsOptionalLong(JsonElement jElement, String memberName) { try { - return Optional.of(JsonUtils.getAsLong(jElement, memberName)); + return Optional.of(getAsLong(jElement, memberName)); } catch (OpenemsNamedException e) { return Optional.empty(); } } /** - * Gets the {@link JsonElement} as {@link Float}. - * - * @param jElement the {@link JsonElement} + * Gets the {@link JsonPrimitive} as {@link Float}. + * + * @param jPrimitive the {@link JsonPrimitive} * @return the {@link Float} value * @throws OpenemsNamedException on error */ - public static float getAsFloat(JsonElement jElement) throws OpenemsNamedException { - var jPrimitive = JsonUtils.getAsPrimitive(jElement); + public static float getAsFloat(JsonPrimitive jPrimitive) throws OpenemsNamedException { if (jPrimitive.isNumber()) { return jPrimitive.getAsFloat(); - } - if (jPrimitive.isString()) { + } else if (jPrimitive.isString()) { var string = jPrimitive.getAsString(); return Float.parseFloat(string); } throw OpenemsError.JSON_NO_FLOAT.exception(jPrimitive.toString().replace("%", "%%")); } + /** + * Gets the {@link JsonElement} as {@link Float}. + * + * @param jElement the {@link JsonElement} + * @return the {@link Float} value + * @throws OpenemsNamedException on error + */ + public static float getAsFloat(JsonElement jElement) throws OpenemsNamedException { + return getAsFloat(getAsPrimitive(jElement)); + } + /** * Gets the member of the {@link JsonElement} as {@link Float}. * @@ -834,36 +866,37 @@ public static float getAsFloat(JsonElement jElement) throws OpenemsNamedExceptio * @throws OpenemsNamedException on error */ public static float getAsFloat(JsonElement jElement, String memberName) throws OpenemsNamedException { - var jPrimitive = JsonUtils.getAsPrimitive(jElement, memberName); - if (jPrimitive.isNumber()) { - return jPrimitive.getAsFloat(); - } - if (jPrimitive.isString()) { - var string = jPrimitive.getAsString(); - return Float.parseFloat(string); - } - throw OpenemsError.JSON_NO_FLOAT_MEMBER.exception(memberName, jPrimitive.toString().replace("%", "%%")); + return getAsFloat(getAsPrimitive(jElement, memberName)); } /** - * Gets the {@link JsonElement} as {@link Double}. - * - * @param jElement the {@link JsonElement} + * Gets the {@link JsonPrimitive} as {@link Double}. + * + * @param jPrimitive the {@link JsonPrimitive} * @return the {@link Double} value * @throws OpenemsNamedException on error */ - public static double getAsDouble(JsonElement jElement) throws OpenemsNamedException { - var jPrimitive = JsonUtils.getAsPrimitive(jElement); + public static double getAsDouble(JsonPrimitive jPrimitive) throws OpenemsNamedException { if (jPrimitive.isNumber()) { return jPrimitive.getAsDouble(); - } - if (jPrimitive.isString()) { + } else if (jPrimitive.isString()) { var string = jPrimitive.getAsString(); return Double.parseDouble(string); } throw OpenemsError.JSON_NO_INTEGER.exception(jPrimitive.toString().replace("%", "%%")); } + /** + * Gets the {@link JsonElement} as {@link Double}. + * + * @param jElement the {@link JsonElement} + * @return the {@link Double} value + * @throws OpenemsNamedException on error + */ + public static double getAsDouble(JsonElement jElement) throws OpenemsNamedException { + return getAsDouble(getAsPrimitive(jElement)); + } + /** * Gets the member of the {@link JsonElement} as {@link Double}. * @@ -873,15 +906,7 @@ public static double getAsDouble(JsonElement jElement) throws OpenemsNamedExcept * @throws OpenemsNamedException on error */ public static double getAsDouble(JsonElement jElement, String memberName) throws OpenemsNamedException { - var jPrimitive = JsonUtils.getAsPrimitive(jElement, memberName); - if (jPrimitive.isNumber()) { - return jPrimitive.getAsDouble(); - } - if (jPrimitive.isString()) { - var string = jPrimitive.getAsString(); - return Double.parseDouble(string); - } - throw OpenemsError.JSON_NO_INTEGER_MEMBER.exception(memberName, jPrimitive.toString().replace("%", "%%")); + return getAsDouble(getAsPrimitive(jElement, memberName)); } /** @@ -895,9 +920,9 @@ public static double getAsDouble(JsonElement jElement, String memberName) throws */ public static > E getAsEnum(Class enumType, JsonElement jElement) throws OpenemsNamedException { - var element = JsonUtils.getAsString(jElement); + var element = getAsString(jElement); try { - return Enum.valueOf(enumType, element); + return (E) Enum.valueOf(enumType, element); } catch (IllegalArgumentException e) { throw OpenemsError.JSON_NO_ENUM.exception(element); } @@ -915,9 +940,9 @@ public static > E getAsEnum(Class enumType, JsonElement jEl */ public static > E getAsEnum(Class enumType, JsonElement jElement, String memberName) throws OpenemsNamedException { - var element = JsonUtils.getAsString(jElement, memberName); + var element = getAsString(jElement, memberName); try { - return Enum.valueOf(enumType, element); + return (E) Enum.valueOf(enumType, element); } catch (IllegalArgumentException e) { throw OpenemsError.JSON_NO_ENUM_MEMBER.exception(memberName, element); } @@ -935,7 +960,7 @@ public static > E getAsEnum(Class enumType, JsonElement jEl */ public static > Optional getAsOptionalEnum(Class enumType, JsonElement jElement, String memberName) { - var elementOpt = JsonUtils.getAsOptionalString(jElement, memberName); + var elementOpt = getAsOptionalString(jElement, memberName); if (!elementOpt.isPresent()) { return Optional.empty(); } @@ -955,7 +980,7 @@ public static > Optional getAsOptionalEnum(Class enumTyp */ public static Inet4Address getAsInet4Address(JsonElement jElement) throws OpenemsNamedException { try { - return (Inet4Address) InetAddress.getByName(JsonUtils.getAsString(jElement)); + return (Inet4Address) Inet4Address.getByName(getAsString(jElement)); } catch (UnknownHostException e) { throw OpenemsError.JSON_NO_INET4ADDRESS.exception(jElement.toString().replace("%", "%%")); } @@ -972,8 +997,7 @@ public static Inet4Address getAsInet4Address(JsonElement jElement) throws Openem */ public static Optional getAsOptionalInet4Address(JsonElement jElement, String memberName) { try { - return Optional - .ofNullable((Inet4Address) InetAddress.getByName(JsonUtils.getAsString(jElement, memberName))); + return Optional.ofNullable((Inet4Address) Inet4Address.getByName(getAsString(jElement, memberName))); } catch (OpenemsNamedException | UnknownHostException e) { return Optional.empty(); } @@ -991,7 +1015,7 @@ public static Optional getAsOptionalInet4Address(JsonElement jElem public static UUID getAsUUID(JsonElement jElement, String memberName) throws OpenemsNamedException { // CHECKSTYLE:ON try { - return UUID.fromString(JsonUtils.getAsString(jElement, memberName)); + return UUID.fromString(getAsString(jElement, memberName)); } catch (IllegalArgumentException e) { throw new OpenemsException("Unable to parse UUID: " + e.getMessage()); } @@ -1008,7 +1032,7 @@ public static UUID getAsUUID(JsonElement jElement, String memberName) throws Ope // CHECKSTYLE:OFF public static Optional getAsOptionalUUID(JsonElement jElement, String memberName) { // CHECKSTYLE:ON - var uuid = JsonUtils.getAsOptionalString(jElement, memberName); + Optional uuid = getAsOptionalString(jElement, memberName); if (uuid.isPresent()) { return Optional.ofNullable(UUID.fromString(uuid.get())); } @@ -1054,8 +1078,7 @@ public static Object getAsBestType(JsonElement j) throws OpenemsNamedException { result[i] = jA.get(i).getAsBoolean(); } return result; - } - if (isInt) { + } else if (isInt) { // convert to int array var result = new int[jA.size()]; for (var i = 0; i < jA.size(); i++) { @@ -1117,8 +1140,7 @@ public static JsonElement getAsJsonElement(Object value) { * Number */ return new JsonPrimitive((Number) value); - } - if (value instanceof String) { + } else if (value instanceof String) { /* * String */ @@ -1241,8 +1263,7 @@ public static Object getAsType(Class type, JsonElement j) throws NotImplement */ return j.getAsInt(); - } - if (Long.class.isAssignableFrom(type)) { + } else if (Long.class.isAssignableFrom(type)) { /* * Asking for an Long */ @@ -1359,7 +1380,7 @@ public static Object getAsType(Optional> typeOptional, JsonElement j) t throw new NotImplementedException("Type of Channel was not set: " + j.getAsString()); } Class type = typeOptional.get(); - return JsonUtils.getAsType(type, j); + return getAsType(type, j); } /** @@ -1411,15 +1432,33 @@ public static JsonObject parseToJsonObject(String string) throws OpenemsNamedExc return JsonUtils.getAsJsonObject(JsonUtils.parse(string)); } + /** + * Parses a string to a {@link JsonArray}. + * + * @param string the String + * @return the {@link JsonArray} + * @throws OpenemsNamedException on error + */ + public static JsonArray parseToJsonArray(String string) throws OpenemsNamedException { + return JsonUtils.getAsJsonArray(JsonUtils.parse(string)); + } + /** * Pretty print a {@link JsonElement}. * * @param j the {@link JsonElement} */ public static void prettyPrint(JsonElement j) { - var gson = new GsonBuilder().setPrettyPrinting().create(); - var json = gson.toJson(j); - System.out.println(json); + System.out.println(prettyToString(j)); + } + + /** + * Pretty toString()-method for a {@link JsonElement}. + * + * @param j the {@link JsonElement} + */ + public static String prettyToString(JsonElement j) { + return new GsonBuilder().setPrettyPrinting().create().toJson(j); } /** diff --git a/io.openems.common/src/io/openems/common/websocket/OnRequestHandler.java b/io.openems.common/src/io/openems/common/websocket/OnRequestHandler.java index 775b839ba43..c225f32faf0 100644 --- a/io.openems.common/src/io/openems/common/websocket/OnRequestHandler.java +++ b/io.openems.common/src/io/openems/common/websocket/OnRequestHandler.java @@ -8,11 +8,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gson.JsonObject; + import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.base.JsonrpcMessage; import io.openems.common.jsonrpc.base.JsonrpcRequest; import io.openems.common.jsonrpc.base.JsonrpcResponse; import io.openems.common.jsonrpc.base.JsonrpcResponseError; import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; +import io.openems.common.utils.JsonUtils; public class OnRequestHandler implements Runnable { @@ -45,19 +49,55 @@ public final void run() { // ...without timeout response = responseFuture.get(); } - } catch (OpenemsNamedException e) { - // Get Named Exception error response - this.parent.logWarn(this.log, "JSON-RPC Error Response: " + e.getMessage()); - response = new JsonrpcResponseError(this.request.getId(), e); + } catch (Exception e) { - // Get GENERIC error response - this.parent.logWarn(this.log, "JSON-RPC Error Response. " + e.getClass().getSimpleName() + ". " // - + "Request: " + this.request.toString() + ". " // - + "Message: " + e.getMessage()); - response = new JsonrpcResponseError(this.request.getId(), e.getMessage()); + // Log Error + var log = new StringBuilder() // + .append("JSON-RPC Error ") // + .append("Response \"").append(e.getMessage()).append("\" "); + if (!(e instanceof OpenemsNamedException)) { + log.append("of type ").append(e.getClass().getCanonicalName()).append("] "); + } + log.append("for Request ").append(simplifyJsonrpcMessage(this.request.toJsonObject()).toString()); // + this.parent.logWarn(this.log, log.toString()); + + // Get JSON-RPC Response Error + if (e instanceof OpenemsNamedException) { + response = new JsonrpcResponseError(this.request.getId(), (OpenemsNamedException) e); + } else { + response = new JsonrpcResponseError(this.request.getId(), e.getMessage()); + } } this.responseCallback.accept(response); } + /** + * Simplifies a {@link JsonrpcMessage} by recursively removing unnecessary + * elements "jsonrpc" and "id". + * + * @param j the {@link JsonrpcMessage#toJsonObject()} + * @return a simplified {@link JsonObject} + */ + protected static JsonObject simplifyJsonrpcMessage(JsonObject j) { + if (j.has("jsonrpc")) { + j.remove("jsonrpc"); + j.remove("id"); + } + if (j.has("params")) { + try { + var params = JsonUtils.getAsJsonObject(j, "params"); + if (params.has("payload")) { + var originalPayload = JsonUtils.getAsJsonObject(params, "payload"); + var simplifiedPayload = simplifyJsonrpcMessage(originalPayload); + params.add("payload", simplifiedPayload); + j.add("params", params); + } + } catch (OpenemsNamedException e) { + // ignore -> do not replace params/payload + } + } + return j; + } + } diff --git a/io.openems.common/test/io/openems/common/websocket/OnRequestHandlerTest.java b/io.openems.common/test/io/openems/common/websocket/OnRequestHandlerTest.java new file mode 100644 index 00000000000..546e36de5b4 --- /dev/null +++ b/io.openems.common/test/io/openems/common/websocket/OnRequestHandlerTest.java @@ -0,0 +1,42 @@ +package io.openems.common.websocket; + +import static org.junit.Assert.assertEquals; + +import java.util.Optional; +import java.util.UUID; + +import org.junit.Test; + +import io.openems.common.jsonrpc.base.GenericJsonrpcRequest; +import io.openems.common.utils.JsonUtils; + +public class OnRequestHandlerTest { + + @Test + public void testSimplifyJsonrpcMessage() { + var r = new GenericJsonrpcRequest(UUID.randomUUID(), "fooMethod", JsonUtils.buildJsonObject() // + .addProperty("foo", "bar") // + .add("payload", new GenericJsonrpcRequest(UUID.randomUUID(), "barMethod", JsonUtils.buildJsonObject() // + .addProperty("lorem", "ipsum") // + .build(), Optional.empty()).toJsonObject()) // + .build(), 123); + + assertEquals(JsonUtils.buildJsonObject() // + .addProperty("method", "fooMethod") // + .add("params", JsonUtils.buildJsonObject() // + .addProperty("foo", "bar") // + .add("payload", JsonUtils.buildJsonObject() // + .addProperty("method", "barMethod") // + .add("params", JsonUtils.buildJsonObject() // + .addProperty("lorem", "ipsum") // + .build()) // + .build()) // + .build()) + .addProperty("timeout", 123) // + .build(), // + OnRequestHandler.simplifyJsonrpcMessage(r.toJsonObject())); + + System.out.println(OnRequestHandler.simplifyJsonrpcMessage(r.toJsonObject())); + } + +} diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index 03734fc808b..e24bab08fac 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -18,7 +18,7 @@ -runbundles+: \ org.apache.felix.scr;startlevel=10,\ org.eclipse.equinox.event;startlevel=11,\ - org.ops4j.pax.logging.pax-logging-log4j2;startlevel=12 + org.ops4j.pax.logging.pax-logging-log4j2;startlevel=12 -runrequires: \ bnd.identity;id='org.ops4j.pax.logging.pax-logging-api',\ diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBattery.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBattery.java index 437e7ef81ea..0b4ba4bccc2 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBattery.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBattery.java @@ -5,7 +5,6 @@ import io.openems.common.channel.Unit; import io.openems.common.types.OpenemsType; import io.openems.edge.battery.api.Battery; -import io.openems.edge.battery.fenecon.home.enums.BmsControl; import io.openems.edge.battery.fenecon.home.statemachine.StateMachine.State; import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.Doc; @@ -22,7 +21,7 @@ public interface FeneconHomeBattery extends Battery, OpenemsComponent, StartStop * * @return the Channel */ - public default Channel getBmsControlChannel() { + public default Channel getBmsControlChannel() { return this.channel(ChannelId.BMS_CONTROL); } @@ -31,8 +30,8 @@ public default Channel getBmsControlChannel() { * * @return the Channel {@link Value} */ - public default BmsControl getBmsControl() { - return this.getBmsControlChannel().value().asEnum(); + public default Value getBmsControl() { + return this.getBmsControlChannel().value(); } /** @@ -41,7 +40,7 @@ public default BmsControl getBmsControl() { * * @param value the next value */ - public default void _setBmsControl(BmsControl value) { + public default void _setBmsControl(Boolean value) { this.getBmsControlChannel().setNextValue(value); } @@ -73,107 +72,107 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .accessMode(AccessMode.READ_ONLY) // .text("Rack Cell Under Voltage warning")), // - RACK_PRE_ALARM_CELL_OVER_VOLTAGE(Doc.of(Level.INFO) // + RACK_PRE_ALARM_CELL_OVER_VOLTAGE(Doc.of(OpenemsType.BOOLEAN) // .text("Rack Cell Over Voltage Alarm")), // - RACK_PRE_ALARM_OVER_CHARGING_CURRENT(Doc.of(Level.INFO) // + RACK_PRE_ALARM_OVER_CHARGING_CURRENT(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Over Charging Current Alarm")), // - RACK_PRE_ALARM_OVER_DISCHARGING_CURRENT(Doc.of(Level.INFO) // + RACK_PRE_ALARM_OVER_DISCHARGING_CURRENT(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Over Discharging Current Alarm")), // - RACK_PRE_ALARM_OVER_TEMPERATURE(Doc.of(Level.INFO) // + RACK_PRE_ALARM_OVER_TEMPERATURE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Over Temperature Alarm")), // - RACK_PRE_ALARM_UNDER_TEMPERATURE(Doc.of(Level.INFO) // + RACK_PRE_ALARM_UNDER_TEMPERATURE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Under Temperature Alarm")), // - RACK_PRE_ALARM_CELL_VOLTAGE_DIFFERENCE(Doc.of(Level.INFO) // + RACK_PRE_ALARM_CELL_VOLTAGE_DIFFERENCE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Cell VOltage Difference Alarm")), // - RACK_PRE_ALARM_BCU_TEMP_DIFFERENCE(Doc.of(Level.INFO) // + RACK_PRE_ALARM_BCU_TEMP_DIFFERENCE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack BCU Temp Difference Alarm")), // - RACK_PRE_ALARM_UNDER_SOH(Doc.of(Level.INFO) // + RACK_PRE_ALARM_UNDER_SOH(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Under SOH Alarm")), // - RACK_PRE_ALARM_OVER_CHARGING_POWER(Doc.of(Level.INFO) // + RACK_PRE_ALARM_OVER_CHARGING_POWER(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Over Charging Alarm")), // - RACK_PRE_ALARM_OVER_DISCHARGING_POWER(Doc.of(Level.INFO) // + RACK_PRE_ALARM_OVER_DISCHARGING_POWER(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Over Discharging Alarm")), // - RACK_LEVEL_1_CELL_OVER_VOLTAGE(Doc.of(Level.WARNING) // + RACK_LEVEL_1_CELL_OVER_VOLTAGE(Doc.of(OpenemsType.BOOLEAN) // .text("Rack Cell Over Voltage warning")), // - RACK_LEVEL_1_OVER_CHARGING_CURRENT(Doc.of(Level.WARNING) // + RACK_LEVEL_1_OVER_CHARGING_CURRENT(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Over Charging Current warning")), // - RACK_LEVEL_1_OVER_DISCHARGING_CURRENT(Doc.of(Level.WARNING) // + RACK_LEVEL_1_OVER_DISCHARGING_CURRENT(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Over Discharging Current warning")), // - RACK_LEVEL_1_OVER_TEMPERATURE(Doc.of(Level.WARNING) // + RACK_LEVEL_1_OVER_TEMPERATURE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Over Temperature warning")), // - RACK_LEVEL_1_UNDER_TEMPERATURE(Doc.of(Level.WARNING) // + RACK_LEVEL_1_UNDER_TEMPERATURE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Under Temperature warning")), // - RACK_LEVEL_1_CELL_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // + RACK_LEVEL_1_CELL_VOLTAGE_DIFFERENCE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Cell VOltage Difference warning")), // - RACK_LEVEL_1_BCU_TEMP_DIFFERENCE(Doc.of(Level.WARNING) // + RACK_LEVEL_1_BCU_TEMP_DIFFERENCE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack BCU Temp Difference warning")), // - RACK_LEVEL_1_UNDER_SOH(Doc.of(Level.WARNING) // + RACK_LEVEL_1_UNDER_SOH(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Under SOH warning")), // - RACK_LEVEL_1_OVER_CHARGING_POWER(Doc.of(Level.WARNING) // + RACK_LEVEL_1_OVER_CHARGING_POWER(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Over Charging warning")), // - RACK_LEVEL_1_OVER_DISCHARGING_POWER(Doc.of(Level.WARNING) // + RACK_LEVEL_1_OVER_DISCHARGING_POWER(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Over Discharging warning")), // - RACK_LEVEL_2_CELL_OVER_VOLTAGE(Doc.of(Level.FAULT) // + RACK_LEVEL_2_CELL_OVER_VOLTAGE(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Cell Over Voltage Fault")), // - RACK_LEVEL_2_CELL_UNDER_VOLTAGE(Doc.of(Level.FAULT) // + RACK_LEVEL_2_CELL_UNDER_VOLTAGE(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Cell Under Voltage Fault")), // - RACK_LEVEL_2_OVER_CHARGING_CURRENT(Doc.of(Level.FAULT) // + RACK_LEVEL_2_OVER_CHARGING_CURRENT(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Over Charging Current Fault")), // - RACK_LEVEL_2_OVER_DISCHARGING_CURRENT(Doc.of(Level.FAULT) // + RACK_LEVEL_2_OVER_DISCHARGING_CURRENT(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Over Discharging Current Fault")), // - RACK_LEVEL_2_OVER_TEMPERATURE(Doc.of(Level.FAULT) // + RACK_LEVEL_2_OVER_TEMPERATURE(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Over Temperature Fault")), // - RACK_LEVEL_2_UNDER_TEMPERATURE(Doc.of(Level.FAULT) // + RACK_LEVEL_2_UNDER_TEMPERATURE(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Under Temperature Fault")), // - RACK_LEVEL_2_CELL_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_LEVEL_2_CELL_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Cell Voltage Difference Fault")), // - RACK_LEVEL_2_BCU_TEMP_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_LEVEL_2_BCU_TEMP_DIFFERENCE(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack BCU Temp Difference Fault")), // - RACK_LEVEL_2_CELL_TEMPERATURE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_LEVEL_2_CELL_TEMPERATURE_DIFFERENCE(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Cell Temperature Difference Fault")), // - RACK_LEVEL_2_INTERNAL_COMMUNICATION(Doc.of(Level.FAULT) // + RACK_LEVEL_2_INTERNAL_COMMUNICATION(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Internal Communication Fault")), // - RACK_LEVEL_2_EXTERNAL_COMMUNICATION(Doc.of(Level.FAULT) // + RACK_LEVEL_2_EXTERNAL_COMMUNICATION(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack External Communication Fault")), // - RACK_LEVEL_2_PRE_CHARGE_FAIL(Doc.of(Level.FAULT) // + RACK_LEVEL_2_PRE_CHARGE_FAIL(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Pre Charge Fault")), // - RACK_LEVEL_2_PARALLEL_FAIL(Doc.of(Level.FAULT) // + RACK_LEVEL_2_PARALLEL_FAIL(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Parallel Fault")), // - RACK_LEVEL_2_SYSTEM_FAIL(Doc.of(Level.FAULT) // + RACK_LEVEL_2_SYSTEM_FAIL(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack System Fault")), // - RACK_LEVEL_2_HARDWARE_FAIL(Doc.of(Level.FAULT) // + RACK_LEVEL_2_HARDWARE_FAIL(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY) // .text("Rack Hardware Fault")), // @@ -184,25 +183,25 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId ALARM_POSITION_BCU_3(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Alarm BCU 3 Position")), // - ALARM_POSITION_BCU_4(Doc.of(Level.INFO) // + ALARM_POSITION_BCU_4(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Alarm BCU 4 Position")), // - ALARM_POSITION_BCU_5(Doc.of(Level.INFO) // + ALARM_POSITION_BCU_5(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Alarm BCU 5 Position")), // - ALARM_POSITION_BCU_6(Doc.of(Level.INFO) // + ALARM_POSITION_BCU_6(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Alarm BCU 6 Position")), // - ALARM_POSITION_BCU_7(Doc.of(Level.INFO) // + ALARM_POSITION_BCU_7(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Alarm BCU 7 Position")), // - ALARM_POSITION_BCU_8(Doc.of(Level.INFO) // + ALARM_POSITION_BCU_8(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Alarm BCU 8 Position")), // - ALARM_POSITION_BCU_9(Doc.of(Level.INFO) // + ALARM_POSITION_BCU_9(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Alarm BCU 9 Position")), // - ALARM_POSITION_BCU_10(Doc.of(Level.INFO) // + ALARM_POSITION_BCU_10(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Alarm BCU 10 Position")), // @@ -213,25 +212,25 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId WARNING_POSITION_BCU_3(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Warning BCU 3 Position")), // - WARNING_POSITION_BCU_4(Doc.of(Level.WARNING) // + WARNING_POSITION_BCU_4(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Warning BCU 4 Position")), // - WARNING_POSITION_BCU_5(Doc.of(Level.WARNING) // + WARNING_POSITION_BCU_5(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Warning BCU 5 Position")), // - WARNING_POSITION_BCU_6(Doc.of(Level.WARNING) // + WARNING_POSITION_BCU_6(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Warning BCU 6 Position")), // - WARNING_POSITION_BCU_7(Doc.of(Level.WARNING) // + WARNING_POSITION_BCU_7(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Warning BCU 7 Position")), // - WARNING_POSITION_BCU_8(Doc.of(Level.WARNING) // + WARNING_POSITION_BCU_8(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Warning BCU 8 Position")), // - WARNING_POSITION_BCU_9(Doc.of(Level.WARNING) // + WARNING_POSITION_BCU_9(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Warning BCU 9 Position")), // - WARNING_POSITION_BCU_10(Doc.of(Level.WARNING) // + WARNING_POSITION_BCU_10(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("Warning BCU 10 Position")), // @@ -370,107 +369,107 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .text("CV Point")), // BCU Status Flags - STATUS_ALARM(Doc.of(Level.INFO) // + STATUS_ALARM(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Status Alarm")), - STATUS_WARNING(Doc.of(Level.WARNING) // + STATUS_WARNING(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Status WARNNG")), - STATUS_FAULT(Doc.of(Level.WARNING) // + STATUS_FAULT(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Status BCU Status Fault")), - STATUS_PFET(Doc.of(Level.INFO) // + STATUS_PFET(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Status Pre-Charge FET On/Off")), - STATUS_CFET(Doc.of(Level.INFO) // + STATUS_CFET(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Status Charge FET On/Off")), - STATUS_DFET(Doc.of(Level.INFO) // + STATUS_DFET(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Status Discharge FET On/Off")), - STATUS_BATTERY_IDLE(Doc.of(Level.INFO) // + STATUS_BATTERY_IDLE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Status Battery Idle")), - STATUS_BATTERY_CHARGING(Doc.of(Level.INFO) // + STATUS_BATTERY_CHARGING(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Status Battery Charging")), - STATUS_BATTERY_DISCHARGING(Doc.of(Level.INFO) // + STATUS_BATTERY_DISCHARGING(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Status Battery Discharging")), // Bcu Alarm Flags - PRE_ALARM_CELL_OVER_VOLTAGE(Doc.of(Level.INFO) // + PRE_ALARM_CELL_OVER_VOLTAGE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Alarm Cell Over Voltage")), - PRE_ALARM_CELL_UNDER_VOLTAGE(Doc.of(Level.INFO) // + PRE_ALARM_CELL_UNDER_VOLTAGE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Alarm Cell Under Voltage")), - PRE_ALARM_OVER_CHARGING_CURRENT(Doc.of(Level.INFO) // + PRE_ALARM_OVER_CHARGING_CURRENT(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Alarm Over Charging Current")), - PRE_ALARM_OVER_DISCHARGING_CURRENT(Doc.of(Level.INFO) // + PRE_ALARM_OVER_DISCHARGING_CURRENT(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Alarm Over Discharging Current")), - PRE_ALARM_OVER_TEMPERATURE(Doc.of(Level.INFO) // + PRE_ALARM_OVER_TEMPERATURE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Alarm Over Temperature")), - PRE_ALARM_UNDER_TEMPERATURE(Doc.of(Level.INFO) // + PRE_ALARM_UNDER_TEMPERATURE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Alarm Under Temperature")), - PRE_ALARM_CELL_VOLTAGE_DIFFERENCE(Doc.of(Level.INFO) // + PRE_ALARM_CELL_VOLTAGE_DIFFERENCE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Alarm Cell Voltage Difference")), - PRE_ALARM_BCU_TEMP_DIFFERENCE(Doc.of(Level.INFO) // + PRE_ALARM_BCU_TEMP_DIFFERENCE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Alarm BCU Temperature Difference")), - PRE_ALARM_UNDER_SOC(Doc.of(Level.INFO) // + PRE_ALARM_UNDER_SOC(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Alarm Under SOC")), - PRE_ALARM_UNDER_SOH(Doc.of(Level.INFO) // + PRE_ALARM_UNDER_SOH(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Alarm Under SOH")), - PRE_ALARM_OVER_CHARGING_POWER(Doc.of(Level.INFO) // + PRE_ALARM_OVER_CHARGING_POWER(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Alarm Over Charging Power")), - PRE_ALARM_OVER_DISCHARGING_POWER(Doc.of(Level.INFO) // + PRE_ALARM_OVER_DISCHARGING_POWER(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Alarm Over Discharging Power")), // Bcu Warning Flags - LEVEL_1_CELL_OVER_VOLTAGE(Doc.of(Level.WARNING) // + LEVEL_1_CELL_OVER_VOLTAGE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Warning Cell Over Voltage")), - LEVEL_1_CELL_UNDER_VOLTAGE(Doc.of(Level.WARNING) // + LEVEL_1_CELL_UNDER_VOLTAGE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Warning Cell Under Voltage")), - LEVEL_1_OVER_CHARGING_CURRENT(Doc.of(Level.WARNING) // + LEVEL_1_OVER_CHARGING_CURRENT(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Warning Over Charging Current")), - LEVEL_1_OVER_DISCHARGING_CURRENT(Doc.of(Level.WARNING) // + LEVEL_1_OVER_DISCHARGING_CURRENT(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Warning Over Discharging Current")), - LEVEL_1_OVER_TEMPERATURE(Doc.of(Level.WARNING) // + LEVEL_1_OVER_TEMPERATURE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Warning Over Temperature")), - LEVEL_1_UNDER_TEMPERATURE(Doc.of(Level.WARNING) // + LEVEL_1_UNDER_TEMPERATURE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Warning Under Temperature")), - LEVEL_1_CELL_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // + LEVEL_1_CELL_VOLTAGE_DIFFERENCE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Warning Cell Voltage Difference")), - LEVEL_1_BCU_TEMP_DIFFERENCE(Doc.of(Level.WARNING) // + LEVEL_1_BCU_TEMP_DIFFERENCE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Warning BCU Temperature Difference")), - LEVEL_1_UNDER_SOC(Doc.of(Level.WARNING) // + LEVEL_1_UNDER_SOC(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Warning Under SOC")), - LEVEL_1_UNDER_SOH(Doc.of(Level.WARNING) // + LEVEL_1_UNDER_SOH(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Warning Under SOH")), - LEVEL_1_OVER_CHARGING_POWER(Doc.of(Level.WARNING) // + LEVEL_1_OVER_CHARGING_POWER(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Warning Over Charging Power")), - LEVEL_1_OVER_DISCHARGING_POWER(Doc.of(Level.WARNING) // + LEVEL_1_OVER_DISCHARGING_POWER(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_ONLY) // .text("BCU Warning Over Discharging Power")), @@ -609,8 +608,8 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .accessMode(AccessMode.READ_ONLY) // .text("Bms software version of first tower")), - BMS_CONTROL(Doc.of(BmsControl.values()) // - .text("BMS CONTROL(1: Shutdown, 0: no action, 2: Ignore)")), + BMS_CONTROL(Doc.of(OpenemsType.BOOLEAN) // + .text("BMS CONTROL(1: Shutdown, 0: no action)")), STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // RUN_FAILED(Doc.of(Level.FAULT) // diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryImpl.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryImpl.java index 2ffaee16f66..ae6e69866f3 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryImpl.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryImpl.java @@ -319,8 +319,9 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException { m(FeneconHomeBattery.ChannelId.TOWER_0_BMS_SOFTWARE_VERSION, new UnsignedWordElement(10000)), // new DummyRegisterElement(10001, 10023), // m(FeneconHomeBattery.ChannelId.NUMBER_OF_MODULES_PER_TOWER, new UnsignedWordElement(10024))), // - new FC3ReadRegistersTask(44000, Priority.LOW, // - m(FeneconHomeBattery.ChannelId.BMS_CONTROL, new UnsignedWordElement(44000)) // + new FC3ReadRegistersTask(44000, Priority.HIGH, // + m(new BitsWordElement(44000, this) // + .bit(0, FeneconHomeBattery.ChannelId.BMS_CONTROL)) // )); } @@ -495,10 +496,10 @@ private synchronized void initializeTowerModulesChannels(int numberOfTowers, int m(this.generateTowerChannel(tower, "BMS_HARDWARE_VERSION", OpenemsType.INTEGER), new UnsignedWordElement(towerOffset + 1)), // m(new BitsWordElement(towerOffset + 2, this)// - .bit(0, this.generateTowerChannel(tower, "STATUS_ALARM", Level.INFO)) // - .bit(1, this.generateTowerChannel(tower, "STATUS_WARNING", Level.INFO)) // - .bit(2, this.generateTowerChannel(tower, "STATUS_FAULT", Level.INFO)) // - .bit(3, this.generateTowerChannel(tower, "STATUS_PFET", Level.INFO)) // + .bit(0, this.generateTowerChannel(tower, "STATUS_ALARM", OpenemsType.BOOLEAN)) // + .bit(1, this.generateTowerChannel(tower, "STATUS_WARNING", OpenemsType.BOOLEAN)) // + .bit(2, this.generateTowerChannel(tower, "STATUS_FAULT", OpenemsType.BOOLEAN)) // + .bit(3, this.generateTowerChannel(tower, "STATUS_PFET", OpenemsType.BOOLEAN)) // // CFET (1: Charge FET ON, 0: OFF) .bit(4, this.generateTowerChannel(tower, "STATUS_CFET", OpenemsType.BOOLEAN)) // // DFET (1: Discharge FET ON, 0: OFF) @@ -515,117 +516,117 @@ private synchronized void initializeTowerModulesChannels(int numberOfTowers, int ), // m(new BitsWordElement(towerOffset + 3, this) .bit(0, this.generateTowerChannel(tower, "PRE_ALARM_CELL_OVER_VOLTAGE", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(1, this.generateTowerChannel(tower, "PRE_ALARM_CELL_UNDER_VOLTAGE", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(2, this.generateTowerChannel(tower, "PRE_ALARM_OVER_CHARGING_CURRENT", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(3, this.generateTowerChannel(tower, "PRE_ALARM_OVER_DISCHARGING_CURRENT", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(4, this.generateTowerChannel(tower, "PRE_ALARM_OVER_TEMPERATURE", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(5, this.generateTowerChannel(tower, "PRE_ALARM_UNDER_TEMPERATURE", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(6, this.generateTowerChannel(tower, "PRE_ALARM_CELL_VOLTAGE_DIFFERENCE", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(7, this.generateTowerChannel(tower, "PRE_ALARM_BCU_TEMP_DIFFERENCE", - Level.INFO)) // - .bit(8, this.generateTowerChannel(tower, "PRE_ALARM_UNDER_SOC", Level.INFO)) // - .bit(9, this.generateTowerChannel(tower, "PRE_ALARM_UNDER_SOH", Level.INFO)) // + OpenemsType.BOOLEAN)) // + .bit(8, this.generateTowerChannel(tower, "PRE_ALARM_UNDER_SOC", OpenemsType.BOOLEAN)) // + .bit(9, this.generateTowerChannel(tower, "PRE_ALARM_UNDER_SOH", OpenemsType.BOOLEAN)) // .bit(10, this.generateTowerChannel(tower, "PRE_ALARM_OVER_CHARGING_POWER", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(11, this.generateTowerChannel(tower, "PRE_ALARM_OVER_DISCHARGING_POWER", - Level.INFO)) + OpenemsType.BOOLEAN)) .bit(12, this.generateTowerChannel(tower, "PRE_ALARM_BAT_OVER_VOLTAGE", - Level.INFO)) + OpenemsType.BOOLEAN)) .bit(13, this.generateTowerChannel(tower, "PRE_ALARM_BAT_UNDER_VOLTAGE", - Level.INFO))), // + OpenemsType.BOOLEAN))), // m(new BitsWordElement(towerOffset + 4, this) .bit(0, this.generateTowerChannel(tower, "LEVEL_1_CELL_OVER_VOLTAGE", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(1, this.generateTowerChannel(tower, "LEVEL_1_CELL_UNDER_VOLTAGE", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(2, this.generateTowerChannel(tower, "LEVEL_1_OVER_CHARGING_CURRENT", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(3, this.generateTowerChannel(tower, "LEVEL_1_OVER_DISCHARGING_CURRENT", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(4, this.generateTowerChannel(tower, "LEVEL_1_OVER_TEMPERATURE", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(5, this.generateTowerChannel(tower, "LEVEL_1_UNDER_TEMPERATURE", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(6, this.generateTowerChannel(tower, "LEVEL_1_CELL_VOLTAGE_DIFFERENCE", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(7, this.generateTowerChannel(tower, "LEVEL_1_BCU_TEMP_DIFFERENCE", - Level.INFO)) // - .bit(8, this.generateTowerChannel(tower, "LEVEL_1_UNDER_SOC", Level.INFO)) // - .bit(9, this.generateTowerChannel(tower, "LEVEL_1_UNDER_SOH", Level.INFO)) // + OpenemsType.BOOLEAN)) // + .bit(8, this.generateTowerChannel(tower, "LEVEL_1_UNDER_SOC", OpenemsType.BOOLEAN)) // + .bit(9, this.generateTowerChannel(tower, "LEVEL_1_UNDER_SOH", OpenemsType.BOOLEAN)) // .bit(10, this.generateTowerChannel(tower, "LEVEL_1_OVER_CHARGING_POWER", - Level.INFO)) // + OpenemsType.BOOLEAN)) // .bit(11, this.generateTowerChannel(tower, "LEVEL_1_OVER_DISCHARGING_POWER", - Level.INFO)) + OpenemsType.BOOLEAN)) .bit(12, this.generateTowerChannel(tower, "LEVEL_1_BAT_OVER_VOLTAGE", - Level.INFO)) + OpenemsType.BOOLEAN)) .bit(13, this.generateTowerChannel(tower, "LEVEL_1_BAT_UNDER_VOLTAGE", - Level.INFO))), + OpenemsType.BOOLEAN))), m(new BitsWordElement(towerOffset + 5, this) .bit(0, this.generateTowerChannel(tower, "LEVEL_2_CELL_OVER_VOLTAGE", - Level.INFO)) // + Level.WARNING)) // .bit(1, this.generateTowerChannel(tower, "LEVEL_2_CELL_UNDER_VOLTAGE", - Level.INFO)) // + Level.WARNING)) // .bit(2, this.generateTowerChannel(tower, "LEVEL_2_OVER_CHARGING_CURRENT", - Level.INFO)) // + Level.WARNING)) // .bit(3, this.generateTowerChannel(tower, "LEVEL_2_OVER_DISCHARGING_CURRENT", - Level.INFO)) // + Level.WARNING)) // .bit(4, this.generateTowerChannel(tower, "LEVEL_2_OVER_TEMPERATURE", - Level.INFO)) // + Level.WARNING)) // .bit(5, this.generateTowerChannel(tower, "LEVEL_2_UNDER_TEMPERATURE", - Level.INFO)) // + Level.WARNING)) // .bit(6, this.generateTowerChannel(tower, "LEVEL_2_CELL_VOLTAGE_DIFFERENCE", - Level.INFO)) // + Level.WARNING)) // .bit(7, this.generateTowerChannel(tower, "LEVEL_2_BCU_TEMP_DIFFERENCE", - Level.INFO)) // + Level.WARNING)) // .bit(8, this.generateTowerChannel(tower, "LEVEL_2_BAT_OVER_VOLTAGE", - Level.INFO)) // + Level.WARNING)) // .bit(9, this.generateTowerChannel(tower, "LEVEL_2_INTERNAL_COMMUNICATION", - Level.INFO)) // + Level.WARNING)) // .bit(10, this.generateTowerChannel(tower, "LEVEL_2_EXTERNAL_COMMUNICATION", - Level.INFO)) // - .bit(11, this.generateTowerChannel(tower, "LEVEL_2_PRECHARGE_FAIL", Level.INFO)) // - .bit(12, this.generateTowerChannel(tower, "LEVEL_2_PARALLEL_FAIL", Level.INFO)) // - .bit(13, this.generateTowerChannel(tower, "LEVEL_2_SYSTEM_FAIL", Level.INFO)) // - .bit(14, this.generateTowerChannel(tower, "LEVEL_2_HARDWARE_FAIL", Level.INFO)) // + Level.WARNING)) // + .bit(11, this.generateTowerChannel(tower, "LEVEL_2_PRECHARGE_FAIL", Level.WARNING)) // + .bit(12, this.generateTowerChannel(tower, "LEVEL_2_PARALLEL_FAIL", Level.WARNING)) // + .bit(13, this.generateTowerChannel(tower, "LEVEL_2_SYSTEM_FAIL", Level.WARNING)) // + .bit(14, this.generateTowerChannel(tower, "LEVEL_2_HARDWARE_FAIL", Level.WARNING)) // .bit(14, this.generateTowerChannel(tower, "LEVEL_2_BAT_UNDER_VOLTAGE", - Level.INFO))), // + Level.WARNING))), // m(new BitsWordElement(towerOffset + 6, this) .bit(0, this.generateTowerChannel(tower, "HW_AFE_COMMUNICAITON_FAULT", - Level.INFO)) // - .bit(1, this.generateTowerChannel(tower, "HW_ACTOR_DRIVER_FAULT", Level.INFO)) // + Level.WARNING)) // + .bit(1, this.generateTowerChannel(tower, "HW_ACTOR_DRIVER_FAULT", Level.WARNING)) // .bit(2, this.generateTowerChannel(tower, "HW_EEPROM_COMMUNICATION_FAULT", - Level.INFO)) // - .bit(3, this.generateTowerChannel(tower, "HW_VOLTAGE_DETECT_FAULT", Level.INFO)) // + Level.WARNING)) // + .bit(3, this.generateTowerChannel(tower, "HW_VOLTAGE_DETECT_FAULT", Level.WARNING)) // .bit(4, this.generateTowerChannel(tower, "HW_TEMPERATURE_DETECT_FAULT", - Level.INFO)) // - .bit(5, this.generateTowerChannel(tower, "HW_CURRENT_DETECT_FAULT", Level.INFO)) // - .bit(6, this.generateTowerChannel(tower, "HW_ACTOR_NOT_CLOSE", Level.INFO)) // - .bit(7, this.generateTowerChannel(tower, "HW_ACTOR_NOT_OPEN", Level.INFO)) // - .bit(8, this.generateTowerChannel(tower, "HW_FUSE_BROKEN", Level.INFO))), // + Level.WARNING)) // + .bit(5, this.generateTowerChannel(tower, "HW_CURRENT_DETECT_FAULT", Level.WARNING)) // + .bit(6, this.generateTowerChannel(tower, "HW_ACTOR_NOT_CLOSE", Level.WARNING)) // + .bit(7, this.generateTowerChannel(tower, "HW_ACTOR_NOT_OPEN", Level.WARNING)) // + .bit(8, this.generateTowerChannel(tower, "HW_FUSE_BROKEN", Level.WARNING))), // m(new BitsWordElement(towerOffset + 7, this) .bit(0, this.generateTowerChannel(tower, "SYSTEM_AFE_OVER_TEMPERATURE", - Level.INFO)) // + Level.WARNING)) // .bit(1, this.generateTowerChannel(tower, "SYSTEM_AFE_UNDER_TEMPERATURE", - Level.INFO)) // - .bit(2, this.generateTowerChannel(tower, "SYSTEM_AFE_OVER_VOLTAGE", Level.INFO)) // + Level.WARNING)) // + .bit(2, this.generateTowerChannel(tower, "SYSTEM_AFE_OVER_VOLTAGE", Level.WARNING)) // .bit(3, this.generateTowerChannel(tower, "SYSTEM_AFE_UNDER_VOLTAGE", - Level.INFO)) // + Level.WARNING)) // .bit(4, this.generateTowerChannel(tower, - "SYSTEM_HIGH_TEMPERATURE_PERMANENT_FAILURE", Level.INFO)) // + "SYSTEM_HIGH_TEMPERATURE_PERMANENT_FAILURE", Level.WARNING)) // .bit(5, this.generateTowerChannel(tower, - "SYSTEM_LOW_TEMPERATURE_PERMANENT_FAILURE", Level.INFO)) // + "SYSTEM_LOW_TEMPERATURE_PERMANENT_FAILURE", Level.WARNING)) // .bit(6, this.generateTowerChannel(tower, - "SYSTEM_HIGH_CELL_VOLTAGE_PERMANENT_FAILURE", Level.INFO)) // + "SYSTEM_HIGH_CELL_VOLTAGE_PERMANENT_FAILURE", Level.WARNING)) // .bit(7, this.generateTowerChannel(tower, - "SYSTEM_LOW_CELL_VOLTAGE_PERMANENT_FAILURE", Level.INFO)) // - .bit(8, this.generateTowerChannel(tower, "SYSTEM_SHORT_CIRCUIT", Level.INFO))), // + "SYSTEM_LOW_CELL_VOLTAGE_PERMANENT_FAILURE", Level.WARNING)) // + .bit(8, this.generateTowerChannel(tower, "SYSTEM_SHORT_CIRCUIT", Level.WARNING))), // m(this.generateTowerChannel(tower, "_SOC", OpenemsType.INTEGER), new UnsignedWordElement(towerOffset + 8), // [%] ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/enums/BmsControl.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/enums/BmsControl.java deleted file mode 100644 index 896c66ef1e8..00000000000 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/enums/BmsControl.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.openems.edge.battery.fenecon.home.enums; - -import io.openems.common.types.OptionsEnum; - -public enum BmsControl implements OptionsEnum { - UNDEFINED(-1, "Undefined"), // - SWITCHED_ON(0, "Switch ON Pre-charge & Main Power Contactor"), // - SWITCHED_OFF(0x1, "Shut Down Main Power Contactor & Pre-charge"), // - // 0x2 is not documented in the modbus protocol, but we still read it from time - // to time. - IGNORED(0x2, "Switch ON Pre-charge & Main Power Contactor"); - - private final int value; - private final String name; - - private BmsControl(int value, String name) { - this.value = value; - this.name = name; - } - - @Override - public int getValue() { - return this.value; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public OptionsEnum getUndefined() { - return UNDEFINED; - } -} diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java index 32337911adc..edd165f36ba 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java @@ -17,16 +17,10 @@ public Context(FeneconHomeBattery parent, BooleanWriteChannel batteryStartUpRela } protected boolean isBatteryStarted() { - switch (this.getParent().getBmsControl()) { - case SWITCHED_ON: - case IGNORED: - return true; - - case SWITCHED_OFF: - case UNDEFINED: + var isNotStarted = this.getParent().getBmsControl(); + if (!isNotStarted.isDefined()) { return false; } - return false; + return !isNotStarted.get(); } - } \ No newline at end of file diff --git a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryTest.java b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryTest.java index 3476154eb3d..7d3626f2d49 100644 --- a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryTest.java +++ b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryTest.java @@ -8,7 +8,6 @@ import io.openems.common.types.ChannelAddress; import io.openems.edge.battery.api.Battery; -import io.openems.edge.battery.fenecon.home.enums.BmsControl; import io.openems.edge.battery.fenecon.home.statemachine.StateMachine; import io.openems.edge.battery.protection.BatteryProtection; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; @@ -62,7 +61,7 @@ public void test() throws Exception { .next(new TestCase("Battery Relay false") // .input(BATTERY_RELAY, false)// - .input(BMS_CONTROL, BmsControl.SWITCHED_OFF)// + .input(BMS_CONTROL, true)// Switched Off .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// .next(new TestCase() // .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // @@ -73,7 +72,7 @@ public void test() throws Exception { .next(new TestCase("in WAIT_FOR_BMS_CONTROL") // .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// .next(new TestCase("in WAIT_FOR_BMS_CONTROL")// - .input(BMS_CONTROL, BmsControl.SWITCHED_ON)) // + .input(BMS_CONTROL, false)) // Switched On .next(new TestCase("in WAIT_FOR_BMS_CONTROL")// .output(BATTERY_RELAY, false)) // .next(new TestCase("in WAIT_FOR_SWITCH_OFF") // @@ -131,7 +130,7 @@ public void test2() throws Exception { .next(new TestCase()// .input(BATTERY_RELAY, true)// - .input(BMS_CONTROL, BmsControl.SWITCHED_OFF)// + .input(BMS_CONTROL, Boolean.TRUE)// Switched Off .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// .next(new TestCase() // .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // @@ -140,7 +139,7 @@ public void test2() throws Exception { .next(new TestCase("in WAIT_FOR_BMS_CONTROL") // .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// .next(new TestCase("in WAIT_FOR_BMS_CONTROL")// - .input(BMS_CONTROL, BmsControl.SWITCHED_ON)) // + .input(BMS_CONTROL, false))// Switched On .next(new TestCase("in WAIT_FOR_SWITCH_OFF") // .input(BATTERY_RELAY, false) // .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // @@ -173,7 +172,7 @@ public void test3() throws Exception { .next(new TestCase()// .input(BATTERY_RELAY, false)// - .input(BMS_CONTROL, BmsControl.SWITCHED_ON)// + .input(BMS_CONTROL, false)// Switched On .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// .next(new TestCase() // .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // @@ -210,7 +209,7 @@ public void test4() throws Exception { .next(new TestCase()// .input(BATTERY_RELAY, false)// - .input(BMS_CONTROL, BmsControl.SWITCHED_OFF)// + .input(BMS_CONTROL, true)// Switched Off .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// .next(new TestCase() // .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // @@ -232,7 +231,7 @@ public void test4() throws Exception { .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// // Ex; after long time if hard switch turned on.... .next(new TestCase("in WAIT_FOR_BMS_CONTROL")// - .input(BMS_CONTROL, BmsControl.SWITCHED_ON)// + .input(BMS_CONTROL, false)// Switched On .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// .next(new TestCase("in WAIT_FOR_SWITCH_OFF") // .input(BATTERY_RELAY, false) // @@ -266,7 +265,7 @@ public void test5() throws Exception { .next(new TestCase("Battery Relay false") // .input(BATTERY_RELAY, false)// - .input(BMS_CONTROL, BmsControl.SWITCHED_OFF)// + .input(BMS_CONTROL, true)// Switched Off .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// .next(new TestCase() // .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // @@ -310,7 +309,7 @@ public void test6() throws Exception { .next(new TestCase()// .input(BATTERY_RELAY, false)// - .input(BMS_CONTROL, BmsControl.SWITCHED_ON)// + .input(BMS_CONTROL, false)// Switched On .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// .next(new TestCase() // .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/Sinexcel.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/Sinexcel.java index 71321c1907e..342d0250284 100644 --- a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/Sinexcel.java +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/Sinexcel.java @@ -687,7 +687,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { // Example: Rated frequency is 60Hz, the target active power is set to 10kW, // Freq/Watt regulation point is set to 2Hz, ramp rate is set as 0.5, If the // actual frequency reaches 63Hz, the output active power will be - // 10kW-(63Hz-62Hz) 0.5*(10kW/Hz) = 5kW + // 10kW-(63Hz-62Hz) x 0.5*(10kW/Hz) = 5kW SLOPE_OF_FREQUENCY_DROP(Doc.of(OpenemsType.INTEGER) // .accessMode(AccessMode.READ_WRITE)), // // AS4777 only, bias Bias from rated frequency 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 f5906588e94..f551dea48e6 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 @@ -9,6 +9,8 @@ import org.osgi.service.component.ComponentContext; +import com.google.gson.JsonObject; + import io.openems.common.exceptions.OpenemsError; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.jsonrpc.base.JsonrpcRequest; @@ -29,6 +31,7 @@ public class DummyComponentManager implements ComponentManager { private final List components = new ArrayList<>(); private final Clock clock; + private JsonObject edgeConfigJson; public DummyComponentManager() { this(Clock.systemDefaultZone()); @@ -72,9 +75,27 @@ public DummyComponentManager addComponent(OpenemsComponent component) { return this; } + /** + * Sets a {@link EdgeConfig} json. + * + * @param the {@link EdgeConfig} json + */ + public void setConfigJson(JsonObject json) { + this.edgeConfigJson = json; + } + @Override public EdgeConfig getEdgeConfig() { - return new EdgeConfig(); + if (this.edgeConfigJson != null) { + try { + return EdgeConfig.fromJson(this.edgeConfigJson); + } catch (OpenemsNamedException e) { + e.printStackTrace(); + throw new IllegalArgumentException(e.getMessage()); + } + } else { + return new EdgeConfig(); + } } @Override diff --git a/io.openems.edge.controller.api.rest/src/io/openems/edge/controller/api/rest/RestHandler.java b/io.openems.edge.controller.api.rest/src/io/openems/edge/controller/api/rest/RestHandler.java index 9566377f47a..89b880ebdcc 100644 --- a/io.openems.edge.controller.api.rest/src/io/openems/edge/controller/api/rest/RestHandler.java +++ b/io.openems.edge.controller.api.rest/src/io/openems/edge/controller/api/rest/RestHandler.java @@ -402,7 +402,7 @@ private static JsonObject parseJson(Request baseRequest) throws OpenemsException */ private void handleJsonRpc(User user, Request baseRequest, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws OpenemsNamedException { - user.assertRoleIsAtLeast("HTTP POST JSON-RPC", Role.ADMIN); + user.assertRoleIsAtLeast("HTTP POST JSON-RPC", Role.OWNER); UUID requestId = new UUID(0L, 0L); /* dummy UUID */ try { diff --git a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/RestApiReadWriteImplTest.java b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/RestApiReadWriteImplTest.java index 6eb7406ecae..b5bb0f9ef84 100644 --- a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/RestApiReadWriteImplTest.java +++ b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/RestApiReadWriteImplTest.java @@ -20,6 +20,7 @@ import io.openems.common.channel.Unit; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; import io.openems.common.jsonrpc.request.GetEdgeConfigRequest; import io.openems.common.session.Role; import io.openems.common.types.ChannelAddress; @@ -108,9 +109,15 @@ public void test() throws OpenemsException, Exception { /* * JSON-RPC */ - // POST fails as INSTALLER + // POST successful as OWNER + var request = new GetEdgeConfigRequest().toJsonObject(); + JsonrpcResponseSuccess.from(// + JsonUtils.getAsJsonObject(// + sendPostRequest(port, OWNER, "/jsonrpc", request))); + + // POST fails as GUEST try { - sendPostRequest(port, INSTALLER, "/jsonrpc", new GetEdgeConfigRequest().toJsonObject()); + sendPostRequest(port, GUEST, "/jsonrpc", new GetEdgeConfigRequest().toJsonObject()); assertTrue(false); } catch (OpenemsNamedException e) { diff --git a/io.openems.edge.controller.symmetric.timeslotpeakshaving/src/io/openems/edge/controller/timeslotpeakshaving/TimeslotPeakshaving.java b/io.openems.edge.controller.symmetric.timeslotpeakshaving/src/io/openems/edge/controller/timeslotpeakshaving/TimeslotPeakshaving.java index d4475bc698e..49b03180568 100644 --- a/io.openems.edge.controller.symmetric.timeslotpeakshaving/src/io/openems/edge/controller/timeslotpeakshaving/TimeslotPeakshaving.java +++ b/io.openems.edge.controller.symmetric.timeslotpeakshaving/src/io/openems/edge/controller/timeslotpeakshaving/TimeslotPeakshaving.java @@ -118,7 +118,6 @@ public void run() throws OpenemsNamedException { private void applyPower(ManagedSymmetricEss ess, Integer activePower) throws OpenemsNamedException { if (activePower != null) { ess.setActivePowerEqualsWithPid(activePower); - ess.setReactivePowerEquals(0); } } 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 new file mode 100644 index 00000000000..25149951b58 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AbstractOpenemsApp.java @@ -0,0 +1,286 @@ +package io.openems.edge.core.appmanager; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.osgi.service.component.ComponentConstants; +import org.osgi.service.component.ComponentContext; + +import com.google.common.base.Objects; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.InvalidValueException; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.function.ThrowingFunction; +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; + +public abstract class AbstractOpenemsApp> implements OpenemsApp { + + private final ComponentManager componentManager; + private final ComponentContext componentContext; + + protected AbstractOpenemsApp(ComponentManager componentManager, ComponentContext componentContext) { + this.componentManager = componentManager; + this.componentContext = componentContext; + } + + @Override + public String getAppId() { + return this.componentContext.getProperties().get(ComponentConstants.COMPONENT_NAME).toString(); + } + + protected static class AppConfiguration { + public final List components; + public final List schedulerExecutionOrder; + + public AppConfiguration(List components, List schedulerExecutionOrder) { + this.components = components; + this.schedulerExecutionOrder = schedulerExecutionOrder; + } + } + + protected abstract Class getPropertyClass(); + + /** + * Provides a factory for {@link AppConfiguration}s. + * + * @return a {@link Function} that creates a {@link AppConfiguration} from a + * JsonObject config. + */ + protected abstract ThrowingFunction, AppConfiguration, OpenemsNamedException> appConfigurationFactory(); + + @Override + public void validate(JsonObject properties) throws OpenemsException { + var errors = this.getValidationErrors(properties); + if (!errors.isEmpty()) { + throw new OpenemsException(errors.stream().collect(Collectors.joining("|"))); + } + } + + /** + * Validate the App configuration. + * + * @param jProperties a JsonObject holding the App properties + * @return a list of validation errors. Empty list says 'no errors' + */ + private List getValidationErrors(JsonObject jProperties) { + final var errors = new ArrayList(); + + final var properties = this.convertToEnumMap(errors, jProperties); + final var appConfiguration = this.getAppConfiguration(errors, properties); + if (appConfiguration == null) { + return errors; + } + + final var edgeConfig = this.componentManager.getEdgeConfig(); + + this.validateComponentConfigurations(errors, edgeConfig, appConfiguration); + this.validateScheduler(errors, edgeConfig, appConfiguration); + + return errors; + } + + /** + * Validates the execution order in the Scheduler. + * + * @param errors a collection of validation errors + * @param actualEdgeConfig the currently active {@link EdgeConfig} + * @param expectedAppConfiguration the expected {@link AppConfiguration} + */ + private void validateScheduler(ArrayList errors, EdgeConfig actualEdgeConfig, + AppConfiguration expectedAppConfiguration) { + var schedulerComponents = actualEdgeConfig.getComponentsByFactory("Scheduler.AllAlphabetically"); + if (schedulerComponents.isEmpty()) { + errors.add("Scheduler is missing"); + return; + } + if (schedulerComponents.size() > 1) { + errors.add("More than one Scheduler configured"); + return; + } + + var schedulerComponent = schedulerComponents.get(0); + var controllerIdsElement = schedulerComponent.getProperty("controllers.ids").orElse(new JsonArray()); + JsonArray controllerIds; + try { + controllerIds = JsonUtils.getAsJsonArray(controllerIdsElement); + } catch (OpenemsNamedException e) { + errors.add("Undefined error in Scheduler: " + e.getMessage()); + return; + } + + // Prepare Queue + var controllers = new LinkedList(); + controllers.addAll(expectedAppConfiguration.schedulerExecutionOrder); + var nextControllerId = controllers.poll(); + + // Remove found Controllers from Queue in order + for (var controllerIdElement : controllerIds) { + String controllerId; + try { + controllerId = JsonUtils.getAsString(controllerIdElement); + } catch (OpenemsNamedException e) { + errors.add("Undefined error in Scheduler: " + e.getMessage()); + continue; + } + + if (controllerId.equals(nextControllerId)) { + nextControllerId = controllers.poll(); + } + } + if (nextControllerId != null) { + errors.add("Controller [" + nextControllerId + "] is not/wrongly configured in Scheduler"); + } + } + + /** + * Gets the {@link AppConfiguration} for the given properties. + * + * @param errors a collection of validation errors + * @param properties the configured App properties + * @return the {@link AppConfiguration} or null + */ + private AppConfiguration getAppConfiguration(ArrayList errors, EnumMap properties) { + try { + return this.appConfigurationFactory().apply(properties); + } catch (OpenemsNamedException e) { + errors.add(e.getMessage()); + return null; + } + } + + /** + * Convert JsonObject with Properties to EnumMap. + * + * @param errors a collection of validation errors + * @param properties the configured App properties + * @return a typed {@link EnumMap} of Properties + */ + private EnumMap convertToEnumMap(ArrayList errors, JsonObject properties) { + var clazz = this.getPropertyClass(); + var result = new EnumMap(clazz); + var unknownProperties = new ArrayList(); + for (Entry entry : properties.entrySet()) { + final PROPERTY key; + try { + key = Enum.valueOf(clazz, entry.getKey()); + } catch (IllegalArgumentException e) { + unknownProperties.add(entry.getKey()); + continue; + } + result.put(key, entry.getValue()); + } + if (!unknownProperties.isEmpty()) { + errors.add("Unknown Configuration Propert" // + + (unknownProperties.size() > 1 ? "ies" : "y") + ":" // + + unknownProperties.stream().collect(Collectors.joining(","))); + } + return result; + } + + /** + * Compare actual and expected Components. + * + * @param errors a collection of validation errors + * @param actualEdgeConfig the currently active {@link EdgeConfig} + * @param expectedAppConfiguration the expected {@link AppConfiguration} + */ + private void validateComponentConfigurations(ArrayList errors, EdgeConfig actualEdgeConfig, + AppConfiguration expectedAppConfiguration) { + var missingComponents = new ArrayList(); + for (Component expectedComponent : expectedAppConfiguration.components) { + var componentId = expectedComponent.getId(); + + // Get Actual Component Configuration + Component actualComponent; + try { + actualComponent = actualEdgeConfig.getComponentOrError(componentId); + } catch (InvalidValueException e) { + missingComponents.add(componentId); + continue; + } + + var componentErrors = new ArrayList(); + + // Validate the Component Factory (i.e. is the Component of the correct type) + if (!Objects.equal(expectedComponent.getFactoryId(), actualComponent.getFactoryId())) { + componentErrors.add("Factory-ID: " // + + "expected '" + expectedComponent.getFactoryId() + "', " // + + "got '" + actualComponent.getFactoryId() + "'"); + } + + for (Entry entry : expectedComponent.getProperties().entrySet()) { + String key = entry.getKey(); + JsonElement expectedProperty = entry.getValue(); + JsonElement actualProperty; + try { + actualProperty = actualComponent.getPropertyOrError(key); + } catch (InvalidValueException e) { + componentErrors.add("Property '" + key + "': " // + + "expected '" + expectedProperty.toString() + "', " // + + "but property does not exist"); + continue; + } + + if (!equals(expectedProperty, actualProperty)) { + componentErrors.add("Property '" + key + "': " // + + "expected '" + expectedProperty.toString() + "', " // + + "got '" + actualProperty.toString() + "'"); + } + } + + if (!componentErrors.isEmpty()) { + errors.add(componentId + ": " // + + componentErrors.stream().collect(Collectors.joining("; "))); + } + } + + if (!missingComponents.isEmpty()) { + errors.add("Missing Component" // + + (missingComponents.size() > 1 ? "s" : "") + ":" // + + missingComponents.stream().collect(Collectors.joining(","))); + } + } + + /** + * Validates if the 'actual' matches the 'expected' value. + * + * @param expected the expected value + * @param actual the actual value + * @return true if they match + */ + protected static boolean equals(JsonElement expected, JsonElement actual) { + if (Objects.equal(expected, actual)) { + return true; + } + if ((expected == null && actual != null) || (expected != null && actual == null)) { + return false; + } + // both are not null + + if (!expected.isJsonPrimitive() || !actual.isJsonPrimitive()) { + return false; + } + // both are JsonPrimitives + var e = expected.getAsJsonPrimitive(); + var a = actual.getAsJsonPrimitive(); + + if (e.getAsString().equals(a.getAsString())) { + // compare 'toString' + return true; + } + return false; + } + +} 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 new file mode 100644 index 00000000000..e8ee1f887ca --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppAssistant.java @@ -0,0 +1,68 @@ +package io.openems.edge.core.appmanager; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import io.openems.common.utils.JsonUtils; + +public class AppAssistant { + + /** + * Creates an {@link AppAssistant} using a Builder. + * + * @return the {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + /** + * A temporary builder class for an {@link AppAssistant}. + */ + public static class Builder { + + private JsonArray fields = new JsonArray(); + + protected Builder() { + } + + /** + * Sets the Fields. + * + * @param fields the fields + * @return the {@link Builder} + */ + public Builder fields(JsonArray fields) { + this.fields = fields; + return this; + } + + /** + * Return the built {@link JsonArray}. + * + * @return the {@link JsonArray} + */ + public AppAssistant build() { + return new AppAssistant(this.fields); + } + + } + + private final JsonArray fields; + + private AppAssistant(JsonArray fields) { + this.fields = fields; + } + + /** + * Gets this {@link AppAssistant} as {@link JsonObject}. + * + * @return the {@link JsonObject} + */ + public JsonObject toJsonObject() { + return JsonUtils.buildJsonObject() // + .add("fields", this.fields) // + .build(); + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManager.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManager.java new file mode 100644 index 00000000000..c2e56989b5b --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManager.java @@ -0,0 +1,94 @@ +package io.openems.edge.core.appmanager; + +import io.openems.common.channel.Level; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.StateChannel; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.jsonapi.JsonApi; + +/** + * A Service that manages OpenEMS Apps. + */ +public interface AppManager extends OpenemsComponent, JsonApi { + + public static final String SINGLETON_SERVICE_PID = "Core.AppManager"; + public static final String SINGLETON_COMPONENT_ID = "_appManager"; + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + WRONG_APP_CONFIGURATION(Doc.of(Level.WARNING) // + .text("App-Manager configuration is wrong")), // + DEFECTIVE_APP(Doc.of(Level.INFO) // + // TODO should be a WARNING eventually + .text("Defective App detected")), // + ; + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + public Doc doc() { + return this.doc; + } + } + + /** + * Gets the Channel for {@link ChannelId#WRONG_APP_CONFIGURATION}. + * + * @return the Channel + */ + public default StateChannel getWrongAppConfigurationChannel() { + return this.channel(ChannelId.WRONG_APP_CONFIGURATION); + } + + /** + * Gets the Wrong-App-Configuration Warning State. See + * {@link ChannelId#WRONG_APP_CONFIGURATION}. + * + * @return the Channel {@link Value} + */ + public default Value getWrongAppConfiguration() { + return this.getWrongAppConfigurationChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#WRONG_APP_CONFIGURATION} Channel. + * + * @param value the next value + */ + public default void _setWrongAppConfiguration(boolean value) { + this.getWrongAppConfigurationChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#DEFECTIVE_APP}. + * + * @return the Channel + */ + public default StateChannel getDefectiveAppChannel() { + return this.channel(ChannelId.DEFECTIVE_APP); + } + + /** + * Gets the Defective-App Warning State. See {@link ChannelId#DEFECTIVE_APP}. + * + * @return the Channel {@link Value} + */ + public default Value getDefectiveApp() { + return this.getDefectiveAppChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#DEFECTIVE_APP} + * Channel. + * + * @param value the next value + */ + public default void _setDefectiveApp(boolean value) { + this.getDefectiveAppChannel().setNextValue(value); + } + +} 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 new file mode 100644 index 00000000000..4085a568988 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java @@ -0,0 +1,322 @@ +package io.openems.edge.core.appmanager; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import org.osgi.service.cm.Configuration; +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.cm.ConfigurationEvent; +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; +import org.osgi.service.metatype.annotations.Designate; + +import com.google.gson.JsonArray; + +import io.openems.common.OpenemsConstants; +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.session.Role; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.jsonapi.JsonApi; +import io.openems.edge.common.user.User; +import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance; +import io.openems.edge.core.appmanager.jsonrpc.GetAppAssistant; +import io.openems.edge.core.appmanager.jsonrpc.GetAppInstances; +import io.openems.edge.core.appmanager.jsonrpc.GetApps; +import io.openems.edge.core.appmanager.jsonrpc.UpdateAppInstance; + +@Designate(ocd = Config.class, factory = false) +@Component(// + name = AppManager.SINGLETON_SERVICE_PID, // + immediate = true, // + property = { // + "enabled=true" // + }) +public class AppManagerImpl extends AbstractOpenemsComponent + implements AppManager, OpenemsComponent, JsonApi, ConfigurationListener { + + private final AppValidateWorker worker; + + @Reference + private ConfigurationAdmin cm; + + @Reference + protected List availableApps; + + @Reference + protected ComponentManager componentManager; + + protected List instantiatedApps = new ArrayList<>(); + + public AppManagerImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + AppManager.ChannelId.values() // + ); + this.worker = new AppValidateWorker(this); + } + + @Activate + private void activate(ComponentContext componentContext, Config config) { + super.activate(componentContext, SINGLETON_COMPONENT_ID, SINGLETON_SERVICE_PID, true); + + if (OpenemsComponent.validateSingleton(this.cm, SINGLETON_SERVICE_PID, SINGLETON_COMPONENT_ID)) { + return; + } + + this.worker.activate(this.id()); + this.applyConfig(config); + } + + @Modified + private void modified(ComponentContext componentContext, Config config) throws OpenemsNamedException { + this.applyConfig(config); + super.modified(componentContext, SINGLETON_COMPONENT_ID, SINGLETON_SERVICE_PID, true); + this.worker.triggerNextRun(); + } + + private synchronized void applyConfig(Config config) { + String apps = config.apps(); + if (apps.isBlank()) { + apps = "[]"; // default to empty array + } + try { + this.instantiatedApps = parseInstantiatedApps(JsonUtils.parseToJsonArray(apps)); + this._setWrongAppConfiguration(false); + + } catch (OpenemsNamedException e) { + this._setWrongAppConfiguration(true); + e.printStackTrace(); + return; + } + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + this.worker.deactivate(); + } + + /** + * Parses the configured apps to a List of {@link OpenemsAppInstance}s. + * + * @param apps the app configuration from Config.json as {@link JsonArray} + * @return List of {@link OpenemsAppInstance}s + * @throws OpenemsNamedException on parse error + */ + private static List parseInstantiatedApps(JsonArray apps) throws OpenemsNamedException { + var result = new ArrayList(apps.size()); + for (var appElement : apps) { + var json = JsonUtils.getAsJsonObject(appElement); + var appId = JsonUtils.getAsString(json, "appId"); + var instanceId = JsonUtils.getAsUUID(json, "instanceId"); + var properties = JsonUtils.getAsJsonObject(json, "properties"); + result.add(new OpenemsAppInstance(appId, instanceId, properties)); + } + return result; + } + + @Override + public void configurationEvent(ConfigurationEvent event) { + this.worker.configurationEvent(event); + } + + @Override + public String debugLog() { + return this.worker.debugLog(); + } + + @Override + public CompletableFuture handleJsonrpcRequest(User user, JsonrpcRequest request) + throws OpenemsNamedException { + user.assertRoleIsAtLeast("handleJsonrpcRequest", Role.OWNER); + + switch (request.getMethod()) { + + case GetApps.Request.METHOD: + return this.handleGetAppsRequest(user, GetApps.Request.from(request)); + + case GetAppAssistant.METHOD: + return this.handleGetAppAssistantRequest(user, GetAppAssistant.Request.from(request)); + + case GetAppInstances.METHOD: + return this.handleGetAppInstancesRequest(user, GetAppInstances.Request.from(request)); + + case AddAppInstance.METHOD: + return this.handleAddAppInstanceRequest(user, AddAppInstance.Request.from(request)); + + case UpdateAppInstance.METHOD: + return this.handleUpdateAppInstanceRequest(user, UpdateAppInstance.Request.from(request)); + + default: + throw OpenemsError.JSONRPC_UNHANDLED_METHOD.exception(request.getMethod()); + } + } + + /** + * Handles a {@link GetAppsRequest}. + * + * @param user the User + * @param request the {@link GetAppsRequest} + * @return the Future JSON-RPC Response + * @throws OpenemsNamedException on error + */ + private CompletableFuture handleGetAppsRequest(User user, GetApps.Request request) + throws OpenemsNamedException { + return CompletableFuture + .completedFuture(new GetApps.Response(request.id, this.availableApps, this.instantiatedApps)); + } + + /** + * Handles {@link GetAppAssistant}. + * + * @param user the User + * @param request the {@link GetAppAssistant} Request + * @return the Future JSON-RPC Response + * @throws OpenemsNamedException on error + */ + private CompletableFuture handleGetAppAssistantRequest(User user, + 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())); + } + } + throw new OpenemsException("App-ID [" + request.appId + "] is unknown"); + } + + /** + * Handles {@link GetAppInstances}. + * + * @param user the User + * @param request the {@link GetAppInstances} Request + * @return the Future JSON-RPC Response + * @throws OpenemsNamedException on error + */ + private CompletableFuture handleGetAppInstancesRequest(User user, + GetAppInstances.Request request) throws OpenemsNamedException { + var instances = this.instantiatedApps.stream() // + .filter(i -> i.appId.equals(request.appId)) // + .collect(Collectors.toList()); + return CompletableFuture.completedFuture(new GetAppInstances.Response(request.id, instances)); + } + + /** + * Handles {@link AddAppInstance}. + * + * @param user the User + * @param request the {@link AddAppInstance} Request + * @return the Future JSON-RPC Response + * @throws OpenemsNamedException on error + */ + private CompletableFuture handleAddAppInstanceRequest(User user, + AddAppInstance.Request request) throws OpenemsNamedException { + // Create new list of Apps + final var newApps = new ArrayList(this.instantiatedApps); + var instanceId = UUID.randomUUID(); + var app = new OpenemsAppInstance(request.appId, instanceId, request.properties); + newApps.add(app); + + // Update App-Manager configuration + try { + this.updateAppManagerConfiguration(user, newApps); + } catch (IOException e) { + e.printStackTrace(); + throw new OpenemsException("AddAppInstance: unable to update App-Manager configuration: " + e.getMessage()); + } + + return CompletableFuture.completedFuture(new AddAppInstance.Response(request.id, instanceId)); + } + + /** + * Handles {@link UpdateAppInstance}. + * + * @param user the User + * @param request the {@link UpdateAppInstance} Request + * @return the Future JSON-RPC Response + * @throws OpenemsNamedException on error + */ + private CompletableFuture handleUpdateAppInstanceRequest(User user, + UpdateAppInstance.Request request) throws OpenemsNamedException { + var foundInstanceId = false; + final var newApps = new ArrayList(); + synchronized (this.instantiatedApps) { + // Create new list of Apps + for (var oldApp : this.instantiatedApps) { + if (oldApp.instanceId.equals(request.instanceId)) { + foundInstanceId = true; + var newApp = new OpenemsAppInstance(oldApp.appId, oldApp.instanceId, request.properties); + newApps.add(newApp); + + } else { + newApps.add(oldApp); + } + } + } + + if (!foundInstanceId) { + throw new OpenemsException("App-Instance-ID [" + request.instanceId + "] is unknown"); + } + + // Update App-Manager configuration + try { + this.updateAppManagerConfiguration(user, newApps); + } catch (IOException e) { + e.printStackTrace(); + throw new OpenemsException("Unable to update App-Manager configuration for ID [" + request.instanceId + + "]: " + e.getMessage()); + } + + return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.id)); + } + + /** + * Reconfigure myself to persist the actual App configuration. + * + * @param user the executing user + * @param apps a list of {@link OpenemsAppInstance}s + * @throws IOException on error + */ + private void updateAppManagerConfiguration(User user, List apps) throws IOException { + String factoryPid = this.serviceFactoryPid(); + final Configuration config = this.cm.getConfiguration(factoryPid, null); + Dictionary properties = config.getProperties(); + if (properties == null) { + // No configuration existing yet -> create new configuration + properties = new Hashtable<>(); + } else { + // 'Host' configuration exists -> update configuration + } + var appsProperty = JsonUtils.buildJsonArray(); + for (var app : apps) { + appsProperty.add(app.toJsonObject()); + } + + properties.put("apps", JsonUtils.prettyToString(appsProperty.build())); + properties.put(OpenemsConstants.PROPERTY_LAST_CHANGE_BY, user.getId() + ": " + user.getName()); + properties.put(OpenemsConstants.PROPERTY_LAST_CHANGE_AT, + LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS).toString()); + config.update(properties); + } +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppValidateWorker.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppValidateWorker.java new file mode 100644 index 00000000000..1f19b3cc12c --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppValidateWorker.java @@ -0,0 +1,140 @@ +package io.openems.edge.core.appmanager; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import org.osgi.service.cm.ConfigurationEvent; +import org.osgi.service.cm.ConfigurationListener; + +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.worker.AbstractWorker; + +/** + * This Worker constantly validates:. + * + *

    + *
  • that all enabled OpenEMS Apps are properly configured, including required + * OpenEMS Components, IP addresses, Scheduler settings, etc. + *
+ */ +public class AppValidateWorker extends AbstractWorker { + + /* + * For INITIAL_CYCLES cycles the distance between two checks is + * INITIAL_CYCLE_TIME, afterwards the check runs every REGULAR_CYCLE_TIME + * milliseconds. + * + * Why? In the beginning it takes a while till all components are up and + * running. So it is likely, that in the beginning not all are immediately + * running. + */ + private static final int INITIAL_CYCLES = 60; + private static final int INITIAL_CYCLE_TIME = 10_000; // in ms + private static final int REGULAR_CYCLE_TIME = 60 * 60_000; // in ms + + private final AppManagerImpl parent; + + /** + * Map from App to defect details. + */ + protected final Map defectiveApps = new HashMap<>(); + + public AppValidateWorker(AppManagerImpl parent) { + this.parent = parent; + } + + @Override + protected void forever() { + this.validateApps(); + + this.parent._setDefectiveApp(!this.defectiveApps.isEmpty()); + } + + /** + * Validates all Apps. + * + *

+ * 'protected' so that it can be used in a JUnit test. + */ + protected void validateApps() { + for (var instantiatedApp : this.parent.instantiatedApps) { + for (OpenemsApp app : this.parent.availableApps) { + if (app.getAppId().equals(instantiatedApp.appId)) { + this.validateApp(app, instantiatedApp.properties); + } + } + } + } + + /** + * Validates all Apps. + * + *

+ * 'protected' so that it can be used in a JUnit test. + * + * @param app the {@link OpenemsApp} + * @param properties the App properties + */ + protected void validateApp(OpenemsApp app, JsonObject properties) { + // Found correct OpenemsApp -> validate + String key = app.getName(); + try { + app.validate(properties); + + this.defectiveApps.remove(key); + } catch (OpenemsNamedException e1) { + this.defectiveApps.put(key, e1.getMessage()); + } + } + + private int cycleCountDown = AppValidateWorker.INITIAL_CYCLES; + + @Override + protected int getCycleTime() { + if (this.cycleCountDown > 0) { + this.cycleCountDown--; + return AppValidateWorker.INITIAL_CYCLE_TIME; + } else { + return AppValidateWorker.REGULAR_CYCLE_TIME; + } + } + + /** + * Called by {@link ConfigurationListener}. + * + * @param event a {@link ConfigurationEvent} + */ + protected void configurationEvent(ConfigurationEvent event) { + // trigger immediate validation on configuration event + this.triggerNextRun(); + } + + @Override + public void triggerNextRun() { + // Reset Cycle-Counter on explicit run + this.cycleCountDown = AppValidateWorker.INITIAL_CYCLES; + super.triggerNextRun(); + } + + /** + * Called by parent debugLog. + * + * @return a debug log String or null + */ + protected String debugLog() { + var defectiveApps = this.defectiveApps.entrySet().stream() // + .map(e -> e.getKey() + "[" + e.getValue() + "]") // + .collect(Collectors.joining(" ")); + + if (defectiveApps.isEmpty()) { + return null; + + } else { + return defectiveApps; + } + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/Config.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/Config.java new file mode 100644 index 00000000000..c96c7e77f2d --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/Config.java @@ -0,0 +1,16 @@ +package io.openems.edge.core.appmanager; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(// + name = "Core App-Manager", // + description = "The global manager for OpenEMS Apps.") +@interface Config { + + String webconsole_configurationFactory_nameHint() default "Core App-Manager"; + + @AttributeDefinition(name = "OpenEMS Apps", description = "OpenEMS App properties as a JSON Array") // + String apps() default "[]"; + +} \ No newline at end of file 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 new file mode 100644 index 00000000000..e23a00d06f8 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsApp.java @@ -0,0 +1,54 @@ +package io.openems.edge.core.appmanager; + +import org.osgi.service.component.ComponentConstants; + +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; + +public interface OpenemsApp { + + /** + * Gets the {@link OpenemsAppCategory} of the {@link OpenemsApp}. + * + * @return the category + */ + public OpenemsAppCategory getCategory(); + + /** + * Gets the unique App-ID of the {@link OpenemsApp}. + * + * @return a unique PID, usually the {@link ComponentConstants#COMPONENT_NAME} + * of the OSGi Component provider. + */ + public String getAppId(); + + /** + * Gets the name of the {@link OpenemsApp}. + * + * @return a human readable name + */ + public String getName(); + + /** + * Gets the image of the {@link OpenemsApp} in Base64 encoding. + * + * @return a human readable name + */ + public String getImage(); + + /** + * Validate the {@link OpenemsApp}. + * + * @param config the configured app 'properties' + */ + public void validate(JsonObject config) throws OpenemsNamedException; + + /** + * Gets the {@link AppAssistant} for this {@link OpenemsApp}. + * + * @return the AppAssistant + */ + public AppAssistant getAppAssistant(); + +} 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 new file mode 100644 index 00000000000..a4aa588fcb0 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppCategory.java @@ -0,0 +1,10 @@ +package io.openems.edge.core.appmanager; + +public enum OpenemsAppCategory { + + /** + * Integrated Systems. + */ + INTEGRATED_SYSTEM; + +} 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 new file mode 100644 index 00000000000..076287153a5 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java @@ -0,0 +1,38 @@ +package io.openems.edge.core.appmanager; + +import java.util.UUID; + +import com.google.gson.JsonObject; + +import io.openems.common.utils.JsonUtils; + +/** + * An {@link OpenemsAppInstance} is one instance of an {@link OpenemsApp} with a + * specific configuration. + */ +public class OpenemsAppInstance { + + public final String appId; + public final UUID instanceId; + public final JsonObject properties; + + public OpenemsAppInstance(String appId, UUID instanceId, JsonObject properties) { + this.appId = appId; + this.instanceId = instanceId; + this.properties = properties; + } + + /** + * Gets this {@link OpenemsAppInstance} as {@link JsonObject}. + * + * @return the {@link JsonObject} + */ + public JsonObject toJsonObject() { + return JsonUtils.buildJsonObject() // + .addProperty("appId", this.appId) // + .addProperty("instanceId", this.instanceId.toString()) // + .add("properties", this.properties) // + .build(); + } + +} 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 new file mode 100644 index 00000000000..2961b603646 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/AddAppInstance.java @@ -0,0 +1,105 @@ +package io.openems.edge.core.appmanager.jsonrpc; + +import java.util.UUID; + +import com.google.gson.JsonObject; + +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; + +/** + * Adds an {@link OpenemsAppInstance}. + * + *

+ * Request: + * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "addAppInstance",
+ *   "params": {
+ *     "appId": string,
+ *     "properties": {}
+ *   }
+ * }
+ * 
+ * + *

+ * Response: + * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "result": {
+ *     "instanceId": string (uuid)
+ *   }
+ * }
+ * 
+ */ +public class AddAppInstance { + + public static final String METHOD = "addAppInstance"; + + public static class Request extends JsonrpcRequest { + + /** + * Parses a generic {@link JsonrpcRequest} to a {@link AddAppInstance}. + * + * @param r the {@link JsonrpcRequest} + * @return the {@link AddAppInstance} Request + * @throws OpenemsNamedException on error + */ + public static Request from(JsonrpcRequest r) throws OpenemsNamedException { + var p = r.getParams(); + var appId = JsonUtils.getAsString(p, "appId"); + var properties = JsonUtils.getAsJsonObject(p, "properties"); + return new Request(r, appId, properties); + } + + public final String appId; + public final JsonObject properties; + + public Request(String appId, JsonObject properties) { + super(METHOD); + this.appId = appId; + this.properties = properties; + } + + private Request(JsonrpcRequest request, String appId, JsonObject properties) { + super(request, METHOD); + this.appId = appId; + this.properties = properties; + } + + @Override + public JsonObject getParams() { + return JsonUtils.buildJsonObject() // + .addProperty("appId", this.appId) // + .add("properties", this.properties) // + .build(); + } + } + + public static class Response extends JsonrpcResponseSuccess { + + private final UUID instanceId; + + public Response(UUID id, UUID instanceId) { + super(id); + this.instanceId = instanceId; + } + + @Override + public JsonObject getResult() { + return JsonUtils.buildJsonObject() // + .addProperty("instanceId", this.instanceId.toString()) // + .build(); + } + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppAssistant.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppAssistant.java new file mode 100644 index 00000000000..90cfd0a6226 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppAssistant.java @@ -0,0 +1,98 @@ +package io.openems.edge.core.appmanager.jsonrpc; + +import java.util.UUID; + +import com.google.gson.JsonObject; + +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.AppAssistant; +import io.openems.edge.core.appmanager.OpenemsApp; + +/** + * Gets the App-Assistant for a {@link OpenemsApp}. + * + *

+ * Request: + * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "getAppAssistant",
+ *   "params": {
+ *   	"appId": string
+ *   }
+ * }
+ * 
+ * + *

+ * Response: + * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "result": {
+ *     ... {@link AppAssistant#toJsonObject()}
+ *   }
+ * }
+ * 
+ */ +public class GetAppAssistant { + + public static final String METHOD = "getAppAssistant"; + + public static class Request extends JsonrpcRequest { + + /** + * Parses a generic {@link JsonrpcRequest} to a {@link GetAppAssistantRequest}. + * + * @param r the {@link JsonrpcRequest} + * @return the {@link GetAppAssistantRequest} + * @throws OpenemsNamedException on error + */ + public static Request from(JsonrpcRequest r) throws OpenemsNamedException { + JsonObject p = r.getParams(); + String appId = JsonUtils.getAsString(p, "appId"); + return new Request(r, appId); + } + + public final String appId; + + public Request(String appId) { + super(METHOD); + this.appId = appId; + } + + public Request(JsonrpcRequest request, String appId) { + super(request, METHOD); + this.appId = appId; + } + + @Override + public JsonObject getParams() { + return JsonUtils.buildJsonObject() // + .addProperty("appId", this.appId) // + .build(); + } + } + + public static class Response extends JsonrpcResponseSuccess { + + private final AppAssistant appAssistant; + + public Response(UUID id, AppAssistant appAssistant) { + super(id); + this.appAssistant = appAssistant; + } + + @Override + public JsonObject getResult() { + return this.appAssistant.toJsonObject(); + } + } + +} 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 new file mode 100644 index 00000000000..3bd7ad1cbba --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppInstances.java @@ -0,0 +1,108 @@ +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 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.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppInstance; + +/** + * Gets the active instances of an {@link OpenemsApp}. + * + *

+ * Request: + * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "getAppInstances",
+ *   "params": {
+ *   	"appId": string
+ *   }
+ * }
+ * 
+ * + *

+ * Response: + * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "result": {
+ *     "instances": {@link OpenemsAppInstance#toJsonObject()}[]
+ *   }
+ * }
+ * 
+ */ +public class GetAppInstances { + + public static final String METHOD = "getAppInstances"; + + public static class Request extends JsonrpcRequest { + + /** + * Parses a generic {@link JsonrpcRequest} to a {@link GetAppInstances}. + * + * @param r the {@link JsonrpcRequest} + * @return the {@link GetAppInstances} + * @throws OpenemsNamedException on error + */ + public static Request from(JsonrpcRequest r) throws OpenemsNamedException { + var p = r.getParams(); + var appId = JsonUtils.getAsString(p, "appId"); + return new Request(r, appId); + } + + public final String appId; + + public Request(String appId) { + super(METHOD); + this.appId = appId; + } + + private Request(JsonrpcRequest request, String appId) { + super(request, METHOD); + this.appId = appId; + } + + @Override + public JsonObject getParams() { + return JsonUtils.buildJsonObject() // + .addProperty("appId", this.appId) // + .build(); + } + } + + public static class Response extends JsonrpcResponseSuccess { + + private final JsonArray instances; + + public Response(UUID id, List instances) { + super(id); + + var result = JsonUtils.buildJsonArray(); // + for (var instance : instances) { + result.add(instance.toJsonObject()); + } + this.instances = result.build(); + } + + @Override + public JsonObject getResult() { + return JsonUtils.buildJsonObject() // + .add("instances", this.instances) // + .build(); // + } + + } + +} 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 new file mode 100644 index 00000000000..12c1450e512 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java @@ -0,0 +1,124 @@ +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 io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +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.OpenemsApp; +import io.openems.edge.core.appmanager.OpenemsAppInstance; + +/** + * Gets the available {@link OpenemsApp}s. + * + *

+ * Request: + * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "getApps",
+ *   "params": {}
+ * }
+ * 
+ * + *

+ * Response: + * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "result": {
+ *     apps: [{
+ *       "category": string (OpenemsAppCategory enum),
+ *       "appId": string,
+ *       "name": string,
+ *       "image": string (base64), 
+ *       "instanceIds": UUID[],
+ *     }]
+ *   }
+ * }
+ * 
+ */ +public class GetApps { + + public static final String METHOD = "getAppInstances"; + + public static class Request extends JsonrpcRequest { + public static final String METHOD = "getApps"; + + /** + * Parses a generic {@link JsonrpcRequest} to a {@link Request}. + * + * @param r the {@link JsonrpcRequest} + * @return the {@link GetAppsRequest} + * @throws OpenemsNamedException on error + */ + public static Request from(JsonrpcRequest r) throws OpenemsException { + return new Request(r); + } + + public Request() { + super(METHOD); + } + + private Request(JsonrpcRequest request) { + super(request, METHOD); + } + + @Override + public JsonObject getParams() { + return new JsonObject(); + } + + } + + public static class Response extends JsonrpcResponseSuccess { + + private final JsonArray apps; + + public Response(UUID id, List availableApps, List instantiatedApps) { + super(id); + this.apps = createAppsArray(availableApps, instantiatedApps); + } + + private static JsonArray createAppsArray(List availableApps, + List instantiatedApps) { + var result = JsonUtils.buildJsonArray(); + for (var app : availableApps) { + // Map Instantiated-Apps to Available-Apps + var instanceIds = JsonUtils.buildJsonArray(); + for (var instantiatedApp : instantiatedApps) { + if (app.getAppId().equals(instantiatedApp.appId)) { + instanceIds.add(instantiatedApp.instanceId.toString()); + continue; + } + } + result.add(JsonUtils.buildJsonObject() // + .addProperty("category", app.getCategory().name()) // + .addProperty("appId", app.getAppId()) // + .addProperty("name", app.getName()) // + .addProperty("image", app.getImage()) // + .add("instanceIds", instanceIds.build()) // + .build()); + } + return result.build(); + } + + @Override + public JsonObject getResult() { + return JsonUtils.buildJsonObject() // + .add("apps", this.apps) // + .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 new file mode 100644 index 00000000000..ab1d10fde3e --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppInstance.java @@ -0,0 +1,85 @@ +package io.openems.edge.core.appmanager.jsonrpc; + +import java.util.UUID; + +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.appmanager.OpenemsAppInstance; + +/** + * Updates an {@link OpenemsAppInstance}.. + * + *

+ * Request: + * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "updateAppInstance",
+ *   "params": {
+ *     "instanceId": string (uuid),
+ *     "properties": {}
+ *   }
+ * }
+ * 
+ * + *

+ * Response: + * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "result": {}
+ * }
+ * 
+ */ +public class UpdateAppInstance { + + public static final String METHOD = "updateAppInstance"; + + public static class Request extends JsonrpcRequest { + + /** + * Parses a generic {@link JsonrpcRequest} to a {@link UpdateAppInstance}. + * + * @param r the {@link JsonrpcRequest} + * @return the {@link UpdateAppInstance} + * @throws OpenemsNamedException on error + */ + public static Request from(JsonrpcRequest r) throws OpenemsNamedException { + var p = r.getParams(); + var instanceId = JsonUtils.getAsUUID(p, "instanceId"); + var properties = JsonUtils.getAsJsonObject(p, "properties"); + return new Request(r, instanceId, properties); + } + + public final UUID instanceId; + public final JsonObject properties; + + public Request(UUID instanceId, JsonObject properties) { + super(METHOD); + this.instanceId = instanceId; + this.properties = properties; + } + + private Request(JsonrpcRequest request, UUID instanceId, JsonObject properties) { + super(request, METHOD); + this.instanceId = instanceId; + this.properties = properties; + } + + @Override + public JsonObject getParams() { + return JsonUtils.buildJsonObject() // + .addProperty("instanceId", this.instanceId.toString()) // + .add("properties", this.properties) // + .build(); + } + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/NetworkConfigurationWorker.java b/io.openems.edge.core/src/io/openems/edge/core/host/NetworkConfigurationWorker.java index 4d947e655d0..7abd23a18f0 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/host/NetworkConfigurationWorker.java +++ b/io.openems.edge.core/src/io/openems/edge/core/host/NetworkConfigurationWorker.java @@ -12,6 +12,7 @@ import io.openems.common.OpenemsConstants; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.utils.JsonUtils; import io.openems.common.worker.AbstractWorker; /** @@ -31,8 +32,8 @@ public NetworkConfigurationWorker(HostImpl parent) { @Override protected void forever() { try { - String actualNetworkConfiguration = this.parent.operatingSystem.getNetworkConfiguration().toJson() - .toString(); + String actualNetworkConfiguration = JsonUtils + .prettyToString(this.parent.operatingSystem.getNetworkConfiguration().toJson()); String persistedNetworkConfiguration = this.parent.config.networkConfiguration(); if (!actualNetworkConfiguration.equals(persistedNetworkConfiguration)) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/SystemUpdateHandler.java b/io.openems.edge.core/src/io/openems/edge/core/host/SystemUpdateHandler.java index c7bb3ba526c..f4e0001930c 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/host/SystemUpdateHandler.java +++ b/io.openems.edge.core/src/io/openems/edge/core/host/SystemUpdateHandler.java @@ -1,7 +1,9 @@ package io.openems.edge.core.host; import java.io.BufferedReader; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -227,7 +229,9 @@ private void executeUpdate(CompletableFuture result) thr // Read log output boolean keepReading = true; - try (final BufferedReader reader = Files.newBufferedReader(logFile, StandardCharsets.US_ASCII)) { + + try (final BufferedReader reader = new BufferedReader( + new InputStreamReader(new FileInputStream(logFile.toFile()), StandardCharsets.ISO_8859_1))) { while (keepReading) { final String line = reader.readLine(); if (line == null) { diff --git a/io.openems.edge.core/test/io/openems/edge/core/componentmanager/ComponentManagerImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/componentmanager/ComponentManagerImplTest.java new file mode 100644 index 00000000000..4d88fdcb548 --- /dev/null +++ b/io.openems.edge.core/test/io/openems/edge/core/componentmanager/ComponentManagerImplTest.java @@ -0,0 +1,22 @@ +package io.openems.edge.core.componentmanager; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyConfigurationAdmin; + +public class ComponentManagerImplTest { + + @Test + public void test() throws OpenemsException, Exception { + final DummyConfigurationAdmin cm = new DummyConfigurationAdmin(); + cm.getOrCreateEmptyConfiguration(ComponentManager.SINGLETON_SERVICE_PID); + new ComponentTest(new ComponentManagerImpl()) // + .addReference("cm", cm) // + .activate(MyConfig.create() // + .build()); + } + +} diff --git a/io.openems.edge.core/test/io/openems/edge/core/componentmanager/MyConfig.java b/io.openems.edge.core/test/io/openems/edge/core/componentmanager/MyConfig.java new file mode 100644 index 00000000000..8c690fb47fa --- /dev/null +++ b/io.openems.edge.core/test/io/openems/edge/core/componentmanager/MyConfig.java @@ -0,0 +1,35 @@ +package io.openems.edge.core.componentmanager; + +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.test.AbstractComponentConfig; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + public static class Builder { + + private Builder() { + } + + public MyConfig build() { + return new MyConfig(this); + } + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfig(Builder builder) { + super(Config.class, ComponentManager.SINGLETON_COMPONENT_ID); + this.builder = builder; + } + +} \ No newline at end of file diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/dccharger/api/EssDcCharger.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/dccharger/api/EssDcCharger.java index ebd069b2af0..cb3b6d1cde8 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/dccharger/api/EssDcCharger.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/dccharger/api/EssDcCharger.java @@ -2,6 +2,7 @@ import org.osgi.annotation.versioning.ProviderType; +import io.openems.common.channel.AccessMode; import io.openems.common.channel.PersistencePriority; import io.openems.common.channel.Unit; import io.openems.common.types.OpenemsType; @@ -13,6 +14,9 @@ import io.openems.edge.common.channel.LongReadChannel; import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; +import io.openems.edge.common.modbusslave.ModbusType; +import io.openems.edge.ess.api.SymmetricEss; @ProviderType public interface EssDcCharger extends OpenemsComponent { @@ -203,4 +207,10 @@ public default void _setActualEnergy(long value) { this.getActualEnergyChannel().setNextValue(value); } + public static ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode accessMode) { + return ModbusSlaveNatureTable.of(SymmetricEss.class, accessMode, 100) // + .channel(0, ChannelId.ACTUAL_POWER, ModbusType.FLOAT32) // + .channel(2, ChannelId.ACTUAL_ENERGY, ModbusType.FLOAT64) // + .build(); + } } diff --git a/io.openems.edge.ess.fenecon.commercial40/src/io/openems/edge/ess/fenecon/commercial40/charger/AbstractEssDcChargerFeneconCommercial40.java b/io.openems.edge.ess.fenecon.commercial40/src/io/openems/edge/ess/fenecon/commercial40/charger/AbstractEssDcChargerFeneconCommercial40.java index 740c72e3cc7..5a456a037b5 100644 --- a/io.openems.edge.ess.fenecon.commercial40/src/io/openems/edge/ess/fenecon/commercial40/charger/AbstractEssDcChargerFeneconCommercial40.java +++ b/io.openems.edge.ess.fenecon.commercial40/src/io/openems/edge/ess/fenecon/commercial40/charger/AbstractEssDcChargerFeneconCommercial40.java @@ -3,6 +3,7 @@ import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; +import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; @@ -17,6 +18,9 @@ import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.ess.dccharger.api.EssDcCharger; import io.openems.edge.timedata.api.TimedataProvider; @@ -24,7 +28,7 @@ public abstract class AbstractEssDcChargerFeneconCommercial40 extends AbstractOpenemsModbusComponent implements EssDcChargerFeneconCommercial40, EssDcCharger, ModbusComponent, OpenemsComponent, TimedataProvider, - EventHandler { + EventHandler, ModbusSlave { private final CalculateEnergyFromPower calculateActualEnergy = new CalculateEnergyFromPower(this, EssDcCharger.ChannelId.ACTUAL_ENERGY); @@ -231,4 +235,13 @@ private void calculateEnergy() { this.calculateActualEnergy.update(0); } } + + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(// + OpenemsComponent.getModbusSlaveNatureTable(accessMode), // + EssDcCharger.getModbusSlaveNatureTable(accessMode), // + ModbusSlaveNatureTable.of(EssDcChargerFeneconCommercial40.class, accessMode, 100) // + .build()); + } } diff --git a/io.openems.edge.fenecon.dess/src/io/openems/edge/fenecon/dess/charger/AbstractFeneconDessCharger.java b/io.openems.edge.fenecon.dess/src/io/openems/edge/fenecon/dess/charger/AbstractFeneconDessCharger.java index c1c7ac520cf..8a3d4fe0a7a 100644 --- a/io.openems.edge.fenecon.dess/src/io/openems/edge/fenecon/dess/charger/AbstractFeneconDessCharger.java +++ b/io.openems.edge.fenecon.dess/src/io/openems/edge/fenecon/dess/charger/AbstractFeneconDessCharger.java @@ -3,6 +3,7 @@ import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; +import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; @@ -15,13 +16,16 @@ import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.ess.dccharger.api.EssDcCharger; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; -public abstract class AbstractFeneconDessCharger extends AbstractOpenemsModbusComponent - implements FeneconDessCharger, EssDcCharger, ModbusComponent, OpenemsComponent, TimedataProvider, EventHandler { +public abstract class AbstractFeneconDessCharger extends AbstractOpenemsModbusComponent implements FeneconDessCharger, + EssDcCharger, ModbusComponent, OpenemsComponent, TimedataProvider, EventHandler, ModbusSlave { private final CalculateEnergyFromPower calculateActualEnergy = new CalculateEnergyFromPower(this, EssDcCharger.ChannelId.ACTUAL_ENERGY); @@ -78,4 +82,13 @@ private void calculateEnergy() { this.calculateActualEnergy.update(0); } } + + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(// + OpenemsComponent.getModbusSlaveNatureTable(accessMode), // + EssDcCharger.getModbusSlaveNatureTable(accessMode), // + ModbusSlaveNatureTable.of(FeneconDessCharger.class, accessMode, 100) // + .build()); + } } \ No newline at end of file diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/Config.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/Config.java index bc0eabfc24c..f3d5969bf03 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/Config.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/Config.java @@ -4,10 +4,9 @@ import org.osgi.service.metatype.annotations.ObjectClassDefinition; import io.openems.edge.goodwe.GoodWeConstants; -import io.openems.edge.goodwe.common.enums.BackupEnable; import io.openems.edge.goodwe.common.enums.ControlMode; +import io.openems.edge.goodwe.common.enums.EnableDisable; import io.openems.edge.goodwe.common.enums.FeedInPowerSettings; -import io.openems.edge.goodwe.common.enums.FeedPowerEnable; import io.openems.edge.goodwe.common.enums.SafetyCountry; @ObjectClassDefinition(// @@ -37,25 +36,21 @@ String Modbus_target() default "(enabled=true)"; @AttributeDefinition(name = "Country Setting", description = "Selection of feed in country") - SafetyCountry safetyCountry() default SafetyCountry.UNDEFINED; + SafetyCountry safetyCountry() default SafetyCountry.GERMANY; - @AttributeDefinition(name = "Enable/Disable Backup Power", description = "In case of Off-Grid case backup power will provide power to battery, " - + "in this case battery is connected to inverter backup side. " - + "Otherwise battery is connected to grid side of the inverter." - + "When the battery connected to backup side, " - + "if 'backupEnable' select as DISABLE there will be power outage on battery side !!!!! ") - BackupEnable backupEnable() default BackupEnable.ENABLE; + @AttributeDefinition(name = "Mppt For Shadow Enable or Disable", description = "Selection the activation of Mppt tracker") + EnableDisable mpptForShadowEnable() default EnableDisable.ENABLE; + + @AttributeDefinition(name = "Enable/Disable Backup Power", description = "In case of Off-Grid case backup power will be activated") + EnableDisable backupEnable() default EnableDisable.ENABLE; @AttributeDefinition(name = "Enable Feed In To Grid", description = "Enable-disable feed in to grid") - FeedPowerEnable feedPowerEnable() default FeedPowerEnable.DISABLE; + EnableDisable feedPowerEnable() default EnableDisable.DISABLE; @AttributeDefinition(name = "Feed In To Grid Power", description = "Fixed feed in to grid power (Only positive [0 - 10_000])") int feedPowerPara() default 0; - @AttributeDefinition(name = "Feed In Power Selection", description = "This is the selection of inverter power settings. " - + "For QuEnableCurve inverter will operate based on power and frequency curve, " - + "For PuEnableCurve inverter will operate based on power factor(cos phi)" - + "Rest of the selections for specific Lagging or Leading factors.") + @AttributeDefinition(name = "Feed In Power Selection", description = "This is the selection of inverter power settings") FeedInPowerSettings setfeedInPowerSettings() default FeedInPowerSettings.UNDEFINED; String webconsole_configurationFactory_nameHint() default "GoodWe Battery Inverter [{id}]"; diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java index fce0e05867f..6c59dd25ff2 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java @@ -26,6 +26,7 @@ import io.openems.edge.batteryinverter.api.SymmetricBatteryInverter; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; +import io.openems.edge.common.channel.BooleanWriteChannel; import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.EnumWriteChannel; import io.openems.edge.common.channel.IntegerReadChannel; @@ -44,9 +45,9 @@ import io.openems.edge.goodwe.common.ApplyPowerHandler; import io.openems.edge.goodwe.common.GoodWe; import io.openems.edge.goodwe.common.enums.AppModeIndex; -import io.openems.edge.goodwe.common.enums.BackupEnable; import io.openems.edge.goodwe.common.enums.ControlMode; import io.openems.edge.goodwe.common.enums.EnableCurve; +import io.openems.edge.goodwe.common.enums.EnableDisable; import io.openems.edge.goodwe.common.enums.FeedInPowerSettings; import io.openems.edge.timedata.api.Timedata; @@ -154,8 +155,6 @@ public GoodWeBatteryInverterImpl() throws OpenemsNamedException { private void applyConfig(Config config) throws OpenemsNamedException { this.config = config; - // TODO write values only if update is required - // (0x00) 'General Mode: Self use' instead of (0x01) 'Off-grid Mode', (0x02) // 'Backup Mode' or (0x03) 'Economic Mode'. this.writeToChannel(GoodWe.ChannelId.SELECT_WORK_MODE, AppModeIndex.SELF_USE); @@ -163,6 +162,9 @@ private void applyConfig(Config config) throws OpenemsNamedException { // country setting this.writeToChannel(GoodWe.ChannelId.SAFETY_COUNTRY_CODE, config.safetyCountry()); + // Mppt Shadow enable / disable + this.writeToChannel(GoodWe.ChannelId.MPPT_FOR_SHADOW_ENABLE, config.mpptForShadowEnable()); + // Backup Power on / off this.writeToChannel(GoodWe.ChannelId.BACK_UP_ENABLE, config.backupEnable()); @@ -194,7 +196,7 @@ private void applyConfig(Config config) throws OpenemsNamedException { this.writeToChannel(GoodWe.ChannelId.V3_VOLTAGE, 237); this.writeToChannel(GoodWe.ChannelId.V3_VALUE, 0); this.writeToChannel(GoodWe.ChannelId.V4_VOLTAGE, 247); - this.writeToChannel(GoodWe.ChannelId.V4_VALUE, 65009); + this.writeToChannel(GoodWe.ChannelId.V4_VALUE, -526); break; case PU_ENABLE_CURVE: this.writeToChannel(GoodWe.ChannelId.A_POINT_POWER, 2000); @@ -357,6 +359,19 @@ private void writeToChannel(GoodWe.ChannelId channelId, OptionsEnum value) channel.setNextWriteValue(value); } + private void writeToChannel(GoodWe.ChannelId channelId, EnableDisable value) + throws IllegalArgumentException, OpenemsNamedException { + BooleanWriteChannel channel = this.channel(channelId); + switch (value) { + case ENABLE: + channel.setNextWriteValue(true); + break; + case DISABLE: + channel.setNextWriteValue(false); + break; + } + } + private void writeToChannel(GoodWe.ChannelId channelId, Integer value) throws IllegalArgumentException, OpenemsNamedException { IntegerWriteChannel channel = this.channel(channelId); @@ -397,9 +412,14 @@ public Integer getSurplusPower() { int surplusPower = productionPower // /* Charge-Max-Current */ - this.getBmsChargeMaxCurrent().orElse(0) // /* Battery Voltage */ * wbmsVoltageChannel.value().orElse(0); + + if(surplusPower <= 0) { + // PV Production is less than the maximum charge power -> no surplus power + return null; + } - // Must be positive - return Math.max(surplusPower, 0); + // Surplus power is always positive here + return surplusPower; } @Override @@ -445,7 +465,7 @@ public boolean isManaged() { @Override public boolean isOffGridPossible() { - return this.config.backupEnable().equals(BackupEnable.ENABLE); + return this.config.backupEnable().equals(EnableDisable.ENABLE); } } diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/AbstractGoodWeEtCharger.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/AbstractGoodWeEtCharger.java index ae955b8f479..a24f288621f 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/AbstractGoodWeEtCharger.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/charger/AbstractGoodWeEtCharger.java @@ -3,6 +3,7 @@ import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; +import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; import io.openems.edge.bridge.modbus.api.ElementToChannelConverter; @@ -13,14 +14,17 @@ import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.ess.dccharger.api.EssDcCharger; import io.openems.edge.goodwe.common.GoodWe; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; -public abstract class AbstractGoodWeEtCharger extends AbstractOpenemsModbusComponent - implements GoodWeEtCharger, EssDcCharger, ModbusComponent, OpenemsComponent, TimedataProvider, EventHandler { +public abstract class AbstractGoodWeEtCharger extends AbstractOpenemsModbusComponent implements GoodWeEtCharger, + EssDcCharger, ModbusComponent, OpenemsComponent, TimedataProvider, EventHandler, ModbusSlave { protected abstract GoodWe getEssOrBatteryInverter(); @@ -101,4 +105,13 @@ public final String debugLog() { } protected abstract int getStartAddress(); + + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(// + OpenemsComponent.getModbusSlaveNatureTable(accessMode), // + EssDcCharger.getModbusSlaveNatureTable(accessMode), // + ModbusSlaveNatureTable.of(GoodWeEtCharger.class, accessMode, 100) // + .build()); + } } diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java index 0309b46614c..5def56a1a2e 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/AbstractGoodWe.java @@ -569,7 +569,7 @@ protected final ModbusProtocol defineModbusProtocol() throws OpenemsException { new FC3ReadRegistersTask(45250, Priority.LOW, // m(GoodWe.ChannelId.PV_START_VOLTAGE, new UnsignedWordElement(45250), ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // - m(GoodWe.ChannelId.ENABLE_MPPT4_SHADOW, new UnsignedWordElement(45251)), // + m(GoodWe.ChannelId.MPPT_FOR_SHADOW_ENABLE, new UnsignedWordElement(45251)), // m(GoodWe.ChannelId.BACK_UP_ENABLE, new UnsignedWordElement(45252)), // m(GoodWe.ChannelId.AUTO_START_BACKUP, new UnsignedWordElement(45253)), // m(GoodWe.ChannelId.GRID_WAVE_CHECK_LEVEL, new UnsignedWordElement(45254)), // @@ -940,7 +940,7 @@ protected final ModbusProtocol defineModbusProtocol() throws OpenemsException { m(GoodWe.ChannelId.PV_START_VOLTAGE, new UnsignedWordElement(45250), ElementToChannelConverter.SCALE_FACTOR_MINUS_1), // // as default is deactivated, set "1" to activate "Shadow Scan" functtion - m(GoodWe.ChannelId.ENABLE_MPPT4_SHADOW, new UnsignedWordElement(45251)), // + m(GoodWe.ChannelId.MPPT_FOR_SHADOW_ENABLE, new UnsignedWordElement(45251)), // // as default is deactivated, set "1" to activate "Shadow Scan" functtion m(GoodWe.ChannelId.BACK_UP_ENABLE, new UnsignedWordElement(45252)), // // Off-Grid Auto startup, as default is deactivated, set "1" to activate "Shadow diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/ApplyPowerHandler.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/ApplyPowerHandler.java index 2e7d688334a..dfbd6459cff 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/ApplyPowerHandler.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/ApplyPowerHandler.java @@ -96,7 +96,7 @@ private static Result handleSmartMode(AbstractGoodWe goodWe, int activePowerSetP // Is Surplus-Feed-In active? final Integer surplusPower = goodWe.getSurplusPower(); int diffSurplus = Integer.MAX_VALUE; - if (surplusPower != null) { + if (surplusPower != null && surplusPower > 0 && activePowerSetPoint != 0) { diffSurplus = activePowerSetPoint - surplusPower; } diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java index 3af174eda81..b22dea15cf8 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java @@ -18,7 +18,6 @@ import io.openems.edge.goodwe.charger.GoodWeEtCharger2; import io.openems.edge.goodwe.common.enums.AppModeIndex; import io.openems.edge.goodwe.common.enums.ArcSelfCheckStatus; -import io.openems.edge.goodwe.common.enums.BackupEnable; import io.openems.edge.goodwe.common.enums.BatteryMode; import io.openems.edge.goodwe.common.enums.BatteryProtocol; import io.openems.edge.goodwe.common.enums.ComMode; @@ -34,7 +33,6 @@ import io.openems.edge.goodwe.common.enums.ExternalEmsFlag; import io.openems.edge.goodwe.common.enums.EzloggerProCommStatus; import io.openems.edge.goodwe.common.enums.FeedInPowerSettings.FixedPowerFactor; -import io.openems.edge.goodwe.common.enums.FeedPowerEnable; import io.openems.edge.goodwe.common.enums.GoodweGridMeterType; import io.openems.edge.goodwe.common.enums.GoodweType; import io.openems.edge.goodwe.common.enums.GridProtect; @@ -45,7 +43,6 @@ import io.openems.edge.goodwe.common.enums.MeterCommunicateStatus; import io.openems.edge.goodwe.common.enums.MeterConnectCheckFlag; import io.openems.edge.goodwe.common.enums.MeterConnectStatus; -import io.openems.edge.goodwe.common.enums.MeterReverseEnable; import io.openems.edge.goodwe.common.enums.OperationMode; import io.openems.edge.goodwe.common.enums.OutputTypeAC; import io.openems.edge.goodwe.common.enums.PvMode; @@ -520,11 +517,11 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .accessMode(AccessMode.READ_WRITE)), // PV_START_VOLTAGE(Doc.of(OpenemsType.INTEGER) // .unit(Unit.VOLT).accessMode(AccessMode.READ_WRITE)), // - ENABLE_MPPT4_SHADOW(Doc.of(OpenemsType.INTEGER) // + MPPT_FOR_SHADOW_ENABLE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_WRITE)), // - BACK_UP_ENABLE(Doc.of(BackupEnable.values()) // + BACK_UP_ENABLE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_WRITE)), // - AUTO_START_BACKUP(Doc.of(BackupEnable.values()) // + AUTO_START_BACKUP(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_WRITE)), // GRID_WAVE_CHECK_LEVEL(Doc.of(GridWaveCheckLevel.values()) // .accessMode(AccessMode.READ_WRITE)), // @@ -967,7 +964,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .accessMode(AccessMode.READ_WRITE)), // COM_LED_STATE(Doc.of(LedState.values()) // .accessMode(AccessMode.READ_WRITE)), // - METER_CT1_REVERSE_ENABLE(Doc.of(MeterReverseEnable.values()) // + METER_CT1_REVERSE_ENABLE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_WRITE)), // ERROR_LOG_READ_PAGE(Doc.of(OpenemsType.INTEGER) // .accessMode(AccessMode.READ_WRITE)), // @@ -1022,7 +1019,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId BMS_AVG_CHG_HOURS(Doc.of(OpenemsType.INTEGER) // .unit(Unit.HOUR) // .accessMode(AccessMode.READ_WRITE)), // - FEED_POWER_ENABLE(Doc.of(FeedPowerEnable.values()) // + FEED_POWER_ENABLE(Doc.of(OpenemsType.BOOLEAN) // .accessMode(AccessMode.READ_WRITE)), // FEED_POWER_PARA_SET(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/AutoStartBackup.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/AutoStartBackup.java deleted file mode 100644 index 372ea7b0eea..00000000000 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/AutoStartBackup.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.openems.edge.goodwe.common.enums; - -import io.openems.common.types.OptionsEnum; - -public enum AutoStartBackup implements OptionsEnum { - UNDEFINED(-1, "Undefined"), // - OFF(0, "Off grid auto startup is off"), // - ON(1, "Off grid auto startup is on");// - - private final int value; - private final String option; - - private AutoStartBackup(int value, String option) { - this.value = value; - this.option = option; - } - - @Override - public int getValue() { - return this.value; - } - - @Override - public String getName() { - return this.option; - } - - @Override - public OptionsEnum getUndefined() { - return UNDEFINED; - } -} \ No newline at end of file diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/BackupEnable.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/BackupEnable.java deleted file mode 100644 index 4d5247c174c..00000000000 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/BackupEnable.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.openems.edge.goodwe.common.enums; - -import io.openems.common.types.OptionsEnum; - -public enum BackupEnable implements OptionsEnum { - UNDEFINED(-1, "Undefined"), // - DISABLE(0, "Backup output Off"), // - ENABLE(1, "Backup output On");// - - private final int value; - private final String option; - - private BackupEnable(int value, String option) { - this.value = value; - this.option = option; - } - - @Override - public int getValue() { - return this.value; - } - - @Override - public String getName() { - return this.option; - } - - @Override - public OptionsEnum getUndefined() { - return UNDEFINED; - } -} \ No newline at end of file diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/EnableDisable.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/EnableDisable.java new file mode 100644 index 00000000000..e7f238925c1 --- /dev/null +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/EnableDisable.java @@ -0,0 +1,17 @@ +package io.openems.edge.goodwe.common.enums; + +public enum EnableDisable { + + ENABLE("Enable"), // + DISABLE("Disable"); + + private final String value; + + private EnableDisable(String value) { + this.value = value; + } + + public String getValue() { + return this.value; + } +} diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/FeedPowerEnable.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/FeedPowerEnable.java deleted file mode 100644 index 5a304f4ee01..00000000000 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/FeedPowerEnable.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.openems.edge.goodwe.common.enums; - -import io.openems.common.types.OptionsEnum; - -public enum FeedPowerEnable implements OptionsEnum { - - // TODO Is 0 disables or enables, likewise 1 disables or enables - - UNDEFINED(-1, "Undefined"), // - DISABLE(0, "Feed Power Disable"), // - ENABLE(1, "Feed Power Enable");// - - private final int value; - private final String option; - - private FeedPowerEnable(int value, String option) { - this.value = value; - this.option = option; - } - - @Override - public int getValue() { - return this.value; - } - - @Override - public String getName() { - return this.option; - } - - @Override - public OptionsEnum getUndefined() { - return UNDEFINED; - } -} \ No newline at end of file diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/MeterReverseEnable.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/MeterReverseEnable.java deleted file mode 100644 index 3147ce74fa4..00000000000 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/enums/MeterReverseEnable.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.openems.edge.goodwe.common.enums; - -import io.openems.common.types.OptionsEnum; - -public enum MeterReverseEnable implements OptionsEnum { - UNDEFINED(-1, "Undefined"), // - REVERSE_DISABLE(0, "Smart meter ct1 reverse disable"), // - REVERSE_ENABLE(1, "Smart meter ct1 reverse enable"); - - private final int value; - private final String option; - - private MeterReverseEnable(int value, String option) { - this.value = value; - this.option = option; - } - - @Override - public int getValue() { - return this.value; - } - - @Override - public String getName() { - return this.option; - } - - @Override - public OptionsEnum getUndefined() { - return UNDEFINED; - } -} \ No newline at end of file diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImplTest.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImplTest.java index d6ee161c30c..b106c0ea30a 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImplTest.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImplTest.java @@ -13,11 +13,10 @@ import io.openems.edge.ess.test.DummyPower; import io.openems.edge.goodwe.GoodWeConstants; import io.openems.edge.goodwe.charger.GoodWeEtCharger1; -import io.openems.edge.goodwe.common.enums.BackupEnable; import io.openems.edge.goodwe.common.enums.ControlMode; import io.openems.edge.goodwe.common.enums.EmsPowerMode; +import io.openems.edge.goodwe.common.enums.EnableDisable; import io.openems.edge.goodwe.common.enums.FeedInPowerSettings; -import io.openems.edge.goodwe.common.enums.FeedPowerEnable; import io.openems.edge.goodwe.common.enums.MeterCommunicateStatus; import io.openems.edge.goodwe.common.enums.SafetyCountry; @@ -73,8 +72,9 @@ public void testEt() throws Exception { .setModbusId(MODBUS_ID) // .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // - .setBackupEnable(BackupEnable.ENABLE) // - .setFeedPowerEnable(FeedPowerEnable.ENABLE) // + .setMpptForShadowEnable(EnableDisable.ENABLE) // + .setBackupEnable(EnableDisable.ENABLE) // + .setFeedPowerEnable(EnableDisable.ENABLE) // .setFeedPowerPara(3000) // .setFeedInPowerSettings(FeedInPowerSettings.PU_ENABLE_CURVE) // .setControlMode(ControlMode.REMOTE) // @@ -105,8 +105,9 @@ public void testNegativSetActivePoint() throws Exception { .setModbusId(MODBUS_ID) // .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // - .setBackupEnable(BackupEnable.ENABLE) // - .setFeedPowerEnable(FeedPowerEnable.ENABLE) // + .setMpptForShadowEnable(EnableDisable.ENABLE) // + .setBackupEnable(EnableDisable.ENABLE) // + .setFeedPowerEnable(EnableDisable.ENABLE) // .setFeedPowerPara(3000) // .setFeedInPowerSettings(FeedInPowerSettings.PU_ENABLE_CURVE) // .setControlMode(ControlMode.REMOTE) // @@ -136,8 +137,9 @@ public void testDischargeBattery() throws Exception { .setModbusId(MODBUS_ID) // .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // - .setBackupEnable(BackupEnable.ENABLE) // - .setFeedPowerEnable(FeedPowerEnable.ENABLE) // + .setMpptForShadowEnable(EnableDisable.ENABLE) // + .setBackupEnable(EnableDisable.ENABLE) // + .setFeedPowerEnable(EnableDisable.ENABLE) // .setFeedPowerPara(3000) // .setFeedInPowerSettings(FeedInPowerSettings.PU_ENABLE_CURVE) // .setControlMode(ControlMode.REMOTE) // @@ -167,8 +169,9 @@ public void testEmsPowerModeAutoWithBalancing() throws Exception { .setModbusId(MODBUS_ID) // .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // - .setBackupEnable(BackupEnable.ENABLE) // - .setFeedPowerEnable(FeedPowerEnable.ENABLE) // + .setMpptForShadowEnable(EnableDisable.ENABLE) // + .setBackupEnable(EnableDisable.ENABLE) // + .setFeedPowerEnable(EnableDisable.ENABLE) // .setFeedPowerPara(3000) // .setFeedInPowerSettings(FeedInPowerSettings.PU_ENABLE_CURVE) // .setControlMode(ControlMode.SMART) // @@ -211,8 +214,9 @@ public void testEmsPowerModeAutoWithSurplus() throws Exception { .setModbusId(MODBUS_ID) // .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // - .setBackupEnable(BackupEnable.ENABLE) // - .setFeedPowerEnable(FeedPowerEnable.ENABLE) // + .setMpptForShadowEnable(EnableDisable.ENABLE) // + .setBackupEnable(EnableDisable.ENABLE) // + .setFeedPowerEnable(EnableDisable.ENABLE) // .setFeedPowerPara(3000) // .setFeedInPowerSettings(FeedInPowerSettings.PU_ENABLE_CURVE) // .setControlMode(ControlMode.SMART) // @@ -240,8 +244,9 @@ public void testEmsPowerModeAutoWithMaxAcImport() throws Exception { .setModbusId(MODBUS_ID) // .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // - .setBackupEnable(BackupEnable.ENABLE) // - .setFeedPowerEnable(FeedPowerEnable.ENABLE) // + .setMpptForShadowEnable(EnableDisable.ENABLE) // + .setBackupEnable(EnableDisable.ENABLE) // + .setFeedPowerEnable(EnableDisable.ENABLE) // .setFeedPowerPara(3000) // .setFeedInPowerSettings(FeedInPowerSettings.PU_ENABLE_CURVE) // .setControlMode(ControlMode.SMART) // @@ -269,8 +274,9 @@ public void testEmsPowerModeAutoWithMaxAcExport() throws Exception { .setModbusId(MODBUS_ID) // .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // - .setBackupEnable(BackupEnable.ENABLE) // - .setFeedPowerEnable(FeedPowerEnable.ENABLE) // + .setMpptForShadowEnable(EnableDisable.ENABLE) // + .setBackupEnable(EnableDisable.ENABLE) // + .setFeedPowerEnable(EnableDisable.ENABLE) // .setFeedPowerPara(3000) // .setFeedInPowerSettings(FeedInPowerSettings.PU_ENABLE_CURVE) // .setControlMode(ControlMode.SMART) // @@ -298,8 +304,9 @@ public void testBatteryIsFull() throws Exception { .setModbusId(MODBUS_ID) // .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // - .setBackupEnable(BackupEnable.ENABLE) // - .setFeedPowerEnable(FeedPowerEnable.ENABLE) // + .setMpptForShadowEnable(EnableDisable.ENABLE) // + .setBackupEnable(EnableDisable.ENABLE) // + .setFeedPowerEnable(EnableDisable.ENABLE) // .setFeedPowerPara(3000) // .setFeedInPowerSettings(FeedInPowerSettings.PU_ENABLE_CURVE) // .setControlMode(ControlMode.SMART) // @@ -327,8 +334,9 @@ public void testBatteryIsEmpty() throws Exception { .setModbusId(MODBUS_ID) // .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // - .setBackupEnable(BackupEnable.ENABLE) // - .setFeedPowerEnable(FeedPowerEnable.ENABLE) // + .setMpptForShadowEnable(EnableDisable.ENABLE) // + .setBackupEnable(EnableDisable.ENABLE) // + .setFeedPowerEnable(EnableDisable.ENABLE) // .setFeedPowerPara(3000) // .setFeedInPowerSettings(FeedInPowerSettings.PU_ENABLE_CURVE) // .setControlMode(ControlMode.SMART) // @@ -356,8 +364,9 @@ public void testAcCalculation() throws Exception { .setModbusId(MODBUS_ID) // .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // - .setBackupEnable(BackupEnable.ENABLE) // - .setFeedPowerEnable(FeedPowerEnable.ENABLE) // + .setMpptForShadowEnable(EnableDisable.ENABLE) // + .setBackupEnable(EnableDisable.ENABLE) // + .setFeedPowerEnable(EnableDisable.ENABLE) // .setFeedPowerPara(3000) // .setFeedInPowerSettings(FeedInPowerSettings.PU_ENABLE_CURVE) // .setControlMode(ControlMode.SMART) // diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/MyConfig.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/MyConfig.java index bd398ea608f..c96031c346a 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/MyConfig.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/MyConfig.java @@ -2,10 +2,9 @@ import io.openems.common.utils.ConfigUtils; import io.openems.edge.common.test.AbstractComponentConfig; -import io.openems.edge.goodwe.common.enums.BackupEnable; import io.openems.edge.goodwe.common.enums.ControlMode; +import io.openems.edge.goodwe.common.enums.EnableDisable; import io.openems.edge.goodwe.common.enums.FeedInPowerSettings; -import io.openems.edge.goodwe.common.enums.FeedPowerEnable; import io.openems.edge.goodwe.common.enums.SafetyCountry; @SuppressWarnings("all") @@ -17,8 +16,9 @@ protected static class Builder { public String modbusId; public int modbusUnitId; public SafetyCountry safetyCountry; - public BackupEnable backupEnable; - public FeedPowerEnable feedPowerEnable; + public EnableDisable mpptForShadowEnable; + public EnableDisable backupEnable; + public EnableDisable feedPowerEnable; public int feedPowerPara; public FeedInPowerSettings feedInPowerSettings; @@ -51,12 +51,17 @@ public Builder setSafetyCountry(SafetyCountry safetyCountry) { return this; } - public Builder setBackupEnable(BackupEnable backupEnable) { + public Builder setMpptForShadowEnable(EnableDisable mpptForShadowEnable) { + this.mpptForShadowEnable = mpptForShadowEnable; + return this; + } + + public Builder setBackupEnable(EnableDisable backupEnable) { this.backupEnable = backupEnable; return this; } - public Builder setFeedPowerEnable(FeedPowerEnable feedPowerEnable) { + public Builder setFeedPowerEnable(EnableDisable feedPowerEnable) { this.feedPowerEnable = feedPowerEnable; return this; } @@ -113,12 +118,17 @@ public SafetyCountry safetyCountry() { } @Override - public BackupEnable backupEnable() { + public EnableDisable mpptForShadowEnable() { + return this.builder.mpptForShadowEnable; + } + + @Override + public EnableDisable backupEnable() { return this.builder.backupEnable; } @Override - public FeedPowerEnable feedPowerEnable() { + public EnableDisable feedPowerEnable() { return this.builder.feedPowerEnable; } diff --git a/io.openems.edge.kostal.piko/src/io/openems/edge/kostal/piko/charger/KostalPikoCharger.java b/io.openems.edge.kostal.piko/src/io/openems/edge/kostal/piko/charger/KostalPikoCharger.java index 4e1a54687ee..94a0d9bc6c5 100644 --- a/io.openems.edge.kostal.piko/src/io/openems/edge/kostal/piko/charger/KostalPikoCharger.java +++ b/io.openems.edge.kostal.piko/src/io/openems/edge/kostal/piko/charger/KostalPikoCharger.java @@ -15,10 +15,14 @@ import org.osgi.service.event.EventConstants; import org.osgi.service.metatype.annotations.Designate; +import io.openems.common.channel.AccessMode; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.ess.dccharger.api.EssDcCharger; import io.openems.edge.kostal.piko.core.api.KostalPikoCore; @@ -29,7 +33,7 @@ configurationPolicy = ConfigurationPolicy.REQUIRE, // property = EventConstants.EVENT_TOPIC + "=" + EdgeEventConstants.TOPIC_CYCLE_AFTER_WRITE // ) -public class KostalPikoCharger extends AbstractOpenemsComponent implements EssDcCharger, OpenemsComponent { +public class KostalPikoCharger extends AbstractOpenemsComponent implements EssDcCharger, OpenemsComponent, ModbusSlave { @Reference protected ConfigurationAdmin cm; @@ -87,4 +91,13 @@ protected void deactivate() { public String debugLog() { return "P:" + this.getActualPower().asString(); } + + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(// + OpenemsComponent.getModbusSlaveNatureTable(accessMode), // + EssDcCharger.getModbusSlaveNatureTable(accessMode), // + ModbusSlaveNatureTable.of(KostalPikoCharger.class, accessMode, 100) // + .build()); + } } diff --git a/io.openems.edge.pvinverter.api/src/io/openems/edge/pvinverter/api/ManagedSymmetricPvInverter.java b/io.openems.edge.pvinverter.api/src/io/openems/edge/pvinverter/api/ManagedSymmetricPvInverter.java index 91ae01ebc09..1eba4e434dc 100644 --- a/io.openems.edge.pvinverter.api/src/io/openems/edge/pvinverter/api/ManagedSymmetricPvInverter.java +++ b/io.openems.edge.pvinverter.api/src/io/openems/edge/pvinverter/api/ManagedSymmetricPvInverter.java @@ -11,6 +11,7 @@ import io.openems.edge.common.channel.IntegerWriteChannel; import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.SymmetricMeter; @@ -171,4 +172,9 @@ public default void setActivePowerLimit(int value) throws OpenemsNamedException this.getActivePowerLimitChannel().setNextWriteValue(value); } + public static ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode accessMode) { + return ModbusSlaveNatureTable.of(ManagedSymmetricPvInverter.class, accessMode, 100) // + .build(); + } + } diff --git a/io.openems.edge.pvinverter.cluster/src/io/openems/edge/pvinverter/cluster/PvInverterCluster.java b/io.openems.edge.pvinverter.cluster/src/io/openems/edge/pvinverter/cluster/PvInverterCluster.java index aee7ee258e9..6960d577f32 100644 --- a/io.openems.edge.pvinverter.cluster/src/io/openems/edge/pvinverter/cluster/PvInverterCluster.java +++ b/io.openems.edge.pvinverter.cluster/src/io/openems/edge/pvinverter/cluster/PvInverterCluster.java @@ -20,6 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.openems.common.channel.AccessMode; import io.openems.common.channel.Level; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; @@ -31,6 +32,9 @@ import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.meter.api.SymmetricMeter; import io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter; @@ -45,7 +49,7 @@ "type=PRODUCTION" // }) public class PvInverterCluster extends AbstractOpenemsComponent - implements ManagedSymmetricPvInverter, SymmetricMeter, OpenemsComponent, EventHandler { + implements ManagedSymmetricPvInverter, SymmetricMeter, OpenemsComponent, EventHandler, ModbusSlave { private final Logger log = LoggerFactory.getLogger(PvInverterCluster.class); @@ -224,4 +228,14 @@ private List getPvInverters() throws OpenemsNamedExc } return result; } + + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(// + OpenemsComponent.getModbusSlaveNatureTable(accessMode), // + SymmetricMeter.getModbusSlaveNatureTable(accessMode), // + ManagedSymmetricPvInverter.getModbusSlaveNatureTable(accessMode), // + ModbusSlaveNatureTable.of(PvInverterCluster.class, accessMode, 100) // + .build()); + } } diff --git a/io.openems.edge.pvinverter.kaco.blueplanet/src/io/openems/edge/pvinverter/kaco/blueplanet/KacoBlueplanet.java b/io.openems.edge.pvinverter.kaco.blueplanet/src/io/openems/edge/pvinverter/kaco/blueplanet/KacoBlueplanet.java index 8d450327980..701e6b92194 100644 --- a/io.openems.edge.pvinverter.kaco.blueplanet/src/io/openems/edge/pvinverter/kaco/blueplanet/KacoBlueplanet.java +++ b/io.openems.edge.pvinverter.kaco.blueplanet/src/io/openems/edge/pvinverter/kaco/blueplanet/KacoBlueplanet.java @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableMap; +import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -26,6 +27,9 @@ import io.openems.edge.bridge.modbus.sunspec.SunSpecModel; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.SymmetricMeter; import io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter; @@ -42,7 +46,7 @@ "type=PRODUCTION" // }) public class KacoBlueplanet extends AbstractSunSpecPvInverter implements SunSpecPvInverter, ManagedSymmetricPvInverter, - ModbusComponent, SymmetricMeter, OpenemsComponent, EventHandler { + ModbusComponent, SymmetricMeter, OpenemsComponent, EventHandler, ModbusSlave { private static final Map ACTIVE_MODELS = ImmutableMap.builder() .put(DefaultSunSpecModel.S_1, Priority.LOW) // from 40002 @@ -103,4 +107,14 @@ protected void deactivate() { public void handleEvent(Event event) { super.handleEvent(event); } + + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(// + OpenemsComponent.getModbusSlaveNatureTable(accessMode), // + SymmetricMeter.getModbusSlaveNatureTable(accessMode), // + ManagedSymmetricPvInverter.getModbusSlaveNatureTable(accessMode), // + ModbusSlaveNatureTable.of(KacoBlueplanet.class, accessMode, 100) // + .build()); + } } diff --git a/io.openems.edge.pvinverter.sma/src/io/openems/edge/pvinverter/sma/SmaPvInverter.java b/io.openems.edge.pvinverter.sma/src/io/openems/edge/pvinverter/sma/SmaPvInverter.java index 8eb5bdf615d..1ef395dccb1 100644 --- a/io.openems.edge.pvinverter.sma/src/io/openems/edge/pvinverter/sma/SmaPvInverter.java +++ b/io.openems.edge.pvinverter.sma/src/io/openems/edge/pvinverter/sma/SmaPvInverter.java @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableMap; +import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusComponent; @@ -26,6 +27,9 @@ import io.openems.edge.bridge.modbus.sunspec.SunSpecModel; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.SymmetricMeter; import io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter; @@ -42,7 +46,7 @@ "type=PRODUCTION" // }) public class SmaPvInverter extends AbstractSunSpecPvInverter implements SunSpecPvInverter, ManagedSymmetricPvInverter, - SymmetricMeter, ModbusComponent, OpenemsComponent, EventHandler { + SymmetricMeter, ModbusComponent, OpenemsComponent, EventHandler, ModbusSlave { private static final Map ACTIVE_MODELS = ImmutableMap.builder() .put(DefaultSunSpecModel.S_1, Priority.LOW) // from 40002 @@ -104,4 +108,14 @@ protected void deactivate() { public void handleEvent(Event event) { super.handleEvent(event); } + + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(// + OpenemsComponent.getModbusSlaveNatureTable(accessMode), // + SymmetricMeter.getModbusSlaveNatureTable(accessMode), // + ManagedSymmetricPvInverter.getModbusSlaveNatureTable(accessMode), // + ModbusSlaveNatureTable.of(SmaPvInverter.class, accessMode, 100) // + .build()); + } } diff --git a/io.openems.edge.pvinverter.solarlog/src/io/openems/edge/pvinverter/solarlog/SolarLog.java b/io.openems.edge.pvinverter.solarlog/src/io/openems/edge/pvinverter/solarlog/SolarLog.java index 739f9544aa7..06c15b9ba11 100644 --- a/io.openems.edge.pvinverter.solarlog/src/io/openems/edge/pvinverter/solarlog/SolarLog.java +++ b/io.openems.edge.pvinverter.solarlog/src/io/openems/edge/pvinverter/solarlog/SolarLog.java @@ -8,10 +8,12 @@ import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.modbusslave.ModbusSlave; import io.openems.edge.meter.api.SymmetricMeter; import io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter; -public interface SolarLog extends ManagedSymmetricPvInverter, SymmetricMeter, OpenemsComponent, EventHandler { +public interface SolarLog + extends ManagedSymmetricPvInverter, SymmetricMeter, OpenemsComponent, EventHandler, ModbusSlave { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { LAST_UPDATE_TIME(Doc.of(OpenemsType.INTEGER) // diff --git a/io.openems.edge.pvinverter.solarlog/src/io/openems/edge/pvinverter/solarlog/SolarLogImpl.java b/io.openems.edge.pvinverter.solarlog/src/io/openems/edge/pvinverter/solarlog/SolarLogImpl.java index dc9ffd2e9f8..5b5cb529952 100644 --- a/io.openems.edge.pvinverter.solarlog/src/io/openems/edge/pvinverter/solarlog/SolarLogImpl.java +++ b/io.openems.edge.pvinverter.solarlog/src/io/openems/edge/pvinverter/solarlog/SolarLogImpl.java @@ -16,6 +16,7 @@ import org.osgi.service.metatype.annotations.Designate; import org.slf4j.Logger; +import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent; @@ -33,6 +34,9 @@ import io.openems.edge.bridge.modbus.api.task.FC4ReadInputRegistersTask; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.SymmetricMeter; @@ -48,7 +52,7 @@ "type=PRODUCTION" // }) public class SolarLogImpl extends AbstractOpenemsModbusComponent implements SolarLog, ManagedSymmetricPvInverter, - SymmetricMeter, ModbusComponent, OpenemsComponent, EventHandler { + SymmetricMeter, ModbusComponent, OpenemsComponent, EventHandler, ModbusSlave { private final SetPvLimitHandler setPvLimitHandler = new SetPvLimitHandler(this, ManagedSymmetricPvInverter.ChannelId.ACTIVE_POWER_LIMIT); @@ -178,4 +182,14 @@ public String debugLog() { protected void logInfo(Logger log, String message) { super.logInfo(log, message); } + + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(// + OpenemsComponent.getModbusSlaveNatureTable(accessMode), // + SymmetricMeter.getModbusSlaveNatureTable(accessMode), // + ManagedSymmetricPvInverter.getModbusSlaveNatureTable(accessMode), // + ModbusSlaveNatureTable.of(SolarLog.class, accessMode, 100) // + .build()); + } } diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/ess/symmetric/reacting/EssSymmetric.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/ess/symmetric/reacting/EssSymmetric.java index 0cf9602516a..35c9a1a3a91 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/ess/symmetric/reacting/EssSymmetric.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/ess/symmetric/reacting/EssSymmetric.java @@ -29,6 +29,8 @@ import io.openems.edge.common.modbusslave.ModbusSlave; import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; import io.openems.edge.common.modbusslave.ModbusSlaveTable; +import io.openems.edge.common.startstop.StartStop; +import io.openems.edge.common.startstop.StartStoppable; import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.power.api.Power; @@ -43,8 +45,8 @@ property = { // EventConstants.EVENT_TOPIC + "=" + EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // }) -public class EssSymmetric extends AbstractOpenemsComponent - implements ManagedSymmetricEss, SymmetricEss, OpenemsComponent, TimedataProvider, EventHandler, ModbusSlave { +public class EssSymmetric extends AbstractOpenemsComponent implements ManagedSymmetricEss, SymmetricEss, + OpenemsComponent, TimedataProvider, EventHandler, StartStoppable, ModbusSlave { /** * Current Energy in the battery [Wms], based on SoC @@ -108,6 +110,7 @@ public EssSymmetric() { OpenemsComponent.ChannelId.values(), // SymmetricEss.ChannelId.values(), // ManagedSymmetricEss.ChannelId.values(), // + StartStoppable.ChannelId.values(), // ChannelId.values() // ); } @@ -217,7 +220,8 @@ public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { OpenemsComponent.getModbusSlaveNatureTable(accessMode), // SymmetricEss.getModbusSlaveNatureTable(accessMode), // ManagedSymmetricEss.getModbusSlaveNatureTable(accessMode), // - ModbusSlaveNatureTable.of(EssSymmetric.class, accessMode, 300) // + StartStoppable.getModbusSlaveNatureTable(accessMode), // + ModbusSlaveNatureTable.of(EssSymmetric.class, accessMode, 100) // .build()); } @@ -247,4 +251,8 @@ public Timedata getTimedata() { return this.timedata; } + @Override + public void setStartStop(StartStop value) { + this._setStartStop(value); + } } diff --git a/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/AwattarImpl.java b/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/AwattarImpl.java index 556d181577a..c97463cb4ce 100644 --- a/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/AwattarImpl.java +++ b/io.openems.edge.timeofusetariff.awattar/src/io/openems/edge/timeofusetariff/awattar/AwattarImpl.java @@ -47,7 +47,7 @@ ) public class AwattarImpl extends AbstractOpenemsComponent implements TimeOfUseTariff, OpenemsComponent, Awattar { - private static final String AWATTAR_API_URL = "https://api.awattar.com/v1/marketdata"; + private static final String AWATTAR_API_URL = "https://api.awattar.de/v1/marketdata"; private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); diff --git a/io.openems.edge.timeofusetariff.tibber/src/io/openems/edge/timeofusetariff/tibber/TibberImpl.java b/io.openems.edge.timeofusetariff.tibber/src/io/openems/edge/timeofusetariff/tibber/TibberImpl.java index 5aecf72d88c..af6697ddda9 100644 --- a/io.openems.edge.timeofusetariff.tibber/src/io/openems/edge/timeofusetariff/tibber/TibberImpl.java +++ b/io.openems.edge.timeofusetariff.tibber/src/io/openems/edge/timeofusetariff/tibber/TibberImpl.java @@ -198,7 +198,7 @@ public static ImmutableSortedMap parsePrices(String jsonDa // parse the arrays for price and time stamps. for (JsonArray day : days) { for (JsonElement element : day) { - float marketPrice = JsonUtils.getAsFloat(element, "total"); + float marketPrice = JsonUtils.getAsFloat(element, "total") * 1000; ZonedDateTime startTime = ZonedDateTime .parse(JsonUtils.getAsString(element, "startsAt"), DateTimeFormatter.ISO_DATE_TIME) .withZoneSameInstant(ZoneId.systemDefault()); diff --git a/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/TibberProviderTest.java b/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/TibberProviderTest.java index 0fe5d65b5c5..06ea3a91c7f 100644 --- a/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/TibberProviderTest.java +++ b/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/TibberProviderTest.java @@ -150,7 +150,7 @@ public void nonEmptyStringTest() throws OpenemsNamedException { assertFalse(prices.isEmpty()); // To check if the a value input from the string is present in map. - assertTrue(prices.containsValue(0.1853f)); + assertTrue(prices.containsValue(0.1853f * 1000)); } diff --git a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConnector.java b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConnector.java index f8fcc7b65db..d82e99d925d 100644 --- a/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConnector.java +++ b/io.openems.shared.influxdb/src/io/openems/shared/influxdb/InfluxConnector.java @@ -48,7 +48,7 @@ public class InfluxConnector { private static final Logger LOG = LoggerFactory.getLogger(InfluxConnector.class); private static final int CONNECT_TIMEOUT = 10; // [s] - private static final int READ_TIMEOUT = 10; // [s] + private static final int READ_TIMEOUT = 60; // [s] private static final int WRITE_TIMEOUT = 10; // [s] private static final int EXECUTOR_MIN_THREADS = 1; @@ -208,11 +208,13 @@ public QueryResult executeQuery(String query) throws OpenemsException { queryResult = influxDB.query(new Query(query, this.database), TimeUnit.MILLISECONDS); } catch (RuntimeException e) { this.queryLimit.increase(); - throw new OpenemsException("InfluxDB query runtime error. Query: " + query + ", Error: " + e.getMessage()); + this.log.error("InfluxDB query runtime error. Query: " + query + ", Error: " + e.getMessage()); + throw new OpenemsException(e.getMessage()); } if (queryResult.hasError()) { this.queryLimit.increase(); - throw new OpenemsException("InfluxDB query error. Query: " + query + ", Error: " + queryResult.getError()); + this.log.error("InfluxDB query error. Query: " + query + ", Error: " + queryResult.getError()); + throw new OpenemsException(queryResult.getError()); } this.queryLimit.decrease(); return queryResult; diff --git a/ui/src/app/edge/history/timeofusetariffdischarge/chart.component.ts b/ui/src/app/edge/history/timeofusetariffdischarge/chart.component.ts index 12d1cd7a5b6..1dd34ba75bc 100644 --- a/ui/src/app/edge/history/timeofusetariffdischarge/chart.component.ts +++ b/ui/src/app/edge/history/timeofusetariffdischarge/chart.component.ts @@ -72,7 +72,10 @@ export class TimeOfUseTariffDischargeChartComponent extends AbstractHistoryChart let quarterlyPricesDelayedDischargeData = []; // let predictedSocWithoutLogicData = []; - for (let i = 0; i < 96; i++) { + //Size of the data + let size = result.data[TimeOfUseTariffState].length; + + for (let i = 0; i < size; i++) { let quarterlyPrice = this.formatPrice(result.data[quarterlyPrices][i]); let state = result.data[TimeOfUseTariffState][i]; diff --git a/ui/src/app/index/index.component.html b/ui/src/app/index/index.component.html index fe40395a2d2..63a5ac02d4a 100644 --- a/ui/src/app/index/index.component.html +++ b/ui/src/app/index/index.component.html @@ -19,7 +19,7 @@ -
+
@@ -44,7 +44,7 @@ - + E-Mail / Login.passwordLabel { - return format(new Date(value * 1000), 'HH:mm:ss') + return new Date(value * 1000).toLocaleTimeString() } public static CONVERT_TO_PERCENT = (value: any): string => {