getAllEdges();
+ /**
+ * Assigns Edge with given setupPassword to the logged in user and returns it.
+ *
+ *
+ * If the setupPassword is invalid, an OpenemsNamedException is thrown.
+ *
+ * @param user the {@link User}
+ * @param setupPassword the Setup-Password
+ * @return the Edge for the given Setup-Password
+ * @throws OpenemsNamedException on error
+ */
+ public default Edge addEdgeToUser(User user, String setupPassword) throws OpenemsNamedException {
+ Optional optEdge = this.getEdgeBySetupPassword(setupPassword);
+ if (!optEdge.isPresent()) {
+ throw OpenemsError.COMMON_AUTHENTICATION_FAILED.exception();
+ }
+
+ Edge edge = optEdge.get();
+ this.addEdgeToUser(user, edge);
+
+ return edge;
+ }
+
+ /**
+ * Assigns Edge to current user.
+ *
+ *
+ * If assignment fails, an OpenemsNamedException is thrown.
+ *
+ * @param user The {@link User}
+ * @param edge The {@link Edge}
+ *
+ * @throws OpenemsNamedException on error
+ */
+ public void addEdgeToUser(User user, Edge edge) throws OpenemsNamedException;
+
/**
* Helper method for creating a String of all active State-Channels by Level.
*
@@ -178,4 +224,50 @@ public static String activeStateChannelsToString(
return result.toString();
}
+ /**
+ * Gets information about the given user {@link User}.
+ *
+ * @param user {@link User} to read information
+ * @return {@link Map} about the user
+ * @throws OpenemsNamedException on error
+ */
+ public Map getUserInformation(User user) throws OpenemsNamedException;
+
+ /**
+ * Update the given user {@link User} with new information {@link JsonObject}.
+ *
+ * @param user {@link User} to update
+ * @param jsonObject {@link JsonObject} information about the user
+ * @throws OpenemsNamedException on error
+ */
+ public void setUserInformation(User user, JsonObject jsonObject) throws OpenemsNamedException;
+
+ /**
+ * Returns the Setup Protocol PDF for the given id.
+ *
+ * @param user {@link User} the current user
+ * @param setupProtocolId the setup protocol id to search
+ * @return the Setup Protocol PDF as a byte array
+ * @throws OpenemsNamedException on error
+ */
+ public byte[] getSetupProtocol(User user, int setupProtocolId) throws OpenemsNamedException;
+
+ /**
+ * Submit the installation assistant protocol.
+ *
+ * @param user {@link User} the current user
+ * @param jsonObject {@link JsonObject} the setup protocol
+ * @return id of created setup protocol
+ * @throws OpenemsNamedException on error
+ */
+ public int submitSetupProtocol(User user, JsonObject jsonObject) throws OpenemsNamedException;
+
+ /**
+ * Register a user.
+ *
+ * @param jsonObject {@link JsonObject} that represents an user
+ * @throws OpenemsNamedException on error
+ */
+ public void registerUser(JsonObject jsonObject) throws OpenemsNamedException;
+
}
diff --git a/io.openems.backend.metadata.dummy/src/io/openems/backend/metadata/dummy/DummyMetadata.java b/io.openems.backend.metadata.dummy/src/io/openems/backend/metadata/dummy/DummyMetadata.java
index 86cb7e94060..8b9be0bfc93 100644
--- a/io.openems.backend.metadata.dummy/src/io/openems/backend/metadata/dummy/DummyMetadata.java
+++ b/io.openems.backend.metadata.dummy/src/io/openems/backend/metadata/dummy/DummyMetadata.java
@@ -19,12 +19,15 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.gson.JsonObject;
+
import io.openems.backend.common.metadata.AbstractMetadata;
import io.openems.backend.common.metadata.Edge;
import io.openems.backend.common.metadata.Edge.State;
import io.openems.backend.common.metadata.Metadata;
import io.openems.backend.common.metadata.User;
import io.openems.common.channel.Level;
+import io.openems.common.exceptions.NotImplementedException;
import io.openems.common.exceptions.OpenemsError;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.session.Role;
@@ -104,15 +107,18 @@ public Optional getEdgeIdForApikey(String apikey) {
Optional idOpt = DummyMetadata.parseNumberFromName(apikey);
int id;
String edgeId;
+ String setupPassword;
if (idOpt.isPresent()) {
edgeId = apikey;
id = idOpt.get();
+ setupPassword = edgeId;
} else {
// create new ID
id = this.nextEdgeId.incrementAndGet();
edgeId = "edge" + id;
+ setupPassword = edgeId;
}
- MyEdge edge = new MyEdge(edgeId, apikey, "OpenEMS Edge #" + id, State.ACTIVE, "", "", Level.OK,
+ MyEdge edge = new MyEdge(edgeId, apikey, setupPassword, "OpenEMS Edge #" + id, State.ACTIVE, "", "", Level.OK,
new EdgeConfig());
edge.onSetConfig(config -> {
this.logInfo(this.log, "Edge [" + edgeId + "]. Update config: "
@@ -123,6 +129,19 @@ public Optional getEdgeIdForApikey(String apikey) {
}
+ @Override
+ public Optional getEdgeBySetupPassword(String setupPassword) {
+ Optional optEdge = this.edges.values().stream()
+ .filter(edge -> edge.getSetupPassword().equals(setupPassword)).findFirst();
+
+ if (optEdge.isPresent()) {
+ MyEdge edge = optEdge.get();
+ return Optional.of(edge);
+ }
+
+ return Optional.empty();
+ }
+
@Override
public Optional getEdge(String edgeId) {
Edge edge = this.edges.get(edgeId);
@@ -151,4 +170,35 @@ private static Optional parseNumberFromName(String name) {
}
return Optional.empty();
}
+
+ @Override
+ public void addEdgeToUser(User user, Edge edge) throws OpenemsNamedException {
+ throw new NotImplementedException("DummyMetadata.addEdgeToUser()");
+ }
+
+ @Override
+ public Map getUserInformation(User user) throws OpenemsNamedException {
+ throw new NotImplementedException("DummyMetadata.getUserInformation()");
+ }
+
+ @Override
+ public void setUserInformation(User user, JsonObject jsonObject) throws OpenemsNamedException {
+ throw new NotImplementedException("DummyMetadata.setUserInformation()");
+ }
+
+ @Override
+ public byte[] getSetupProtocol(User user, int setupProtocolId) throws OpenemsNamedException {
+ throw new IllegalArgumentException("DummyMetadata.getSetupProtocol() is not implemented");
+ }
+
+ @Override
+ public int submitSetupProtocol(User user, JsonObject jsonObject) {
+ throw new IllegalArgumentException("DummyMetadata.submitSetupProtocol() is not implemented");
+ }
+
+ @Override
+ public void registerUser(JsonObject jsonObject) throws OpenemsNamedException {
+ throw new IllegalArgumentException("DummyMetadata.registerUser() is not implemented");
+ }
+
}
diff --git a/io.openems.backend.metadata.dummy/src/io/openems/backend/metadata/dummy/MyEdge.java b/io.openems.backend.metadata.dummy/src/io/openems/backend/metadata/dummy/MyEdge.java
index bbf310e2e04..247da599887 100644
--- a/io.openems.backend.metadata.dummy/src/io/openems/backend/metadata/dummy/MyEdge.java
+++ b/io.openems.backend.metadata.dummy/src/io/openems/backend/metadata/dummy/MyEdge.java
@@ -7,15 +7,21 @@
public class MyEdge extends Edge {
private final String apikey;
+ private final String setupPassword;
- public MyEdge(String id, String apikey, String comment, State state, String version, String producttype,
- Level sumState, EdgeConfig config) {
+ public MyEdge(String id, String apikey, String setupPassword, String comment, State state, String version,
+ String producttype, Level sumState, EdgeConfig config) {
super(id, comment, state, version, producttype, sumState, config);
this.apikey = apikey;
+ this.setupPassword = setupPassword;
}
public String getApikey() {
- return apikey;
+ return this.apikey;
+ }
+
+ public String getSetupPassword() {
+ return this.setupPassword;
}
}
diff --git a/io.openems.backend.metadata.file/src/io/openems/backend/metadata/file/FileMetadata.java b/io.openems.backend.metadata.file/src/io/openems/backend/metadata/file/FileMetadata.java
index 9792c323be8..900cf2aecc0 100644
--- a/io.openems.backend.metadata.file/src/io/openems/backend/metadata/file/FileMetadata.java
+++ b/io.openems.backend.metadata.file/src/io/openems/backend/metadata/file/FileMetadata.java
@@ -32,6 +32,7 @@
import io.openems.backend.common.metadata.Metadata;
import io.openems.backend.common.metadata.User;
import io.openems.common.channel.Level;
+import io.openems.common.exceptions.NotImplementedException;
import io.openems.common.exceptions.OpenemsError;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.session.Role;
@@ -125,6 +126,17 @@ public synchronized Optional getEdgeIdForApikey(String apikey) {
return Optional.empty();
}
+ @Override
+ public synchronized Optional getEdgeBySetupPassword(String setupPassword) {
+ this.refreshData();
+ for (MyEdge edge : this.edges.values()) {
+ if (edge.getSetupPassword().equals(setupPassword)) {
+ return Optional.of(edge);
+ }
+ }
+ return Optional.empty();
+ }
+
@Override
public synchronized Optional getEdge(String edgeId) {
this.refreshData();
@@ -169,6 +181,7 @@ private synchronized void refreshData() {
edges.add(new MyEdge(//
entry.getKey(), // Edge-ID
JsonUtils.getAsString(edge, "apikey"), //
+ JsonUtils.getAsString(edge, "setuppassword"), //
JsonUtils.getAsString(edge, "comment"), //
State.ACTIVE, // State
"", // Version
@@ -197,4 +210,34 @@ private static User generateUser() {
FileMetadata.USER_GLOBAL_ROLE, new TreeMap<>());
}
+ @Override
+ public void addEdgeToUser(User user, Edge edge) throws OpenemsNamedException {
+ throw new NotImplementedException("FileMetadata.addEdgeToUser()");
+ }
+
+ @Override
+ public Map getUserInformation(User user) throws OpenemsNamedException {
+ throw new NotImplementedException("FileMetadata.getUserInformation()");
+ }
+
+ @Override
+ public void setUserInformation(User user, JsonObject jsonObject) throws OpenemsNamedException {
+ throw new NotImplementedException("FileMetadata.setUserInformation()");
+ }
+
+ @Override
+ public byte[] getSetupProtocol(User user, int setupProtocolId) throws OpenemsNamedException {
+ throw new IllegalArgumentException("FileMetadata.getSetupProtocol() is not implemented");
+ }
+
+ @Override
+ public int submitSetupProtocol(User user, JsonObject jsonObject) {
+ throw new IllegalArgumentException("FileMetadata.submitSetupProtocol() is not implemented");
+ }
+
+ @Override
+ public void registerUser(JsonObject jsonObject) throws OpenemsNamedException {
+ throw new IllegalArgumentException("FileMetadata.registerUser() is not implemented");
+ }
+
}
diff --git a/io.openems.backend.metadata.file/src/io/openems/backend/metadata/file/MyEdge.java b/io.openems.backend.metadata.file/src/io/openems/backend/metadata/file/MyEdge.java
index eb94aad6dfe..ba950b32dd7 100644
--- a/io.openems.backend.metadata.file/src/io/openems/backend/metadata/file/MyEdge.java
+++ b/io.openems.backend.metadata.file/src/io/openems/backend/metadata/file/MyEdge.java
@@ -7,15 +7,21 @@
public class MyEdge extends Edge {
private final String apikey;
+ private final String setupPassword;
- public MyEdge(String id, String apikey, String comment, State state, String version, String producttype,
- Level sumState, EdgeConfig config) {
+ public MyEdge(String id, String apikey, String setupPassword, String comment, State state, String version,
+ String producttype, Level sumState, EdgeConfig config) {
super(id, comment, state, version, producttype, sumState, config);
this.apikey = apikey;
+ this.setupPassword = setupPassword;
}
public String getApikey() {
- return apikey;
+ return this.apikey;
+ }
+
+ public String getSetupPassword() {
+ return this.setupPassword;
}
}
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 f6eea9289f1..2f940210394 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
@@ -31,6 +31,7 @@ public static String getSqlQueryFields(Field[] fields) {
public enum EdgeDevice implements Field {
ID("id", true), //
APIKEY("apikey", true), //
+ SETUP_PASSWORD("setup_password", true), //
NAME("name", true), //
COMMENT("comment", true), //
STATE("state", true), //
@@ -78,6 +79,7 @@ public int index() {
public boolean isQuery() {
return this.query;
}
+
}
/**
@@ -129,6 +131,7 @@ public int index() {
public boolean isQuery() {
return this.query;
}
+
}
/**
@@ -175,5 +178,399 @@ public int index() {
public boolean isQuery() {
return this.query;
}
+
+ }
+
+ /**
+ * The EdgeDeviceUserRole-Model.
+ */
+ public enum EdgeDeviceUserRole implements Field {
+ DEVICE_ID("device_id", false), //
+ USER_ID("user_id", false), //
+ ROLE("role", false);
+
+ public static final String ODOO_MODEL = "edge.device_user_role";
+ public static final String ODOO_TABLE = ODOO_MODEL.replace(".", "_");
+
+ private static final class StaticFields {
+ private static int nextQueryIndex = 1;
+ }
+
+ private final int queryIndex;
+ private final String id;
+
+ /**
+ * Holds information if this Field should be queried from and written to
+ * Database.
+ */
+ private final boolean query;
+
+ private EdgeDeviceUserRole(String id, boolean query) {
+ this.id = id;
+ this.query = query;
+ if (query) {
+ this.queryIndex = StaticFields.nextQueryIndex++;
+ } else {
+ this.queryIndex = -1;
+ }
+ }
+
+ @Override
+ public String id() {
+ return this.id;
+ }
+
+ @Override
+ public int index() {
+ return this.queryIndex;
+ }
+
+ @Override
+ public boolean isQuery() {
+ return this.query;
+ }
+
+ }
+
+ public enum User implements Field {
+ LOGIN("login", true), //
+ PASSWORD("password", true), //
+ PARTNER("partner_id", true), //
+ GLOBAL_ROLE("global_role", true), //
+ GROUPS("groups_id", true);
+
+ public static final String ODOO_MODEL = "res.users";
+ public static final String ODOO_TABLE = ODOO_MODEL.replace(".", "_");
+
+ private static final class StaticFields {
+ private static int nextQueryIndex = 1;
+ }
+
+ private final int queryIndex;
+ private final String id;
+
+ /**
+ * Holds information if this Field should be queried from and written to
+ * Database.
+ */
+ private final boolean query;
+
+ private User(String id, boolean query) {
+ this.id = id;
+ this.query = query;
+ if (query) {
+ this.queryIndex = StaticFields.nextQueryIndex++;
+ } else {
+ this.queryIndex = -1;
+ }
+ }
+
+ @Override
+ public String id() {
+ return this.id;
+ }
+
+ @Override
+ public int index() {
+ return this.queryIndex;
+ }
+
+ @Override
+ public boolean isQuery() {
+ return this.query;
+ }
+
}
+
+ public enum Partner implements Field {
+ FIRSTNAME("firstname", true), //
+ LASTNAME("lastname", true), //
+ EMAIL("email", true), //
+ PHONE("phone", true), //
+ COMPANY_NAME("commercial_company_name", true), //
+ NAME("name", true), //
+ IS_COMPANY("is_company", true), //
+ PARENT("parent_id", true), //
+ STREET("street", true), //
+ ZIP("zip", true), //
+ CITY("city", true), //
+ COUNTRY("country_id", true), //
+ ADDRESS_TYPE("type", true), //
+ LANGUAGE("lang", true);
+
+ public static final String ODOO_MODEL = "res.partner";
+ public static final String ODOO_TABLE = ODOO_MODEL.replace(".", "_");
+
+ private static final class StaticFields {
+ private static int nextQueryIndex = 1;
+ }
+
+ private final int queryIndex;
+ private final String id;
+
+ /**
+ * Holds information if this Field should be queried from and written to
+ * Database.
+ */
+ private final boolean query;
+
+ private Partner(String id, boolean query) {
+ this.id = id;
+ this.query = query;
+ if (query) {
+ this.queryIndex = StaticFields.nextQueryIndex++;
+ } else {
+ this.queryIndex = -1;
+ }
+ }
+
+ @Override
+ public String id() {
+ return this.id;
+ }
+
+ @Override
+ public int index() {
+ return this.queryIndex;
+ }
+
+ @Override
+ public boolean isQuery() {
+ return this.query;
+ }
+
+ }
+
+ public enum Country implements Field {
+ NAME("name", true), //
+ CODE("code", true);
+
+ public static final String ODOO_MODEL = "res.country";
+ public static final String ODOO_TABLE = ODOO_MODEL.replace(".", "_");
+
+ private static final class StaticFields {
+ private static int nextQueryIndex = 1;
+ }
+
+ private final int queryIndex;
+ private final String id;
+
+ /**
+ * Holds information if this Field should be queried from and written to
+ * Database.
+ */
+ private final boolean query;
+
+ private Country(String id, boolean query) {
+ this.id = id;
+ this.query = query;
+ if (query) {
+ this.queryIndex = StaticFields.nextQueryIndex++;
+ } else {
+ this.queryIndex = -1;
+ }
+ }
+
+ @Override
+ public String id() {
+ return this.id;
+ }
+
+ @Override
+ public int index() {
+ return this.queryIndex;
+ }
+
+ @Override
+ public boolean isQuery() {
+ return this.query;
+ }
+ }
+
+ public enum SetupProtocol implements Field {
+ CUSTOMER("customer_id", true), //
+ DIFFERENT_LOCATION("different_location_id", true), //
+ INSTALLER("installer_id", true), //
+ EDGE("edge_device_id", true);
+
+ public static final String ODOO_MODEL = "edge.setup_protocol";
+ public static final String ODOO_TABLE = ODOO_MODEL.replace(".", "_");
+
+ private static final class StaticFields {
+ private static int nextQueryIndex = 1;
+ }
+
+ private final int queryIndex;
+ private final String id;
+
+ /**
+ * Holds information if this Field should be queried from and written to
+ * Database.
+ */
+ private final boolean query;
+
+ private SetupProtocol(String id, boolean query) {
+ this.id = id;
+ this.query = query;
+ if (query) {
+ this.queryIndex = StaticFields.nextQueryIndex++;
+ } else {
+ this.queryIndex = -1;
+ }
+ }
+
+ @Override
+ public String id() {
+ return this.id;
+ }
+
+ @Override
+ public int index() {
+ return this.queryIndex;
+ }
+
+ @Override
+ public boolean isQuery() {
+ return this.query;
+ }
+ }
+
+ public enum SetupProtocolProductionLot implements Field {
+ SETUP_PROTOCOL("setup_protocol_id", true), //
+ SEQUENCE("sequence", true), //
+ LOT("lot_id", true);
+
+ public static final String ODOO_MODEL = "edge.setup_protocol_production_lot";
+ public static final String ODOO_TABLE = ODOO_MODEL.replace(".", "_");
+
+ private static final class StaticFields {
+ private static int nextQueryIndex = 1;
+ }
+
+ private final int queryIndex;
+ private final String id;
+
+ /**
+ * Holds information if this Field should be queried from and written to
+ * Database.
+ */
+ private final boolean query;
+
+ private SetupProtocolProductionLot(String id, boolean query) {
+ this.id = id;
+ this.query = query;
+ if (query) {
+ this.queryIndex = StaticFields.nextQueryIndex++;
+ } else {
+ this.queryIndex = -1;
+ }
+ }
+
+ @Override
+ public String id() {
+ return this.id;
+ }
+
+ @Override
+ public int index() {
+ return this.queryIndex;
+ }
+
+ @Override
+ public boolean isQuery() {
+ return this.query;
+ }
+ }
+
+ public enum SetupProtocolItem implements Field {
+ SETUP_PROTOCOL("setup_protocol_id", true), //
+ SEQUENCE("sequence", true);
+
+ public static final String ODOO_MODEL = "edge.setup_protocol_item";
+ public static final String ODOO_TABLE = ODOO_MODEL.replace(".", "_");
+
+ private static final class StaticFields {
+ private static int nextQueryIndex = 1;
+ }
+
+ private final int queryIndex;
+ private final String id;
+
+ /**
+ * Holds information if this Field should be queried from and written to
+ * Database.
+ */
+ private final boolean query;
+
+ private SetupProtocolItem(String id, boolean query) {
+ this.id = id;
+ this.query = query;
+ if (query) {
+ this.queryIndex = StaticFields.nextQueryIndex++;
+ } else {
+ this.queryIndex = -1;
+ }
+ }
+
+ @Override
+ public String id() {
+ return this.id;
+ }
+
+ @Override
+ public int index() {
+ return this.queryIndex;
+ }
+
+ @Override
+ public boolean isQuery() {
+ return this.query;
+ }
+ }
+
+ public enum StockProductionLot implements Field {
+ SERIAL_NUMBER("name", true), //
+ PRODUCT("product_id", true);
+ public static final String ODOO_MODEL = "stock.production.lot";
+ public static final String ODOO_TABLE = ODOO_MODEL.replace(".", "_");
+
+ private static final class StaticFields {
+ private static int nextQueryIndex = 1;
+ }
+
+ private final int queryIndex;
+ private final String id;
+
+ /**
+ * Holds information if this Field should be queried from and written to
+ * Database.
+ */
+ private final boolean query;
+
+ private StockProductionLot(String id, boolean query) {
+ this.id = id;
+ this.query = query;
+ if (query) {
+ this.queryIndex = StaticFields.nextQueryIndex++;
+ } else {
+ this.queryIndex = -1;
+ }
+ }
+
+ @Override
+ public String id() {
+ return this.id;
+ }
+
+ @Override
+ public int index() {
+ return this.queryIndex;
+ }
+
+ @Override
+ public boolean isQuery() {
+ return this.query;
+ }
+ }
+
}
diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/OdooMetadata.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/OdooMetadata.java
index 0d4f2c2415d..e577befe02d 100644
--- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/OdooMetadata.java
+++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/OdooMetadata.java
@@ -2,6 +2,7 @@
import java.sql.SQLException;
import java.util.Collection;
+import java.util.Map;
import java.util.NavigableMap;
import java.util.Optional;
import java.util.TreeMap;
@@ -24,6 +25,7 @@
import io.openems.backend.common.metadata.Metadata;
import io.openems.backend.common.metadata.User;
import io.openems.backend.metadata.odoo.odoo.OdooHandler;
+import io.openems.backend.metadata.odoo.odoo.OdooUserRole;
import io.openems.backend.metadata.odoo.postgres.PostgresHandler;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.exceptions.OpenemsException;
@@ -129,6 +131,15 @@ public Optional getEdgeIdForApikey(String apikey) {
}
}
+ @Override
+ public Optional getEdgeBySetupPassword(String setupPassword) {
+ Optional optEdgeId = this.odooHandler.getEdgeIdBySetupPassword(setupPassword);
+ if (!optEdgeId.isPresent()) {
+ return Optional.empty();
+ }
+ return this.getEdge(optEdgeId.get());
+ }
+
@Override
public Optional getEdge(String edgeId) {
return Optional.ofNullable(this.edgeCache.getEdgeFromEdgeId(edgeId));
@@ -176,4 +187,42 @@ public void logWarn(Logger log, String message) {
public void logError(Logger log, String message) {
super.logError(log, message);
}
+
+ @Override
+ public void addEdgeToUser(User user, Edge edge) throws OpenemsNamedException {
+ this.odooHandler.assignEdgeToUser((MyUser) user, (MyEdge) edge, OdooUserRole.INSTALLER);
+ }
+
+ @Override
+ public Map getUserInformation(User user) throws OpenemsNamedException {
+ return this.odooHandler.getUserInformation((MyUser) user);
+ }
+
+ @Override
+ public void setUserInformation(User user, JsonObject jsonObject) throws OpenemsNamedException {
+ this.odooHandler.setUserInformation((MyUser) user, jsonObject);
+ }
+
+ @Override
+ public byte[] getSetupProtocol(User user, int setupProtocolId) throws OpenemsNamedException {
+ return this.odooHandler.getOdooSetupProtocolReport(setupProtocolId);
+ }
+
+ @Override
+ public int submitSetupProtocol(User user, JsonObject jsonObject) throws OpenemsNamedException {
+ return this.odooHandler.submitSetupProtocol((MyUser) user, jsonObject);
+ }
+
+ @Override
+ public void registerUser(JsonObject jsonObject) throws OpenemsNamedException {
+ OdooUserRole role = OdooUserRole.OWNER;
+
+ Optional optRole = JsonUtils.getAsOptionalString(jsonObject, "role");
+ if (optRole.isPresent()) {
+ role = OdooUserRole.getRole(optRole.get());
+ }
+
+ this.odooHandler.registerUser(jsonObject, role);
+ }
+
}
diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/Domain.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/Domain.java
index 6b5eea5bddb..1a84447b662 100644
--- a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/Domain.java
+++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/Domain.java
@@ -1,5 +1,7 @@
package io.openems.backend.metadata.odoo.odoo;
+import io.openems.backend.metadata.odoo.Field;
+
public class Domain {
protected final String field;
protected final String operator;
@@ -10,4 +12,8 @@ public Domain(String field, String operator, Object value) {
this.operator = operator;
this.value = value;
}
+
+ public Domain(Field field, String operator, Object value) {
+ this(field.id(), operator, value);
+ }
}
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 40701861fb9..088b9809e63 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
@@ -1,5 +1,8 @@
package io.openems.backend.metadata.odoo.odoo;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -10,17 +13,23 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.openems.backend.metadata.odoo.Config;
import io.openems.backend.metadata.odoo.Field;
+import io.openems.backend.metadata.odoo.Field.Partner;
+import io.openems.backend.metadata.odoo.Field.SetupProtocol;
+import io.openems.backend.metadata.odoo.Field.SetupProtocolItem;
import io.openems.backend.metadata.odoo.MyEdge;
+import io.openems.backend.metadata.odoo.MyUser;
import io.openems.backend.metadata.odoo.OdooMetadata;
-import io.openems.backend.metadata.odoo.odoo.OdooUtils.SuccessResponseAndHeaders;
-import io.openems.common.exceptions.OpenemsError;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.exceptions.OpenemsException;
import io.openems.common.utils.JsonUtils;
+import io.openems.common.utils.ObjectUtils;
+import io.openems.common.utils.PasswordUtils;
public class OdooHandler {
@@ -68,6 +77,75 @@ public void addChatterMessage(MyEdge edge, String message) {
}
}
+ /**
+ * Returns Edge by setupPassword, otherwise an empty {@link Optional}.
+ *
+ * @param setupPassword to find Edge
+ * @return Edge or empty {@link Optional}
+ */
+ public Optional getEdgeIdBySetupPassword(String setupPassword) {
+ Domain filter = new Domain(Field.EdgeDevice.SETUP_PASSWORD, "=", setupPassword);
+
+ try {
+ int[] search = OdooUtils.search(this.credentials, Field.EdgeDevice.ODOO_MODEL, filter);
+ if (search.length == 0) {
+ return Optional.empty();
+ }
+
+ Map read = OdooUtils.readOne(this.credentials, Field.EdgeDevice.ODOO_MODEL, search[0],
+ Field.EdgeDevice.NAME);
+
+ String name = (String) read.get(Field.EdgeDevice.NAME.id());
+ if (name == null) {
+ return Optional.empty();
+ }
+
+ return Optional.of(name);
+ } catch (OpenemsException e) {
+ this.parent.logInfo(this.log, "Unable to find edge by setup passowrd [" + setupPassword + "]");
+ }
+
+ return Optional.empty();
+ }
+
+ /**
+ * Assigns the given user with given {@link OdooUserRole} to the Edge. If Edge
+ * already assigned to user exit method.
+ *
+ * @param user the Odoo user
+ * @param edge the Odoo edge
+ * @param userRole the Odoo user role
+ * @throws OpenemsNamedException on error
+ */
+ public void assignEdgeToUser(MyUser user, MyEdge edge, OdooUserRole userRole) throws OpenemsNamedException {
+ this.assignEdgeToUser(user.getOdooId(), edge.getOdooId(), userRole);
+ this.parent.authenticate(user.getToken());
+ }
+
+ /**
+ * Assigns the given user with given {@link OdooUserRole} to the Edge. If Edge
+ * already assigned to user exit method.
+ *
+ * @param userId the Odoo user id
+ * @param edgeId the Odoo edge
+ * @param userRole the Odoo user role
+ * @throws OpenemsException on error
+ */
+ private void assignEdgeToUser(int userId, int edgeId, OdooUserRole userRole) throws OpenemsException {
+ int[] found = OdooUtils.search(this.credentials, Field.EdgeDeviceUserRole.ODOO_MODEL,
+ new Domain(Field.EdgeDeviceUserRole.USER_ID, "=", userId),
+ new Domain(Field.EdgeDeviceUserRole.DEVICE_ID, "=", edgeId));
+
+ if (found.length > 0) {
+ return;
+ }
+
+ OdooUtils.create(this.credentials, Field.EdgeDeviceUserRole.ODOO_MODEL, //
+ new FieldValue(Field.EdgeDeviceUserRole.USER_ID, userId), //
+ new FieldValue(Field.EdgeDeviceUserRole.DEVICE_ID, edgeId), //
+ new FieldValue(Field.EdgeDeviceUserRole.ROLE, userRole.getOdooRole()));
+ }
+
/**
* Authenticates a user using Username and Password.
*
@@ -77,23 +155,7 @@ public void addChatterMessage(MyEdge edge, String message) {
* @throws OpenemsNamedException on login error
*/
public String authenticate(String username, String password) throws OpenemsNamedException {
- JsonObject request = JsonUtils.buildJsonObject() //
- .addProperty("jsonrpc", "2.0") //
- .addProperty("method", "call") //
- .add("params", JsonUtils.buildJsonObject() //
- .addProperty("db", "v12") //
- .addProperty("login", username) //
- .addProperty("password", password) //
- .build()) //
- .build();
- SuccessResponseAndHeaders response = OdooUtils
- .sendJsonrpcRequest(this.credentials.getUrl() + "/web/session/authenticate", request);
- Optional sessionId = getFieldFromSetCookieHeader(response.headers, "session_id");
- if (!sessionId.isPresent()) {
- throw OpenemsError.COMMON_AUTHENTICATION_FAILED.exception();
- } else {
- return sessionId.get();
- }
+ return OdooUtils.login(this.credentials, username, password);
}
/**
@@ -154,4 +216,527 @@ public static Optional getFieldFromSetCookieHeader(Map getUserInformation(MyUser user) throws OpenemsNamedException {
+ int partnerId = this.getOdooPartnerId(user);
+
+ Map odooPartner = OdooUtils.readOne(this.credentials, Field.Partner.ODOO_MODEL, partnerId,
+ Field.Partner.FIRSTNAME, //
+ Field.Partner.LASTNAME, //
+ Field.Partner.EMAIL, //
+ Field.Partner.PHONE, //
+ Field.Partner.STREET, //
+ Field.Partner.ZIP, //
+ Field.Partner.CITY, //
+ Field.Partner.COUNTRY, //
+ Field.Partner.COMPANY_NAME);
+
+ Object[] odooCountryId = ObjectUtils.getAsObjectArrray(odooPartner.get("country_id"));
+ if (odooCountryId.length > 1) {
+ Map countryCode = OdooUtils.readOne(this.credentials, Field.Country.ODOO_MODEL,
+ (Integer) odooCountryId[0], Field.Country.CODE);
+
+ Optional optCode = ObjectUtils.getAsOptionalString(countryCode.get("code"));
+ if (optCode.isPresent()) {
+ Object[] countryElement = Arrays.copyOf(odooCountryId, odooCountryId.length + 1);
+ countryElement[2] = countryCode.get("code");
+
+ odooPartner.put("country_id", countryElement);
+ }
+ }
+
+ return odooPartner;
+ }
+
+ /**
+ * Update the given {@link MyUser} with information from {@link JsonObject}.
+ *
+ * @param user the {@link MyUser} to update
+ * @param userJson the {@link JsonObject} information to update
+ * @throws OpenemsException on error
+ */
+ public void setUserInformation(MyUser user, JsonObject userJson) throws OpenemsNamedException {
+ Map fieldValues = new HashMap<>();
+ fieldValues.putAll(this.updateAddress(userJson));
+ fieldValues.putAll(this.updateCompany(user, userJson));
+
+ JsonUtils.getAsOptionalString(userJson, "firstname") //
+ .ifPresent(firstname -> fieldValues.put(Field.Partner.FIRSTNAME.id(), firstname));
+ JsonUtils.getAsOptionalString(userJson, "lastname") //
+ .ifPresent(lastname -> fieldValues.put(Field.Partner.LASTNAME.id(), lastname));
+ JsonUtils.getAsOptionalString(userJson, "email") //
+ .ifPresent(email -> fieldValues.put(Field.Partner.EMAIL.id(), email));
+ JsonUtils.getAsOptionalString(userJson, "phone") //
+ .ifPresent(phone -> fieldValues.put(Field.Partner.PHONE.id(), phone));
+
+ int odooPartnerId = this.getOdooPartnerId(user.getOdooId());
+ OdooUtils.write(this.credentials, Field.Partner.ODOO_MODEL, new Integer[] { odooPartnerId }, fieldValues);
+ }
+
+ /**
+ * Get address to update for an Odoo user.
+ *
+ * @param addressJson {@link JsonObject} to get the fields to update
+ * @return Fields to update
+ * @throws OpenemsException on error
+ */
+ private Map updateAddress(JsonObject addressJson) throws OpenemsException {
+ Optional optAddress = JsonUtils.getAsOptionalJsonObject(addressJson, "address");
+ if (!optAddress.isPresent()) {
+ return new HashMap<>();
+ }
+ JsonObject address = optAddress.get();
+
+ Map addressFields = new HashMap<>();
+ addressFields.put("type", "private");
+ JsonUtils.getAsOptionalString(address, "street") //
+ .ifPresent(street -> addressFields.put(Field.Partner.STREET.id(), street));
+ JsonUtils.getAsOptionalString(address, "zip") //
+ .ifPresent(zip -> addressFields.put(Field.Partner.ZIP.id(), zip));
+ JsonUtils.getAsOptionalString(address, "city") //
+ .ifPresent(city -> addressFields.put(Field.Partner.CITY.id(), city));
+
+ Optional optCountryCode = JsonUtils.getAsOptionalString(address, "country");
+ if (optCountryCode.isPresent()) {
+ String countryCode = optCountryCode.get().toUpperCase();
+
+ int[] countryFound = OdooUtils.search(this.credentials, Field.Country.ODOO_MODEL, //
+ new Domain(Field.Country.CODE, "=", countryCode));
+ if (countryFound.length == 1) {
+ addressFields.put(Field.Partner.COUNTRY.id(), countryFound[0]);
+ } else {
+ this.log.info("Country with code [" + countryCode + "] not found");
+ }
+ }
+
+ return addressFields;
+ }
+
+ /**
+ * Get company to update for an Odoo user. Checks if the given company exits in
+ * Odoo and assign the company to the Odoo user. Otherwise a new company will be
+ * created in Odoo.
+ *
+ * @param companyJson {@link JsonObject} to get the fields to update
+ * @return Fields to update
+ * @throws OpenemsException on error
+ */
+ private Map updateCompany(JsonObject companyJson) throws OpenemsException {
+ return this.updateCompany(null, companyJson);
+ }
+
+ /**
+ * Get company to update for an Odoo user. If given user is not null, check the
+ * users company with the new company name for equality. Both are equal nothing
+ * to update. Otherwise the new company will be assigned to the user or the new
+ * company will be created in Odoo.
+ *
+ * @param user {@link MyUser} to check company name
+ * @param companyJson {@link JsonObject} to get the fields to update
+ * @return Fields to update
+ * @throws OpenemsException on error
+ */
+ private Map updateCompany(MyUser user, JsonObject companyJson) throws OpenemsException {
+ Optional optCompany = JsonUtils.getAsOptionalJsonObject(companyJson, "company");
+ if (!optCompany.isPresent()) {
+ return new HashMap<>();
+ }
+ Optional optCompanyName = JsonUtils.getAsOptionalString(optCompany.get(), "name");
+ if (!optCompanyName.isPresent()) {
+ return new HashMap<>();
+ }
+ String jsonCompanyName = optCompanyName.get();
+
+ if (user != null) {
+ Map odooPartner = OdooUtils.readOne(this.credentials, Field.Partner.ODOO_MODEL, //
+ this.getOdooPartnerId(user.getOdooId()), //
+ Field.Partner.COMPANY_NAME);
+
+ Optional optPartnerCompanyName = ObjectUtils
+ .getAsOptionalString(odooPartner.get(Field.Partner.COMPANY_NAME.id()));
+ if (optPartnerCompanyName.isPresent()) {
+ if (jsonCompanyName.equals(optPartnerCompanyName.get())) {
+ return new HashMap<>();
+ }
+ }
+ }
+
+ int[] companyFound = OdooUtils.search(this.credentials, Field.Partner.ODOO_MODEL, //
+ new Domain(Field.Partner.IS_COMPANY, "=", true),
+ new Domain(Field.Partner.COMPANY_NAME, "=", jsonCompanyName));
+
+ Map companyFields = new HashMap<>();
+ if (companyFound.length > 0) {
+ companyFields.put(Field.Partner.PARENT.id(), companyFound[0]);
+ } else {
+ int createdCompany = OdooUtils.create(this.credentials, Field.Partner.ODOO_MODEL, //
+ new FieldValue<>(Field.Partner.IS_COMPANY, true),
+ new FieldValue<>(Field.Partner.NAME, jsonCompanyName));
+ companyFields.put(Field.Partner.PARENT.id(), createdCompany);
+ }
+
+ return companyFields;
+ }
+
+ /**
+ * Returns the Odoo report for a setup protocol.
+ *
+ * @param setupProtocolId the Odoo setup protocol id
+ * @return report as a byte array
+ * @throws OpenemsNamedException on error
+ */
+ public byte[] getOdooSetupProtocolReport(int setupProtocolId) throws OpenemsNamedException {
+ return OdooUtils.getOdooReport(this.credentials, "edge.report_edge_setup_protocol_template", setupProtocolId);
+ }
+
+ /**
+ * Save the Setup Protocol to Odoo.
+ *
+ * @param user {@link MyUser} current user
+ * @param setupProtocolJson {@link SetupProtocol} the setup protocol
+ * @return the Setup Protocol ID
+ * @throws OpenemsNamedException on error
+ */
+ public int submitSetupProtocol(MyUser user, JsonObject setupProtocolJson) throws OpenemsNamedException {
+ JsonObject userJson = JsonUtils.getAsJsonObject(setupProtocolJson, "customer");
+ JsonObject edgeJson = JsonUtils.getAsJsonObject(setupProtocolJson, "edge");
+
+ String edgeId = JsonUtils.getAsString(edgeJson, "id");
+ int[] foundEdge = OdooUtils.search(this.credentials, Field.EdgeDevice.ODOO_MODEL,
+ new Domain(Field.EdgeDevice.NAME, "=", edgeId));
+ if (foundEdge.length != 1) {
+ throw new OpenemsException("Edge not found for id [" + edgeId + "]");
+ }
+
+ String password = PasswordUtils.generateRandomPassword(24);
+ int odooUserId = this.createOdooUser(userJson, password);
+
+ int customerId = this.getOdooPartnerId(odooUserId);
+ int installerId = this.getOdooPartnerId(user);
+ this.assignEdgeToUser(odooUserId, foundEdge[0], OdooUserRole.OWNER);
+
+ int protocolId = this.createSetupProtocol(setupProtocolJson, foundEdge[0], customerId, installerId);
+
+ try {
+ this.sendSetupProtocolMail(user, protocolId, edgeId);
+ } catch (OpenemsNamedException ex) {
+ this.log.warn("Unable to send email", ex);
+ }
+
+ return protocolId;
+ }
+
+ /**
+ * Call Odoo api to send mail via Odoo.
+ *
+ * @param user the Odoo user
+ * @param protocolId the Odoo setup protocol id
+ * @param edgeId the Odoo edge
+ * @throws OpenemsNamedException on error
+ */
+ private void sendSetupProtocolMail(MyUser user, int protocolId, String edgeId) throws OpenemsNamedException {
+ OdooUtils.sendAdminJsonrpcRequest(this.credentials, "/openems_backend/sendSetupProtocolEmail",
+ JsonUtils.buildJsonObject() //
+ .add("params", JsonUtils.buildJsonObject() //
+ .addProperty("setupProtocolId", protocolId) //
+ .addProperty("edgeId", edgeId) //
+ .build()) //
+ .build());
+ }
+
+ /**
+ * Create an Odoo user and return thats id. If user already exists the user will
+ * be updated and return the user id.
+ *
+ * @param userJson the {@link Partner} to create user
+ * @param password the password to set for the new user
+ * @return the Odoo user id
+ * @throws OpenemsNamedException on error
+ */
+ private int createOdooUser(JsonObject userJson, String password) throws OpenemsNamedException {
+ Map customerFields = new HashMap<>();
+ customerFields.putAll(this.updateAddress(userJson));
+ customerFields.putAll(this.updateCompany(userJson));
+
+ JsonUtils.getAsOptionalString(userJson, "firstname") //
+ .ifPresent(firstname -> customerFields.put(Field.Partner.FIRSTNAME.id(), firstname));
+ JsonUtils.getAsOptionalString(userJson, "lastname") //
+ .ifPresent(lastname -> customerFields.put(Field.Partner.LASTNAME.id(), lastname));
+
+ String email = JsonUtils.getAsString(userJson, "email");
+ JsonUtils.getAsOptionalString(userJson, "email") //
+ .ifPresent(mail -> customerFields.put(Field.Partner.EMAIL.id(), mail));
+ JsonUtils.getAsOptionalString(userJson, "phone") //
+ .ifPresent(phone -> customerFields.put(Field.Partner.PHONE.id(), phone));
+
+ int[] userFound = OdooUtils.search(this.credentials, Field.User.ODOO_MODEL,
+ new Domain(Field.User.LOGIN, "=", email));
+
+ if (userFound.length == 1) {
+ int userId = userFound[0];
+ OdooUtils.write(this.credentials, Field.User.ODOO_MODEL, new Integer[] { userId }, customerFields);
+ return userId;
+ } else {
+ 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());
+ int createdUserId = OdooUtils.create(this.credentials, Field.User.ODOO_MODEL, customerFields);
+
+ this.sendRegistrationMail(createdUserId, password);
+ return createdUserId;
+ }
+ }
+
+ /**
+ * Create a setup protocol in Odoo.
+ *
+ * @param jsonObject {@link SetupProtocol} to create
+ * @param edgeId the Edge-ID
+ * @param customerId Odoo customer id to set
+ * @param installerId Odoo installer id to set
+ * @return the Odoo id of created setup protocol
+ * @throws OpenemsException on error
+ */
+ private int createSetupProtocol(JsonObject jsonObject, int edgeId, int customerId, int installerId)
+ throws OpenemsException {
+ Integer locationId = null;
+
+ Optional jsonLocation = JsonUtils.getAsOptionalJsonObject(jsonObject, "location");
+ if (jsonLocation.isPresent()) {
+ JsonObject location = jsonLocation.get();
+
+ Map locationFields = new HashMap<>();
+ locationFields.putAll(this.updateAddress(location));
+ locationFields.putAll(this.updateCompany(location));
+
+ JsonUtils.getAsOptionalString(location, "firstname") //
+ .ifPresent(firstname -> locationFields.put(Field.Partner.FIRSTNAME.id(), firstname));
+ JsonUtils.getAsOptionalString(location, "lastname") //
+ .ifPresent(lastname -> locationFields.put(Field.Partner.LASTNAME.id(), lastname));
+ JsonUtils.getAsOptionalString(location, "email") //
+ .ifPresent(mail -> locationFields.put(Field.Partner.EMAIL.id(), mail));
+ JsonUtils.getAsOptionalString(location, "phone") //
+ .ifPresent(phone -> locationFields.put(Field.Partner.PHONE.id(), phone));
+
+ locationId = OdooUtils.create(this.credentials, Field.Partner.ODOO_MODEL, locationFields);
+ }
+
+ Map setupProtocolFields = new HashMap<>();
+ setupProtocolFields.put(Field.SetupProtocol.CUSTOMER.id(), customerId);
+ setupProtocolFields.put(Field.SetupProtocol.DIFFERENT_LOCATION.id(), locationId);
+ setupProtocolFields.put(Field.SetupProtocol.INSTALLER.id(), installerId);
+ setupProtocolFields.put(Field.SetupProtocol.EDGE.id(), edgeId);
+
+ int setupProtocolId = OdooUtils.create(this.credentials, Field.SetupProtocol.ODOO_MODEL, setupProtocolFields);
+
+ Optional lots = JsonUtils.getAsOptionalJsonArray(jsonObject, "lots");
+ if (lots.isPresent()) {
+ this.createSetupProtocolProductionLots(setupProtocolId, lots.get());
+ }
+ Optional items = JsonUtils.getAsOptionalJsonArray(jsonObject, "items");
+ if (items.isPresent()) {
+ this.createSetupProtocolItems(setupProtocolId, items.get());
+ }
+
+ return setupProtocolId;
+ }
+
+ /**
+ * Create production lots for the given setup protocol id.
+ *
+ * @param setupProtocolId assign to the lots
+ * @param lots list of setup protocol production lots to create
+ * @throws OpenemsException on error
+ */
+ private void createSetupProtocolProductionLots(int setupProtocolId, JsonArray lots) throws OpenemsException {
+ List serialNumbersNotFound = new ArrayList<>();
+ for (int i = 0; i < lots.size(); i++) {
+ JsonElement lot = lots.get(i);
+
+ Map lotFields = new HashMap<>();
+ lotFields.put(Field.SetupProtocolProductionLot.SETUP_PROTOCOL.id(), setupProtocolId);
+ lotFields.put(Field.SetupProtocolProductionLot.SEQUENCE.id(), i);
+
+ JsonUtils.getAsOptionalString(lot, "category") //
+ .ifPresent(category -> lotFields.put("category", category));
+ JsonUtils.getAsOptionalString(lot, "name") //
+ .ifPresent(name -> lotFields.put("name", name));
+
+ Optional optSerialNumber = JsonUtils.getAsOptionalString(lot, "serialNumber");
+ if (optSerialNumber.isPresent()) {
+ int[] lotId = OdooUtils.search(this.credentials, Field.StockProductionLot.ODOO_MODEL, //
+ new Domain(Field.StockProductionLot.SERIAL_NUMBER, "=", optSerialNumber.get()));
+
+ if (lotId.length > 0) {
+ lotFields.put(Field.SetupProtocolProductionLot.LOT.id(), lotId[0]);
+ OdooUtils.create(this.credentials, Field.SetupProtocolProductionLot.ODOO_MODEL, lotFields);
+ } else {
+ serialNumbersNotFound.add(lot);
+ }
+ }
+
+ }
+
+ this.createNotFoundSerialNumbers(setupProtocolId, serialNumbersNotFound);
+ }
+
+ /**
+ * Create for the given serial numbers that were not found a
+ * {@link SetupProtocolItem}.
+ *
+ * @param setupProtocolId the protocol id
+ * @param serialNumbers not found serial numbers
+ * @throws OpenemsException on error
+ */
+ private void createNotFoundSerialNumbers(int setupProtocolId, List serialNumbers)
+ throws OpenemsException {
+ for (int i = 0; i < serialNumbers.size(); i++) {
+ Map setupProtocolItem = new HashMap<>();
+ setupProtocolItem.put(Field.SetupProtocolItem.SETUP_PROTOCOL.id(), setupProtocolId);
+ setupProtocolItem.put(Field.SetupProtocolItem.SEQUENCE.id(), i);
+ setupProtocolItem.put("category", "Seriennummern wurden im System nicht gefunden");
+
+ JsonElement item = serialNumbers.get(i);
+ JsonUtils.getAsOptionalString(item, "name") //
+ .ifPresent(name -> setupProtocolItem.put("name", name));
+ JsonUtils.getAsOptionalString(item, "serialNumber") //
+ .ifPresent(serialNumber -> setupProtocolItem.put("value", serialNumber));
+
+ OdooUtils.create(this.credentials, Field.SetupProtocolItem.ODOO_MODEL, setupProtocolItem);
+ }
+ }
+
+ /**
+ * Create items for the given setup protocol id.
+ *
+ * @param setupProtocolId assign to the items
+ * @param items list of setup protocol items to create
+ * @throws OpenemsException on error
+ */
+ private void createSetupProtocolItems(int setupProtocolId, JsonArray items) throws OpenemsException {
+ for (int i = 0; i < items.size(); i++) {
+ JsonElement item = items.get(i);
+
+ Map setupProtocolItem = new HashMap<>();
+ setupProtocolItem.put(Field.SetupProtocolItem.SETUP_PROTOCOL.id(), setupProtocolId);
+ setupProtocolItem.put(Field.SetupProtocolItem.SEQUENCE.id(), i);
+
+ JsonUtils.getAsOptionalString(item, "category") //
+ .ifPresent(category -> setupProtocolItem.put("category", category));
+ JsonUtils.getAsOptionalString(item, "name") //
+ .ifPresent(name -> setupProtocolItem.put("name", name));
+ JsonUtils.getAsOptionalString(item, "value") //
+ .ifPresent(value -> setupProtocolItem.put("value", value));
+
+ OdooUtils.create(this.credentials, Field.SetupProtocolItem.ODOO_MODEL, setupProtocolItem);
+ }
+ }
+
+ /**
+ * Gets the referenced Odoo partner id for an Odoo user.
+ *
+ * @param user the Odoo user
+ * @return the Odoo partner id
+ * @throws OpenemsException on error
+ */
+ private int getOdooPartnerId(MyUser user) throws OpenemsException {
+ return this.getOdooPartnerId(user.getOdooId());
+ }
+
+ /**
+ * Gets the referenced Odoo partner id for an Odoo user id.
+ *
+ * @param odooUserId of the Odoo user
+ * @return the Odoo partner id
+ * @throws OpenemsException on error
+ */
+ private int getOdooPartnerId(int odooUserId) throws OpenemsException {
+ Map odooUser = OdooUtils.readOne(this.credentials, Field.User.ODOO_MODEL, odooUserId,
+ Field.User.PARTNER);
+
+ Optional odooPartnerId = OdooUtils.getOdooRefernceId(odooUser.get(Field.User.PARTNER.id()));
+
+ if (!odooPartnerId.isPresent()) {
+ throw new OpenemsException("Odoo partner not found for user ['" + odooUserId + "']");
+ }
+
+ return odooPartnerId.get();
+ }
+
+ /**
+ * Register an user in Odoo with the given {@link OdooUserRole}.
+ *
+ * @param jsonObject {@link JsonObject} that represents an user
+ * @param role {@link OdooUserRole} to set for the user
+ * @throws OpenemsNamedException on error
+ */
+ public void registerUser(JsonObject jsonObject, OdooUserRole role) throws OpenemsNamedException {
+ Optional optEmail = JsonUtils.getAsOptionalString(jsonObject, "email");
+ if (!optEmail.isPresent()) {
+ throw new OpenemsException("No email specified");
+ }
+ String email = optEmail.get();
+
+ int[] userFound = OdooUtils.search(this.credentials, Field.User.ODOO_MODEL, //
+ new Domain(Field.User.LOGIN, "=", email));
+ if (userFound.length > 0) {
+ throw new OpenemsException("User already exists with email [" + email + "]");
+ }
+
+ Map userFields = new HashMap<>();
+ userFields.put(Field.User.LOGIN.id(), email);
+ userFields.put(Field.Partner.EMAIL.id(), email);
+ userFields.put(Field.User.GLOBAL_ROLE.id(), role.getOdooRole());
+ userFields.put(Field.User.GROUPS.id(), role.toOdooIds());
+ userFields.putAll(this.updateAddress(jsonObject));
+ userFields.putAll(this.updateCompany(jsonObject));
+
+ JsonUtils.getAsOptionalString(jsonObject, "firstname") //
+ .ifPresent(firstname -> userFields.put("firstname", firstname));
+ JsonUtils.getAsOptionalString(jsonObject, "lastname") //
+ .ifPresent(lastname -> userFields.put("lastname", lastname));
+ JsonUtils.getAsOptionalString(jsonObject, "phone") //
+ .ifPresent(phone -> userFields.put("phone", phone));
+ JsonUtils.getAsOptionalString(jsonObject, "password") //
+ .ifPresent(password -> userFields.put("password", password));
+
+ int createdUserId = OdooUtils.create(this.credentials, Field.User.ODOO_MODEL, userFields);
+ this.sendRegistrationMail(createdUserId);
+ }
+
+ /**
+ * Call Odoo api to send registration mail via Odoo.
+ *
+ * @param odooUserId Odoo user id to send the mail
+ * @throws OpenemsNamedException error
+ */
+ private void sendRegistrationMail(int odooUserId) throws OpenemsNamedException {
+ this.sendRegistrationMail(odooUserId, null);
+ }
+
+ /**
+ * Call Odoo api to send registration mail via Odoo.
+ *
+ * @param odooUserId Odoo user id to send the mail
+ * @param password password for the user
+ * @throws OpenemsNamedException error
+ */
+ private void sendRegistrationMail(int odooUserId, String password) throws OpenemsNamedException {
+ OdooUtils.sendAdminJsonrpcRequest(this.credentials, "/openems_backend/sendRegistrationEmail",
+ JsonUtils.buildJsonObject() //
+ .add("params", JsonUtils.buildJsonObject() //
+ .addProperty("userId", odooUserId) //
+ .addProperty("password", password) //
+ .build()) //
+ .build());
+ }
+
}
diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUserGroup.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUserGroup.java
new file mode 100644
index 00000000000..70ae323bce9
--- /dev/null
+++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUserGroup.java
@@ -0,0 +1,18 @@
+package io.openems.backend.metadata.odoo.odoo;
+
+public enum OdooUserGroup {
+
+ // XXX PORTAL id might differ between different Odoo databases
+ PORTAL(65);
+
+ private final int groupId;
+
+ private OdooUserGroup(int groupId) {
+ this.groupId = groupId;
+ }
+
+ public int getGroupId() {
+ return this.groupId;
+ }
+
+}
diff --git a/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUserRole.java b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUserRole.java
new file mode 100644
index 00000000000..dc683d84f1c
--- /dev/null
+++ b/io.openems.backend.metadata.odoo/src/io/openems/backend/metadata/odoo/odoo/OdooUserRole.java
@@ -0,0 +1,74 @@
+package io.openems.backend.metadata.odoo.odoo;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import io.openems.common.exceptions.OpenemsException;
+
+public enum OdooUserRole {
+
+ ADMIN("admin"), //
+ INSTALLER("installer", OdooUserGroup.PORTAL), //
+ OWNER("owner", OdooUserGroup.PORTAL), //
+ GUEST("guest");
+
+ private final String odooRole;
+ private final OdooUserGroup[] odooGroups;
+
+ OdooUserRole(String odooRole, OdooUserGroup... odooGroups) {
+ this.odooRole = odooRole;
+ this.odooGroups = odooGroups;
+ }
+
+ /**
+ * Get the Odoo role.
+ *
+ * @return Odoo role
+ */
+ public String getOdooRole() {
+ return this.odooRole;
+ }
+
+ /**
+ * Get the specified Odoo groups for the role.
+ *
+ * @return Groups for an Odoo role
+ */
+ public OdooUserGroup[] getOdooGroups() {
+ return this.odooGroups;
+ }
+
+ /**
+ * Transform the specified Odoo group objects to a list of IDs.
+ *
+ * @return The Odoo groups as a list of IDs.
+ */
+ public List toOdooIds() {
+ return Arrays.stream(this.odooGroups) //
+ .map(OdooUserGroup::getGroupId) //
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Get the {@link OdooUserRole} for the given role as {@link String}.
+ *
+ * @param role as {@link String} to parse
+ * @return The Odoo role
+ * @throws OpenemsException if role does not exist
+ */
+ public static OdooUserRole getRole(String role) throws OpenemsException {
+ role = role.toLowerCase();
+
+ if (role.equals("admin")) {
+ return OdooUserRole.ADMIN;
+ } else if (role.equals("installer")) {
+ return OdooUserRole.INSTALLER;
+ } else if (role.equals("owner")) {
+ return OdooUserRole.OWNER;
+ } else {
+ throw new OpenemsException("Role [" + role + "] does not exist");
+ }
+ }
+
+}
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 1d99aba4e55..8c96c4703f7 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
@@ -11,11 +11,13 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
+import com.google.common.io.ByteStreams;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -165,6 +167,50 @@ public static SuccessResponseAndHeaders sendJsonrpcRequest(String url, String co
}
}
+ /**
+ * Sends a request with admin privileges.
+ *
+ * @param credentials the Odoo credentials
+ * @param url to send the request
+ * @param request to send
+ * @throws OpenemsNamedException on error
+ */
+ protected static void sendAdminJsonrpcRequest(Credentials credentials, String url, JsonObject request)
+ throws OpenemsNamedException {
+ String session = login(credentials, "admin", credentials.getPassword());
+ sendJsonrpcRequest(credentials.getUrl() + url, "session_id=" + session, request);
+ }
+
+ /**
+ * Authenticates a user using Username and Password.
+ *
+ * @param credentials used to get Odoo url
+ * @param username the Username
+ * @param password the Password
+ * @return the session_id
+ * @throws OpenemsNamedException on login error
+ */
+ protected static String login(Credentials credentials, String username, String password)
+ throws OpenemsNamedException {
+ JsonObject request = JsonUtils.buildJsonObject() //
+ .addProperty("jsonrpc", "2.0") //
+ .addProperty("method", "call") //
+ .add("params", JsonUtils.buildJsonObject() //
+ .addProperty("db", "v12") //
+ .addProperty("login", username) //
+ .addProperty("password", password) //
+ .build()) //
+ .build();
+ SuccessResponseAndHeaders response = OdooUtils
+ .sendJsonrpcRequest(credentials.getUrl() + "/web/session/authenticate", request);
+ Optional sessionId = OdooHandler.getFieldFromSetCookieHeader(response.headers, "session_id");
+ if (!sessionId.isPresent()) {
+ throw OpenemsError.COMMON_AUTHENTICATION_FAILED.exception();
+ } else {
+ return sessionId.get();
+ }
+ }
+
private static Object executeKw(String url, Object[] params) throws XmlRpcException, MalformedURLException {
final XmlRpcClient client = new XmlRpcClient();
XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
@@ -422,6 +468,53 @@ protected static void addChatterMessage(Credentials credentials, String model, i
}
}
+ /**
+ * Create a record in Odoo.
+ *
+ * @param credentials the Odoo credentials
+ * @param model the Oddo model
+ * @param fieldValues fields and values that should be written
+ * @return Odoo id of created record
+ * @throws OpenemsException on error
+ */
+ protected static int create(Credentials credentials, String model, FieldValue>... fieldValues)
+ throws OpenemsException {
+ Map paramsFieldValues = new HashMap<>();
+ for (FieldValue> fieldValue : fieldValues) {
+ paramsFieldValues.put(fieldValue.getField().id(), fieldValue.getValue());
+ }
+
+ return create(credentials, model, paramsFieldValues);
+ }
+
+ /**
+ * Create a record in Odoo.
+ *
+ * @param credentials the Odoo credentials
+ * @param model the Oddo model
+ * @param fieldValues fields and values that should be written
+ * @return Odoo id of created record
+ * @throws OpenemsException on error
+ */
+ protected static int create(Credentials credentials, String model, Map fieldValues)
+ throws OpenemsException {
+ String action = "create";
+
+ Object[] params = new Object[] { credentials.getDatabase(), credentials.getUid(), credentials.getPassword(),
+ model, action, new Object[] { fieldValues } };
+
+ try {
+ Object resultObj = (Object) executeKw(credentials.getUrl(), params);
+ if (resultObj == null) {
+ throw new OpenemsException("Not created.");
+ }
+
+ return OdooUtils.getAsInteger(resultObj);
+ } catch (Throwable e) {
+ throw new OpenemsException("Unable to write to Odoo: " + e.getMessage());
+ }
+ }
+
/**
* Update a record in Odoo.
*
@@ -444,16 +537,32 @@ public static void write(Credentials credentials, String model, Integer[] ids, F
// }
// System.out.println(b.toString());
- // Create request params
- String action = "write";
// Add fieldValues
Map paramsFieldValues = new HashMap<>();
for (FieldValue> fieldValue : fieldValues) {
paramsFieldValues.put(fieldValue.getField().id(), fieldValue.getValue());
}
+
+ write(credentials, model, ids, paramsFieldValues);
+ }
+
+ /**
+ * Update a record in Odoo.
+ *
+ * @param credentials the Odoo credentials
+ * @param model the Odoo model
+ * @param ids ids of model to update
+ * @param fieldValues fields and values that should be written
+ * @throws OpenemsException on error
+ */
+ protected static void write(Credentials credentials, String model, Integer[] ids, Map fieldValues)
+ throws OpenemsException {
+ // Create request params
+ String action = "write";
+
// Create request params
Object[] params = new Object[] { credentials.getDatabase(), credentials.getUid(), credentials.getPassword(),
- model, action, new Object[] { ids, paramsFieldValues } };
+ model, action, new Object[] { ids, fieldValues } };
try {
// Execute XML request
Boolean resultObj = (Boolean) executeKw(credentials.getUrl(), params);
@@ -492,4 +601,58 @@ protected static Integer getAsInteger(Object object) {
return null;
}
}
+
+ /**
+ * Return the odoo reference id as a {@link Integer}, otherwise empty
+ * {@link Optional}.
+ *
+ * @param object the odoo reference to extract
+ * @return the odoo reference id or empty {@link Optional}
+ */
+ protected static Optional getOdooRefernceId(Object object) {
+ if (object != null && object instanceof Object[]) {
+ Object[] odooRefernce = (Object[]) object;
+
+ if (odooRefernce[0] != null && odooRefernce[0] instanceof Integer) {
+ return Optional.of((Integer) odooRefernce[0]);
+ }
+ }
+
+ return Optional.empty();
+ }
+
+ /**
+ * Returns a Odoo report as a byte array. Search for the given template id in
+ * combination with the concrete report id.
+ *
+ * @param credentials the Odoo credentialss
+ * @param report the Odoo template id
+ * @param id the Odoo report id
+ * @return the Odoo report as a byte array
+ * @throws OpenemsNamedException on error
+ */
+ protected static byte[] getOdooReport(Credentials credentials, String report, int id)
+ throws OpenemsNamedException {
+ String session = login(credentials, "admin", credentials.getPassword());
+
+ HttpURLConnection connection = null;
+ try {
+ connection = (HttpURLConnection) new URL(
+ credentials.getUrl() + "/report/pdf/" + report + "/" + id + "?session_id=" + session)
+ .openConnection();
+ connection.setConnectTimeout(5000);
+ connection.setReadTimeout(5000);
+ connection.setRequestMethod("GET");
+ connection.setDoOutput(true);
+
+ return ByteStreams.toByteArray(connection.getInputStream());
+ } catch (Exception e) {
+ throw OpenemsError.GENERIC.exception(e.getMessage());
+ } finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ }
+
}
diff --git a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/OnRequest.java b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/OnRequest.java
index 24d66640ebd..5cc4b44232e 100644
--- a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/OnRequest.java
+++ b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/OnRequest.java
@@ -1,5 +1,6 @@
package io.openems.backend.uiwebsocket.impl;
+import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@@ -8,6 +9,17 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.gson.JsonObject;
+
+import io.openems.backend.common.jsonrpc.request.AddEdgeToUserRequest;
+import io.openems.backend.common.jsonrpc.request.GetSetupProtocolRequest;
+import io.openems.backend.common.jsonrpc.request.GetUserInformationRequest;
+import io.openems.backend.common.jsonrpc.request.RegisterUserRequest;
+import io.openems.backend.common.jsonrpc.request.SetUserInformationRequest;
+import io.openems.backend.common.jsonrpc.request.SubmitSetupProtocolRequest;
+import io.openems.backend.common.jsonrpc.response.AddEdgeToUserResponse;
+import io.openems.backend.common.jsonrpc.response.GetUserInformationResponse;
+import io.openems.backend.common.metadata.Edge;
import io.openems.backend.common.metadata.User;
import io.openems.common.exceptions.OpenemsError;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
@@ -21,8 +33,10 @@
import io.openems.common.jsonrpc.request.SubscribeChannelsRequest;
import io.openems.common.jsonrpc.request.SubscribeSystemLogRequest;
import io.openems.common.jsonrpc.response.AuthenticateResponse;
+import io.openems.common.jsonrpc.response.Base64PayloadResponse;
import io.openems.common.jsonrpc.response.EdgeRpcResponse;
import io.openems.common.session.Role;
+import io.openems.common.utils.JsonUtils;
public class OnRequest implements io.openems.common.websocket.OnRequest {
@@ -46,6 +60,9 @@ public CompletableFuture extends JsonrpcResponseSuccess> run(WebSocket ws, Jso
case AuthenticateWithPasswordRequest.METHOD:
return this.handleAuthenticateWithPasswordRequest(wsData, AuthenticateWithPasswordRequest.from(request));
+
+ case RegisterUserRequest.METHOD:
+ return this.handleRegisterUserReuqest(wsData, RegisterUserRequest.from(request));
}
// should be authenticated
@@ -55,10 +72,24 @@ public CompletableFuture extends JsonrpcResponseSuccess> run(WebSocket ws, Jso
case LogoutRequest.METHOD:
result = this.handleLogoutRequest(wsData, user, LogoutRequest.from(request));
break;
-
case EdgeRpcRequest.METHOD:
result = this.handleEdgeRpcRequest(wsData, user, EdgeRpcRequest.from(request));
break;
+ case AddEdgeToUserRequest.METHOD:
+ result = this.handleAddEdgeToUserRequest(user, AddEdgeToUserRequest.from(request));
+ break;
+ case GetUserInformationRequest.METHOD:
+ result = this.handleGetUserInformationRequest(user, GetUserInformationRequest.from(request));
+ break;
+ case SetUserInformationRequest.METHOD:
+ result = this.handleSetUserInformationRequest(user, SetUserInformationRequest.from(request));
+ break;
+ case GetSetupProtocolRequest.METHOD:
+ result = this.handleGetSetupProtocolRequest(user, GetSetupProtocolRequest.from(request));
+ break;
+ case SubmitSetupProtocolRequest.METHOD:
+ result = this.handleSubmitSetupProtocolRequest(user, SubmitSetupProtocolRequest.from(request));
+ break;
}
if (result != null) {
@@ -123,6 +154,21 @@ private CompletableFuture handleAuthentication(WsData ws
User.generateEdgeMetadatas(user, this.parent.metadata)));
}
+ /**
+ * Handles a {@link RegisterUserRequest}.
+ *
+ * @param wsData the WebSocket attachment
+ * @param request the {@link RegisterUserRequest}
+ * @return the JSON-RPC Success Response Future
+ * @throws OpenemsNamedException on error
+ */
+ private CompletableFuture handleRegisterUserReuqest(WsData wsData,
+ RegisterUserRequest request) throws OpenemsNamedException {
+ this.parent.metadata.registerUser(request.getJsonObject());
+
+ return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.getId()));
+ }
+
/**
* Handles a {@link LogoutRequest}.
*
@@ -163,7 +209,7 @@ private User assertUser(WsData wsData, JsonrpcRequest request) throws OpenemsNam
}
/**
- * Handles an EdgeRpcRequest.
+ * Handles an {@link EdgeRpcRequest}.
*
* @param wsData the WebSocket attachment
* @param user the authenticated {@link User}
@@ -211,7 +257,7 @@ private CompletableFuture handleEdgeRpcRequest(WsData wsData, U
}
/**
- * Handles a SubscribeChannelsRequest.
+ * Handles a {@link SubscribeChannelsRequest}.
*
* @param wsData the WebSocket attachment
* @param edgeId the Edge-ID
@@ -249,4 +295,83 @@ private CompletableFuture handleSubscribeSystemLogReques
return this.parent.edgeWebsocket.handleSubscribeSystemLogRequest(edgeId, user, token, request);
}
+ /**
+ * Handles an {@link AddEdgeToUserRequest}.
+ *
+ * @param user the {@link User}
+ * @param request the {@link AddEdgeToUserRequest}
+ * @return the JSON-RPC Success Response Future
+ * @throws OpenemsNamedException on error
+ */
+ private CompletableFuture handleAddEdgeToUserRequest(User user, AddEdgeToUserRequest request)
+ throws OpenemsNamedException {
+ Edge edge = this.parent.metadata.addEdgeToUser(user, request.getSetupPassword());
+
+ return CompletableFuture.completedFuture(new AddEdgeToUserResponse(request.getId(), edge));
+ }
+
+ /**
+ * Handles a {@link GetUserInformationRequest}.
+ *
+ * @param user the {@link User}
+ * @param request the {@link GetUserInformationRequest}
+ * @return the JSON-RPC Success Response Future
+ * @throws OpenemsNamedException on error
+ */
+ private CompletableFuture handleGetUserInformationRequest(User user,
+ GetUserInformationRequest request) throws OpenemsNamedException {
+ Map userInformation = this.parent.metadata.getUserInformation(user);
+
+ return CompletableFuture.completedFuture(new GetUserInformationResponse(request.getId(), userInformation));
+ }
+
+ /**
+ * Handles a {@link SetUserInformationRequest}.
+ *
+ * @param user the {@link User}r
+ * @param request the {@link SetUserInformationRequest}
+ * @return the JSON-RPC Success Response Future
+ * @throws OpenemsNamedException on error
+ */
+ private CompletableFuture handleSetUserInformationRequest(User user,
+ SetUserInformationRequest request) throws OpenemsNamedException {
+ this.parent.metadata.setUserInformation(user, request.getJsonObject());
+
+ return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.getId()));
+ }
+
+ /**
+ * Handles a {@link SubmitSetupProtocolRequest}.
+ *
+ * @param user the {@link User}r
+ * @param request the {@link SubmitSetupProtocolRequest}
+ * @return the JSON-RPC Success Response Future
+ * @throws OpenemsNamedException on error
+ */
+ private CompletableFuture handleSubmitSetupProtocolRequest(User user,
+ SubmitSetupProtocolRequest request) throws OpenemsNamedException {
+ int protocolId = this.parent.metadata.submitSetupProtocol(user, request.getJsonObject());
+
+ JsonObject response = JsonUtils.buildJsonObject() //
+ .addProperty("setupProtocolId", protocolId) //
+ .build();
+
+ return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.getId(), response));
+ }
+
+ /**
+ * Handles a {@link GetSetupProtocolRequest}.
+ *
+ * @param user the {@link User}r
+ * @param request the {@link GetSetupProtocolRequest}
+ * @return the JSON-RPC Success Response Future
+ * @throws OpenemsNamedException on error
+ */
+ private CompletableFuture handleGetSetupProtocolRequest(User user,
+ GetSetupProtocolRequest request) throws OpenemsNamedException {
+ byte[] protocol = this.parent.metadata.getSetupProtocol(user, request.getSetupProtocolId());
+
+ return CompletableFuture.completedFuture(new Base64PayloadResponse(request.getId(), protocol));
+ }
+
}
diff --git a/io.openems.common/resources/templates/device-modbus/$srcDir$/$basePackageDir$/Config.java b/io.openems.common/resources/templates/device-modbus/$srcDir$/$basePackageDir$/Config.java
index 88c057255e5..a197f3b2585 100644
--- a/io.openems.common/resources/templates/device-modbus/$srcDir$/$basePackageDir$/Config.java
+++ b/io.openems.common/resources/templates/device-modbus/$srcDir$/$basePackageDir$/Config.java
@@ -24,8 +24,8 @@
int modbusUnitId() default 1;
@AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
- String Modbus_target() default "";
-
+ String Modbus_target() default "(enabled=true)";
+
String webconsole_configurationFactory_nameHint() default "$projectName$ [{id}]";
}
\ No newline at end of file
diff --git a/io.openems.common/src/io/openems/common/OpenemsConstants.java b/io.openems.common/src/io/openems/common/OpenemsConstants.java
index 55e3bd30a27..18d96164dad 100644
--- a/io.openems.common/src/io/openems/common/OpenemsConstants.java
+++ b/io.openems.common/src/io/openems/common/OpenemsConstants.java
@@ -20,7 +20,7 @@ public class OpenemsConstants {
*
* This is usually the number of the sprint within the year
*/
- public final static short VERSION_MINOR = 13;
+ public final static short VERSION_MINOR = 14;
/**
* The patch version of OpenEMS.
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 a83016ee0c2..8f9a43f4571 100644
--- a/io.openems.common/src/io/openems/common/utils/JsonUtils.java
+++ b/io.openems.common/src/io/openems/common/utils/JsonUtils.java
@@ -1298,22 +1298,40 @@ public static Object getAsType(OpenemsType type, JsonElement j) throws OpenemsNa
if (j.isJsonNull()) {
return null;
}
- switch (type) {
- case BOOLEAN:
- return JsonUtils.getAsBoolean(j);
- case DOUBLE:
- return JsonUtils.getAsDouble(j);
- case FLOAT:
- return JsonUtils.getAsFloat(j);
- case INTEGER:
- return JsonUtils.getAsInt(j);
- case LONG:
- return JsonUtils.getAsLong(j);
- case SHORT:
- return JsonUtils.getAsShort(j);
- case STRING:
- return JsonUtils.getAsString(j);
+
+ if (j.isJsonPrimitive()) {
+ switch (type) {
+ case BOOLEAN:
+ return JsonUtils.getAsBoolean(j);
+ case DOUBLE:
+ return JsonUtils.getAsDouble(j);
+ case FLOAT:
+ return JsonUtils.getAsFloat(j);
+ case INTEGER:
+ return JsonUtils.getAsInt(j);
+ case LONG:
+ return JsonUtils.getAsLong(j);
+ case SHORT:
+ return JsonUtils.getAsShort(j);
+ case STRING:
+ return JsonUtils.getAsString(j);
+ }
+ }
+
+ if (j.isJsonObject() || j.isJsonArray()) {
+ switch (type) {
+ case BOOLEAN:
+ case DOUBLE:
+ case FLOAT:
+ case INTEGER:
+ case LONG:
+ case SHORT:
+ break;
+ case STRING:
+ return j.toString();
+ }
}
+
throw new NotImplementedException(
"Converter for value [" + j + "] to class type [" + type + "] is not implemented.");
}
@@ -1393,4 +1411,34 @@ public static void prettyPrint(JsonElement j) {
System.out.println(json);
}
+ /**
+ * Check if the given {@link JsonElement} is an empty JsonObject {}.
+ *
+ * @param j the {@link JsonElement} to check
+ * @return true if is empty, otherwise false
+ */
+ public static boolean isEmptyJsonObject(JsonElement j) {
+ if (j != null && j.isJsonObject()) {
+ JsonObject object = j.getAsJsonObject();
+ return object.size() == 0;
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if the given {@link JsonElement} is an empty JsonArray [].
+ *
+ * @param j the {@link JsonElement} to check
+ * @return true if is empty, otherwise false
+ */
+ public static boolean isEmptyJsonArray(JsonElement j) {
+ if (j != null && j.isJsonArray()) {
+ JsonArray array = j.getAsJsonArray();
+ return array.size() == 0;
+ }
+
+ return false;
+ }
+
}
diff --git a/io.openems.common/src/io/openems/common/utils/ObjectUtils.java b/io.openems.common/src/io/openems/common/utils/ObjectUtils.java
new file mode 100644
index 00000000000..f943f32b780
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/utils/ObjectUtils.java
@@ -0,0 +1,62 @@
+package io.openems.common.utils;
+
+import java.util.Optional;
+
+public class ObjectUtils {
+
+ /**
+ * Cast and return the given {@link Object} as a {@link String}. If given object
+ * is no {@link String} it will return null.
+ *
+ * @param object to cast an return as {@link String}
+ * @return a {@link String} or null
+ */
+ public static String getAsString(Object object) {
+ if (object != null && object instanceof String) {
+ return (String) object;
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the given {@link Object} as a {@link Optional} {@link String}.
+ *
+ * @param object to get as {@link Optional}
+ * @return the {@link Optional} {@link String} value
+ */
+ public static Optional getAsOptionalString(Object object) {
+ return Optional.ofNullable(getAsString(object));
+ }
+
+ /**
+ * Cast and return the given {@link Object} as a {@link Integer}. If given
+ * object is no {@link Integer} it will return null.
+ *
+ * @param object to cast an return as {@link Integer}
+ * @return a {@link Integer} or null
+ */
+ public static Integer getAsInteger(Object object) {
+ if (object != null && object instanceof Integer) {
+ return (Integer) object;
+ }
+
+ return null;
+ }
+
+ /**
+ * Cast and return the given {@link Object} as a {@link Object} array. If given
+ * object is no {@link Object} array it will return an empty array.
+ *
+ * @param object to cast an return as {@link Object} array
+ * @return a {@link Object} array or empty array
+ */
+ public static Object[] getAsObjectArrray(Object object) {
+ if (object != null && object instanceof Object[]) {
+ return (Object[]) object;
+ }
+
+ return new Object[] {};
+ }
+
+}
diff --git a/io.openems.common/src/io/openems/common/utils/PasswordUtils.java b/io.openems.common/src/io/openems/common/utils/PasswordUtils.java
new file mode 100644
index 00000000000..9f663b64766
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/utils/PasswordUtils.java
@@ -0,0 +1,22 @@
+package io.openems.common.utils;
+
+import java.security.SecureRandom;
+
+public class PasswordUtils {
+
+ /**
+ * Generate a random alphabetic numeric password with given length.
+ *
+ * @param length of the Password
+ * @return Generated Password
+ */
+ public static String generateRandomPassword(int length) {
+ SecureRandom random = new SecureRandom();
+
+ return random.ints(48, 122) //
+ .filter(i -> Character.isAlphabetic(i) || Character.isDigit(i)) //
+ .limit(length) //
+ .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) //
+ .toString();
+ }
+}
diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun
index 95bf4a7ffcd..cdf28d695c2 100644
--- a/io.openems.edge.application/EdgeApp.bndrun
+++ b/io.openems.edge.application/EdgeApp.bndrun
@@ -1,4 +1,4 @@
--runfw: org.eclipse.osgi
+-runfw: org.apache.felix.framework;version='[7.0.1,7.0.1]'
-runee: JavaSE-1.8
-runprovidedcapabilities: ${native_capability}
@@ -35,6 +35,7 @@
bnd.identity;id='io.openems.edge.batteryinverter.refu88k',\
bnd.identity;id='io.openems.edge.batteryinverter.sunspec',\
bnd.identity;id='io.openems.edge.battery.soltaro',\
+ bnd.identity;id='io.openems.edge.bosch.bpts5hybrid',\
bnd.identity;id='io.openems.edge.bridge.mbus',\
bnd.identity;id='io.openems.edge.bridge.modbus',\
bnd.identity;id='io.openems.edge.bridge.onewire',\
@@ -131,6 +132,7 @@
bnd.identity;id='io.openems.edge.meter.weidmueller',\
bnd.identity;id='io.openems.edge.onewire.thermometer',\
bnd.identity;id='io.openems.edge.predictor.persistencemodel',\
+ bnd.identity;id='io.openems.edge.predictor.similardaymodel',\
bnd.identity;id='io.openems.edge.pvinverter.cluster',\
bnd.identity;id='io.openems.edge.pvinverter.kaco.blueplanet',\
bnd.identity;id='io.openems.edge.pvinverter.sma',\
@@ -164,6 +166,7 @@
io.openems.edge.batteryinverter.kaco.blueplanetgridsave;version=snapshot,\
io.openems.edge.batteryinverter.refu88k;version=snapshot,\
io.openems.edge.batteryinverter.sunspec;version=snapshot,\
+ io.openems.edge.bosch.bpts5hybrid;version=snapshot,\
io.openems.edge.bridge.mbus;version=snapshot,\
io.openems.edge.bridge.modbus;version=snapshot,\
io.openems.edge.bridge.onewire;version=snapshot,\
@@ -266,6 +269,7 @@
io.openems.edge.onewire.thermometer;version=snapshot,\
io.openems.edge.predictor.api;version=snapshot,\
io.openems.edge.predictor.persistencemodel;version=snapshot,\
+ io.openems.edge.predictor.similardaymodel;version=snapshot,\
io.openems.edge.pvinverter.api;version=snapshot,\
io.openems.edge.pvinverter.cluster;version=snapshot,\
io.openems.edge.pvinverter.kaco.blueplanet;version=snapshot,\
@@ -303,6 +307,7 @@
org.apache.felix.webconsole;version='[4.6.2,4.6.3)',\
org.apache.felix.webconsole.plugins.ds;version='[2.1.0,2.1.1)',\
org.eclipse.paho.mqttv5.client;version='[1.2.5,1.2.6)',\
+ org.jsoup;version='[1.10.2,1.10.3)',\
org.jsr-305;version='[3.0.2,3.0.3)',\
org.openmuc.jmbus;version='[3.3.0,3.3.1)',\
org.openmuc.jrxtx;version='[1.0.1,1.0.2)',\
diff --git a/io.openems.edge.battery.api/src/io/openems/edge/battery/protection/BatteryProtection.java b/io.openems.edge.battery.api/src/io/openems/edge/battery/protection/BatteryProtection.java
index 5689735a788..4147f61ec55 100644
--- a/io.openems.edge.battery.api/src/io/openems/edge/battery/protection/BatteryProtection.java
+++ b/io.openems.edge.battery.api/src/io/openems/edge/battery/protection/BatteryProtection.java
@@ -300,6 +300,9 @@ protected BatteryProtection(Battery battery, ChargeMaxCurrentHandler chargeMaxCu
this.battery = battery;
this.chargeMaxCurrentHandler = chargeMaxCurrentHandler;
this.dischargeMaxCurrentHandler = dischargeMaxCurrentHandler;
+
+ this.battery.getChargeMaxCurrentChannel().setMetaInfo("Battery-Protection");
+ this.battery.getDischargeMaxCurrentChannel().setMetaInfo("Battery-Protection");
}
/**
diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/Config.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/Config.java
index 7acb2213e6d..afa7774e300 100644
--- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/Config.java
+++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/Config.java
@@ -32,7 +32,7 @@
long errorDelay() default 600;
@AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
- String Modbus_target() default "";
+ String Modbus_target() default "(enabled=true)";
@AttributeDefinition(name = "Max Start Attempts", description = "Sets the counter how many time the system should try to start")
int maxStartAttempts() default 5;
diff --git a/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/Config.java b/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/Config.java
index 8f3966cdb34..aea196a1ab2 100644
--- a/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/Config.java
+++ b/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/Config.java
@@ -64,7 +64,7 @@
* @return String
*/
@AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
- String Modbus_target() default "";
+ String Modbus_target() default "(enabled=true)";
/**
* The webconsole_configurationFactory_nameHint.
diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/Config.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/Config.java
index 4e3391fcf08..6ef5a5042cc 100644
--- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/Config.java
+++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/Config.java
@@ -29,7 +29,7 @@
int modbusUnitId() default 1;
@AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
- String Modbus_target() default "";
+ String Modbus_target() default "(enabled=true)";
String webconsole_configurationFactory_nameHint() default "Battery FENECON Home [{id}]";
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/Config.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/Config.java
index d66eecc718b..3a396135318 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/Config.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/Config.java
@@ -57,7 +57,7 @@
int minimalCellVoltage() default 2800;
@AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
- String Modbus_target() default "";
+ String Modbus_target() default "(enabled=true)";
String webconsole_configurationFactory_nameHint() default "BMS Soltaro Cluster Version B [{id}]";
}
\ No newline at end of file
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImpl.java
index dacf2bf9ffe..0315fc653ce 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImpl.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImpl.java
@@ -1,8 +1,8 @@
package io.openems.edge.battery.soltaro.cluster.versionc;
-import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
+import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
import org.osgi.service.cm.ConfigurationAdmin;
@@ -31,8 +31,8 @@
import io.openems.edge.battery.soltaro.cluster.versionc.statemachine.Context;
import io.openems.edge.battery.soltaro.cluster.versionc.statemachine.StateMachine;
import io.openems.edge.battery.soltaro.cluster.versionc.statemachine.StateMachine.State;
-import io.openems.edge.battery.soltaro.common.batteryprotection.BatteryProtectionDefinitionSoltaro3500Wh;
import io.openems.edge.battery.soltaro.common.batteryprotection.BatteryProtectionDefinitionSoltaro3000Wh;
+import io.openems.edge.battery.soltaro.common.batteryprotection.BatteryProtectionDefinitionSoltaro3500Wh;
import io.openems.edge.battery.soltaro.common.enums.ModuleType;
import io.openems.edge.battery.soltaro.single.versionc.enums.PreChargeControl;
import io.openems.edge.battery.soltaro.versionc.SoltaroBatteryVersionC;
@@ -89,7 +89,7 @@ public class ClusterVersionCImpl extends AbstractOpenemsModbusComponent implemen
private final StateMachine stateMachine = new StateMachine(State.UNDEFINED);
private Config config;
- private Set racks = new HashSet<>();
+ private TreeSet racks = new TreeSet<>();
private BatteryProtection batteryProtection = null;
public ClusterVersionCImpl() {
@@ -381,18 +381,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
// getModbusProtocol, and it is using racks...
for (Rack r : this.racks) {
protocol.addTasks(//
- // Single Cluster Control Registers (running without Master BMS)
- new FC6WriteRegisterTask(r.offset + 0x0010, //
- m(this.rack(r, RackChannel.PRE_CHARGE_CONTROL), new UnsignedWordElement(r.offset + 0x0010)) //
- ), //
- new FC16WriteRegistersTask(r.offset + 0x000B, //
- m(this.rack(r, RackChannel.EMS_ADDRESS), new UnsignedWordElement(r.offset + 0x000B)), //
- m(this.rack(r, RackChannel.EMS_BAUDRATE), new UnsignedWordElement(r.offset + 0x000C)) //
- ), //
- new FC6WriteRegisterTask(r.offset + 0x00F4, //
- m(this.rack(r, RackChannel.EMS_COMMUNICATION_TIMEOUT),
- new UnsignedWordElement(r.offset + 0x00F4)) //
- ), //
+
new FC3ReadRegistersTask(r.offset + 0x000B, Priority.LOW, //
m(this.rack(r, RackChannel.EMS_ADDRESS), new UnsignedWordElement(r.offset + 0x000B)), //
m(this.rack(r, RackChannel.EMS_BAUDRATE), new UnsignedWordElement(r.offset + 0x000C)), //
@@ -407,6 +396,19 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
new UnsignedWordElement(r.offset + 0x00F4)) //
),
+ // Single Cluster Control Registers (running without Master BMS)
+ new FC6WriteRegisterTask(r.offset + 0x0010, //
+ m(this.rack(r, RackChannel.PRE_CHARGE_CONTROL), new UnsignedWordElement(r.offset + 0x0010)) //
+ ), //
+ new FC6WriteRegisterTask(r.offset + 0x00F4, //
+ m(this.rack(r, RackChannel.EMS_COMMUNICATION_TIMEOUT),
+ new UnsignedWordElement(r.offset + 0x00F4)) //
+ ), //
+ new FC16WriteRegistersTask(r.offset + 0x000B, //
+ m(this.rack(r, RackChannel.EMS_ADDRESS), new UnsignedWordElement(r.offset + 0x000B)), //
+ m(this.rack(r, RackChannel.EMS_BAUDRATE), new UnsignedWordElement(r.offset + 0x000C)) //
+ ), //
+
// Single Cluster Control Registers (General)
new FC6WriteRegisterTask(r.offset + 0x00CC, //
m(this.rack(r, RackChannel.SYSTEM_TOTAL_CAPACITY),
@@ -732,11 +734,8 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
new UnsignedWordElement(r.offset + 0x400)), //
m(this.rack(r, RackChannel.LEVEL2_CELL_OVER_VOLTAGE_RECOVER),
new UnsignedWordElement(r.offset + 0x401)), //
- m(new UnsignedWordElement(r.offset + 0x402)) //
- .m(this.rack(r, RackChannel.LEVEL2_SYSTEM_OVER_VOLTAGE_PROTECTION),
- ElementToChannelConverter.SCALE_FACTOR_2) // [mV]
- .m(Battery.ChannelId.CHARGE_MAX_VOLTAGE, ElementToChannelConverter.SCALE_FACTOR_MINUS_1) // [V]
- .build(), //
+ m(this.rack(r, RackChannel.LEVEL2_SYSTEM_OVER_VOLTAGE_PROTECTION),
+ new UnsignedWordElement(r.offset + 0x402)), //
m(this.rack(r, RackChannel.LEVEL2_SYSTEM_OVER_VOLTAGE_RECOVER),
new UnsignedWordElement(r.offset + 0x403), ElementToChannelConverter.SCALE_FACTOR_2), //
m(this.rack(r, RackChannel.LEVEL2_SYSTEM_CHARGE_OVER_CURRENT_PROTECTION),
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/Config.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/Config.java
index 742db185882..955d6a4b758 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/Config.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/Config.java
@@ -114,7 +114,7 @@
* @return String
*/
@AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
- String Modbus_target() default "";
+ String Modbus_target() default "(enabled=true)";
/**
* Gets the webconsole_configurationFactory_nameHint.
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/Config.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/Config.java
index 3536fee03c7..3ebea8f973a 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/Config.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/Config.java
@@ -50,7 +50,7 @@
BatteryState batteryState() default BatteryState.DEFAULT;
@AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
- String Modbus_target() default "";
+ String Modbus_target() default "(enabled=true)";
String webconsole_configurationFactory_nameHint() default "BMS Soltaro Single Rack Version A [{id}]";
}
\ No newline at end of file
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/Config.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/Config.java
index 9d1eaa081db..10d9f3f499e 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/Config.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/Config.java
@@ -145,7 +145,7 @@
* @return Modbus_target
*/
@AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
- String Modbus_target() default "";
+ String Modbus_target() default "(enabled=true)";
/**
* Return the webconsole_configurationFactory_nameHint.
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/Config.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/Config.java
index a768d510724..2b34119e05d 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/Config.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/Config.java
@@ -114,7 +114,7 @@
* @return Modbus_target
*/
@AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
- String Modbus_target() default "";
+ String Modbus_target() default "(enabled=true)";
/**
* Return the webconsole_configurationFactory_nameHint.
diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/ClusterVersionBTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/ClusterVersionBTest.java
index f23129080e0..cbb1adaa66c 100644
--- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/ClusterVersionBTest.java
+++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/ClusterVersionBTest.java
@@ -1,9 +1,7 @@
package io.openems.edge.battery.soltaro.cluster.versionb;
-import org.junit.Before;
import org.junit.Test;
-import io.openems.edge.battery.soltaro.cluster.versionc.ResetChannelSources;
import io.openems.edge.battery.soltaro.common.enums.BatteryState;
import io.openems.edge.battery.soltaro.common.enums.ModuleType;
import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
@@ -15,11 +13,6 @@ public class ClusterVersionBTest {
private static final String BATTERY_ID = "battery0";
private static final String MODBUS_ID = "modbus0";
- @Before
- public void before() {
- ResetChannelSources.run();
- }
-
@Test
public void test() throws Exception {
new ComponentTest(new ClusterVersionB()) //
diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImplTest.java
index 28e762f829b..75bcef0675a 100644
--- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImplTest.java
+++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/ClusterVersionCImplTest.java
@@ -1,6 +1,5 @@
package io.openems.edge.battery.soltaro.cluster.versionc;
-import org.junit.Before;
import org.junit.Test;
import io.openems.edge.battery.soltaro.common.enums.ModuleType;
@@ -14,11 +13,6 @@ public class ClusterVersionCImplTest {
private static final String BATTERY_ID = "battery0";
private static final String MODBUS_ID = "modbus0";
- @Before
- public void before() {
- ResetChannelSources.run();
- }
-
@Test
public void test() throws Exception {
new ComponentTest(new ClusterVersionCImpl()) //
@@ -30,9 +24,9 @@ public void test() throws Exception {
.setModuleType(ModuleType.MODULE_3_5_KWH) //
.setStartStop(StartStopConfig.AUTO) //
.setNumberOfSlaves(0) //
- .setRack1Used(false) //
- .setRack2Used(false) //
- .setRack3Used(false) //
+ .setRack1Used(true) //
+ .setRack2Used(true) //
+ .setRack3Used(true) //
.setRack4Used(false) //
.setRack5Used(false) //
.build()) //
diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/ResetChannelSources.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/ResetChannelSources.java
deleted file mode 100644
index 503a70540d9..00000000000
--- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/ResetChannelSources.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package io.openems.edge.battery.soltaro.cluster.versionc;
-
-import io.openems.edge.battery.api.Battery;
-import io.openems.edge.battery.protection.BatteryProtection;
-import io.openems.edge.battery.soltaro.cluster.SoltaroCluster;
-import io.openems.edge.common.channel.ChannelId;
-
-/**
- * Reset Channel sources to avoid 'Unable to add Modbus mapping' errors on
- * running all tests at once.
- */
-public class ResetChannelSources {
-
- public static void run() {
- resetChannelSources(Battery.ChannelId.values());
- resetChannelSources(BatteryProtection.ChannelId.values());
- resetChannelSources(SoltaroCluster.ChannelId.values());
- }
-
- private static void resetChannelSources(ChannelId[] channelIds) {
- for (ChannelId channelId : channelIds) {
- channelId.doc().source(null);
- }
- }
-
-}
diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/SingleRackTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/SingleRackTest.java
index d41c4898c7e..1cb9c66f722 100644
--- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/SingleRackTest.java
+++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/SingleRackTest.java
@@ -1,9 +1,7 @@
package io.openems.edge.battery.soltaro.single.versiona;
-import org.junit.Before;
import org.junit.Test;
-import io.openems.edge.battery.soltaro.cluster.versionc.ResetChannelSources;
import io.openems.edge.battery.soltaro.common.enums.BatteryState;
import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
import io.openems.edge.common.test.ComponentTest;
@@ -14,11 +12,6 @@ public class SingleRackTest {
private static final String BATTERY_ID = "battery0";
private static final String MODBUS_ID = "modbus0";
- @Before
- public void before() {
- ResetChannelSources.run();
- }
-
@Test
public void test() throws Exception {
new ComponentTest(new SingleRack()) //
diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/SingleRackVersionBImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/SingleRackVersionBImplTest.java
index 6725532fb13..092cc01973c 100644
--- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/SingleRackVersionBImplTest.java
+++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/SingleRackVersionBImplTest.java
@@ -1,9 +1,7 @@
package io.openems.edge.battery.soltaro.single.versionb;
-import org.junit.Before;
import org.junit.Test;
-import io.openems.edge.battery.soltaro.cluster.versionc.ResetChannelSources;
import io.openems.edge.battery.soltaro.common.enums.ModuleType;
import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
import io.openems.edge.common.startstop.StartStopConfig;
@@ -16,11 +14,6 @@ public class SingleRackVersionBImplTest {
private static final String BATTERY_ID = "battery0";
private static final String MODBUS_ID = "modbus0";
- @Before
- public void before() {
- ResetChannelSources.run();
- }
-
@Test
public void test() throws Exception {
new ComponentTest(new SingleRackVersionBImpl()) //
diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionCImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionCImplTest.java
index c907e4dc1ae..3ab3252ded9 100644
--- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionCImplTest.java
+++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/SingleRackVersionCImplTest.java
@@ -1,9 +1,7 @@
package io.openems.edge.battery.soltaro.single.versionc;
-import org.junit.Before;
import org.junit.Test;
-import io.openems.edge.battery.soltaro.cluster.versionc.ResetChannelSources;
import io.openems.edge.battery.soltaro.common.enums.ModuleType;
import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
import io.openems.edge.common.startstop.StartStopConfig;
@@ -15,11 +13,6 @@ public class SingleRackVersionCImplTest {
private static final String BATTERY_ID = "battery0";
private static final String MODBUS_ID = "modbus0";
- @Before
- public void before() {
- ResetChannelSources.run();
- }
-
@Test
public void test() throws Exception {
new ComponentTest(new SingleRackVersionCImpl()) //
diff --git a/io.openems.edge.batteryinverter.api/src/io/openems/edge/batteryinverter/api/HybridManagedSymmetricBatteryInverter.java b/io.openems.edge.batteryinverter.api/src/io/openems/edge/batteryinverter/api/HybridManagedSymmetricBatteryInverter.java
index fb9df511140..c437ec3d204 100644
--- a/io.openems.edge.batteryinverter.api/src/io/openems/edge/batteryinverter/api/HybridManagedSymmetricBatteryInverter.java
+++ b/io.openems.edge.batteryinverter.api/src/io/openems/edge/batteryinverter/api/HybridManagedSymmetricBatteryInverter.java
@@ -2,6 +2,7 @@
import org.osgi.annotation.versioning.ProviderType;
+import io.openems.common.channel.PersistencePriority;
import io.openems.common.channel.Unit;
import io.openems.common.types.OpenemsType;
import io.openems.edge.common.channel.Doc;
@@ -39,6 +40,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
DC_DISCHARGE_POWER(Doc.of(OpenemsType.INTEGER) //
.unit(Unit.WATT) //
.text(POWER_DOC_TEXT) //
+ .persistencePriority(PersistencePriority.HIGH) //
),
/**
* DC Charge Energy.
@@ -50,7 +52,9 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
*
*/
DC_CHARGE_ENERGY(Doc.of(OpenemsType.LONG) //
- .unit(Unit.WATT_HOURS)),
+ .unit(Unit.WATT_HOURS) //
+ .persistencePriority(PersistencePriority.HIGH) //
+ ),
/**
* DC Discharge Energy.
*
@@ -61,7 +65,9 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
*
*/
DC_DISCHARGE_ENERGY(Doc.of(OpenemsType.LONG) //
- .unit(Unit.WATT_HOURS)),;
+ .unit(Unit.WATT_HOURS) //
+ .persistencePriority(PersistencePriority.HIGH) //
+ );
private final Doc doc;
diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/Config.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/Config.java
index 110537aa802..ccacdf58560 100644
--- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/Config.java
+++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/Config.java
@@ -29,7 +29,7 @@
String modbus_id() default "modbus0";
@AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
- String Modbus_target() default "";
+ String Modbus_target() default "(enabled=true)";
String webconsole_configurationFactory_nameHint() default "Battery-Inverter KACO blueplanet gridsave [{id}]";
diff --git a/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/Config.java b/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/Config.java
index 50f12485815..b65ebf63e7f 100644
--- a/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/Config.java
+++ b/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/Config.java
@@ -32,7 +32,7 @@
StartStopConfig startStop() default StartStopConfig.AUTO;
@AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
- String Modbus_target() default "";
+ String Modbus_target() default "(enabled=true)";
@AttributeDefinition(name = "Watchdog", description = "Sets the watchdog timer interval in seconds, 0=disable")
int watchdoginterval() default 0;
diff --git a/io.openems.edge.batteryinverter.sunspec/src/io/openems/edge/batteryinverter/sunspec/Config.java b/io.openems.edge.batteryinverter.sunspec/src/io/openems/edge/batteryinverter/sunspec/Config.java
index 63907168eda..9f83d153962 100644
--- a/io.openems.edge.batteryinverter.sunspec/src/io/openems/edge/batteryinverter/sunspec/Config.java
+++ b/io.openems.edge.batteryinverter.sunspec/src/io/openems/edge/batteryinverter/sunspec/Config.java
@@ -26,7 +26,7 @@
int readFromCommonBlockNo() default 1;
@AttributeDefinition(name = "Modbus target filter", description = "This is auto-generated by 'Modbus-ID'.")
- String Modbus_target() default "";
+ String Modbus_target() default "(enabled=true)";
String webconsole_configurationFactory_nameHint() default "Battery-Inverter SunSpec [{id}]";
diff --git a/io.openems.edge.bosch.bpts5hybrid/.classpath b/io.openems.edge.bosch.bpts5hybrid/.classpath
new file mode 100644
index 00000000000..7a6fc254361
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/.classpath
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/io.openems.edge.bosch.bpts5hybrid/.gitignore b/io.openems.edge.bosch.bpts5hybrid/.gitignore
new file mode 100644
index 00000000000..c2b941a96de
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/.gitignore
@@ -0,0 +1,2 @@
+/bin_test/
+/generated/
diff --git a/io.openems.edge.bosch.bpts5hybrid/.project b/io.openems.edge.bosch.bpts5hybrid/.project
new file mode 100644
index 00000000000..cc1c0271c97
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/.project
@@ -0,0 +1,23 @@
+
+
+ io.openems.edge.bosch.bpts5hybrid
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ bndtools.core.bndbuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ bndtools.core.bndnature
+
+
diff --git a/io.openems.edge.bosch.bpts5hybrid/bnd.bnd b/io.openems.edge.bosch.bpts5hybrid/bnd.bnd
new file mode 100644
index 00000000000..5a62b121efc
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/bnd.bnd
@@ -0,0 +1,22 @@
+Bundle-Name: OpenEMS Edge Bosch BPT-S 5 Hybrid
+Bundle-Vendor: OpenEMS Association e.V.
+Bundle-License: https://opensource.org/licenses/EPL-2.0
+Bundle-Version: 1.0.0.${tstamp}
+
+-buildpath: \
+ ${buildpath},\
+ io.openems.common,\
+ io.openems.edge.common,\
+ io.openems.edge.ess.api;version=latest,\
+ io.openems.edge.meter.api;version=latest,\
+ org.eclipse.jetty.client;version='9.4.35',\
+ org.eclipse.jetty.http;version='9.4.35',\
+ org.eclipse.jetty.util;version='9.4.35',\
+ org.jsoup;version='1.10.2',\
+
+-testpath: \
+ ${testpath},\
+ org.eclipse.jetty.client,\
+ org.eclipse.jetty.http,\
+ org.eclipse.jetty.util,\
+ org.eclipse.jetty.io,\
diff --git a/io.openems.edge.bosch.bpts5hybrid/readme.adoc b/io.openems.edge.bosch.bpts5hybrid/readme.adoc
new file mode 100644
index 00000000000..9cfa19139fa
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/readme.adoc
@@ -0,0 +1,3 @@
+= Bosch BPT-S 5 Hybrid
+
+https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.bosch.bpts5hybrid[Source Code icon:github[]]
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridApiClient.java b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridApiClient.java
new file mode 100644
index 00000000000..a4f8af4f97d
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridApiClient.java
@@ -0,0 +1,212 @@
+package io.openems.edge.bosch.bpts5hybrid.core;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+
+import io.openems.common.exceptions.OpenemsException;
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+
+public class BoschBpts5HybridApiClient {
+
+ private static final String POST_REQUEST_DATA = "action=get.hyb.overview&flow=1";
+ private static final String REQUEST_LOG_BOOK_VIEW = "&action=get.logbookview&page=0&id=&type=BATTERY&dtype=";
+ private static final String GET_VALUES_URL_PART = "/cgi-bin/ipcclient.fcgi?";
+ private static String BASE_URL;
+ private String wui_sid;
+ private Integer pvLeistungWatt = Integer.valueOf(0);
+ private Integer soc = Integer.valueOf(0);
+ private Integer einspeisung = Integer.valueOf(0);
+ private Integer batterieLadeStrom = Integer.valueOf(0);
+ private Integer verbrauchVonPv = Integer.valueOf(0);
+ private Integer verbrauchVonBatterie = Integer.valueOf(0);
+ private Integer strombezugAusNetz = Integer.valueOf(0);
+ private HttpClient httpClient;
+
+ public BoschBpts5HybridApiClient(String ipaddress) {
+ BASE_URL = "http://"+ipaddress;
+ httpClient = new HttpClient();
+ httpClient.setConnectTimeout(5000);
+ httpClient.setFollowRedirects(true);
+ try {
+ httpClient.start();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ connect();
+ }
+
+ public void connect() {
+ try {
+ wui_sid = getWuiSidRequest();
+ } catch (OpenemsNamedException e) {
+ wui_sid = "";
+ e.printStackTrace();
+ }
+ }
+
+ private String getWuiSidRequest() throws OpenemsNamedException {
+ try {
+ ContentResponse response = httpClient.GET(BASE_URL);
+
+ int status = response.getStatus();
+ if(status < 300) {
+ String body = response.getContentAsString();
+ return extractWuiSidFromBody(body);
+ } else {
+ throw new OpenemsException(
+ "Error while reading from Bosch BPT-S 5. Response code: " + status);
+ }
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ throw new OpenemsException(
+ "Unable to read from Bosch BPT-S 5. " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+ }
+
+ private String extractWuiSidFromBody(String body) throws OpenemsException {
+ int index = body.indexOf("WUI_SID=");
+
+ if(index < 0) {
+ throw new OpenemsException(
+ "Error while extracting WUI_SID. Body was= " + body);
+ }
+
+ return body.substring(index + 9, index + 9 + 15);
+ }
+
+ public void retreiveValues() throws OpenemsException {
+ Request postRequest = httpClient.POST(BASE_URL+GET_VALUES_URL_PART+wui_sid);
+ postRequest.timeout(5, TimeUnit.SECONDS);
+ postRequest.header(HttpHeader.CONTENT_TYPE, "text/plain");
+ postRequest.content(new StringContentProvider(POST_REQUEST_DATA));
+
+ ContentResponse response;
+
+ try {
+ response = postRequest.send();
+
+ int status = response.getStatus();
+
+ if (status < 300) {
+ extractValuesFromAnswer(response.getContentAsString());
+ } else {
+ throw new OpenemsException(
+ "Error while reading from Bosch BPT-S 5. Response code: " + status);
+ }
+ } catch (InterruptedException | TimeoutException | ExecutionException | OpenemsNamedException e) {
+ throw new OpenemsException(
+ "Unable to read from Bosch BPT-S 5. " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+ }
+
+ public int retreiveBatterieStatus() throws OpenemsException {
+ try {
+ ContentResponse response = httpClient.GET(BASE_URL+GET_VALUES_URL_PART+wui_sid+REQUEST_LOG_BOOK_VIEW);
+
+ int status = response.getStatus();
+ if(status < 300) {
+ String content = response.getContentAsString();
+ Document document = Jsoup.parse(content);
+ Element tableNode = document.select("table").get(0);
+ Element firstRow = tableNode.select("tr").get(0);
+ String firstRowText = firstRow.text();
+ if(firstRowText.contains("Störung") && !firstRowText.contains("Keine")) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ } else {
+ throw new OpenemsException(
+ "Error while reading from Bosch BPT-S 5. Response code: " + status);
+ }
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ throw new OpenemsException(
+ "Unable to read from Bosch BPT-S 5. " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+ }
+
+ private void extractValuesFromAnswer(String body) throws OpenemsNamedException {
+ if(body.contains("session invalid")) {
+ getWuiSidRequest();
+ return;
+ }
+
+ String[] values = body.split("\\|");
+
+// pvLeistungProzent = Integer.valueOf(values[1]);
+
+ pvLeistungWatt = parseWattValue(values[2]);
+
+ soc = Integer.valueOf(values[3]);
+
+// autarkieGrad = Float.valueOf(values[5]).floatValue();
+
+// currentOverallConsumption = parseWattValue(values[6]);
+
+// gridStatusString = values[7];
+
+// systemStatusString = values[9];
+
+ batterieLadeStrom = parseWattValue(values[10]);
+
+ einspeisung = parseWattValue(values[11]);
+
+ verbrauchVonPv = parseWattValue(values[12]);
+
+ verbrauchVonBatterie = parseWattValue(values[13]);
+
+ if(values.length<15) {
+ strombezugAusNetz = 0;
+ }
+ else {
+ strombezugAusNetz = parseWattValue(values[14]);
+ }
+ }
+
+ private Integer parseWattValue(String inputString) {
+ if(inputString.trim().length() == 0 || inputString.contains("nbsp;")) {
+ return Integer.valueOf(0);
+ }
+
+ String wattString = inputString.replace("kW", " ").replace("von"," ").trim();
+ return Integer.valueOf((int) (Float.parseFloat(wattString) * 1000.0f));
+ }
+
+ public Integer getCurrentSoc() {
+ return soc;
+ }
+
+ public Integer getCurrentChargePower() {
+ return batterieLadeStrom;
+ }
+
+ public Integer getCurrentStromAusNetz() {
+ return strombezugAusNetz;
+ }
+
+ public Integer getCurrentEinspeisung() {
+ return einspeisung;
+ }
+
+ public Integer getCurrentDischargePower() {
+ return verbrauchVonBatterie;
+ }
+
+ public Integer getCurrentPvProduction() {
+ return pvLeistungWatt;
+ }
+
+ public Integer getCurrentVerbrauchVonPv() {
+ return verbrauchVonPv;
+ }
+}
diff --git a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCore.java b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCore.java
new file mode 100644
index 00000000000..043e0553529
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCore.java
@@ -0,0 +1,71 @@
+package io.openems.edge.bosch.bpts5hybrid.core;
+
+import java.util.Optional;
+
+import io.openems.common.channel.Level;
+import io.openems.edge.bosch.bpts5hybrid.ess.BoschBpts5HybridEss;
+import io.openems.edge.bosch.bpts5hybrid.meter.BoschBpts5HybridMeter;
+import io.openems.edge.bosch.bpts5hybrid.pv.BoschBpts5HybridPv;
+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;
+
+public interface BoschBpts5HybridCore extends OpenemsComponent {
+
+ public void setEss(BoschBpts5HybridEss ess);
+
+ public void setPv(BoschBpts5HybridPv boschBpts5HybridPv);
+
+ public void setMeter(BoschBpts5HybridMeter boschBpts5HybridMeter);
+
+ public Optional getEss();
+
+ public Optional getPv();
+
+ public Optional getMeter();
+
+ public enum CoreChannelId implements io.openems.edge.common.channel.ChannelId {
+ SLAVE_COMMUNICATION_FAILED(Doc.of(Level.FAULT));
+
+ private final Doc doc;
+
+ private CoreChannelId(Doc doc) {
+ this.doc = doc;
+ }
+
+ @Override
+ public Doc doc() {
+ return this.doc;
+ }
+ }
+
+ /**
+ * Gets the Channel for {@link ChannelId#SLAVE_COMMUNICATION_FAILED}.
+ *
+ * @return the Channel
+ */
+ public default StateChannel getSlaveCommunicationFailedChannel() {
+ return this.channel(CoreChannelId.SLAVE_COMMUNICATION_FAILED);
+ }
+
+ /**
+ * Gets the Slave Communication Failed State. See
+ * {@link ChannelId#SLAVE_COMMUNICATION_FAILED}.
+ *
+ * @return the Channel {@link Value}
+ */
+ public default Value getSlaveCommunicationFailed() {
+ return this.getSlaveCommunicationFailedChannel().value();
+ }
+
+ /**
+ * Internal method to set the 'nextValue' on
+ * {@link ChannelId#SLAVE_COMMUNICATION_FAILED} Channel.
+ *
+ * @param value the next value
+ */
+ public default void _setSlaveCommunicationFailed(boolean value) {
+ this.getSlaveCommunicationFailedChannel().setNextValue(value);
+ }
+}
diff --git a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCoreImpl.java b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCoreImpl.java
new file mode 100644
index 00000000000..89e7ca15c00
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCoreImpl.java
@@ -0,0 +1,109 @@
+package io.openems.edge.bosch.bpts5hybrid.core;
+
+import java.io.IOException;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+import org.osgi.service.metatype.annotations.Designate;
+
+import io.openems.edge.bosch.bpts5hybrid.ess.BoschBpts5HybridEss;
+import io.openems.edge.bosch.bpts5hybrid.meter.BoschBpts5HybridMeter;
+import io.openems.edge.bosch.bpts5hybrid.pv.BoschBpts5HybridPv;
+import io.openems.edge.common.component.AbstractOpenemsComponent;
+import io.openems.edge.common.component.OpenemsComponent;
+import io.openems.edge.common.event.EdgeEventConstants;
+
+@Designate(ocd = Config.class, factory = true)
+@Component(//
+ name = "Bosch.BPTS5Hybrid.Core", //
+ immediate = true, //
+ configurationPolicy = ConfigurationPolicy.REQUIRE, //
+ property = { //
+ EventConstants.EVENT_TOPIC + "=" + EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE //
+ } //
+)
+public class BoschBpts5HybridCoreImpl extends AbstractOpenemsComponent
+ implements BoschBpts5HybridCore, OpenemsComponent, EventHandler {
+
+ @Reference
+ private ConfigurationAdmin cm;
+
+ private BoschBpts5HybridReadWorker worker = null;
+
+ private AtomicReference ess = new AtomicReference<>();
+ private AtomicReference pv = new AtomicReference<>();
+ private AtomicReference meter = new AtomicReference<>();
+
+ public BoschBpts5HybridCoreImpl() {
+ super(//
+ OpenemsComponent.ChannelId.values(), //
+ CoreChannelId.values());
+ }
+
+ @Activate
+ void activate(ComponentContext context, Config config) throws ConfigurationException, IOException {
+ super.activate(context, config.id(), config.alias(), config.enabled());
+ this.worker = new BoschBpts5HybridReadWorker(this, config.ipaddress(), config.interval());
+ this.worker.activate(config.id());
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ if (this.worker != null) {
+ this.worker.deactivate();
+ }
+ super.deactivate();
+ }
+
+ @Override
+ public void handleEvent(Event event) {
+ if (!this.isEnabled() || this.worker == null) {
+ return;
+ }
+
+ switch (event.getTopic()) {
+ case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE:
+ this.worker.triggerNextRun();
+ break;
+ }
+ }
+
+ public void setEss(BoschBpts5HybridEss boschBpts5HybridEss) {
+ this.ess.set(boschBpts5HybridEss);
+ }
+
+ public Optional getEss() {
+ return Optional.ofNullable(ess.get());
+ }
+
+ @Override
+ public void setPv(BoschBpts5HybridPv boschBpts5HybridPv) {
+ this.pv.set(boschBpts5HybridPv);
+ }
+
+ @Override
+ public Optional getPv() {
+ return Optional.ofNullable(pv.get());
+ }
+
+ @Override
+ public void setMeter(BoschBpts5HybridMeter boschBpts5HybridMeter) {
+ this.meter.set(boschBpts5HybridMeter);
+ }
+
+ @Override
+ public Optional getMeter() {
+ return Optional.ofNullable(meter.get());
+ }
+}
diff --git a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridReadWorker.java b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridReadWorker.java
new file mode 100644
index 00000000000..35cbb2076a4
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridReadWorker.java
@@ -0,0 +1,83 @@
+package io.openems.edge.bosch.bpts5hybrid.core;
+
+import java.time.LocalDateTime;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.openems.common.exceptions.OpenemsException;
+import io.openems.common.worker.AbstractCycleWorker;
+
+public class BoschBpts5HybridReadWorker extends AbstractCycleWorker{
+
+ private final Logger log = LoggerFactory.getLogger(BoschBpts5HybridReadWorker.class);
+ private final BoschBpts5HybridCoreImpl parent;
+ private final BoschBpts5HybridApiClient apiClient;
+ private int refreshIntervalSeconds;
+ private LocalDateTime refreshTime;
+
+ public BoschBpts5HybridReadWorker(BoschBpts5HybridCoreImpl parent, String ipaddress, int interval) {
+ this.parent = parent;
+ this.refreshIntervalSeconds = interval;
+ this.apiClient = new BoschBpts5HybridApiClient(ipaddress);
+ }
+
+ @Override
+ public void activate(String name) {
+ super.activate(name);
+ this.refreshTime = LocalDateTime.now();
+ }
+
+ @Override
+ protected void forever() throws Throwable {
+ if (this.refreshTime.plusSeconds(refreshIntervalSeconds).isBefore(LocalDateTime.now())) {
+ this.refreshTime = LocalDateTime.now();
+
+ try {
+ this.apiClient.retreiveValues();
+ int batterieStatus = this.apiClient.retreiveBatterieStatus();
+ if(batterieStatus == 0) {
+ this.parent._setSlaveCommunicationFailed(false);
+ }
+ else {
+ this.parent._setSlaveCommunicationFailed(true);
+ }
+ } catch (OpenemsException e) {
+ log.error(e.getMessage());
+ this.parent._setSlaveCommunicationFailed(true);
+ this.apiClient.connect();
+ return;
+ }
+ }
+
+ this.parent.getEss().ifPresent(ess -> {
+ if(this.apiClient.getCurrentDischargePower() > 0) {
+ ess._setActivePower(this.apiClient.getCurrentDischargePower() + this.apiClient.getCurrentVerbrauchVonPv());
+ }
+ else {
+ int currentDirectUsageOfPv = this.apiClient.getCurrentVerbrauchVonPv() + this.apiClient.getCurrentEinspeisung();
+ ess._setActivePower(currentDirectUsageOfPv);
+ }
+ ess._setSoc(this.apiClient.getCurrentSoc());
+ });
+
+ this.parent.getPv().ifPresent(pv -> {
+ pv._setActualPower(this.apiClient.getCurrentPvProduction());
+ });
+
+ this.parent.getMeter().ifPresent(meter -> {
+ if(this.apiClient.getCurrentStromAusNetz() > 0) {
+ meter._setActivePower(this.apiClient.getCurrentStromAusNetz());
+ }
+ else {
+ if(this.apiClient.getCurrentEinspeisung() > 0) {
+ meter._setActivePower(-1 * this.apiClient.getCurrentEinspeisung());
+ }
+ else{
+ meter._setActivePower(0);
+ }
+ }
+ });
+ }
+
+}
diff --git a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/Config.java b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/Config.java
new file mode 100644
index 00000000000..877e909cc2a
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/core/Config.java
@@ -0,0 +1,28 @@
+package io.openems.edge.bosch.bpts5hybrid.core;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+
+@ObjectClassDefinition(//
+ name = "Bosch BPT-S 5 Core", //
+ description = "Bosch BPT-S 5 Hybrid core component")
+@interface Config {
+
+ @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component")
+ String id() default "boschBpts5hybridCore0";
+
+ @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID")
+ String alias() default "";
+
+ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?")
+ boolean enabled() default true;
+
+ @AttributeDefinition(name = "IP-Address", description = "IPv4 address of the Bosch BPT-S 5 Hybrid")
+ String ipaddress() default "192.168.178.22";
+
+ @AttributeDefinition(name = "Update Interval", description = "Update interval in seconds")
+ int interval() default 2;
+
+ String webconsole_configurationFactory_nameHint() default "Bosch BPT-S 5 Hybrid Core [{id}]";
+}
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/ess/BoschBpts5HybridEss.java b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/ess/BoschBpts5HybridEss.java
new file mode 100644
index 00000000000..efadd0a58f2
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/ess/BoschBpts5HybridEss.java
@@ -0,0 +1,94 @@
+package io.openems.edge.bosch.bpts5hybrid.ess;
+
+import java.io.IOException;
+
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.metatype.annotations.Designate;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.edge.bosch.bpts5hybrid.core.BoschBpts5HybridCore;
+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.ess.api.SymmetricEss;
+
+@Designate(ocd = Config.class, factory = true)
+@Component(//
+ name = "Bosch.BPTS5Hybrid.Ess", //
+ immediate = true, //
+ configurationPolicy = ConfigurationPolicy.REQUIRE, //
+ property = { //
+ EventConstants.EVENT_TOPIC + "=" + EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE //
+ } //
+)
+public class BoschBpts5HybridEss extends AbstractOpenemsComponent implements SymmetricEss, OpenemsComponent {
+
+ private final int CAPACITY = 8_800;
+
+ @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY)
+ private BoschBpts5HybridCore core;
+
+ @Reference
+ private ConfigurationAdmin cm;
+
+ public BoschBpts5HybridEss() {
+ super(//
+ OpenemsComponent.ChannelId.values(), //
+ SymmetricEss.ChannelId.values(), //
+ ChannelId.values() //
+ );
+ this._setCapacity(CAPACITY); // TODO: get from read worker
+ }
+
+ @Activate
+ void activate(ComponentContext context, Config config)
+ throws OpenemsNamedException, ConfigurationException, IOException {
+ super.activate(context, config.id(), config.alias(), config.enabled());
+
+ // update filter for 'core'
+ if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "core", config.core_id())) {
+ return;
+ }
+ this.core.setEss(this);
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ if (this.core != null) {
+ this.core.setEss(null);
+ }
+ super.deactivate();
+ }
+
+ @Override
+ public String debugLog() {
+ return "SoC:" + this.getSoc().asString() //
+ + "|L:" + this.getActivePower().asString();
+ }
+
+ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
+ ;
+ private final Doc doc;
+
+ private ChannelId(Doc doc) {
+ this.doc = doc;
+ }
+
+ @Override
+ public Doc doc() {
+ return this.doc;
+ }
+ }
+}
diff --git a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/ess/Config.java b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/ess/Config.java
new file mode 100644
index 00000000000..03cf296b632
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/ess/Config.java
@@ -0,0 +1,27 @@
+package io.openems.edge.bosch.bpts5hybrid.ess;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(//
+ name = "Bosch BPT-S 5 ESS", //
+ description = "Bosch BPT-S 5 Hybrid energy storage system - ESS")
+@interface Config {
+
+ @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component")
+ String id() default "ess0";
+
+ @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID")
+ String alias() default "";
+
+ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?")
+ boolean enabled() default true;
+
+ @AttributeDefinition(name = "Core-ID", description = "Component-ID of \"Bosch BPT-S 5 Hybrid Core\" component ?")
+ String core_id() default "boschBpts5hybridCore0";
+
+ @AttributeDefinition(name = "Core target filter", description = "This is auto-generated by 'Core-ID'.")
+ String core_target() default "(enabled=true)";
+
+ String webconsole_configurationFactory_nameHint() default "Bosch BPT-S 5 Hybrid ESS [{id}]";
+}
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeter.java b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeter.java
new file mode 100644
index 00000000000..203b7e19877
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeter.java
@@ -0,0 +1,95 @@
+package io.openems.edge.bosch.bpts5hybrid.meter;
+
+import java.io.IOException;
+
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.metatype.annotations.Designate;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.edge.bosch.bpts5hybrid.core.BoschBpts5HybridCore;
+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.meter.api.MeterType;
+import io.openems.edge.meter.api.SymmetricMeter;
+
+@Designate(ocd = Config.class, factory = true)
+@Component(//
+ name = "Bosch.BPTS5Hybrid.Meter", //
+ immediate = true, //
+ configurationPolicy = ConfigurationPolicy.REQUIRE, //
+ property = { //
+ EventConstants.EVENT_TOPIC + "=" + EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE //
+ } //
+)
+public class BoschBpts5HybridMeter extends AbstractOpenemsComponent implements SymmetricMeter, OpenemsComponent{
+
+ @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY)
+ private BoschBpts5HybridCore core;
+
+ @Reference
+ private ConfigurationAdmin cm;
+
+ public BoschBpts5HybridMeter() {
+ super(//
+ OpenemsComponent.ChannelId.values(), //
+ SymmetricMeter.ChannelId.values(),//
+ ChannelId.values() //
+ );
+ }
+
+ @Activate
+ void activate(ComponentContext context, Config config) throws OpenemsNamedException, ConfigurationException, IOException {
+ super.activate(context, config.id(), config.alias(), config.enabled());
+
+ // update filter for 'core'
+ if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "core", config.core_id())) {
+ return;
+ }
+ this.core.setMeter(this);
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ if (this.core != null) {
+ this.core.setMeter(null);
+ }
+ super.deactivate();
+ }
+
+ @Override
+ public String debugLog() {
+ return "Meter: Power:" + this.getActivePower().get();
+ }
+
+ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
+ ;
+ private final Doc doc;
+
+ private ChannelId(Doc doc) {
+ this.doc = doc;
+ }
+
+ @Override
+ public Doc doc() {
+ return this.doc;
+ }
+ }
+
+ @Override
+ public MeterType getMeterType() {
+ return MeterType.GRID;
+ }
+}
diff --git a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/meter/Config.java b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/meter/Config.java
new file mode 100644
index 00000000000..260fceea4b3
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/meter/Config.java
@@ -0,0 +1,27 @@
+package io.openems.edge.bosch.bpts5hybrid.meter;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(//
+ name = "Bosch BPT-S 5 Meter", //
+ description = "Bosch BPT-S 5 Hybrid energy storage system - Meter component")
+@interface Config {
+
+ @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component")
+ String id() default "meter0";
+
+ @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID")
+ String alias() default "";
+
+ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?")
+ boolean enabled() default true;
+
+ @AttributeDefinition(name = "Core-ID", description = "Component-ID of \"Bosch BPT-S 5 Hybrid Core\" component ?")
+ String core_id() default "boschBpts5hybridCore0";
+
+ @AttributeDefinition(name = "Core target filter", description = "This is auto-generated by 'Core-ID'.")
+ String core_target() default "(enabled=true)";
+
+ String webconsole_configurationFactory_nameHint() default "Bosch BPT-S 5 Hybrid Meter [{id}]";
+}
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/pv/BoschBpts5HybridPv.java b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/pv/BoschBpts5HybridPv.java
new file mode 100644
index 00000000000..6d38cd186cb
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/pv/BoschBpts5HybridPv.java
@@ -0,0 +1,92 @@
+package io.openems.edge.bosch.bpts5hybrid.pv;
+
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.metatype.annotations.Designate;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.edge.bosch.bpts5hybrid.core.BoschBpts5HybridCore;
+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.ess.dccharger.api.EssDcCharger;
+
+import java.io.IOException;
+
+@Designate(ocd = Config.class, factory = true)
+@Component(//
+ name = "Bosch.BPTS5Hybrid.Pv", //
+ immediate = true, //
+ configurationPolicy = ConfigurationPolicy.REQUIRE, //
+ property = { //
+ EventConstants.EVENT_TOPIC + "=" + EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE //
+ } //
+)
+public class BoschBpts5HybridPv extends AbstractOpenemsComponent implements EssDcCharger, OpenemsComponent{
+
+ private final int PEAK_POWER = 5_500;
+
+ @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY)
+ private BoschBpts5HybridCore core;
+
+ @Reference
+ private ConfigurationAdmin cm;
+
+ public BoschBpts5HybridPv() {
+ super(//
+ OpenemsComponent.ChannelId.values(), //
+ EssDcCharger.ChannelId.values(),//
+ ChannelId.values() //
+ );
+ this._setMaxActualPower(PEAK_POWER); //TODO: get from read worker
+ }
+
+ @Activate
+ void activate(ComponentContext context, Config config) throws OpenemsNamedException, ConfigurationException, IOException {
+ super.activate(context, config.id(), config.alias(), config.enabled());
+
+ // update filter for 'core'
+ if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "core", config.core_id())) {
+ return;
+ }
+ this.core.setPv(this);
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ if (this.core != null) {
+ this.core.setEss(null);
+ }
+ super.deactivate();
+ }
+
+ @Override
+ public String debugLog() {
+ return "PV:" + this.getActualPower().asString();
+ }
+
+ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
+ ;
+ private final Doc doc;
+
+ private ChannelId(Doc doc) {
+ this.doc = doc;
+ }
+
+ @Override
+ public Doc doc() {
+ return this.doc;
+ }
+ }
+}
diff --git a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/pv/Config.java b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/pv/Config.java
new file mode 100644
index 00000000000..3fffb967b92
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/pv/Config.java
@@ -0,0 +1,27 @@
+package io.openems.edge.bosch.bpts5hybrid.pv;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(//
+ name = "Bosch BPT-S 5 PV", //
+ description = "Bosch BPT-S 5 Hybrid energy storage system - PV component")
+@interface Config {
+
+ @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component")
+ String id() default "charger0";
+
+ @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID")
+ String alias() default "";
+
+ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?")
+ boolean enabled() default true;
+
+ @AttributeDefinition(name = "Core-ID", description = "Component-ID of \"Bosch BPT-S 5 Hybrid Core\" component ?")
+ String core_id() default "boschBpts5hybridCore0";
+
+ @AttributeDefinition(name = "Core target filter", description = "This is auto-generated by 'Core-ID'.")
+ String core_target() default "(enabled=true)";
+
+ String webconsole_configurationFactory_nameHint() default "Bosch BPT-S 5 Hybrid PV [{id}]";
+}
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/test/.gitignore b/io.openems.edge.bosch.bpts5hybrid/test/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCoreImplTest.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCoreImplTest.java
new file mode 100644
index 00000000000..0016e3dbc9c
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCoreImplTest.java
@@ -0,0 +1,23 @@
+package io.openems.edge.bosch.bpts5hybrid.core;
+
+import org.junit.Test;
+
+import io.openems.edge.common.test.ComponentTest;
+import io.openems.edge.common.test.DummyConfigurationAdmin;
+
+public class BoschBpts5HybridCoreImplTest {
+
+ private static final String CORE_ID = "core0";
+
+ @Test
+ public void test() throws Exception {
+ new ComponentTest(new BoschBpts5HybridCoreImpl()) //
+ .addReference("cm", new DummyConfigurationAdmin()) //
+ .activate(MyConfig.create() //
+ .setId(CORE_ID) //
+ .setIpaddress("127.0.0.1") //
+ .setInterval(2) //
+ .build()) //
+ ;
+ }
+}
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/core/MyConfig.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/core/MyConfig.java
new file mode 100644
index 00000000000..cedfbf17dfb
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/core/MyConfig.java
@@ -0,0 +1,63 @@
+package io.openems.edge.bosch.bpts5hybrid.core;
+
+import io.openems.edge.common.test.AbstractComponentConfig;
+
+@SuppressWarnings("all")
+public class MyConfig extends AbstractComponentConfig implements Config {
+
+ public static class Builder {
+ private String id = null;
+ private String modbusId = null;
+ public String ipaddress;
+ public int interval;
+
+ private Builder() {
+ }
+
+ public Builder setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder setIpaddress(String ipaddress) {
+ this.ipaddress = ipaddress;
+ return this;
+ }
+
+ public Builder setInterval(int interval) {
+ this.interval = interval;
+ return this;
+ }
+
+ public MyConfig build() {
+ return new MyConfig(this);
+ }
+ }
+
+ /**
+ * Create a Config builder.
+ *
+ * @return a {@link Builder}
+ */
+ public static Builder create() {
+ return new Builder();
+ }
+
+ private final Builder builder;
+
+ private MyConfig(Builder builder) {
+ super(Config.class, builder.id);
+ this.builder = builder;
+ }
+
+ @Override
+ public String ipaddress() {
+ return this.builder.ipaddress;
+ }
+
+ @Override
+ public int interval() {
+ return this.builder.interval;
+ }
+
+}
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/ess/BoschBpts5HybridEssTest.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/ess/BoschBpts5HybridEssTest.java
new file mode 100644
index 00000000000..2cc138b0caf
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/ess/BoschBpts5HybridEssTest.java
@@ -0,0 +1,34 @@
+package io.openems.edge.bosch.bpts5hybrid.ess;
+
+import org.junit.Test;
+
+import io.openems.edge.bosch.bpts5hybrid.core.BoschBpts5HybridCoreImpl;
+import io.openems.edge.common.test.ComponentTest;
+import io.openems.edge.common.test.DummyConfigurationAdmin;
+
+public class BoschBpts5HybridEssTest {
+
+ private static final String CORE_ID = "core0";
+ private static final String ESS_ID = "ess0";
+
+ @Test
+ public void test() throws Exception {
+ BoschBpts5HybridCoreImpl core = new BoschBpts5HybridCoreImpl();
+ new ComponentTest(new BoschBpts5HybridCoreImpl()) //
+ .addReference("cm", new DummyConfigurationAdmin()) //
+ .activate(io.openems.edge.bosch.bpts5hybrid.core.MyConfig.create() //
+ .setId(CORE_ID) //
+ .setIpaddress("127.0.0.1") //
+ .setInterval(2) //
+ .build()); //
+
+ new ComponentTest(new BoschBpts5HybridEss()) //
+ .addReference("cm", new DummyConfigurationAdmin()) //
+ .addReference("core", core) //
+ .activate(MyConfig.create() //
+ .setId(ESS_ID) //
+ .setCoreId(CORE_ID) //
+ .build()) //
+ ;
+ }
+}
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/ess/MyConfig.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/ess/MyConfig.java
new file mode 100644
index 00000000000..9bcff8f1db0
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/ess/MyConfig.java
@@ -0,0 +1,57 @@
+package io.openems.edge.bosch.bpts5hybrid.ess;
+
+import io.openems.common.utils.ConfigUtils;
+import io.openems.edge.common.test.AbstractComponentConfig;
+
+@SuppressWarnings("all")
+public class MyConfig extends AbstractComponentConfig implements Config {
+
+ protected static class Builder {
+ private String id = null;
+ public String coreId;
+
+ private Builder() {
+ }
+
+ public Builder setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder setCoreId(String coreId) {
+ this.coreId = coreId;
+ return this;
+ }
+
+ public MyConfig build() {
+ return new MyConfig(this);
+ }
+ }
+
+ /**
+ * Create a Config builder.
+ *
+ * @return a {@link Builder}
+ */
+ public static Builder create() {
+ return new Builder();
+ }
+
+ private final Builder builder;
+
+ private MyConfig(Builder builder) {
+ super(Config.class, builder.id);
+ this.builder = builder;
+ }
+
+ @Override
+ public String core_id() {
+ return this.builder.coreId;
+ }
+
+ @Override
+ public String core_target() {
+ return ConfigUtils.generateReferenceTargetFilter(this.id(), this.core_id());
+ }
+
+}
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterTest.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterTest.java
new file mode 100644
index 00000000000..066a955a29b
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterTest.java
@@ -0,0 +1,34 @@
+package io.openems.edge.bosch.bpts5hybrid.meter;
+
+import org.junit.Test;
+
+import io.openems.edge.bosch.bpts5hybrid.core.BoschBpts5HybridCoreImpl;
+import io.openems.edge.common.test.ComponentTest;
+import io.openems.edge.common.test.DummyConfigurationAdmin;
+
+public class BoschBpts5HybridMeterTest {
+
+ private static final String CORE_ID = "core0";
+ private static final String METER_ID = "meter0";
+
+ @Test
+ public void test() throws Exception {
+ BoschBpts5HybridCoreImpl core = new BoschBpts5HybridCoreImpl();
+ new ComponentTest(new BoschBpts5HybridCoreImpl()) //
+ .addReference("cm", new DummyConfigurationAdmin()) //
+ .activate(io.openems.edge.bosch.bpts5hybrid.core.MyConfig.create() //
+ .setId(CORE_ID) //
+ .setIpaddress("127.0.0.1") //
+ .setInterval(2) //
+ .build()); //
+
+ new ComponentTest(new BoschBpts5HybridMeter()) //
+ .addReference("cm", new DummyConfigurationAdmin()) //
+ .addReference("core", core) //
+ .activate(MyConfig.create() //
+ .setId(METER_ID) //
+ .setCoreId(CORE_ID) //
+ .build()) //
+ ;
+ }
+}
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/meter/MyConfig.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/meter/MyConfig.java
new file mode 100644
index 00000000000..d03ebf8e596
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/meter/MyConfig.java
@@ -0,0 +1,57 @@
+package io.openems.edge.bosch.bpts5hybrid.meter;
+
+import io.openems.common.utils.ConfigUtils;
+import io.openems.edge.common.test.AbstractComponentConfig;
+
+@SuppressWarnings("all")
+public class MyConfig extends AbstractComponentConfig implements Config {
+
+ protected static class Builder {
+ private String id = null;
+ public String coreId;
+
+ private Builder() {
+ }
+
+ public Builder setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder setCoreId(String coreId) {
+ this.coreId = coreId;
+ return this;
+ }
+
+ public MyConfig build() {
+ return new MyConfig(this);
+ }
+ }
+
+ /**
+ * Create a Config builder.
+ *
+ * @return a {@link Builder}
+ */
+ public static Builder create() {
+ return new Builder();
+ }
+
+ private final Builder builder;
+
+ private MyConfig(Builder builder) {
+ super(Config.class, builder.id);
+ this.builder = builder;
+ }
+
+ @Override
+ public String core_id() {
+ return this.builder.coreId;
+ }
+
+ @Override
+ public String core_target() {
+ return ConfigUtils.generateReferenceTargetFilter(this.id(), this.core_id());
+ }
+
+}
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/pv/BoschBpts5HybridPvTest.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/pv/BoschBpts5HybridPvTest.java
new file mode 100644
index 00000000000..21b71389758
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/pv/BoschBpts5HybridPvTest.java
@@ -0,0 +1,34 @@
+package io.openems.edge.bosch.bpts5hybrid.pv;
+
+import org.junit.Test;
+
+import io.openems.edge.bosch.bpts5hybrid.core.BoschBpts5HybridCoreImpl;
+import io.openems.edge.common.test.ComponentTest;
+import io.openems.edge.common.test.DummyConfigurationAdmin;
+
+public class BoschBpts5HybridPvTest {
+
+ private static final String CORE_ID = "core0";
+ private static final String CHARGER_ID = "charger0";
+
+ @Test
+ public void test() throws Exception {
+ BoschBpts5HybridCoreImpl core = new BoschBpts5HybridCoreImpl();
+ new ComponentTest(new BoschBpts5HybridCoreImpl()) //
+ .addReference("cm", new DummyConfigurationAdmin()) //
+ .activate(io.openems.edge.bosch.bpts5hybrid.core.MyConfig.create() //
+ .setId(CORE_ID) //
+ .setIpaddress("127.0.0.1") //
+ .setInterval(2) //
+ .build()); //
+
+ new ComponentTest(new BoschBpts5HybridPv()) //
+ .addReference("cm", new DummyConfigurationAdmin()) //
+ .addReference("core", core) //
+ .activate(MyConfig.create() //
+ .setId(CHARGER_ID) //
+ .setCoreId(CORE_ID) //
+ .build()) //
+ ;
+ }
+}
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/pv/MyConfig.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/pv/MyConfig.java
new file mode 100644
index 00000000000..69e0d65e4e2
--- /dev/null
+++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/pv/MyConfig.java
@@ -0,0 +1,57 @@
+package io.openems.edge.bosch.bpts5hybrid.pv;
+
+import io.openems.common.utils.ConfigUtils;
+import io.openems.edge.common.test.AbstractComponentConfig;
+
+@SuppressWarnings("all")
+public class MyConfig extends AbstractComponentConfig implements Config {
+
+ protected static class Builder {
+ private String id = null;
+ public String coreId;
+
+ private Builder() {
+ }
+
+ public Builder setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder setCoreId(String coreId) {
+ this.coreId = coreId;
+ return this;
+ }
+
+ public MyConfig build() {
+ return new MyConfig(this);
+ }
+ }
+
+ /**
+ * Create a Config builder.
+ *
+ * @return a {@link Builder}
+ */
+ public static Builder create() {
+ return new Builder();
+ }
+
+ private final Builder builder;
+
+ private MyConfig(Builder builder) {
+ super(Config.class, builder.id);
+ this.builder = builder;
+ }
+
+ @Override
+ public String core_id() {
+ return this.builder.coreId;
+ }
+
+ @Override
+ public String core_target() {
+ return ConfigUtils.generateReferenceTargetFilter(this.id(), this.core_id());
+ }
+
+}
\ No newline at end of file
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java
index 3be71e6858b..ee15745e4d2 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java
@@ -265,7 +265,22 @@ public ChannelMapper(T element) {
*/
public ChannelMapper m(io.openems.edge.common.channel.ChannelId channelId,
ElementToChannelConverter converter) {
+ return this.m(channelId, converter, new ChannelMetaInfo(element.getStartAddress()));
+ }
+
+ /**
+ * Maps the given element 1-to-1 to the Channel identified by channelId.
+ *
+ * @param channelId the Channel-ID
+ * @param converter the {@link ElementToChannelConverter}
+ * @param channelMetaInfo an object that holds meta information about the
+ * Channel
+ * @return the element parameter
+ */
+ public ChannelMapper m(io.openems.edge.common.channel.ChannelId channelId,
+ ElementToChannelConverter converter, ChannelMetaInfo channelMetaInfo) {
Channel> channel = channel(channelId);
+ channel.setMetaInfo(channelMetaInfo);
this.channelMaps.put(channel, converter);
return this;
}
@@ -363,7 +378,7 @@ public T build() {
* Creates a ChannelMapper that can be used with builder pattern inside the
* protocol definition.
*
- * @param the type of the {@link AbstractModbusElement}d
+ * @param the type of the {@link AbstractModbusElement}d
* @param element the ModbusElement
* @return a {@link ChannelMapper}
*/
@@ -394,32 +409,33 @@ protected final > T m(io.openems.edge.common.
return this.m(channelId, element, ElementToChannelConverter.DIRECT_1_TO_1);
}
+ /**
+ * Maps the given element 1-to-1 to the Channel identified by channelId.
+ *
+ * @param the type of the {@link AbstractModbusElement}d
+ * @param channelId the Channel-ID
+ * @param element the ModbusElement
+ * @param channelMetaInfo an object that holds meta information about the
+ * Channel
+ * @return the element parameter
+ */
+ protected final > T m(io.openems.edge.common.channel.ChannelId channelId,
+ T element, ChannelMetaInfo channelMetaInfo) {
+ return this.m(channelId, element, ElementToChannelConverter.DIRECT_1_TO_1, channelMetaInfo);
+ }
+
/**
* Maps the given element to the Channel identified by channelId, applying the
* given @link{ElementToChannelConverter}.
*
- * @param the type of the {@link AbstractModbusElement}d
- * @param channelId the Channel-ID
- * @param element the ModbusElement
- * @param converter the ElementToChannelConverter
- * @param ignoreDuplicatedSource set to
- * {@link ModbusChannelSource#IGNORE_DUPLICATED_SOURCE}
- * to ignore the check for channels with multiple
- * mapped modbus registers
+ * @param the type of the {@link AbstractModbusElement}d
+ * @param channelId the Channel-ID
+ * @param element the ModbusElement
+ * @param converter the ElementToChannelConverter
* @return the element parameter
*/
protected final > T m(io.openems.edge.common.channel.ChannelId channelId,
- T element, ElementToChannelConverter converter,
- ModbusChannelSource.IgnoreDuplicatedSource ignoreDuplicatedSource) {
- if (ignoreDuplicatedSource != null) {
- try {
- channelId.doc().source(new ModbusChannelSource(element.getStartAddress()));
- } catch (IllegalArgumentException e) {
- throw new IllegalArgumentException(
- "Unable to add Modbus mapping for [" + channelId.id() + "]: " + e.getMessage());
- }
- }
-
+ T element, ElementToChannelConverter converter) {
return new ChannelMapper(element) //
.m(channelId, converter) //
.build();
@@ -429,15 +445,19 @@ protected final > T m(io.openems.edge.common.
* Maps the given element to the Channel identified by channelId, applying the
* given @link{ElementToChannelConverter}.
*
- * @param the type of the {@link AbstractModbusElement}
- * @param channelId the Channel-ID
- * @param element the ModbusElement
- * @param converter the {@link ElementToChannelConverter}
+ * @param the type of the {@link AbstractModbusElement}d
+ * @param channelId the Channel-ID
+ * @param element the ModbusElement
+ * @param converter the ElementToChannelConverter
+ * @param channelMetaInfo an object that holds meta information about the
+ * Channel
* @return the element parameter
*/
protected final > T m(io.openems.edge.common.channel.ChannelId channelId,
- T element, ElementToChannelConverter converter) {
- return this.m(channelId, element, converter, null);
+ T element, ElementToChannelConverter converter, ChannelMetaInfo channelMetaInfo) {
+ return new ChannelMapper(element) //
+ .m(channelId, converter, channelMetaInfo) //
+ .build();
}
public enum BitConverter {
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ChannelMetaInfo.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ChannelMetaInfo.java
new file mode 100644
index 00000000000..f7b69b9c100
--- /dev/null
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ChannelMetaInfo.java
@@ -0,0 +1,47 @@
+package io.openems.edge.bridge.modbus.api;
+
+import java.util.Objects;
+
+/**
+ * Describes a Channel that has a read- or read-and-write-mapping to one Modbus
+ * Register.
+ */
+public class ChannelMetaInfo {
+
+ /**
+ * Holds the Address for Modbus Read Register.
+ */
+ protected final int address;
+
+ public ChannelMetaInfo(int address) {
+ this.address = address;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("0x").append(Integer.toHexString(this.address));
+ return b.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.address);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ChannelMetaInfo other = (ChannelMetaInfo) obj;
+ return this.address == other.address;
+ }
+
+}
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusChannelSource.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ChannelMetaInfoBit.java
similarity index 55%
rename from io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusChannelSource.java
rename to io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ChannelMetaInfoBit.java
index 309bd8c9e59..05d0fabad22 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusChannelSource.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ChannelMetaInfoBit.java
@@ -2,34 +2,18 @@
import java.util.Objects;
-public class ModbusChannelSource {
-
- /**
- * This can be used in {@link AbstractOpenemsModbusComponent} to ignore the
- * check for duplicated source mappings.
- */
- public static final IgnoreDuplicatedSource IGNORE_DUPLICATED_SOURCE = new IgnoreDuplicatedSource();
-
- protected static class IgnoreDuplicatedSource {
- }
-
- /**
- * Holds the Start-Address of the Modbus Register.
- */
- private final int address;
+/**
+ * Describes a Channel that has a read-mapping to a Modbus Coil.
+ */
+public class ChannelMetaInfoBit extends ChannelMetaInfo {
/**
* Holds the index of the bit inside the Register if applicable.
*/
private final int bit;
- public ModbusChannelSource(int address) {
- this.address = address;
- this.bit = -1;
- }
-
- public ModbusChannelSource(int address, int bit) {
- this.address = address;
+ public ChannelMetaInfoBit(int address, int bit) {
+ super(address);
this.bit = bit;
}
@@ -59,7 +43,7 @@ public boolean equals(Object obj) {
if (getClass() != obj.getClass()) {
return false;
}
- ModbusChannelSource other = (ModbusChannelSource) obj;
+ ChannelMetaInfoBit other = (ChannelMetaInfoBit) obj;
return this.address == other.address && this.bit == other.bit;
}
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ChannelMetaInfoReadAndWrite.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ChannelMetaInfoReadAndWrite.java
new file mode 100644
index 00000000000..ae6cff010a0
--- /dev/null
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ChannelMetaInfoReadAndWrite.java
@@ -0,0 +1,49 @@
+package io.openems.edge.bridge.modbus.api;
+
+import java.util.Objects;
+
+/**
+ * Describes a Channel that has a read-and-write-mapping to two Modbus
+ * Registers.
+ */
+public class ChannelMetaInfoReadAndWrite extends ChannelMetaInfo {
+
+ /**
+ * Holds the Address for Modbus Write Register.
+ */
+ private final int writeAddress;
+
+ public ChannelMetaInfoReadAndWrite(int readAddress, int writeAaddress) {
+ super(readAddress);
+ this.writeAddress = writeAaddress;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("READ:0x").append(Integer.toHexString(this.address));
+ b.append(" | WRITE:0x").append(Integer.toHexString(this.writeAddress));
+ return b.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.address, this.writeAddress);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ChannelMetaInfoReadAndWrite other = (ChannelMetaInfoReadAndWrite) obj;
+ return this.address == other.address && this.writeAddress == other.writeAddress;
+ }
+
+}
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ChannelToElementConverter.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ChannelToElementConverter.java
new file mode 100644
index 00000000000..c555352403e
--- /dev/null
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ChannelToElementConverter.java
@@ -0,0 +1,260 @@
+package io.openems.edge.bridge.modbus.api;
+
+import java.util.function.Function;
+
+/**
+ * Provides Functions to convert from Element to Channel and back. Also has some
+ * static convenience functions to facilitate conversion.
+ */
+public class ChannelToElementConverter implements Function