handleSimulationRequest(String edgeId, User user, SimulationRequest request) throws OpenemsNamedException {
+
+ final var simulation = this.parent.simulation;
+ if (simulation == null) {
+ throw new OpenemsException("simulation unavailable");
+ }
+
+ return simulation.handleRequest(edgeId, user, request);
+ }
+
private record LogSystemExecuteCommend(//
String edgeId, // non-null
User user, // non-null
diff --git a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/UiWebsocketImpl.java b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/UiWebsocketImpl.java
index 24d90076040..98fe60fb108 100644
--- a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/UiWebsocketImpl.java
+++ b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/UiWebsocketImpl.java
@@ -13,6 +13,8 @@
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.event.Event;
import org.osgi.service.event.EventHandler;
import org.osgi.service.event.propertytypes.EventTopics;
@@ -27,6 +29,7 @@
import io.openems.backend.common.edgewebsocket.EdgeCache;
import io.openems.backend.common.edgewebsocket.EdgeWebsocket;
import io.openems.backend.common.jsonrpc.JsonRpcRequestHandler;
+import io.openems.backend.common.jsonrpc.SimulationEngine;
import io.openems.backend.common.metadata.Metadata;
import io.openems.backend.common.metadata.User;
import io.openems.backend.common.timedata.TimedataManager;
@@ -66,6 +69,9 @@ public class UiWebsocketImpl extends AbstractOpenemsBackendComponent
@Reference
protected volatile TimedataManager timedataManager;
+
+ @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL)
+ protected volatile SimulationEngine simulation;
public UiWebsocketImpl() {
super("Ui.Websocket");
diff --git a/io.openems.common/src/io/openems/common/OpenemsConstants.java b/io.openems.common/src/io/openems/common/OpenemsConstants.java
index 0a1448b2574..4597a187f45 100644
--- a/io.openems.common/src/io/openems/common/OpenemsConstants.java
+++ b/io.openems.common/src/io/openems/common/OpenemsConstants.java
@@ -22,7 +22,7 @@ public class OpenemsConstants {
*
* This is the month of the release.
*/
- public static final short VERSION_MINOR = 4;
+ public static final short VERSION_MINOR = 5;
/**
* The patch version of OpenEMS.
diff --git a/io.openems.common/src/io/openems/common/channel/Unit.java b/io.openems.common/src/io/openems/common/channel/Unit.java
index 8ad4a2ced40..466ea4fbef6 100644
--- a/io.openems.common/src/io/openems/common/channel/Unit.java
+++ b/io.openems.common/src/io/openems/common/channel/Unit.java
@@ -187,9 +187,10 @@ public enum Unit {
// ##########
/**
- * Unit of Energy Price [€/MWh].
+ * Unit of Energy Price, e.g. [€/MWh]. (see Meta.ChannelId#CURRENCY).
*/
- EUROS_PER_MEGAWATT_HOUR("€/MWh"),
+ // TODO symbol should incorporate actual Currency
+ MONEY_PER_MEGAWATT_HOUR("€/MWh"),
// ##########
// Frequency
@@ -357,7 +358,7 @@ public String format(Object value, OpenemsType type) {
case NONE -> //
value.toString();
- case AMPERE, DEGREE_CELSIUS, DEZIDEGREE_CELSIUS, EUROS_PER_MEGAWATT_HOUR, HERTZ, MILLIAMPERE, MICROAMPERE,
+ case AMPERE, DEGREE_CELSIUS, DEZIDEGREE_CELSIUS, MONEY_PER_MEGAWATT_HOUR, HERTZ, MILLIAMPERE, MICROAMPERE,
MILLIHERTZ, MILLIVOLT, MICROVOLT, PERCENT, VOLT, VOLT_AMPERE, VOLT_AMPERE_REACTIVE, WATT, KILOWATT,
MILLIWATT, WATT_HOURS, OHM, KILOOHM, SECONDS, AMPERE_HOURS, HOUR, CUMULATED_SECONDS, KILOAMPERE_HOURS,
KILOVOLT_AMPERE, KILOVOLT_AMPERE_REACTIVE, KILOVOLT_AMPERE_REACTIVE_HOURS, KILOWATT_HOURS, MICROOHM,
diff --git a/io.openems.common/src/io/openems/common/exceptions/OpenemsRuntimeException.java b/io.openems.common/src/io/openems/common/exceptions/OpenemsRuntimeException.java
new file mode 100644
index 00000000000..d0a67297ec5
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/exceptions/OpenemsRuntimeException.java
@@ -0,0 +1,28 @@
+package io.openems.common.exceptions;
+
+public class OpenemsRuntimeException extends RuntimeException {
+
+ private static final long serialVersionUID = -4509666272212124910L;
+
+ public OpenemsRuntimeException() {
+ super();
+ }
+
+ public OpenemsRuntimeException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+ public OpenemsRuntimeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public OpenemsRuntimeException(String message) {
+ super(message);
+ }
+
+ public OpenemsRuntimeException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPath.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPath.java
new file mode 100644
index 00000000000..99ad2a70858
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPath.java
@@ -0,0 +1,39 @@
+package io.openems.common.jsonrpc.serialization;
+
+import java.util.List;
+import java.util.function.Function;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+public interface JsonArrayPath extends JsonPath {
+
+ /**
+ * Gets the elements as a list parsed to the object.
+ *
+ * @param the type of the objects
+ * @param mapper the {@link JsonElement} to object mapper
+ * @return the list with the parsed values
+ */
+ public List getAsList(Function mapper);
+
+ /**
+ * Gets the elements as a list parsed to the object.
+ *
+ * @param the type of the objects
+ * @param serializer the {@link JsonSerializer} to deserialize the elements
+ * @return the list with the parsed values
+ */
+ public default List getAsList(JsonSerializer serializer) {
+ return this.getAsList(serializer::deserializePath);
+ }
+
+ /**
+ * Gets the current element of the path.
+ *
+ * @return the {@link JsonObject}
+ */
+ public JsonArray get();
+
+}
\ No newline at end of file
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathActual.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathActual.java
new file mode 100644
index 00000000000..67ac20a1079
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathActual.java
@@ -0,0 +1,36 @@
+package io.openems.common.jsonrpc.serialization;
+
+import java.util.List;
+import java.util.function.Function;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+
+import io.openems.common.exceptions.OpenemsRuntimeException;
+import io.openems.common.utils.JsonUtils;
+
+public class JsonArrayPathActual implements JsonArrayPath {
+
+ private final JsonArray object;
+
+ public JsonArrayPathActual(JsonElement object) {
+ if (!object.isJsonArray()) {
+ throw new OpenemsRuntimeException(object + " is not a JsonArray!");
+ }
+ this.object = object.getAsJsonArray();
+ }
+
+ @Override
+ public List getAsList(Function mapper) {
+ return JsonUtils.stream(this.object) //
+ .map(JsonElementPathActual::new) //
+ .map(mapper) //
+ .toList();
+ }
+
+ @Override
+ public JsonArray get() {
+ return this.object;
+ }
+
+}
\ No newline at end of file
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathDummy.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathDummy.java
new file mode 100644
index 00000000000..bc592b0eacf
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathDummy.java
@@ -0,0 +1,38 @@
+package io.openems.common.jsonrpc.serialization;
+
+import static java.util.Collections.emptyList;
+
+import java.util.List;
+import java.util.function.Function;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+
+import io.openems.common.utils.JsonUtils;
+
+public class JsonArrayPathDummy implements JsonArrayPath, JsonPathDummy {
+
+ private JsonPathDummy elementType;
+
+ @Override
+ public List getAsList(Function mapper) {
+ final var path = new JsonElementPathDummy();
+ mapper.apply(path);
+ this.elementType = path;
+ return emptyList();
+ }
+
+ @Override
+ public JsonArray get() {
+ return new JsonArray();
+ }
+
+ @Override
+ public JsonElement buildPath() {
+ return JsonUtils.buildJsonObject() //
+ .addProperty("type", "array") //
+ .onlyIf(this.elementType != null, t -> t.add("elementType", this.elementType.buildPath())) //
+ .build();
+ }
+
+}
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPath.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPath.java
new file mode 100644
index 00000000000..043f8eb3779
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPath.java
@@ -0,0 +1,48 @@
+package io.openems.common.jsonrpc.serialization;
+
+import com.google.gson.JsonElement;
+
+public interface JsonElementPath extends JsonPath {
+
+ /**
+ * Gets the current {@link JsonElementPath} as a {@link JsonObjectPath}.
+ *
+ * @return the current element as a {@link JsonObjectPath}
+ */
+ public JsonObjectPath getAsJsonObjectPath();
+
+ /**
+ * Gets the current {@link JsonElementPath} as a {@link JsonArrayPath}.
+ *
+ * @return the current element as a {@link JsonArrayPath}
+ */
+ public JsonArrayPath getAsJsonArrayPath();
+
+ /**
+ * Gets the current {@link JsonElementPath} as a {@link StringPath}.
+ *
+ * @return the current element as a {@link StringPath}
+ */
+ public StringPath getAsStringPath();
+
+ /**
+ * Gets the current {@link JsonElementPath} as a {@link String}.
+ *
+ * @return the current element as a {@link String}
+ */
+ public default String getAsString() {
+ return this.getAsStringPath().get();
+ }
+
+ /**
+ * Gets the current {@link JsonElementPath} as a Object serialized with the
+ * provided {@link JsonSerializer}.
+ *
+ * @param the type of the final object
+ * @param serializer the {@link JsonSerializer} to deserialize the
+ * {@link JsonElement} to the object
+ * @return the current element as a {@link StringPath}
+ */
+ public O getAsObject(JsonSerializer serializer);
+
+}
\ No newline at end of file
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathActual.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathActual.java
new file mode 100644
index 00000000000..e0d955d1a60
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathActual.java
@@ -0,0 +1,32 @@
+package io.openems.common.jsonrpc.serialization;
+
+import com.google.gson.JsonElement;
+
+public class JsonElementPathActual implements JsonElementPath {
+ private final JsonElement element;
+
+ public JsonElementPathActual(JsonElement element) {
+ this.element = element;
+ }
+
+ @Override
+ public JsonArrayPath getAsJsonArrayPath() {
+ return new JsonArrayPathActual(this.element);
+ }
+
+ @Override
+ public JsonObjectPath getAsJsonObjectPath() {
+ return new JsonObjectPathActual(this.element);
+ }
+
+ @Override
+ public StringPath getAsStringPath() {
+ return new StringPathActual(this.element);
+ }
+
+ @Override
+ public O getAsObject(JsonSerializer deserializer) {
+ return deserializer.deserializePath(new JsonElementPathActual(this.element));
+ }
+
+}
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathDummy.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathDummy.java
new file mode 100644
index 00000000000..5d26b9bffb4
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathDummy.java
@@ -0,0 +1,49 @@
+package io.openems.common.jsonrpc.serialization;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+
+public class JsonElementPathDummy implements JsonElementPath, JsonPathDummy {
+
+ private JsonPathDummy dummyPath;
+
+ @Override
+ public JsonArrayPath getAsJsonArrayPath() {
+ return this.withDummyPath(new JsonArrayPathDummy());
+ }
+
+ @Override
+ public JsonObjectPath getAsJsonObjectPath() {
+ return this.withDummyPath(new JsonObjectPathDummy());
+ }
+
+ @Override
+ public StringPath getAsStringPath() {
+ return this.withDummyPath(new StringPathDummy());
+ }
+
+ @Override
+ public O getAsObject(JsonSerializer deserializer) {
+ final var dummyPath = new JsonElementPathDummy();
+ this.withDummyPath(dummyPath);
+ return deserializer.deserializePath(dummyPath);
+ }
+
+ private T withDummyPath(T path) {
+ if (this.dummyPath != null) {
+ throw new RuntimeException("Path already set");
+ }
+ this.dummyPath = path;
+ return path;
+ }
+
+ public JsonPathDummy getDummyPath() {
+ return this.dummyPath;
+ }
+
+ @Override
+ public JsonElement buildPath() {
+ return this.dummyPath == null ? JsonNull.INSTANCE : this.dummyPath.buildPath();
+ }
+
+}
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPath.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPath.java
new file mode 100644
index 00000000000..fbd925cec08
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPath.java
@@ -0,0 +1,131 @@
+package io.openems.common.jsonrpc.serialization;
+
+import java.util.List;
+import java.util.function.Function;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+public interface JsonObjectPath extends JsonPath {
+
+ /**
+ * Gets the element associated with the member name from this object.
+ *
+ * @param member the name of the member
+ * @return the {@link JsonElementPath} of the member value
+ */
+ public JsonElementPath getJsonElementPath(String member);
+
+ /**
+ * Gets the element associated with the member name from this object as a
+ * {@link StringPath}.
+ *
+ * @param member the name of the member
+ * @return the {@link StringPath} of the member value
+ */
+ public default StringPath getStringPath(String member) {
+ return this.getJsonElementPath(member).getAsStringPath();
+ }
+
+ /**
+ * Gets the element associated with the member name from this object as a
+ * {@link String}.
+ *
+ * @param member the name of the member
+ * @return the {@link String} of the member value
+ */
+ public default String getString(String member) {
+ return this.getStringPath(member).get();
+ }
+
+ /**
+ * Gets the element associated with the member name from this object as a
+ * {@link JsonObjectPath}.
+ *
+ * @param member the name of the member
+ * @return the {@link JsonObjectPath} of the member value
+ */
+ public default JsonObjectPath getJsonObjectPath(String member) {
+ return this.getJsonElementPath(member).getAsJsonObjectPath();
+ }
+
+ /**
+ * Gets the element associated with the member name from this object as a
+ * {@link JsonObject}.
+ *
+ * @param member the name of the member
+ * @return the {@link JsonObject} of the member value
+ */
+ public default JsonObject getJsonObject(String member) {
+ return this.getJsonObjectPath(member).get();
+ }
+
+ /**
+ * Gets the element associated with the member name from this object as a
+ * {@link JsonArrayPath}.
+ *
+ * @param member the name of the member
+ * @return the {@link JsonArrayPath} of the member value
+ */
+ public default JsonArrayPath getJsonArrayPath(String member) {
+ return this.getJsonElementPath(member).getAsJsonArrayPath();
+ }
+
+ /**
+ * Gets the element associated with the member name from this object as a
+ * {@link JsonArray}.
+ *
+ * @param member the name of the member
+ * @return the {@link JsonArray} of the member value
+ */
+ public default JsonArray getJsonArray(String member) {
+ return this.getJsonArrayPath(member).get();
+ }
+
+ /**
+ * Gets the element associated with the member name from this object as a
+ * {@link List}.
+ *
+ * @param the type of the elements in the list
+ * @param member the name of the member
+ * @param mapper the mapper to deserialize the elements
+ * @return the {@link List} of the member value
+ */
+ public default List getList(String member, Function mapper) {
+ return this.getJsonArrayPath(member).getAsList(mapper);
+ }
+
+ /**
+ * Gets the element associated with the member name from this object as a
+ * {@link List}.
+ *
+ * @param the type of the elements in the list
+ * @param member the name of the member
+ * @param serializer the {@link JsonSerializer} to deserialize the elements
+ * @return the {@link List} of the member value
+ */
+ public default List getList(String member, JsonSerializer serializer) {
+ return this.getJsonArrayPath(member).getAsList(serializer);
+ }
+
+ /**
+ * Gets the element associated with the member name from this object as the
+ * generic object.
+ *
+ * @param the type of the element
+ * @param member the name of the member
+ * @param serializer the {@link JsonSerializer} to deserialize the element
+ * @return the object of the member value
+ */
+ public default T getElement(String member, JsonSerializer serializer) {
+ return this.getJsonElementPath(member).getAsObject(serializer);
+ }
+
+ /**
+ * Gets the current element of the path.
+ *
+ * @return the {@link JsonObject}
+ */
+ public JsonObject get();
+
+}
\ No newline at end of file
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathActual.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathActual.java
new file mode 100644
index 00000000000..7de86237260
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathActual.java
@@ -0,0 +1,33 @@
+package io.openems.common.jsonrpc.serialization;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+import io.openems.common.exceptions.OpenemsRuntimeException;
+
+public class JsonObjectPathActual implements JsonObjectPath {
+ private final JsonObject object;
+
+ public JsonObjectPathActual(JsonElement object) {
+ if (!object.isJsonObject()) {
+ throw new OpenemsRuntimeException(object + " is not a JsonObject!");
+ }
+ this.object = object.getAsJsonObject();
+ }
+
+ @Override
+ public JsonElementPath getJsonElementPath(String member) {
+ return new JsonElementPathActual(this.object.get(member));
+ }
+
+ @Override
+ public JsonObjectPath getJsonObjectPath(String member) {
+ return new JsonObjectPathActual(this.object.get(member));
+ }
+
+ @Override
+ public JsonObject get() {
+ return this.object;
+ }
+
+}
\ No newline at end of file
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathDummy.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathDummy.java
new file mode 100644
index 00000000000..68639fee6b8
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathDummy.java
@@ -0,0 +1,47 @@
+package io.openems.common.jsonrpc.serialization;
+
+import static io.openems.common.utils.JsonUtils.toJsonObject;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+import io.openems.common.utils.JsonUtils;
+
+public class JsonObjectPathDummy implements JsonObjectPath, JsonPathDummy {
+
+ private final Map paths = new TreeMap<>();
+
+ @Override
+ public JsonElementPath getJsonElementPath(String member) {
+ return this.withDummyPath(member, new JsonElementPathDummy());
+ }
+
+ @Override
+ public JsonObjectPath getJsonObjectPath(String member) {
+ return this.withDummyPath(member, new JsonObjectPathDummy());
+ }
+
+ @Override
+ public JsonObject get() {
+ return new JsonObject();
+ }
+
+ @Override
+ public JsonElement buildPath() {
+ return JsonUtils.buildJsonObject() //
+ .addProperty("type", "object") //
+ .add("properties", this.paths.entrySet().stream() //
+ .collect(toJsonObject(Entry::getKey, input -> input.getValue().buildPath()))) //
+ .build();
+ }
+
+ private final T withDummyPath(String member, T path) {
+ this.paths.put(member, path);
+ return path;
+ }
+
+}
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPath.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPath.java
new file mode 100644
index 00000000000..4c9b0d82a59
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPath.java
@@ -0,0 +1,5 @@
+package io.openems.common.jsonrpc.serialization;
+
+public interface JsonPath {
+
+}
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPathDummy.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPathDummy.java
new file mode 100644
index 00000000000..3148bab82e0
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPathDummy.java
@@ -0,0 +1,14 @@
+package io.openems.common.jsonrpc.serialization;
+
+import com.google.gson.JsonElement;
+
+public interface JsonPathDummy {
+
+ /**
+ * Creates the description of the Path as a {@link JsonElement}.
+ *
+ * @return the created {@link JsonElement}
+ */
+ public JsonElement buildPath();
+
+}
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonSerializer.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonSerializer.java
new file mode 100644
index 00000000000..2b8e52df570
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonSerializer.java
@@ -0,0 +1,41 @@
+package io.openems.common.jsonrpc.serialization;
+
+import com.google.gson.JsonElement;
+
+public interface JsonSerializer {
+
+ /**
+ * Gets the {@link SerializerDescriptor} of the object this serializer
+ * serializes.
+ *
+ * @return the {@link SerializerDescriptor}
+ */
+ public SerializerDescriptor descriptor();
+
+ /**
+ * Serializes from a object to a {@link JsonElement}.
+ *
+ * @param obj the object to serialize
+ * @return the serialized object as a {@link JsonElement}
+ */
+ public JsonElement serialize(T obj);
+
+ /**
+ * Deserializes from a {@link JsonElement} to the object.
+ *
+ * @param json the {@link JsonElement} to deserialize into a object
+ * @return the deserialized object from the {@link JsonElement}
+ */
+ public T deserializePath(JsonElementPath json);
+
+ /**
+ * Deserializes from a {@link JsonElement} to the object.
+ *
+ * @param json the {@link JsonElement} to deserialize into a object
+ * @return the deserialized object from the {@link JsonElement}
+ */
+ public default T deserialize(JsonElement json) {
+ return this.deserializePath(new JsonElementPathActual(json));
+ }
+
+}
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonSerializerUtil.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonSerializerUtil.java
new file mode 100644
index 00000000000..2052d35d0b8
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonSerializerUtil.java
@@ -0,0 +1,157 @@
+package io.openems.common.jsonrpc.serialization;
+
+import java.util.function.Function;
+
+import com.google.common.base.Supplier;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+public final class JsonSerializerUtil {
+
+ /**
+ * Creates a {@link JsonSerializer} for a empty {@link JsonObject}.
+ *
+ * @param the type of the object
+ * @param object the object supplier to create an empty instance of it
+ * @return the created {@link JsonSerializer}
+ */
+ public static JsonSerializer emptyObjectSerializer(//
+ Supplier object //
+ ) {
+ return jsonObjectSerializer(json -> object.get(), json -> new JsonObject());
+ }
+
+ /**
+ * Creates a {@link JsonSerializer} for the provided type.
+ *
+ * @param the type of the object to serialize and deserialize.
+ * @param clazz the {@link Class} of the object
+ * @param toObjMapper the deserializer from {@link JsonObject} to object
+ * @param toJsonMapper the serializer from object to {@link JsonElement}
+ * @return the created {@link JsonSerializer}
+ */
+ public static JsonSerializer jsonObjectSerializer(//
+ Class clazz, //
+ Function toObjMapper, //
+ Function toJsonMapper //
+ ) {
+ return jsonObjectSerializer(toObjMapper, toJsonMapper);
+ }
+
+ /**
+ * Creates a {@link JsonSerializer} for the provided type.
+ *
+ * @param the type of the object to serialize and deserialize.
+ * @param toObjMapper the deserializer from {@link JsonObject} to object
+ * @param toJsonMapper the serializer from object to {@link JsonElement}
+ * @return the created {@link JsonSerializer}
+ */
+ public static JsonSerializer jsonObjectSerializer(//
+ Function toObjMapper, //
+ Function toJsonMapper //
+ ) {
+ return jsonSerializer(toObjMapper.compose(JsonElementPath::getAsJsonObjectPath), toJsonMapper);
+ }
+
+ /**
+ * Creates a {@link JsonSerializer} for the provided type.
+ *
+ * @param the type of the object to serialize and deserialize.
+ * @param clazz the {@link Class} of the object
+ * @param toObjMapper the deserializer from {@link JsonArray} to object
+ * @param toJsonMapper the serializer from object to {@link JsonElement}
+ * @return the created {@link JsonSerializer}
+ */
+ public static JsonSerializer jsonArraySerializer(//
+ Class clazz, //
+ Function toObjMapper, //
+ Function toJsonMapper //
+ ) {
+ return jsonArraySerializer(toObjMapper, toJsonMapper);
+ }
+
+ /**
+ * Creates a {@link JsonSerializer} for the provided type.
+ *
+ * @param the type of the object to serialize and deserialize.
+ * @param toObjMapper the deserializer from {@link JsonArray} to object
+ * @param toJsonMapper the serializer from object to {@link JsonElement}
+ * @return the created {@link JsonSerializer}
+ */
+ public static JsonSerializer jsonArraySerializer(//
+ Function toObjMapper, //
+ Function toJsonMapper //
+ ) {
+ return jsonSerializer(toObjMapper.compose(JsonElementPath::getAsJsonArrayPath), toJsonMapper);
+ }
+
+ /**
+ * Creates a {@link JsonSerializer} for the provided type.
+ *
+ * @param the type of the object to serialize and deserialize.
+ * @param clazz the {@link Class} of the object
+ * @param toObjMapper the deserializer from {@link JsonElement} to object
+ * @param toJsonMapper the serializer from object ot {@link JsonElement}
+ * @return the created {@link JsonSerializer}
+ */
+ public static JsonSerializer jsonSerializer(//
+ Class clazz, //
+ Function toObjMapper, //
+ Function toJsonMapper //
+ ) {
+ return jsonSerializer(toObjMapper, toJsonMapper);
+ }
+
+ /**
+ * Creates a {@link JsonSerializer} for the provided type.
+ *
+ * @param the type of the object to serialize and deserialize.
+ * @param toObjMapper the deserializer from {@link JsonElement} to object
+ * @param toJsonMapper the serializer from object to {@link JsonElement}
+ * @return the created {@link JsonSerializer}
+ */
+ public static JsonSerializer jsonSerializer(//
+ Function toObjMapper, //
+ Function toJsonMapper //
+ ) {
+ final var path = new JsonElementPathDummy();
+ toObjMapper.apply(path);
+ return new SimpleJsonSerializer(new SerializerDescriptor(path), toJsonMapper, toObjMapper);
+ }
+
+ private JsonSerializerUtil() {
+ }
+
+ private static final class SimpleJsonSerializer implements JsonSerializer {
+
+ private final SerializerDescriptor descriptor;
+ private final Function serialize;
+ private final Function deserialize;
+
+ public SimpleJsonSerializer(SerializerDescriptor descriptor, Function serialize,
+ Function deserialize) {
+ super();
+ this.descriptor = descriptor;
+ this.serialize = serialize;
+ this.deserialize = deserialize;
+ }
+
+ @Override
+ public SerializerDescriptor descriptor() {
+ return this.descriptor;
+ }
+
+ @Override
+ public JsonElement serialize(T a) {
+ return this.serialize.apply(a);
+ }
+
+ @Override
+ public T deserializePath(JsonElementPath a) {
+ return this.deserialize.apply(a);
+ }
+
+ }
+
+}
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/SerializerDescriptor.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/SerializerDescriptor.java
new file mode 100644
index 00000000000..568cf1a2c09
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/SerializerDescriptor.java
@@ -0,0 +1,26 @@
+package io.openems.common.jsonrpc.serialization;
+
+import com.google.gson.JsonElement;
+
+public class SerializerDescriptor {
+
+ private final JsonElementPathDummy obj;
+
+ public SerializerDescriptor(JsonElementPathDummy obj) {
+ this.obj = obj;
+ }
+
+ /**
+ * Creates a {@link JsonElement} of the object description.
+ *
+ * @return the created {@link JsonElementPath}
+ */
+ public JsonElement toJson() {
+ return this.obj.buildPath();
+ }
+
+ public JsonElementPathDummy getObj() {
+ return this.obj;
+ }
+
+}
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPath.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPath.java
new file mode 100644
index 00000000000..d32c4056eb0
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPath.java
@@ -0,0 +1,21 @@
+package io.openems.common.jsonrpc.serialization;
+
+import java.util.UUID;
+
+public interface StringPath extends JsonPath {
+
+ /**
+ * Gets the string value of the current path.
+ *
+ * @return the value
+ */
+ public String get();
+
+ /**
+ * Gets the value as a {@link UUID}.
+ *
+ * @return the {@link UUID}
+ */
+ public UUID getAsUuid();
+
+}
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathActual.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathActual.java
new file mode 100644
index 00000000000..d0f2e2799b1
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathActual.java
@@ -0,0 +1,32 @@
+package io.openems.common.jsonrpc.serialization;
+
+import java.util.UUID;
+
+import com.google.gson.JsonElement;
+
+import io.openems.common.exceptions.OpenemsRuntimeException;
+
+public class StringPathActual implements StringPath {
+
+ private final String element;
+
+ public StringPathActual(JsonElement element) throws OpenemsRuntimeException {
+ super();
+ if (!element.isJsonPrimitive() //
+ || !element.getAsJsonPrimitive().isString()) {
+ throw new OpenemsRuntimeException(element + " is not a String!");
+ }
+ this.element = element.getAsString();
+ }
+
+ @Override
+ public String get() {
+ return this.element;
+ }
+
+ @Override
+ public UUID getAsUuid() {
+ return UUID.fromString(this.element);
+ }
+
+}
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathDummy.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathDummy.java
new file mode 100644
index 00000000000..5cd61d65a25
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathDummy.java
@@ -0,0 +1,28 @@
+package io.openems.common.jsonrpc.serialization;
+
+import java.util.UUID;
+
+import com.google.gson.JsonElement;
+
+import io.openems.common.utils.JsonUtils;
+
+public class StringPathDummy implements StringPath, JsonPathDummy {
+
+ @Override
+ public String get() {
+ return "";
+ }
+
+ @Override
+ public UUID getAsUuid() {
+ return UUID.randomUUID();
+ }
+
+ @Override
+ public JsonElement buildPath() {
+ return JsonUtils.buildJsonObject() //
+ .addProperty("type", "string") //
+ .build();
+ }
+
+}
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/package-info.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/package-info.java
new file mode 100644
index 00000000000..5d8dc7bba5c
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/package-info.java
@@ -0,0 +1,3 @@
+@org.osgi.annotation.versioning.Version("1.0.0")
+@org.osgi.annotation.bundle.Export
+package io.openems.common.jsonrpc.serialization;
diff --git a/io.openems.common/src/io/openems/common/types/EdgeConfig.java b/io.openems.common/src/io/openems/common/types/EdgeConfig.java
index 1871fb826d7..232f2de63f0 100644
--- a/io.openems.common/src/io/openems/common/types/EdgeConfig.java
+++ b/io.openems.common/src/io/openems/common/types/EdgeConfig.java
@@ -1254,7 +1254,7 @@ public static EdgeConfig fromJson(JsonObject json) {
private volatile JsonObject _json = null;
/**
- * Build from {@link ActualEdgeConfig} using a {@link Builder}.
+ * Build from {@link ActualEdgeConfig}.
*
* @param actual the {@link ActualEdgeConfig}
*/
diff --git a/io.openems.common/src/io/openems/common/websocket/AbstractWebsocketClient.java b/io.openems.common/src/io/openems/common/websocket/AbstractWebsocketClient.java
index 71b9cbda1e2..79c805a69c3 100644
--- a/io.openems.common/src/io/openems/common/websocket/AbstractWebsocketClient.java
+++ b/io.openems.common/src/io/openems/common/websocket/AbstractWebsocketClient.java
@@ -10,6 +10,7 @@
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.exceptions.WebsocketNotConnectedException;
+import org.java_websocket.extensions.permessage_deflate.PerMessageDeflateExtension;
import org.java_websocket.framing.CloseFrame;
import org.java_websocket.handshake.ServerHandshake;
import org.slf4j.Logger;
@@ -34,7 +35,7 @@ public abstract class AbstractWebsocketClient extends Abstract
public static final Map NO_HTTP_HEADERS = new HashMap<>();
public static final Proxy NO_PROXY = null;
- public static final Draft DEFAULT_DRAFT = new Draft_6455();
+ public static final Draft DEFAULT_DRAFT = new Draft_6455(new PerMessageDeflateExtension());
protected final WebSocketClient ws;
diff --git a/io.openems.common/src/io/openems/common/websocket/AbstractWebsocketServer.java b/io.openems.common/src/io/openems/common/websocket/AbstractWebsocketServer.java
index 07bd5a40d8a..72880facf42 100644
--- a/io.openems.common/src/io/openems/common/websocket/AbstractWebsocketServer.java
+++ b/io.openems.common/src/io/openems/common/websocket/AbstractWebsocketServer.java
@@ -14,7 +14,10 @@
import java.util.function.Function;
import org.java_websocket.WebSocket;
+import org.java_websocket.drafts.Draft;
+import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.exceptions.WebsocketNotConnectedException;
+import org.java_websocket.extensions.permessage_deflate.PerMessageDeflateExtension;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import org.slf4j.Logger;
@@ -63,6 +66,7 @@ public boolean isAtLeast(DebugMode other) {
private final WebSocketServer ws;
private final DebugMode debugMode;
private final Collection connections = ConcurrentHashMap.newKeySet();
+ private final Draft perMessageDeflateDraft = new Draft_6455(new PerMessageDeflateExtension());
/**
* Construct an {@link AbstractWebsocketServer}.
@@ -80,7 +84,7 @@ protected AbstractWebsocketServer(String name, int port, int poolSize, DebugMode
this.port = port;
this.ws = new WebSocketServer(new InetSocketAddress(port),
/* AVAILABLE_PROCESSORS */ Runtime.getRuntime().availableProcessors(), //
- /* drafts, no filter */ Collections.emptyList(), //
+ /* enable perMessageDeflate */ Collections.singletonList(this.perMessageDeflateDraft), //
this.connections) {
@Override
diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun
index 09c9967dc78..0e5c289c8f1 100644
--- a/io.openems.edge.application/EdgeApp.bndrun
+++ b/io.openems.edge.application/EdgeApp.bndrun
@@ -105,6 +105,7 @@
bnd.identity;id='io.openems.edge.controller.symmetric.timeslotpeakshaving',\
bnd.identity;id='io.openems.edge.core',\
bnd.identity;id='io.openems.edge.edge2edge',\
+ bnd.identity;id='io.openems.edge.energy',\
bnd.identity;id='io.openems.edge.ess.adstec.storaxe',\
bnd.identity;id='io.openems.edge.ess.byd.container',\
bnd.identity;id='io.openems.edge.ess.cluster',\
@@ -149,9 +150,9 @@
bnd.identity;id='io.openems.edge.meter.camillebauer.aplus',\
bnd.identity;id='io.openems.edge.meter.carlo.gavazzi.em300',\
bnd.identity;id='io.openems.edge.meter.discovergy',\
+ bnd.identity;id='io.openems.edge.meter.eastron',\
bnd.identity;id='io.openems.edge.meter.janitza',\
bnd.identity;id='io.openems.edge.meter.kdk',\
- bnd.identity;id='io.openems.edge.meter.microcare.sdm630',\
bnd.identity;id='io.openems.edge.meter.phoenixcontact',\
bnd.identity;id='io.openems.edge.meter.plexlog',\
bnd.identity;id='io.openems.edge.meter.pqplus',\
@@ -270,6 +271,8 @@
io.openems.edge.controller.symmetric.timeslotpeakshaving;version=snapshot,\
io.openems.edge.core;version=snapshot,\
io.openems.edge.edge2edge;version=snapshot,\
+ io.openems.edge.energy;version=snapshot,\
+ io.openems.edge.energy.api;version=snapshot,\
io.openems.edge.ess.adstec.storaxe;version=snapshot,\
io.openems.edge.ess.api;version=snapshot,\
io.openems.edge.ess.byd.container;version=snapshot,\
@@ -318,9 +321,9 @@
io.openems.edge.meter.camillebauer.aplus;version=snapshot,\
io.openems.edge.meter.carlo.gavazzi.em300;version=snapshot,\
io.openems.edge.meter.discovergy;version=snapshot,\
+ io.openems.edge.meter.eastron;version=snapshot,\
io.openems.edge.meter.janitza;version=snapshot,\
io.openems.edge.meter.kdk;version=snapshot,\
- io.openems.edge.meter.microcare.sdm630;version=snapshot,\
io.openems.edge.meter.phoenixcontact;version=snapshot,\
io.openems.edge.meter.plexlog;version=snapshot,\
io.openems.edge.meter.pqplus;version=snapshot,\
@@ -394,7 +397,7 @@
org.apache.felix.inventory;version='[2.0.0,2.0.1)',\
org.apache.felix.metatype;version='[1.2.4,1.2.5)',\
org.apache.felix.scr;version='[2.2.10,2.2.11)',\
- org.apache.felix.webconsole;version='[5.0.0,5.0.1)',\
+ org.apache.felix.webconsole;version='[5.0.2,5.0.3)',\
org.apache.felix.webconsole.plugins.ds;version='[2.3.0,2.3.1)',\
org.eclipse.jetty.client;version='[9.4.28,9.4.29)',\
org.eclipse.jetty.http;version='[9.4.28,9.4.29)',\
diff --git a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwBatteryImpl.java b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwBatteryImpl.java
index 20737cf8ab3..7c660f82094 100644
--- a/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwBatteryImpl.java
+++ b/io.openems.edge.battery.bmw/src/io/openems/edge/battery/bmw/BmwBatteryImpl.java
@@ -27,7 +27,6 @@
import io.openems.common.channel.AccessMode;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
-import io.openems.common.exceptions.OpenemsException;
import io.openems.edge.battery.api.Battery;
import io.openems.edge.battery.bmw.enums.BmsState;
import io.openems.edge.battery.bmw.enums.State;
@@ -342,10 +341,8 @@ private void setStateMachineState(State state) {
}
@Override
- protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
-
+ protected ModbusProtocol defineModbusProtocol() {
return new ModbusProtocol(this, //
-
new FC16WriteRegistersTask(1399, //
m(BmwBattery.ChannelId.HEART_BEAT, new UnsignedWordElement(1399)), //
m(BmwBattery.ChannelId.BMS_STATE_COMMAND, new UnsignedWordElement(1400)), //
diff --git a/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130Impl.java b/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130Impl.java
index c9ee669d9f7..0dae16c2fcd 100644
--- a/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130Impl.java
+++ b/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130Impl.java
@@ -3,6 +3,7 @@
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.DIRECT_1_TO_1;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1;
+import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
@@ -26,7 +27,6 @@
import io.openems.common.channel.AccessMode;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
-import io.openems.common.exceptions.OpenemsException;
import io.openems.edge.battery.api.Battery;
import io.openems.edge.battery.bydcommercial.statemachine.Context;
import io.openems.edge.battery.bydcommercial.statemachine.StateMachine;
@@ -176,7 +176,7 @@ public String debugLog() {
}
@Override
- protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
+ protected ModbusProtocol defineModbusProtocol() {
return new ModbusProtocol(this, //
new FC3ReadRegistersTask(0x2010, Priority.HIGH, //
m(BydBatteryBoxCommercialC130.ChannelId.POWER_CIRCUIT_CONTROL, new UnsignedWordElement(0x2010)) //
@@ -964,43 +964,30 @@ public StartStop getStartStopTarget() {
BydBatteryBoxCommercialC130Impl.this.isModbusProtocolInitialized = true;
// Try to read MODULE_QTY Register
- try {
- ModbusUtils.readELementOnce(this.getModbusProtocol(), new UnsignedWordElement(0x210D), false)
- .thenAccept(moduleQtyValue -> {
- if (moduleQtyValue != null) {
- // Register is available -> add Registers for current hardware to protocol
- try {
- this.getModbusProtocol().addTasks(//
- new FC3ReadRegistersTask(0x210D, Priority.LOW, //
- m(BydBatteryBoxCommercialC130.ChannelId.MODULE_QTY,
- new UnsignedWordElement(0x210D)), //
- m(BydBatteryBoxCommercialC130.ChannelId.TOTAL_VOLTAGE_OF_SINGLE_MODULE,
- new UnsignedWordElement(0x210E))), //
- new FC3ReadRegistersTask(0x216E, Priority.LOW, //
- m(Battery.ChannelId.CHARGE_MAX_VOLTAGE, new UnsignedWordElement(0x216E), //
- SCALE_FACTOR_MINUS_1), //
- m(Battery.ChannelId.DISCHARGE_MIN_VOLTAGE,
- new UnsignedWordElement(0x216F), //
- SCALE_FACTOR_MINUS_1) //
- ));
- } catch (OpenemsException e) {
- BydBatteryBoxCommercialC130Impl.this.logError(BydBatteryBoxCommercialC130Impl.this.log,
- "Unable to add registers for detected hardware version: " + e.getMessage());
- e.printStackTrace();
- } //
- } else {
- BydBatteryBoxCommercialC130Impl.this.logInfo(BydBatteryBoxCommercialC130Impl.this.log,
- "Detected old hardware version. Registers are not available. Setting default values.");
-
- this._setChargeMaxVoltage(OLD_VERSION_DEFAULT_CHARGE_MAX_VOLTAGE);
- this._setDischargeMinVoltage(OLD_VERSION_DEFAULT_DISCHARGE_MIN_VOLTAGE);
- }
- });
- } catch (OpenemsException e) {
- BydBatteryBoxCommercialC130Impl.this.logError(BydBatteryBoxCommercialC130Impl.this.log,
- "Unable to detect hardware version: " + e.getMessage());
- e.printStackTrace();
- }
+ readElementOnce(this.getModbusProtocol(), ModbusUtils::doNotRetry, new UnsignedWordElement(0x210D))
+ .thenAccept(moduleQtyValue -> {
+ if (moduleQtyValue != null) {
+ // Register is available -> add Registers for current hardware to protocol
+ this.getModbusProtocol().addTasks(//
+ new FC3ReadRegistersTask(0x210D, Priority.LOW, //
+ m(BydBatteryBoxCommercialC130.ChannelId.MODULE_QTY,
+ new UnsignedWordElement(0x210D)), //
+ m(BydBatteryBoxCommercialC130.ChannelId.TOTAL_VOLTAGE_OF_SINGLE_MODULE,
+ new UnsignedWordElement(0x210E))), //
+ new FC3ReadRegistersTask(0x216E, Priority.LOW, //
+ m(Battery.ChannelId.CHARGE_MAX_VOLTAGE, new UnsignedWordElement(0x216E), //
+ SCALE_FACTOR_MINUS_1), //
+ m(Battery.ChannelId.DISCHARGE_MIN_VOLTAGE, new UnsignedWordElement(0x216F), //
+ SCALE_FACTOR_MINUS_1) //
+ ));
+ } else {
+ BydBatteryBoxCommercialC130Impl.this.logInfo(BydBatteryBoxCommercialC130Impl.this.log,
+ "Detected old hardware version. Registers are not available. Setting default values.");
+
+ this._setChargeMaxVoltage(OLD_VERSION_DEFAULT_CHARGE_MAX_VOLTAGE);
+ this._setDischargeMinVoltage(OLD_VERSION_DEFAULT_DISCHARGE_MIN_VOLTAGE);
+ }
+ });
};
}
diff --git a/io.openems.edge.battery.fenecon.commercial/src/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImpl.java b/io.openems.edge.battery.fenecon.commercial/src/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImpl.java
index d04da464d2d..869077dbe64 100644
--- a/io.openems.edge.battery.fenecon.commercial/src/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImpl.java
+++ b/io.openems.edge.battery.fenecon.commercial/src/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImpl.java
@@ -181,7 +181,7 @@ protected void deactivate() {
});
@Override
- protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
+ protected ModbusProtocol defineModbusProtocol() {
return new ModbusProtocol(this, //
// Versions
new FC3ReadRegistersTask(0, Priority.LOW, //
diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java
index d45e42a80b6..01748d00999 100644
--- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java
+++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java
@@ -2,6 +2,7 @@
import static io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent.BitConverter.INVERT;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1;
+import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce;
import java.util.List;
import java.util.Objects;
@@ -186,7 +187,7 @@ private void handleStateMachine() {
}
@Override
- protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
+ protected ModbusProtocol defineModbusProtocol() {
return new ModbusProtocol(this, //
new FC3ReadRegistersTask(500, Priority.LOW, //
m(new BitsWordElement(500, this) //
@@ -345,7 +346,7 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
*/
private void detectHardwareType() throws OpenemsException {
// Set Battery-Protection
- ModbusUtils.readELementOnce(this.getModbusProtocol(), new UnsignedWordElement(10019), true) //
+ readElementOnce(this.getModbusProtocol(), ModbusUtils::retryOnNull, new UnsignedWordElement(10019))
.thenAccept(value -> {
if (value == null) {
return;
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImpl.java
index 102b6e8481a..771573775ef 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImpl.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImpl.java
@@ -509,7 +509,7 @@ public void setStateMachineState(State state) {
}
@Override
- protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
+ protected ModbusProtocol defineModbusProtocol() {
var protocol = new ModbusProtocol(this,
// -------- control registers of master --------------------------------------
new FC16WriteRegistersTask(0x1004, //
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java
index d51be44a2c3..56c3d916474 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImpl.java
@@ -2,6 +2,7 @@
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1;
+import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce;
import java.util.LinkedList;
import java.util.Optional;
@@ -10,6 +11,7 @@
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiPredicate;
import java.util.function.Consumer;
import org.osgi.service.cm.ConfigurationAdmin;
@@ -57,6 +59,7 @@
import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask;
import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask;
import io.openems.edge.bridge.modbus.api.task.FC6WriteRegisterTask;
+import io.openems.edge.bridge.modbus.api.task.Task.ExecuteState;
import io.openems.edge.common.channel.Channel;
import io.openems.edge.common.channel.EnumReadChannel;
import io.openems.edge.common.channel.IntegerReadChannel;
@@ -185,198 +188,191 @@ protected void deactivate() {
private void updateRackChannels(Integer numberOfModules, TreeSet racks) throws OpenemsException {
for (Rack r : racks) {
- try {
- this.getModbusProtocol().addTasks(//
-
- new FC3ReadRegistersTask(r.offset + 0x000B, Priority.LOW, //
- m(this.createChannelId(r, RackChannel.EMS_ADDRESS),
- new UnsignedWordElement(r.offset + 0x000B)), //
- m(this.createChannelId(r, RackChannel.EMS_BAUDRATE),
- new UnsignedWordElement(r.offset + 0x000C)), //
- new DummyRegisterElement(r.offset + 0x000D, r.offset + 0x000F),
- m(this.createChannelId(r, RackChannel.PRE_CHARGE_CONTROL),
- new UnsignedWordElement(r.offset + 0x0010)), //
- new DummyRegisterElement(r.offset + 0x0011, r.offset + 0x0014),
- m(this.createChannelId(r, RackChannel.SET_SUB_MASTER_ADDRESS),
- new UnsignedWordElement(r.offset + 0x0015)) //
- ), //
- new FC3ReadRegistersTask(r.offset + 0x00F4, Priority.LOW, //
- m(this.createChannelId(r, RackChannel.EMS_COMMUNICATION_TIMEOUT),
- new UnsignedWordElement(r.offset + 0x00F4)) //
- ),
-
- // Single Cluster Control Registers (running without Master BMS)
- new FC6WriteRegisterTask(r.offset + 0x0010, //
- m(this.createChannelId(r, RackChannel.PRE_CHARGE_CONTROL),
- new UnsignedWordElement(r.offset + 0x0010)) //
- ), //
- new FC6WriteRegisterTask(r.offset + 0x00F4, //
- m(this.createChannelId(r, RackChannel.EMS_COMMUNICATION_TIMEOUT),
- new UnsignedWordElement(r.offset + 0x00F4)) //
- ), //
- new FC16WriteRegistersTask(r.offset + 0x000B, //
- m(this.createChannelId(r, RackChannel.EMS_ADDRESS),
- new UnsignedWordElement(r.offset + 0x000B)), //
- m(this.createChannelId(r, RackChannel.EMS_BAUDRATE),
- new UnsignedWordElement(r.offset + 0x000C)) //
- ), //
-
- // Single Cluster Control Registers (General)
- new FC6WriteRegisterTask(r.offset + 0x00CC, //
- m(this.createChannelId(r, RackChannel.SYSTEM_TOTAL_CAPACITY),
- new UnsignedWordElement(r.offset + 0x00CC)) //
- ), //
- new FC6WriteRegisterTask(r.offset + 0x0015, //
- m(this.createChannelId(r, RackChannel.SET_SUB_MASTER_ADDRESS),
- new UnsignedWordElement(r.offset + 0x0015)) //
- ), //
- new FC6WriteRegisterTask(r.offset + 0x00F3, //
- m(this.createChannelId(r, RackChannel.VOLTAGE_LOW_PROTECTION),
- new UnsignedWordElement(r.offset + 0x00F3)) //
- ), //
- new FC3ReadRegistersTask(r.offset + 0x00CC, Priority.LOW, //
- m(this.createChannelId(r, RackChannel.SYSTEM_TOTAL_CAPACITY),
- new UnsignedWordElement(r.offset + 0x00CC)) //
- ),
-
- // Single Cluster Status Registers
- new FC3ReadRegistersTask(r.offset + 0x100, Priority.HIGH, //
- m(this.createChannelId(r, RackChannel.VOLTAGE),
- new UnsignedWordElement(r.offset + 0x100), SCALE_FACTOR_2),
- m(this.createChannelId(r, RackChannel.CURRENT), new SignedWordElement(r.offset + 0x101),
- SCALE_FACTOR_2),
- m(this.createChannelId(r, RackChannel.CHARGE_INDICATION),
- new UnsignedWordElement(r.offset + 0x102)),
- m(this.createChannelId(r, RackChannel.SOC), new UnsignedWordElement(r.offset + 0x103)),
- m(this.createChannelId(r, RackChannel.SOH), new UnsignedWordElement(r.offset + 0x104)),
- m(this.createChannelId(r, RackChannel.MAX_CELL_VOLTAGE_ID),
- new UnsignedWordElement(r.offset + 0x105)),
- m(this.createChannelId(r, RackChannel.MAX_CELL_VOLTAGE),
- new UnsignedWordElement(r.offset + 0x106)),
- m(this.createChannelId(r, RackChannel.MIN_CELL_VOLTAGE_ID),
- new UnsignedWordElement(r.offset + 0x107)),
- m(this.createChannelId(r, RackChannel.MIN_CELL_VOLTAGE),
- new UnsignedWordElement(r.offset + 0x108)),
- m(this.createChannelId(r, RackChannel.MAX_CELL_TEMPERATURE_ID),
- new UnsignedWordElement(r.offset + 0x109)),
- m(this.createChannelId(r, RackChannel.MAX_CELL_TEMPERATURE),
- new SignedWordElement(r.offset + 0x10A), SCALE_FACTOR_MINUS_1),
- m(this.createChannelId(r, RackChannel.MIN_CELL_TEMPERATURE_ID),
- new UnsignedWordElement(r.offset + 0x10B)),
- m(this.createChannelId(r, RackChannel.MIN_CELL_TEMPERATURE),
- new SignedWordElement(r.offset + 0x10C), SCALE_FACTOR_MINUS_1),
- m(this.createChannelId(r, RackChannel.AVERAGE_VOLTAGE),
- new UnsignedWordElement(r.offset + 0x10D)),
- m(this.createChannelId(r, RackChannel.SYSTEM_INSULATION),
- new UnsignedWordElement(r.offset + 0x10E)),
- m(this.createChannelId(r, RackChannel.SYSTEM_MAX_CHARGE_CURRENT),
- new UnsignedWordElement(r.offset + 0x10F), SCALE_FACTOR_2),
- m(this.createChannelId(r, RackChannel.SYSTEM_MAX_DISCHARGE_CURRENT),
- new UnsignedWordElement(r.offset + 0x110), SCALE_FACTOR_2),
- m(this.createChannelId(r, RackChannel.POSITIVE_INSULATION),
- new UnsignedWordElement(r.offset + 0x111)),
- m(this.createChannelId(r, RackChannel.NEGATIVE_INSULATION),
- new UnsignedWordElement(r.offset + 0x112)),
- m(this.createChannelId(r, RackChannel.CLUSTER_RUN_STATE),
- new UnsignedWordElement(r.offset + 0x113)),
- m(this.createChannelId(r, RackChannel.AVG_TEMPERATURE),
- new SignedWordElement(r.offset + 0x114))),
- new FC3ReadRegistersTask(r.offset + 0x18b, Priority.LOW,
- m(this.createChannelId(r, RackChannel.PROJECT_ID),
- new UnsignedWordElement(r.offset + 0x18b)),
- m(this.createChannelId(r, RackChannel.VERSION_MAJOR),
- new UnsignedWordElement(r.offset + 0x18c)),
- m(this.createChannelId(r, RackChannel.VERSION_SUB),
- new UnsignedWordElement(r.offset + 0x18d)),
- m(this.createChannelId(r, RackChannel.VERSION_MODIFY),
- new UnsignedWordElement(r.offset + 0x18e))),
-
- // System Warning/Shut Down Status Registers
- new FC3ReadRegistersTask(r.offset + 0x140, Priority.LOW,
- // Level 2 Alarm: BMS Self-protect, main contactor shut down
- m(new BitsWordElement(r.offset + 0x140, this) //
- .bit(0, this.createChannelId(r, RackChannel.LEVEL2_CELL_VOLTAGE_HIGH)) //
- .bit(1, this.createChannelId(r, RackChannel.LEVEL2_TOTAL_VOLTAGE_HIGH)) //
- .bit(2, this.createChannelId(r, RackChannel.LEVEL2_CHARGE_CURRENT_HIGH)) //
- .bit(3, this.createChannelId(r, RackChannel.LEVEL2_CELL_VOLTAGE_LOW)) //
- .bit(4, this.createChannelId(r, RackChannel.LEVEL2_TOTAL_VOLTAGE_LOW)) //
- .bit(5, this.createChannelId(r, RackChannel.LEVEL2_DISCHARGE_CURRENT_HIGH)) //
- .bit(6, this.createChannelId(r, RackChannel.LEVEL2_CHARGE_TEMP_HIGH)) //
- .bit(7, this.createChannelId(r, RackChannel.LEVEL2_CHARGE_TEMP_LOW)) //
- // 8 -> Reserved
- // 9 -> Reserved
- .bit(10, this.createChannelId(r, RackChannel.LEVEL2_POWER_POLE_TEMP_HIGH)) //
- // 11 -> Reserved
- .bit(12, this.createChannelId(r, RackChannel.LEVEL2_INSULATION_VALUE)) //
- // 13 -> Reserved
- .bit(14, this.createChannelId(r, RackChannel.LEVEL2_DISCHARGE_TEMP_HIGH)) //
- .bit(15, this.createChannelId(r, RackChannel.LEVEL2_DISCHARGE_TEMP_LOW)) //
- ),
- // Level 1 Alarm: EMS Control to stop charge, discharge, charge&discharge
- m(new BitsWordElement(r.offset + 0x141, this) //
- .bit(1, this.createChannelId(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_HIGH)) //
- .bit(2, this.createChannelId(r, RackChannel.LEVEL1_CHARGE_CURRENT_HIGH)) //
- .bit(4, this.createChannelId(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_LOW)) //
- .bit(5, this.createChannelId(r, RackChannel.LEVEL1_DISCHARGE_CURRENT_HIGH)) //
- .bit(6, this.createChannelId(r, RackChannel.LEVEL1_CHARGE_TEMP_HIGH)) //
- .bit(7, this.createChannelId(r, RackChannel.LEVEL1_CHARGE_TEMP_LOW)) //
- .bit(8, this.createChannelId(r, RackChannel.LEVEL1_SOC_LOW)) //
- .bit(9, this.createChannelId(r, RackChannel.LEVEL1_TEMP_DIFF_TOO_BIG)) //
- .bit(10, this.createChannelId(r, RackChannel.LEVEL1_POWER_POLE_TEMP_HIGH)) //
- .bit(11, this.createChannelId(r, RackChannel.LEVEL1_CELL_VOLTAGE_DIFF_TOO_BIG)) //
- .bit(12, this.createChannelId(r, RackChannel.LEVEL1_INSULATION_VALUE)) //
- .bit(13, this.createChannelId(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_DIFF_TOO_BIG)) //
- .bit(14, this.createChannelId(r, RackChannel.LEVEL1_DISCHARGE_TEMP_HIGH)) //
- .bit(15, this.createChannelId(r, RackChannel.LEVEL1_DISCHARGE_TEMP_LOW)) //
- ),
- // Pre-Alarm: Temperature Alarm will active current limication
- m(new BitsWordElement(r.offset + 0x142, this) //
- .bit(2, this.createChannelId(r, RackChannel.PRE_ALARM_CHARGE_CURRENT_HIGH)) //
- .bit(4, this.createChannelId(r, RackChannel.PRE_ALARM_TOTAL_VOLTAGE_LOW)) //
- .bit(5, this.createChannelId(r, RackChannel.PRE_ALARM_DISCHARGE_CURRENT_HIGH)) //
- .bit(6, this.createChannelId(r, RackChannel.PRE_ALARM_CHARGE_TEMP_HIGH)) //
- .bit(7, this.createChannelId(r, RackChannel.PRE_ALARM_CHARGE_TEMP_LOW)) //
- .bit(10, this.createChannelId(r, RackChannel.PRE_ALARM_POWER_POLE_HIGH))//
- .bit(11, this.createChannelId(r,
- RackChannel.PRE_ALARM_CELL_VOLTAGE_DIFF_TOO_BIG)) //
- .bit(12, this.createChannelId(r, RackChannel.PRE_ALARM_INSULATION_FAIL)) //
- .bit(13, this.createChannelId(r,
- RackChannel.PRE_ALARM_TOTAL_VOLTAGE_DIFF_TOO_BIG)) //
- .bit(14, this.createChannelId(r, RackChannel.PRE_ALARM_DISCHARGE_TEMP_HIGH)) //
- .bit(15, this.createChannelId(r, RackChannel.PRE_ALARM_DISCHARGE_TEMP_LOW)) //
- ) //
- ),
- // Other Alarm Info
- new FC3ReadRegistersTask(r.offset + 0x1A5, Priority.LOW, //
- m(new BitsWordElement(r.offset + 0x1A5, this) //
- .bit(0, this.createChannelId(r, RackChannel.ALARM_COMMUNICATION_TO_MASTER_BMS)) //
- .bit(1, this.createChannelId(r, RackChannel.ALARM_COMMUNICATION_TO_SLAVE_BMS)) //
- .bit(2, this.createChannelId(r,
- RackChannel.ALARM_COMMUNICATION_SLAVE_BMS_TO_TEMP_SENSORS)) //
- .bit(3, this.createChannelId(r, RackChannel.ALARM_SLAVE_BMS_HARDWARE)) //
- )),
- // Slave BMS Fault Message Registers
- new FC3ReadRegistersTask(r.offset + 0x185, Priority.LOW, //
- m(new BitsWordElement(r.offset + 0x185, this) //
- .bit(0, this.createChannelId(r, RackChannel.SLAVE_BMS_VOLTAGE_SENSOR_CABLES)) //
- .bit(1, this.createChannelId(r, RackChannel.SLAVE_BMS_POWER_CABLE)) //
- .bit(2, this.createChannelId(r, RackChannel.SLAVE_BMS_LTC6803)) //
- .bit(3, this.createChannelId(r, RackChannel.SLAVE_BMS_VOLTAGE_SENSORS)) //
- .bit(4, this.createChannelId(r, RackChannel.SLAVE_BMS_TEMP_SENSOR_CABLES)) //
- .bit(5, this.createChannelId(r, RackChannel.SLAVE_BMS_TEMP_SENSORS)) //
- .bit(6, this.createChannelId(r, RackChannel.SLAVE_BMS_POWER_POLE_TEMP_SENSOR)) //
- .bit(7, this.createChannelId(r, RackChannel.SLAVE_BMS_TEMP_BOARD_COM)) //
- .bit(8, this.createChannelId(r, RackChannel.SLAVE_BMS_BALANCE_MODULE)) //
- .bit(9, this.createChannelId(r, RackChannel.SLAVE_BMS_TEMP_SENSORS2)) //
- .bit(10, this.createChannelId(r, RackChannel.SLAVE_BMS_INTERNAL_COM)) //
- .bit(11, this.createChannelId(r, RackChannel.SLAVE_BMS_EEPROM)) //
- .bit(12, this.createChannelId(r, RackChannel.SLAVE_BMS_INIT)) //
- )) //
- );
- } catch (OpenemsException e) {
- this.logError(this.log, "Error while creating modbus tasks: " + e.getMessage());
- e.printStackTrace();
- } //
+ this.getModbusProtocol().addTasks(//
+
+ new FC3ReadRegistersTask(r.offset + 0x000B, Priority.LOW, //
+ m(this.createChannelId(r, RackChannel.EMS_ADDRESS),
+ new UnsignedWordElement(r.offset + 0x000B)), //
+ m(this.createChannelId(r, RackChannel.EMS_BAUDRATE),
+ new UnsignedWordElement(r.offset + 0x000C)), //
+ new DummyRegisterElement(r.offset + 0x000D, r.offset + 0x000F),
+ m(this.createChannelId(r, RackChannel.PRE_CHARGE_CONTROL),
+ new UnsignedWordElement(r.offset + 0x0010)), //
+ new DummyRegisterElement(r.offset + 0x0011, r.offset + 0x0014),
+ m(this.createChannelId(r, RackChannel.SET_SUB_MASTER_ADDRESS),
+ new UnsignedWordElement(r.offset + 0x0015)) //
+ ), //
+ new FC3ReadRegistersTask(r.offset + 0x00F4, Priority.LOW, //
+ m(this.createChannelId(r, RackChannel.EMS_COMMUNICATION_TIMEOUT),
+ new UnsignedWordElement(r.offset + 0x00F4)) //
+ ),
+
+ // Single Cluster Control Registers (running without Master BMS)
+ new FC6WriteRegisterTask(r.offset + 0x0010, //
+ m(this.createChannelId(r, RackChannel.PRE_CHARGE_CONTROL),
+ new UnsignedWordElement(r.offset + 0x0010)) //
+ ), //
+ new FC6WriteRegisterTask(r.offset + 0x00F4, //
+ m(this.createChannelId(r, RackChannel.EMS_COMMUNICATION_TIMEOUT),
+ new UnsignedWordElement(r.offset + 0x00F4)) //
+ ), //
+ new FC16WriteRegistersTask(r.offset + 0x000B, //
+ m(this.createChannelId(r, RackChannel.EMS_ADDRESS),
+ new UnsignedWordElement(r.offset + 0x000B)), //
+ m(this.createChannelId(r, RackChannel.EMS_BAUDRATE),
+ new UnsignedWordElement(r.offset + 0x000C)) //
+ ), //
+
+ // Single Cluster Control Registers (General)
+ new FC6WriteRegisterTask(r.offset + 0x00CC, //
+ m(this.createChannelId(r, RackChannel.SYSTEM_TOTAL_CAPACITY),
+ new UnsignedWordElement(r.offset + 0x00CC)) //
+ ), //
+ new FC6WriteRegisterTask(r.offset + 0x0015, //
+ m(this.createChannelId(r, RackChannel.SET_SUB_MASTER_ADDRESS),
+ new UnsignedWordElement(r.offset + 0x0015)) //
+ ), //
+ new FC6WriteRegisterTask(r.offset + 0x00F3, //
+ m(this.createChannelId(r, RackChannel.VOLTAGE_LOW_PROTECTION),
+ new UnsignedWordElement(r.offset + 0x00F3)) //
+ ), //
+ new FC3ReadRegistersTask(r.offset + 0x00CC, Priority.LOW, //
+ m(this.createChannelId(r, RackChannel.SYSTEM_TOTAL_CAPACITY),
+ new UnsignedWordElement(r.offset + 0x00CC)) //
+ ),
+
+ // Single Cluster Status Registers
+ new FC3ReadRegistersTask(r.offset + 0x100, Priority.HIGH, //
+ m(this.createChannelId(r, RackChannel.VOLTAGE), new UnsignedWordElement(r.offset + 0x100),
+ SCALE_FACTOR_2),
+ m(this.createChannelId(r, RackChannel.CURRENT), new SignedWordElement(r.offset + 0x101),
+ SCALE_FACTOR_2),
+ m(this.createChannelId(r, RackChannel.CHARGE_INDICATION),
+ new UnsignedWordElement(r.offset + 0x102)),
+ m(this.createChannelId(r, RackChannel.SOC), new UnsignedWordElement(r.offset + 0x103)),
+ m(this.createChannelId(r, RackChannel.SOH), new UnsignedWordElement(r.offset + 0x104)),
+ m(this.createChannelId(r, RackChannel.MAX_CELL_VOLTAGE_ID),
+ new UnsignedWordElement(r.offset + 0x105)),
+ m(this.createChannelId(r, RackChannel.MAX_CELL_VOLTAGE),
+ new UnsignedWordElement(r.offset + 0x106)),
+ m(this.createChannelId(r, RackChannel.MIN_CELL_VOLTAGE_ID),
+ new UnsignedWordElement(r.offset + 0x107)),
+ m(this.createChannelId(r, RackChannel.MIN_CELL_VOLTAGE),
+ new UnsignedWordElement(r.offset + 0x108)),
+ m(this.createChannelId(r, RackChannel.MAX_CELL_TEMPERATURE_ID),
+ new UnsignedWordElement(r.offset + 0x109)),
+ m(this.createChannelId(r, RackChannel.MAX_CELL_TEMPERATURE),
+ new SignedWordElement(r.offset + 0x10A), SCALE_FACTOR_MINUS_1),
+ m(this.createChannelId(r, RackChannel.MIN_CELL_TEMPERATURE_ID),
+ new UnsignedWordElement(r.offset + 0x10B)),
+ m(this.createChannelId(r, RackChannel.MIN_CELL_TEMPERATURE),
+ new SignedWordElement(r.offset + 0x10C), SCALE_FACTOR_MINUS_1),
+ m(this.createChannelId(r, RackChannel.AVERAGE_VOLTAGE),
+ new UnsignedWordElement(r.offset + 0x10D)),
+ m(this.createChannelId(r, RackChannel.SYSTEM_INSULATION),
+ new UnsignedWordElement(r.offset + 0x10E)),
+ m(this.createChannelId(r, RackChannel.SYSTEM_MAX_CHARGE_CURRENT),
+ new UnsignedWordElement(r.offset + 0x10F), SCALE_FACTOR_2),
+ m(this.createChannelId(r, RackChannel.SYSTEM_MAX_DISCHARGE_CURRENT),
+ new UnsignedWordElement(r.offset + 0x110), SCALE_FACTOR_2),
+ m(this.createChannelId(r, RackChannel.POSITIVE_INSULATION),
+ new UnsignedWordElement(r.offset + 0x111)),
+ m(this.createChannelId(r, RackChannel.NEGATIVE_INSULATION),
+ new UnsignedWordElement(r.offset + 0x112)),
+ m(this.createChannelId(r, RackChannel.CLUSTER_RUN_STATE),
+ new UnsignedWordElement(r.offset + 0x113)),
+ m(this.createChannelId(r, RackChannel.AVG_TEMPERATURE),
+ new SignedWordElement(r.offset + 0x114))),
+ new FC3ReadRegistersTask(r.offset + 0x18b, Priority.LOW,
+ m(this.createChannelId(r, RackChannel.PROJECT_ID),
+ new UnsignedWordElement(r.offset + 0x18b)),
+ m(this.createChannelId(r, RackChannel.VERSION_MAJOR),
+ new UnsignedWordElement(r.offset + 0x18c)),
+ m(this.createChannelId(r, RackChannel.VERSION_SUB),
+ new UnsignedWordElement(r.offset + 0x18d)),
+ m(this.createChannelId(r, RackChannel.VERSION_MODIFY),
+ new UnsignedWordElement(r.offset + 0x18e))),
+
+ // System Warning/Shut Down Status Registers
+ new FC3ReadRegistersTask(r.offset + 0x140, Priority.LOW,
+ // Level 2 Alarm: BMS Self-protect, main contactor shut down
+ m(new BitsWordElement(r.offset + 0x140, this) //
+ .bit(0, this.createChannelId(r, RackChannel.LEVEL2_CELL_VOLTAGE_HIGH)) //
+ .bit(1, this.createChannelId(r, RackChannel.LEVEL2_TOTAL_VOLTAGE_HIGH)) //
+ .bit(2, this.createChannelId(r, RackChannel.LEVEL2_CHARGE_CURRENT_HIGH)) //
+ .bit(3, this.createChannelId(r, RackChannel.LEVEL2_CELL_VOLTAGE_LOW)) //
+ .bit(4, this.createChannelId(r, RackChannel.LEVEL2_TOTAL_VOLTAGE_LOW)) //
+ .bit(5, this.createChannelId(r, RackChannel.LEVEL2_DISCHARGE_CURRENT_HIGH)) //
+ .bit(6, this.createChannelId(r, RackChannel.LEVEL2_CHARGE_TEMP_HIGH)) //
+ .bit(7, this.createChannelId(r, RackChannel.LEVEL2_CHARGE_TEMP_LOW)) //
+ // 8 -> Reserved
+ // 9 -> Reserved
+ .bit(10, this.createChannelId(r, RackChannel.LEVEL2_POWER_POLE_TEMP_HIGH)) //
+ // 11 -> Reserved
+ .bit(12, this.createChannelId(r, RackChannel.LEVEL2_INSULATION_VALUE)) //
+ // 13 -> Reserved
+ .bit(14, this.createChannelId(r, RackChannel.LEVEL2_DISCHARGE_TEMP_HIGH)) //
+ .bit(15, this.createChannelId(r, RackChannel.LEVEL2_DISCHARGE_TEMP_LOW)) //
+ ),
+ // Level 1 Alarm: EMS Control to stop charge, discharge, charge&discharge
+ m(new BitsWordElement(r.offset + 0x141, this) //
+ .bit(1, this.createChannelId(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_HIGH)) //
+ .bit(2, this.createChannelId(r, RackChannel.LEVEL1_CHARGE_CURRENT_HIGH)) //
+ .bit(4, this.createChannelId(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_LOW)) //
+ .bit(5, this.createChannelId(r, RackChannel.LEVEL1_DISCHARGE_CURRENT_HIGH)) //
+ .bit(6, this.createChannelId(r, RackChannel.LEVEL1_CHARGE_TEMP_HIGH)) //
+ .bit(7, this.createChannelId(r, RackChannel.LEVEL1_CHARGE_TEMP_LOW)) //
+ .bit(8, this.createChannelId(r, RackChannel.LEVEL1_SOC_LOW)) //
+ .bit(9, this.createChannelId(r, RackChannel.LEVEL1_TEMP_DIFF_TOO_BIG)) //
+ .bit(10, this.createChannelId(r, RackChannel.LEVEL1_POWER_POLE_TEMP_HIGH)) //
+ .bit(11, this.createChannelId(r, RackChannel.LEVEL1_CELL_VOLTAGE_DIFF_TOO_BIG)) //
+ .bit(12, this.createChannelId(r, RackChannel.LEVEL1_INSULATION_VALUE)) //
+ .bit(13, this.createChannelId(r, RackChannel.LEVEL1_TOTAL_VOLTAGE_DIFF_TOO_BIG)) //
+ .bit(14, this.createChannelId(r, RackChannel.LEVEL1_DISCHARGE_TEMP_HIGH)) //
+ .bit(15, this.createChannelId(r, RackChannel.LEVEL1_DISCHARGE_TEMP_LOW)) //
+ ),
+ // Pre-Alarm: Temperature Alarm will active current limication
+ m(new BitsWordElement(r.offset + 0x142, this) //
+ .bit(2, this.createChannelId(r, RackChannel.PRE_ALARM_CHARGE_CURRENT_HIGH)) //
+ .bit(4, this.createChannelId(r, RackChannel.PRE_ALARM_TOTAL_VOLTAGE_LOW)) //
+ .bit(5, this.createChannelId(r, RackChannel.PRE_ALARM_DISCHARGE_CURRENT_HIGH)) //
+ .bit(6, this.createChannelId(r, RackChannel.PRE_ALARM_CHARGE_TEMP_HIGH)) //
+ .bit(7, this.createChannelId(r, RackChannel.PRE_ALARM_CHARGE_TEMP_LOW)) //
+ .bit(10, this.createChannelId(r, RackChannel.PRE_ALARM_POWER_POLE_HIGH))//
+ .bit(11, this.createChannelId(r, RackChannel.PRE_ALARM_CELL_VOLTAGE_DIFF_TOO_BIG)) //
+ .bit(12, this.createChannelId(r, RackChannel.PRE_ALARM_INSULATION_FAIL)) //
+ .bit(13, this.createChannelId(r, RackChannel.PRE_ALARM_TOTAL_VOLTAGE_DIFF_TOO_BIG)) //
+ .bit(14, this.createChannelId(r, RackChannel.PRE_ALARM_DISCHARGE_TEMP_HIGH)) //
+ .bit(15, this.createChannelId(r, RackChannel.PRE_ALARM_DISCHARGE_TEMP_LOW)) //
+ ) //
+ ),
+ // Other Alarm Info
+ new FC3ReadRegistersTask(r.offset + 0x1A5, Priority.LOW, //
+ m(new BitsWordElement(r.offset + 0x1A5, this) //
+ .bit(0, this.createChannelId(r, RackChannel.ALARM_COMMUNICATION_TO_MASTER_BMS)) //
+ .bit(1, this.createChannelId(r, RackChannel.ALARM_COMMUNICATION_TO_SLAVE_BMS)) //
+ .bit(2, this.createChannelId(r,
+ RackChannel.ALARM_COMMUNICATION_SLAVE_BMS_TO_TEMP_SENSORS)) //
+ .bit(3, this.createChannelId(r, RackChannel.ALARM_SLAVE_BMS_HARDWARE)) //
+ )),
+ // Slave BMS Fault Message Registers
+ new FC3ReadRegistersTask(r.offset + 0x185, Priority.LOW, //
+ m(new BitsWordElement(r.offset + 0x185, this) //
+ .bit(0, this.createChannelId(r, RackChannel.SLAVE_BMS_VOLTAGE_SENSOR_CABLES)) //
+ .bit(1, this.createChannelId(r, RackChannel.SLAVE_BMS_POWER_CABLE)) //
+ .bit(2, this.createChannelId(r, RackChannel.SLAVE_BMS_LTC6803)) //
+ .bit(3, this.createChannelId(r, RackChannel.SLAVE_BMS_VOLTAGE_SENSORS)) //
+ .bit(4, this.createChannelId(r, RackChannel.SLAVE_BMS_TEMP_SENSOR_CABLES)) //
+ .bit(5, this.createChannelId(r, RackChannel.SLAVE_BMS_TEMP_SENSORS)) //
+ .bit(6, this.createChannelId(r, RackChannel.SLAVE_BMS_POWER_POLE_TEMP_SENSOR)) //
+ .bit(7, this.createChannelId(r, RackChannel.SLAVE_BMS_TEMP_BOARD_COM)) //
+ .bit(8, this.createChannelId(r, RackChannel.SLAVE_BMS_BALANCE_MODULE)) //
+ .bit(9, this.createChannelId(r, RackChannel.SLAVE_BMS_TEMP_SENSORS2)) //
+ .bit(10, this.createChannelId(r, RackChannel.SLAVE_BMS_INTERNAL_COM)) //
+ .bit(11, this.createChannelId(r, RackChannel.SLAVE_BMS_EEPROM)) //
+ .bit(12, this.createChannelId(r, RackChannel.SLAVE_BMS_INIT)) //
+ )) //
+ );
Consumer addCellChannels = type -> {
for (var i = 0; i < numberOfModules; i++) {
var elements = new ModbusElement[type.getSensorsPerModule()];
@@ -389,14 +385,9 @@ private void updateRackChannels(Integer numberOfModules, TreeSet racks) th
elements[j] = m(channelId, new UnsignedWordElement(r.offset + type.getOffset() + sensorIndex));
}
// Add a Modbus read task for this module
- try {
- this.getModbusProtocol().addTasks(//
- new FC3ReadRegistersTask(r.offset + type.getOffset() + i * type.getSensorsPerModule(),
- Priority.LOW, elements));
- } catch (OpenemsException e) {
- this.logError(this.log, "Error while creating modbus tasks: " + e.getMessage());
- e.printStackTrace();
- }
+ this.getModbusProtocol().addTasks(//
+ new FC3ReadRegistersTask(r.offset + type.getOffset() + i * type.getSensorsPerModule(),
+ Priority.LOW, elements));
}
};
addCellChannels.accept(CellChannelFactory.Type.VOLTAGE_CLUSTER);
@@ -657,38 +648,24 @@ private void calculateCapacity(int numberOfTowers, int numberOfModules) {
* @throws OpenemsException on error
*/
private CompletableFuture getNumberOfModules() {
- final var result = new CompletableFuture();
-
- try {
- ModbusUtils
- .readELementOnce(this.getModbusProtocol(),
- new UnsignedWordElement(0x20C1 /* No of modules for 1st tower */), true)
- .thenAccept(numberOfModules -> {
- if (numberOfModules == null) {
- return;
- }
- result.complete(numberOfModules);
- });
- } catch (OpenemsException e) {
- result.completeExceptionally(e);
- }
-
- return result;
+ return readElementOnce(this.getModbusProtocol(), ModbusUtils::retryOnNull,
+ new UnsignedWordElement(0x20C1 /* No of modules for 1st tower */));
}
/**
* Recursively reads the 'No of modules' register of each tower. Eventually
* completes the {@link CompletableFuture}.
*
+ * @param retryPredicate yield true to retry reading values; false
+ * otherwise. Parameters are the {@link ExecuteState}
+ * of the entire task and the individual element
+ * value
* @param result the {@link CompletableFuture}
* @param totalNumberOfTowers the recursively incremented total number of towers
* @param addresses Queue with the remaining 'No of modules' registers
- * @param tryAgainOnError if true, tries to read till it receives a value;
- * if false, stops after first try and possibly
- * return null
*/
- private void checkNumberOfTowers(CompletableFuture result, int totalNumberOfTowers,
- final Queue addresses, boolean tryAgainOnError) {
+ private void checkNumberOfTowers(BiPredicate retryPredicate,
+ CompletableFuture result, int totalNumberOfTowers, final Queue addresses) {
final var address = addresses.poll();
if (address == null) {
@@ -697,28 +674,18 @@ private void checkNumberOfTowers(CompletableFuture result, int totalNum
return;
}
- try {
- // Read next address in Queue
- ModbusUtils.readELementOnce(this.getModbusProtocol(), new UnsignedWordElement(address), tryAgainOnError)
- .thenAccept(numberOfModules -> {
- if (numberOfModules == null) {
- if (tryAgainOnError) {
- // Try again
- return;
- }
- // Read error -> this tower does not exist. Stop here.
- result.complete(totalNumberOfTowers);
- return;
- }
-
- // Read successful -> try to read next tower
- this.checkNumberOfTowers(result, totalNumberOfTowers + 1, addresses, false);
- });
- } catch (OpenemsException e) {
- e.printStackTrace();
- result.completeExceptionally(e);
- return;
- }
+ // Read next address in Queue
+ readElementOnce(this.getModbusProtocol(), retryPredicate, new UnsignedWordElement(address))
+ .thenAccept(numberOfModules -> {
+ if (numberOfModules == null) {
+ // Read error -> this tower does not exist. Stop here.
+ result.complete(totalNumberOfTowers);
+ return;
+ }
+
+ // Read successful -> try to read next tower
+ this.checkNumberOfTowers(ModbusUtils::doNotRetry, result, totalNumberOfTowers + 1, addresses);
+ });
}
private CompletableFuture getNumberOfTowers() throws OpenemsException {
@@ -731,7 +698,7 @@ private CompletableFuture getNumberOfTowers() throws OpenemsException {
addresses.add(0x50C1 /* No of modules for 4th tower */);
addresses.add(0x60C1 /* No of modules for 5th tower */);
- this.checkNumberOfTowers(result, 0, addresses, true);
+ this.checkNumberOfTowers(ModbusUtils::retryOnNull, result, 0, addresses);
return result;
}
@@ -786,7 +753,7 @@ public String debugLog() {
}
@Override
- protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
+ protected ModbusProtocol defineModbusProtocol() {
return new ModbusProtocol(this,
/*
* BMS Control Registers
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImpl.java
index 125aba6961e..92bf44d0a43 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImpl.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImpl.java
@@ -435,7 +435,7 @@ private void stopSystem() {
}
@Override
- protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
+ protected ModbusProtocol defineModbusProtocol() {
return new ModbusProtocol(this, //
new FC6WriteRegisterTask(0x2010, //
m(BatterySoltaroSingleRackVersionA.ChannelId.BMS_CONTACTOR_CONTROL,
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java
index 28a0e5f4268..2bc1e0c7db0 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImpl.java
@@ -4,6 +4,7 @@
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_1;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1;
+import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@@ -230,7 +231,7 @@ public String debugLog() {
}
@Override
- protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
+ protected ModbusProtocol defineModbusProtocol() {
var protocol = new ModbusProtocol(this, //
// Main switch
@@ -914,22 +915,9 @@ protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
* Gets the Number of Modules.
*
* @return the Number of Modules as a {@link CompletableFuture}.
- * @throws OpenemsException on error
*/
private CompletableFuture getNumberOfModules() {
- final var result = new CompletableFuture();
- try {
- ModbusUtils.readELementOnce(this.getModbusProtocol(), new UnsignedWordElement(0x20C1), true)
- .thenAccept(numberOfModules -> {
- if (numberOfModules == null) {
- return;
- }
- result.complete(numberOfModules);
- });
- } catch (OpenemsException e) {
- result.completeExceptionally(e);
- }
- return result;
+ return readElementOnce(this.getModbusProtocol(), ModbusUtils::retryOnNull, new UnsignedWordElement(0x20C1));
}
/**
@@ -955,41 +943,35 @@ private void calculateCapacity(Integer numberOfModules) {
* @param numberOfModules the number of battery modules
*/
private void createDynamicChannels(int numberOfModules) {
- try {
- for (var i = 0; i < numberOfModules; i++) {
- var ameVolt = new ModbusElement[SENSORS_PER_MODULE];
- var ameTemp = new ModbusElement[SENSORS_PER_MODULE];
- for (var j = 0; j < SENSORS_PER_MODULE; j++) {
- var sensor = i * SENSORS_PER_MODULE + j;
- {
- // Create Voltage Channel
- var channelId = new ChannelIdImpl(
- "CLUSTER_1_BATTERY_" + String.format("%03d", sensor) + "_VOLTAGE",
- Doc.of(OpenemsType.INTEGER).unit(Unit.MILLIVOLT));
- this.addChannel(channelId);
- // Create Modbus-Mapping for Voltages
- var uwe = new UnsignedWordElement(VOLTAGE_ADDRESS_OFFSET + sensor);
- ameVolt[j] = m(channelId, uwe);
- }
- {
- // Create Temperature Channel
- var channelId = new ChannelIdImpl(
- "CLUSTER_1_BATTERY_" + String.format("%03d", sensor) + "_TEMPERATURE",
- Doc.of(OpenemsType.INTEGER).unit(Unit.DEZIDEGREE_CELSIUS));
- this.addChannel(channelId);
- // Create Modbus-Mapping for Temperatures
- var uwe = new UnsignedWordElement(TEMPERATURE_ADDRESS_OFFSET + sensor);
- ameTemp[j] = m(channelId, uwe);
- }
+ for (var i = 0; i < numberOfModules; i++) {
+ var ameVolt = new ModbusElement[SENSORS_PER_MODULE];
+ var ameTemp = new ModbusElement[SENSORS_PER_MODULE];
+ for (var j = 0; j < SENSORS_PER_MODULE; j++) {
+ var sensor = i * SENSORS_PER_MODULE + j;
+ {
+ // Create Voltage Channel
+ var channelId = new ChannelIdImpl("CLUSTER_1_BATTERY_" + String.format("%03d", sensor) + "_VOLTAGE",
+ Doc.of(OpenemsType.INTEGER).unit(Unit.MILLIVOLT));
+ this.addChannel(channelId);
+ // Create Modbus-Mapping for Voltages
+ var uwe = new UnsignedWordElement(VOLTAGE_ADDRESS_OFFSET + sensor);
+ ameVolt[j] = m(channelId, uwe);
+ }
+ {
+ // Create Temperature Channel
+ var channelId = new ChannelIdImpl(
+ "CLUSTER_1_BATTERY_" + String.format("%03d", sensor) + "_TEMPERATURE",
+ Doc.of(OpenemsType.INTEGER).unit(Unit.DEZIDEGREE_CELSIUS));
+ this.addChannel(channelId);
+ // Create Modbus-Mapping for Temperatures
+ var uwe = new UnsignedWordElement(TEMPERATURE_ADDRESS_OFFSET + sensor);
+ ameTemp[j] = m(channelId, uwe);
}
- this.getModbusProtocol().addTasks(//
- new FC3ReadRegistersTask(VOLTAGE_ADDRESS_OFFSET + i * SENSORS_PER_MODULE, Priority.LOW,
- ameVolt), //
- new FC3ReadRegistersTask(TEMPERATURE_ADDRESS_OFFSET + i * SENSORS_PER_MODULE, Priority.LOW,
- ameTemp));
}
- } catch (OpenemsException e) {
- e.printStackTrace();
+ this.getModbusProtocol().addTasks(//
+ new FC3ReadRegistersTask(VOLTAGE_ADDRESS_OFFSET + i * SENSORS_PER_MODULE, Priority.LOW, ameVolt), //
+ new FC3ReadRegistersTask(TEMPERATURE_ADDRESS_OFFSET + i * SENSORS_PER_MODULE, Priority.LOW,
+ ameTemp));
}
}
}
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java
index 4d1246d3c20..69feb15eeaa 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImpl.java
@@ -3,6 +3,7 @@
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.DIRECT_1_TO_1;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1;
+import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
@@ -27,7 +28,6 @@
import io.openems.common.channel.AccessMode;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
-import io.openems.common.exceptions.OpenemsException;
import io.openems.edge.battery.api.Battery;
import io.openems.edge.battery.protection.BatteryProtection;
import io.openems.edge.battery.soltaro.common.batteryprotection.BatteryProtectionDefinitionSoltaro3500Wh;
@@ -152,22 +152,9 @@ private void calculateCapacity(Integer numberOfModules) {
* Gets the Number of Modules.
*
* @return the Number of Modules as a {@link CompletableFuture}.
- * @throws OpenemsException on error
*/
private CompletableFuture getNumberOfModules() {
- final var result = new CompletableFuture();
- try {
- ModbusUtils.readELementOnce(this.getModbusProtocol(), new UnsignedWordElement(0x20C1), true)
- .thenAccept(numberOfModules -> {
- if (numberOfModules == null) {
- return;
- }
- result.complete(numberOfModules);
- });
- } catch (OpenemsException e) {
- result.completeExceptionally(e);
- }
- return result;
+ return readElementOnce(this.getModbusProtocol(), ModbusUtils::retryOnNull, new UnsignedWordElement(0x20C1));
}
@Override
@@ -219,7 +206,7 @@ public String debugLog() {
}
@Override
- protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
+ protected ModbusProtocol defineModbusProtocol() {
var protocol = new ModbusProtocol(this, //
new FC6WriteRegisterTask(0x2004, //
m(BatterySoltaroSingleRackVersionC.ChannelId.SYSTEM_RESET, new UnsignedWordElement(0x2004)) //
@@ -731,14 +718,8 @@ void createCellVoltageAndTemperatureChannels(int numberOfModules) {
}
// Add a Modbus read task for this module
var startAddress = type.getOffset() + i * type.getSensorsPerModule();
- try {
- this.getModbusProtocol().addTask(//
- new FC3ReadRegistersTask(startAddress, Priority.LOW, elements));
- } catch (OpenemsException e) {
- this.logWarn(this.log, "Error while adding Modbus task for slave [" + i + "] starting at ["
- + startAddress + "]: " + e.getMessage());
- e.printStackTrace();
- }
+ this.getModbusProtocol().addTask(//
+ new FC3ReadRegistersTask(startAddress, Priority.LOW, elements));
}
};
addCellChannels.accept(CellChannelFactory.Type.VOLTAGE_SINGLE);
diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/doc/statemachine.md b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/doc/statemachine.md
index 1e8ee842279..2ba632a58d8 100644
--- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/doc/statemachine.md
+++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/doc/statemachine.md
@@ -2,26 +2,28 @@
```mermaid
graph LR
-Undefined -->|target START| GoRunning
+Undefined --> |hasFault| Error
+Undefined --> |isStarted or Grid Connected| Running
+Undefined --> |isStopped or Off, Standby, Pre-charge| Stopped
+Undefined --> |else| Go_Stopped
-GoRunning -->|not timeout| GoRunning
-GoRunning -->|isRunning| Running
-GoRunning -->|timeout| Undefined
+Go_Stopped --> |Off or Standby or Pre-charge| Stopped
+Go_Stopped --> |hasFault or timeout| Error
+Go_Stopped --> |try for 240 second| Go_Stopped
-Running -->|isRunning && everythingOk| Running
-Running -->|otherwise| Undefined
+Stopped --> |targetStart| Go_Running
+Stopped --> |hasFault| Error
-Undefined -->|target STOP| GoStopped
-GoStopped -->|isStopped| Stopped
-GoStopped -->|not timeout| GoStopped
-GoStopped -->|timeout| Undefined
+Go_Running --> |targetStop| Go_Stopped
+Go_Running --> |hasFault or timeout| Error
+Go_Running --> |try for 240 second| Go_Running
+Go_Running --> |Grid Connected or Throttled| Running
-Stopped -->|isStopped && everythingOk| Stopped
-Stopped -->|otherwise| Undefined
+Running --> |hasFault| Error
+Running --> |targetStop| Go_Stopped
-Undefined -->|hasFault| ErrorHandling
-ErrorHandling -->|not timeout| ErrorHandling
-ErrorHandling -->|eventually| Undefined
+Error --> |!hasFault| Stopped
+Error --> |hasFault| Error
```
View using Mermaid, e.g. https://mermaid-js.github.io/mermaid-live-editor
\ No newline at end of file
diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsave.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsave.java
index 1e83c03b677..193b3dbd1d7 100644
--- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsave.java
+++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsave.java
@@ -8,6 +8,7 @@
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.S64201CurrentState;
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.S64201RequestedState;
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.statemachine.StateMachine.State;
+import io.openems.edge.bridge.modbus.api.ModbusComponent;
import io.openems.edge.bridge.modbus.sunspec.SunSpecPoint;
import io.openems.edge.common.channel.Channel;
import io.openems.edge.common.channel.Doc;
@@ -18,8 +19,8 @@
import io.openems.edge.common.startstop.StartStop;
import io.openems.edge.common.startstop.StartStoppable;
-public interface BatteryInverterKacoBlueplanetGridsave
- extends ManagedSymmetricBatteryInverter, SymmetricBatteryInverter, OpenemsComponent, StartStoppable {
+public interface BatteryInverterKacoBlueplanetGridsave extends ManagedSymmetricBatteryInverter,
+ SymmetricBatteryInverter, ModbusComponent, OpenemsComponent, StartStoppable {
/**
* Sets the KACO watchdog timeout to 60 seconds.
@@ -31,28 +32,19 @@ public interface BatteryInverterKacoBlueplanetGridsave
*/
public static final int WATCHDOG_TRIGGER_SECONDS = 10;
- /**
- * Retry set-command after x Seconds, e.g. for starting battery or
- * battery-inverter.
- */
- public static int RETRY_COMMAND_SECONDS = 30;
-
- /**
- * Retry x attempts for set-command.
- */
- public static int RETRY_COMMAND_MAX_ATTEMPTS = 30;
-
public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
STATE_MACHINE(Doc.of(State.values()) //
.text("Current State of State-Machine")), //
RUN_FAILED(Doc.of(Level.FAULT) //
.text("Running the Logic failed")), //
- MAX_START_ATTEMPTS(Doc.of(Level.FAULT) //
- .text("The maximum number of start attempts failed")), //
- MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) //
- .text("The maximum number of stop attempts failed")), //
+ MAX_START_TIMEOUT(Doc.of(Level.FAULT) //
+ .text("Max start time is exceeded")), //
+ MAX_STOP_TIMEOUT(Doc.of(Level.FAULT) //
+ .text("Max stop time is exceeded")), //
INVERTER_CURRENT_STATE_FAULT(Doc.of(Level.FAULT) //
.text("The 'CurrentState' is invalid")), //
+ GRID_DISCONNECTION(Doc.of(Level.FAULT) //
+ .text("External grid protection disconnection (17)")), //
;
private final Doc doc;
@@ -92,59 +84,59 @@ public Doc doc() {
public S64201CurrentState getCurrentState();
/**
- * Gets the Channel for {@link ChannelId#MAX_START_ATTEMPTS}.
+ * Gets the Channel for {@link ChannelId#MAX_START_TIMEOUT}.
*
* @return the Channel
*/
- public default StateChannel getMaxStartAttemptsChannel() {
- return this.channel(ChannelId.MAX_START_ATTEMPTS);
+ public default StateChannel getMaxStartTimeoutChannel() {
+ return this.channel(ChannelId.MAX_START_TIMEOUT);
}
/**
- * Gets the {@link StateChannel} for {@link ChannelId#MAX_START_ATTEMPTS}.
+ * Gets the {@link StateChannel} for {@link ChannelId#MAX_START_TIMEOUT}.
*
* @return the Channel {@link Value}
*/
- public default Value getMaxStartAttempts() {
- return this.getMaxStartAttemptsChannel().value();
+ public default Value getMaxStartTimeout() {
+ return this.getMaxStartTimeoutChannel().value();
}
/**
- * Internal method to set the 'nextValue' on
- * {@link ChannelId#MAX_START_ATTEMPTS} Channel.
+ * Internal method to set the 'nextValue' on {@link ChannelId#MAX_START_TIMEOUT}
+ * Channel.
*
* @param value the next value
*/
- public default void _setMaxStartAttempts(Boolean value) {
- this.getMaxStartAttemptsChannel().setNextValue(value);
+ public default void _setMaxStartTimeout(boolean value) {
+ this.getMaxStartTimeoutChannel().setNextValue(value);
}
/**
- * Gets the Channel for {@link ChannelId#MAX_STOP_ATTEMPTS}.
+ * Gets the Channel for {@link ChannelId#MAX_STOP_TIMEOUT}.
*
* @return the Channel
*/
- public default StateChannel getMaxStopAttemptsChannel() {
- return this.channel(ChannelId.MAX_STOP_ATTEMPTS);
+ public default StateChannel getMaxStopTimeoutChannel() {
+ return this.channel(ChannelId.MAX_STOP_TIMEOUT);
}
/**
- * Gets the {@link StateChannel} for {@link ChannelId#MAX_STOP_ATTEMPTS}.
+ * Gets the {@link StateChannel} for {@link ChannelId#MAX_STOP_TIMEOUT}.
*
* @return the Channel {@link Value}
*/
- public default Value getMaxStopAttempts() {
- return this.getMaxStopAttemptsChannel().value();
+ public default Value getMaxStopTimeout() {
+ return this.getMaxStopTimeoutChannel().value();
}
/**
- * Internal method to set the 'nextValue' on {@link ChannelId#MAX_STOP_ATTEMPTS}
+ * Internal method to set the 'nextValue' on {@link ChannelId#MAX_STOP_TIMEOUT}
* Channel.
*
* @param value the next value
*/
- public default void _setMaxStopAttempts(Boolean value) {
- this.getMaxStopAttemptsChannel().setNextValue(value);
+ public default void _setMaxStopTimeout(boolean value) {
+ this.getMaxStopTimeoutChannel().setNextValue(value);
}
/**
@@ -166,4 +158,83 @@ public default WriteChannel getRequestedStateChannel() thr
public default void setRequestedState(S64201RequestedState value) throws OpenemsNamedException {
this.getRequestedStateChannel().setNextWriteValue(value);
}
+
+ /**
+ * Gets the Channel for ChannelId.INVERTER_CURRENT_STATE_FAULT.
+ *
+ * @return the Channel
+ */
+ public default Channel getInverterCurrentStateFaultChannel() {
+ return this.channel(ChannelId.INVERTER_CURRENT_STATE_FAULT);
+ }
+
+ /**
+ * Writes the value to the ChannelId.INVERTER_CURRENT_STATE_FAULT.
+ *
+ * @param value the next value
+ */
+ public default void _setInverterCurrentStateFault(boolean value) {
+ this.getInverterCurrentStateFaultChannel().setNextValue(value);
+ }
+
+ /**
+ * Gets the Channel for ChannelId.RUN_FAILED.
+ *
+ * @return the Channel
+ */
+ public default Channel getRunFailedChannel() {
+ return this.channel(ChannelId.RUN_FAILED);
+ }
+
+ /**
+ * Writes the value to the ChannelId.RUN_FAILED.
+ *
+ * @param value the next value
+ */
+ public default void _setRunFailed(boolean value) {
+ this.getRunFailedChannel().setNextValue(value);
+ }
+
+ /**
+ * Gets the Channel for ChannelId.GRID_DISCONNECTION.
+ *
+ * @return the Channel
+ */
+ public default Channel getGridDisconnectionChannel() {
+ return this.channel(ChannelId.GRID_DISCONNECTION);
+ }
+
+ /**
+ * Writes the value to the ChannelId.GRID_DISCONNECTION.
+ *
+ * @param value the next value
+ */
+ public default void _setGridDisconnection(boolean value) {
+ this.getGridDisconnectionChannel().setNextValue(value);
+ }
+
+ /**
+ * Checks if the system is in a running state. This method retrieves the
+ * system's global state and determines whether the system is in a running
+ * state.
+ *
+ * @return true if the system is in a running state, false otherwise.
+ */
+ public boolean isRunning();
+
+ /**
+ * Checks if the system is in a stop state. This method retrieves the system's
+ * global state and determines whether the system is in a stop state.
+ *
+ * @return true if the system is in a stop state, false otherwise.
+ */
+ public boolean isShutdown();
+
+ /**
+ * Checks if the system is in a fault state. This method retrieves the system's
+ * global state and determines whether the system is in a fault state.
+ *
+ * @return true if the system is in a fault state, false otherwise.
+ */
+ public boolean hasFailure();
}
diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImpl.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImpl.java
index 95979bdc2d0..976d430e141 100644
--- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImpl.java
+++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImpl.java
@@ -6,7 +6,6 @@
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.osgi.service.cm.ConfigurationAdmin;
@@ -33,6 +32,7 @@
import io.openems.edge.batteryinverter.api.ManagedSymmetricBatteryInverter;
import io.openems.edge.batteryinverter.api.SymmetricBatteryInverter;
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.S64201CurrentState;
+import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.S64201StVnd;
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64202.S64202EnLimit;
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.statemachine.Context;
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.statemachine.StateMachine;
@@ -49,7 +49,6 @@
import io.openems.edge.common.channel.FloatWriteChannel;
import io.openems.edge.common.channel.IntegerReadChannel;
import io.openems.edge.common.channel.IntegerWriteChannel;
-import io.openems.edge.common.channel.StateChannel;
import io.openems.edge.common.channel.value.Value;
import io.openems.edge.common.component.ComponentManager;
import io.openems.edge.common.component.OpenemsComponent;
@@ -106,6 +105,7 @@ protected void setModbus(BridgeModbus modbus) {
* Kaco 92 does not have model 64203.
*/
private boolean hasSunSpecModel64203 = false;
+ private StartStop startStopTarget = StartStop.UNDEFINED;
/**
* Active SunSpec models for KACO blueplanet gridsave. Commented models are
@@ -113,7 +113,7 @@ protected void setModbus(BridgeModbus modbus) {
*/
private static final Map ACTIVE_MODELS = ImmutableMap.builder()
.put(DefaultSunSpecModel.S_1, Priority.LOW) //
- .put(DefaultSunSpecModel.S_103, Priority.LOW) //
+ .put(DefaultSunSpecModel.S_103, Priority.HIGH) //
.put(DefaultSunSpecModel.S_121, Priority.LOW) //
.put(KacoSunSpecModel.S_64201, Priority.HIGH) //
.put(KacoSunSpecModel.S_64202, Priority.LOW) //
@@ -135,7 +135,7 @@ protected void setModbus(BridgeModbus modbus) {
// .put(SunSpecModel.S_160, Priority.LOW) //
@Activate
- public BatteryInverterKacoBlueplanetGridsaveImpl() throws OpenemsException {
+ public BatteryInverterKacoBlueplanetGridsaveImpl() {
super(//
ACTIVE_MODELS, //
OpenemsComponent.ChannelId.values(), //
@@ -194,6 +194,9 @@ public void run(Battery battery, int setActivePower, int setReactivePower) throw
// Set Battery Limits
this.setBatteryLimits(battery);
+ // Set if there is grid disconnection failure
+ this.setGridDisconnectionFailure();
+
// Calculate the Energy values from ActivePower.
this.calculateEnergy();
@@ -202,24 +205,32 @@ public void run(Battery battery, int setActivePower, int setReactivePower) throw
this.triggerWatchdog();
}
- // Set State-Channels
- this.setStateChannels();
-
// Prepare Context
- var context = new Context(this, battery, this.config, setActivePower, setReactivePower);
+ var context = new Context(this, //
+ battery, //
+ setActivePower, //
+ setReactivePower, //
+ this.componentManager.getClock());
// Call the StateMachine
try {
this.stateMachine.run(context);
-
- this.channel(BatteryInverterKacoBlueplanetGridsave.ChannelId.RUN_FAILED).setNextValue(false);
-
+ this._setRunFailed(false);
} catch (OpenemsNamedException e) {
- this.channel(BatteryInverterKacoBlueplanetGridsave.ChannelId.RUN_FAILED).setNextValue(true);
+ this._setRunFailed(true);
this.logError(this.log, "StateMachine failed: " + e.getMessage());
}
}
+ private void setGridDisconnectionFailure() throws OpenemsException {
+ Channel stVndChannel = this.getSunSpecChannelOrError(KacoSunSpecModel.S64201.ST_VND);
+ Value stVnd = stVndChannel.value();
+ if (!stVnd.isDefined()) {
+ return;
+ }
+ this._setGridDisconnection(stVnd.asEnum() == S64201StVnd.POWADORPROTECT_DISCONNECTION);
+ }
+
@Override
public BatteryInverterConstraint[] getStaticConstraints() throws OpenemsException {
if (this.stateMachine.getCurrentState() == State.RUNNING) {
@@ -313,38 +324,6 @@ private void triggerWatchdog() throws OpenemsNamedException {
}
}
- /**
- * Sets the State-Channels, e.g. Warnings and Faults.
- *
- * @throws OpenemsNamedException on error
- */
- private void setStateChannels() throws OpenemsNamedException {
- /*
- * INVERTER_CURRENT_STATE_FAULT
- */
- StateChannel inverterCurrentStateChannel = this
- .channel(BatteryInverterKacoBlueplanetGridsave.ChannelId.INVERTER_CURRENT_STATE_FAULT);
- switch (this.getCurrentState()) {
- case FAULT:
- case UNDEFINED:
- case NO_ERROR_PENDING:
- inverterCurrentStateChannel.setNextValue(true);
- break;
- case GRID_CONNECTED:
- case GRID_PRE_CONNECTED:
- case MPPT:
- case OFF:
- case PRECHARGE:
- case SHUTTING_DOWN:
- case SLEEPING:
- case STANDBY:
- case STARTING:
- case THROTTLED:
- inverterCurrentStateChannel.setNextValue(false);
- break;
- }
- }
-
/**
* Mark SunSpec initialization completed; this takes some time at startup.
*/
@@ -405,34 +384,18 @@ public String debugLog() {
.toString();
}
- private final AtomicReference startStopTarget = new AtomicReference<>(StartStop.UNDEFINED);
-
@Override
public void setStartStop(StartStop value) {
- if (this.startStopTarget.getAndSet(value) != value) {
- // Set only if value changed
- this.stateMachine.forceNextState(State.UNDEFINED);
- }
+ this.startStopTarget = value;
}
@Override
public StartStop getStartStopTarget() {
- switch (this.config.startStop()) {
- case AUTO:
- // read StartStop-Channel
- return this.startStopTarget.get();
-
- case START:
- // force START
- return StartStop.START;
-
- case STOP:
- // force STOP
- return StartStop.STOP;
- }
-
- assert false;
- return StartStop.UNDEFINED; // can never happen
+ return switch (this.config.startStop()) {
+ case AUTO -> this.startStopTarget;
+ case START -> StartStop.START;
+ case STOP -> StartStop.STOP;
+ };
}
/**
@@ -485,7 +448,7 @@ public Timedata getTimedata() {
}
@Override
- protected void addBlock(int startAddress, SunSpecModel model, Priority priority) throws OpenemsException {
+ protected void addBlock(int startAddress, SunSpecModel model, Priority priority) {
super.addBlock(startAddress, model, priority);
// Mark S_64203 as available
@@ -493,4 +456,23 @@ protected void addBlock(int startAddress, SunSpecModel model, Priority priority)
this.hasSunSpecModel64203 = true;
}
}
+
+ @Override
+ public boolean isRunning() {
+ return this.getCurrentState() == S64201CurrentState.GRID_CONNECTED//
+ || this.getCurrentState() == S64201CurrentState.THROTTLED;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return this.getCurrentState() == S64201CurrentState.OFF //
+ || this.getCurrentState() == S64201CurrentState.STANDBY //
+ || this.getCurrentState() == S64201CurrentState.PRECHARGE//
+ || this.getCurrentState() == S64201CurrentState.SHUTTING_DOWN;
+ }
+
+ @Override
+ public boolean hasFailure() {
+ return this.hasFaults() || this.getCurrentState() == S64201CurrentState.FAULT;
+ }
}
diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/KacoSunSpecModel.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/KacoSunSpecModel.java
index 9f548478ea4..139f38ba225 100644
--- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/KacoSunSpecModel.java
+++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/KacoSunSpecModel.java
@@ -341,8 +341,8 @@ public static enum S64201 implements SunSpecPoint {
PointType.INT16, //
true, //
AccessMode.READ_ONLY, //
- Unit.HERTZ, //
- "HZ_SF", //
+ Unit.MILLIHERTZ, //
+ "mHZ_SF", //
new OptionsEnum[0])), //
RESERVED_36(new ReservedPointImpl("S64201_RESERVED_36")), //
RESERVED_37(new ReservedPointImpl("S64201_RESERVED_37")), //
diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/Context.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/Context.java
index 555e823f8be..9620e98ffba 100644
--- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/Context.java
+++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/Context.java
@@ -1,23 +1,31 @@
package io.openems.edge.batteryinverter.kaco.blueplanetgridsave.statemachine;
+import java.time.Clock;
+import java.time.Instant;
+
import io.openems.edge.battery.api.Battery;
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.BatteryInverterKacoBlueplanetGridsave;
-import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.Config;
import io.openems.edge.common.statemachine.AbstractContext;
public class Context extends AbstractContext {
protected final Battery battery;
- protected final Config config;
protected final int setActivePower;
protected final int setReactivePower;
+ protected final Clock clock;
+
+ private static final int TIMEOUT = 240; // [s]
- public Context(BatteryInverterKacoBlueplanetGridsave parent, Battery battery, Config config, int setActivePower,
- int setReactivePower) {
+ public Context(BatteryInverterKacoBlueplanetGridsave parent, Battery battery, int setActivePower,
+ int setReactivePower, Clock clock) {
super(parent);
this.battery = battery;
- this.config = config;
this.setActivePower = setActivePower;
this.setReactivePower = setReactivePower;
+ this.clock = clock;
+ }
+
+ protected boolean isTimeout(Instant now, Instant entryAt) {
+ return now.minusSeconds(TIMEOUT).isAfter(entryAt);
}
}
\ No newline at end of file
diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/ErrorHandler.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/ErrorHandler.java
index 5000e50caa6..8dc242a57b6 100644
--- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/ErrorHandler.java
+++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/ErrorHandler.java
@@ -1,8 +1,5 @@
package io.openems.edge.batteryinverter.kaco.blueplanetgridsave.statemachine;
-import java.time.Duration;
-import java.time.Instant;
-
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.S64201RequestedState;
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.statemachine.StateMachine.State;
@@ -10,50 +7,18 @@
public class ErrorHandler extends StateHandler {
- private static final int WAIT_SECONDS = 120;
-
- private Instant entryAt = Instant.MIN;
-
@Override
protected void onEntry(Context context) throws OpenemsNamedException {
- this.entryAt = Instant.now();
+ final var inverter = context.getParent();
+ inverter.setRequestedState(S64201RequestedState.OFF);
}
@Override
public State runAndGetNextState(Context context) throws OpenemsNamedException {
- var inverter = context.getParent();
- switch (inverter.getCurrentState()) {
- case STANDBY:
- case GRID_CONNECTED:
- case GRID_PRE_CONNECTED:
- case THROTTLED:
- case PRECHARGE:
- case MPPT:
- case STARTING:
- case OFF:
- case SHUTTING_DOWN:
- case SLEEPING:
- // no more error pending
- return State.UNDEFINED;
- case UNDEFINED:
- // TODO
- break;
- case FAULT:
- case NO_ERROR_PENDING:
- /*
- * According to Manual: to more errors to be acknowledged - try to turn OFF
- */
- // TODO this should not be set all the time
- inverter.setRequestedState(S64201RequestedState.OFF);
- break;
+ final var inverter = context.getParent();
+ if (!inverter.hasFailure()) {
+ return State.GO_STOPPED;
}
-
- if (Duration.between(this.entryAt, Instant.now()).getSeconds() > WAIT_SECONDS) {
- // Try again
- return State.UNDEFINED;
- }
-
return State.ERROR;
}
-
}
diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/GoRunningHandler.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/GoRunningHandler.java
index 1779232b7ac..9ca5d58e034 100644
--- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/GoRunningHandler.java
+++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/GoRunningHandler.java
@@ -1,78 +1,47 @@
package io.openems.edge.batteryinverter.kaco.blueplanetgridsave.statemachine;
-import java.time.Duration;
import java.time.Instant;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
-import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.BatteryInverterKacoBlueplanetGridsave;
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.S64201RequestedState;
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.statemachine.StateMachine.State;
+import io.openems.edge.common.startstop.StartStop;
import io.openems.edge.common.statemachine.StateHandler;
public class GoRunningHandler extends StateHandler {
- private Instant lastAttempt = Instant.MIN;
- private int attemptCounter = 0;
+ private Instant entryAt = Instant.MIN;
@Override
- protected void onEntry(Context context) throws OpenemsNamedException {
- this.lastAttempt = Instant.MIN;
- this.attemptCounter = 0;
- var inverter = context.getParent();
- inverter._setMaxStartAttempts(false);
+ protected void onEntry(Context context) {
+ this.entryAt = Instant.now(context.clock);
}
@Override
public State runAndGetNextState(Context context) throws OpenemsNamedException {
- var inverter = context.getParent();
-
- // Has Faults -> abort
- if (inverter.hasFaults()) {
- return State.UNDEFINED;
+ final var inverter = context.getParent();
+ if (inverter.hasFailure()) {
+ return State.ERROR;
}
- switch (inverter.getCurrentState()) {
- case GRID_CONNECTED:
- // All Good
-
- case THROTTLED:
- // if inverter is throttled, full power is not available, but the device
- // is still working
- return State.RUNNING;
+ final var now = Instant.now(context.clock);
+ if (context.isTimeout(now, this.entryAt)) {
+ inverter._setMaxStartTimeout(true);
+ return State.ERROR;
+ }
- case FAULT:
- case GRID_PRE_CONNECTED:
- case MPPT:
- case NO_ERROR_PENDING:
- case OFF:
- case PRECHARGE:
- case SHUTTING_DOWN:
- case SLEEPING:
- case STANDBY:
- case STARTING:
- case UNDEFINED:
- // Not yet running...
+ if (inverter.getStartStopTarget() == StartStop.STOP) {
+ return State.GO_STOPPED;
}
- var isMaxStartTimePassed = Duration.between(this.lastAttempt, Instant.now())
- .getSeconds() > BatteryInverterKacoBlueplanetGridsave.RETRY_COMMAND_SECONDS;
- if (!isMaxStartTimePassed) {
- // Still waiting...
- return State.GO_RUNNING;
+ if (inverter.isRunning()) {
+ return State.RUNNING;
}
- if (this.attemptCounter > BatteryInverterKacoBlueplanetGridsave.RETRY_COMMAND_MAX_ATTEMPTS) {
- // Too many tries
- inverter._setMaxStartAttempts(true);
- return State.UNDEFINED;
- } else {
- // Trying to switch on
- inverter.setRequestedState(S64201RequestedState.GRID_CONNECTED);
- this.lastAttempt = Instant.now();
- this.attemptCounter++;
- return State.GO_RUNNING;
+ // Trying to switch on
+ inverter.setRequestedState(S64201RequestedState.GRID_CONNECTED);
+ return State.GO_RUNNING;
- }
}
}
diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/GoStoppedHandler.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/GoStoppedHandler.java
index d793a6dfea7..c28f540555d 100644
--- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/GoStoppedHandler.java
+++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/GoStoppedHandler.java
@@ -1,68 +1,46 @@
package io.openems.edge.batteryinverter.kaco.blueplanetgridsave.statemachine;
-import java.time.Duration;
import java.time.Instant;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
-import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.BatteryInverterKacoBlueplanetGridsave;
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.S64201RequestedState;
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.statemachine.StateMachine.State;
import io.openems.edge.common.statemachine.StateHandler;
public class GoStoppedHandler extends StateHandler {
- private Instant lastAttempt = Instant.MIN;
- private int attemptCounter = 0;
+ private Instant entryAt = Instant.MIN;
@Override
protected void onEntry(Context context) {
- this.lastAttempt = Instant.MIN;
- this.attemptCounter = 0;
+ this.entryAt = Instant.now(context.clock);
}
@Override
public State runAndGetNextState(Context context) throws OpenemsNamedException {
- var inverter = context.getParent();
-
- switch (inverter.getCurrentState()) {
- case OFF:
- case STANDBY:
- // All Good
- return State.STOPPED;
-
- case GRID_CONNECTED:
- case THROTTLED:
- case FAULT:
- case GRID_PRE_CONNECTED:
- case MPPT:
- case NO_ERROR_PENDING:
- case PRECHARGE:
- case SHUTTING_DOWN:
- case SLEEPING:
- case STARTING:
- case UNDEFINED:
- // Not yet running...
+ final var inverter = context.getParent();
+
+ // Due to the low battery DC voltage, the inverter receives a battery low
+ // voltage error in the stop process and goes into a fault state that can be
+ // ignored during this time. Due to this situation, hasFault is preferred
+ // instead of hasFailure.
+ if (inverter.hasFaults()) {
+ inverter._setInverterCurrentStateFault(true);
+ return State.ERROR;
}
- var isMaxStartTimePassed = Duration.between(this.lastAttempt, Instant.now())
- .getSeconds() > BatteryInverterKacoBlueplanetGridsave.RETRY_COMMAND_SECONDS;
- if (!isMaxStartTimePassed) {
- // Still waiting...
- return State.GO_STOPPED;
+ final var now = Instant.now(context.clock);
+ if (context.isTimeout(now, this.entryAt)) {
+ inverter._setMaxStartTimeout(true);
+ return State.ERROR;
}
- if (this.attemptCounter > BatteryInverterKacoBlueplanetGridsave.RETRY_COMMAND_MAX_ATTEMPTS) {
- // Too many tries
- inverter._setMaxStopAttempts(true);
- return State.UNDEFINED;
-
- } else {
- // Trying to switch off
- inverter.setRequestedState(S64201RequestedState.OFF);
- this.lastAttempt = Instant.now();
- this.attemptCounter++;
- return State.GO_STOPPED;
+ if (inverter.isShutdown()) {
+ return State.STOPPED;
}
- }
+ // Trying to switch off
+ inverter.setRequestedState(S64201RequestedState.OFF);
+ return State.GO_STOPPED;
+ }
}
diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/RunningHandler.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/RunningHandler.java
index 76301531cb8..986127e902e 100644
--- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/RunningHandler.java
+++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/RunningHandler.java
@@ -15,39 +15,16 @@ public class RunningHandler extends StateHandler {
@Override
public State runAndGetNextState(Context context) throws OpenemsNamedException {
var inverter = context.getParent();
-
- if (inverter.hasFaults()) {
- return State.UNDEFINED;
+ if (inverter.hasFailure() || !inverter.isRunning()) {
+ return State.ERROR;
}
- switch (inverter.getCurrentState()) {
- case FAULT:
- case GRID_PRE_CONNECTED:
- case MPPT:
- case NO_ERROR_PENDING:
- case OFF:
- case PRECHARGE:
- case SHUTTING_DOWN:
- case SLEEPING:
- case STANDBY:
- case STARTING:
- case UNDEFINED:
- return State.UNDEFINED;
-
- case GRID_CONNECTED:
- // All Good
-
- case THROTTLED:
- // if inverter is throttled, full power is not available, but the device
- // is still working
+ if (inverter.getStartStopTarget() == StartStop.STOP) {
+ return State.GO_STOPPED;
}
- // Mark as started
- inverter._setStartStop(StartStop.START);
-
- // Apply Active and Reactive Power Set-Points
this.applyPower(context);
-
+ inverter._setStartStop(StartStop.START);
return State.RUNNING;
}
@@ -75,5 +52,4 @@ private void applyPower(Context context) throws OpenemsNamedException {
var varSetPct = context.setReactivePower * 100F / maxApparentPower;
varSetPctChannel.setNextWriteValue(varSetPct);
}
-
}
diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/StateMachine.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/StateMachine.java
index 743dca4fe1a..ab70913b54d 100644
--- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/StateMachine.java
+++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/StateMachine.java
@@ -51,20 +51,13 @@ public StateMachine(State initialState) {
@Override
public StateHandler getStateHandler(State state) {
- switch (state) {
- case UNDEFINED:
- return new UndefinedHandler();
- case GO_RUNNING:
- return new GoRunningHandler();
- case RUNNING:
- return new RunningHandler();
- case GO_STOPPED:
- return new GoStoppedHandler();
- case STOPPED:
- return new StoppedHandler();
- case ERROR:
- return new ErrorHandler();
- }
- throw new IllegalArgumentException("Unknown State [" + state + "]");
+ return switch (state) {
+ case UNDEFINED -> new UndefinedHandler();
+ case GO_RUNNING -> new GoRunningHandler();
+ case RUNNING -> new RunningHandler();
+ case GO_STOPPED -> new GoStoppedHandler();
+ case STOPPED -> new StoppedHandler();
+ case ERROR -> new ErrorHandler();
+ };
}
}
diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/StoppedHandler.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/StoppedHandler.java
index 75a415711ed..c2a6b23cca2 100644
--- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/StoppedHandler.java
+++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/StoppedHandler.java
@@ -8,11 +8,17 @@ public class StoppedHandler extends StateHandler {
@Override
public State runAndGetNextState(Context context) {
- // Mark as stopped
- var inverter = context.getParent();
- inverter._setStartStop(StartStop.STOP);
+ final var inverter = context.getParent();
+
+ if (inverter.hasFaults()) {
+ return State.ERROR;
+ }
+
+ if (inverter.getStartStopTarget() == StartStop.START) {
+ return State.GO_RUNNING;
+ }
+ inverter._setStartStop(StartStop.STOP);
return State.STOPPED;
}
-
}
diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/UndefinedHandler.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/UndefinedHandler.java
index bd6b4927bac..c177bd4538a 100644
--- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/UndefinedHandler.java
+++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/UndefinedHandler.java
@@ -7,30 +7,24 @@ public class UndefinedHandler extends StateHandler {
@Override
public State runAndGetNextState(Context context) {
- var inverter = context.getParent();
+ final var inverter = context.getParent();
- switch (inverter.getStartStopTarget()) {
- case UNDEFINED:
- // Stuck in UNDEFINED State
+ if (inverter.getCurrentState().isUndefined()) {
return State.UNDEFINED;
+ }
- case START:
- // force START
- if (inverter.hasFaults()) {
- // Has Faults -> error handling
- return State.ERROR;
- } else {
- // No Faults -> start
- return State.GO_RUNNING;
- }
-
- case STOP:
- // force STOP
- return State.GO_STOPPED;
+ if (inverter.hasFailure()) {
+ return State.ERROR;
}
- assert false;
- return State.UNDEFINED; // can never happen
- }
+ if (inverter.isRunning()) {
+ return State.RUNNING;
+ }
+ if (inverter.isShutdown()) {
+ return State.STOPPED;
+ }
+
+ return State.GO_STOPPED;
+ }
}
diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/test/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImplTest.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/test/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImplTest.java
index 7a17c2a68a0..d4d2c215065 100644
--- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/test/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImplTest.java
+++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/test/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImplTest.java
@@ -90,6 +90,7 @@ public void prepareTest() throws Exception {
addChannel.invoke(sut, KacoSunSpecModel.S64201.WATCHDOG.getChannelId());
addChannel.invoke(sut, KacoSunSpecModel.S64201.W_SET_PCT.getChannelId());
addChannel.invoke(sut, KacoSunSpecModel.S64201.WPARAM_RMP_TMS.getChannelId());
+ addChannel.invoke(sut, KacoSunSpecModel.S64201.ST_VND.getChannelId());
test.activate(MyConfig.create() //
.setId(BATTERY_INVERTER_ID) //
@@ -108,7 +109,7 @@ public void testStart() throws Exception {
.output(STATE_MACHINE, State.UNDEFINED)) //
.next(new TestCase() //
.timeleap(clock, 4, ChronoUnit.SECONDS) //
- .output(STATE_MACHINE, State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.STOPPED)) //
.next(new TestCase() //
.timeleap(clock, 1, ChronoUnit.SECONDS) //
.input(CURRENT_STATE, S64201CurrentState.GRID_CONNECTED) //
diff --git a/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImpl.java b/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImpl.java
index d311907ea13..a63d14f76fd 100644
--- a/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImpl.java
+++ b/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImpl.java
@@ -220,7 +220,7 @@ public int getPowerPrecision() {
private static final int SUNSPEC_64800 = 40225; // MESA-PCS Extensions
@Override
- protected ModbusProtocol defineModbusProtocol() throws OpenemsException { // Register
+ protected ModbusProtocol defineModbusProtocol() {
return new ModbusProtocol(this, //
new FC3ReadRegistersTask(SUNSPEC_1, Priority.LOW, //
m(BatteryInverterRefuStore88k.ChannelId.ID_1, new UnsignedWordElement(SUNSPEC_1)), // 40002
diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImpl.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImpl.java
index b41b9e77a2c..5a65149220b 100644
--- a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImpl.java
+++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImpl.java
@@ -391,7 +391,7 @@ public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) {
}
@Override
- protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
+ protected ModbusProtocol defineModbusProtocol() {
return new ModbusProtocol(this, //
new FC3ReadRegistersTask(1, Priority.HIGH, //
m(BatteryInverterSinexcel.ChannelId.MANUFACTURER_AND_MODEL_NUMBER, //
diff --git a/io.openems.edge.batteryinverter.sunspec/src/io/openems/edge/batteryinverter/sunspec/AbstractSunSpecBatteryInverter.java b/io.openems.edge.batteryinverter.sunspec/src/io/openems/edge/batteryinverter/sunspec/AbstractSunSpecBatteryInverter.java
index 646ca7f453b..6e4d8e8a637 100644
--- a/io.openems.edge.batteryinverter.sunspec/src/io/openems/edge/batteryinverter/sunspec/AbstractSunSpecBatteryInverter.java
+++ b/io.openems.edge.batteryinverter.sunspec/src/io/openems/edge/batteryinverter/sunspec/AbstractSunSpecBatteryInverter.java
@@ -23,7 +23,7 @@ public abstract class AbstractSunSpecBatteryInverter extends AbstractOpenemsSunS
public AbstractSunSpecBatteryInverter(Map activeModels,
io.openems.edge.common.channel.ChannelId[] firstInitialChannelIds,
- io.openems.edge.common.channel.ChannelId[]... furtherInitialChannelIds) throws OpenemsException {
+ io.openems.edge.common.channel.ChannelId[]... furtherInitialChannelIds) {
super(activeModels, firstInitialChannelIds, furtherInitialChannelIds);
}
diff --git a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttp.java b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttp.java
index 7a5e3af2b5c..5abccd97132 100644
--- a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttp.java
+++ b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttp.java
@@ -1,27 +1,52 @@
package io.openems.edge.bridge.http.dummy;
+import static java.util.concurrent.CompletableFuture.completedFuture;
+
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import io.openems.edge.bridge.http.api.BridgeHttp;
public class DummyBridgeHttp implements BridgeHttp {
+ public final List cycleEndpoints = new ArrayList<>();
+ public final List timeEndpoints = new ArrayList<>();
+
+ private String nextRequestResult = null;
+
@Override
public void subscribeCycle(CycleEndpoint endpoint) {
- // TODO Auto-generated method stub
-
+ this.cycleEndpoints.add(endpoint);
}
@Override
public void subscribeTime(TimeEndpoint endpoint) {
- // TODO Auto-generated method stub
-
+ this.timeEndpoints.add(endpoint);
}
@Override
public CompletableFuture request(Endpoint endpoint) {
- // TODO Auto-generated method stub
- return null;
+ return completedFuture(this.nextRequestResult);
+ }
+
+ /**
+ * Mocks a result for all {@link CycleEndpoint}s.
+ *
+ * @param result the mocked read result
+ */
+ public void mockCycleResult(String result) {
+ this.cycleEndpoints.forEach(//
+ e -> e.result().accept(result));
+ }
+
+ /**
+ * Mocks a result for simple request {@link Endpoint}.
+ *
+ * @param nextRequestResult the mocked read result
+ */
+ public void mockRequestResult(String nextRequestResult) {
+ this.nextRequestResult = nextRequestResult;
}
}
diff --git a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpFactory.java b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpFactory.java
index d8a4d19c59a..f19ec95eca8 100644
--- a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpFactory.java
+++ b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpFactory.java
@@ -11,16 +11,25 @@
public class DummyBridgeHttpFactory extends BridgeHttpFactory {
+ public final DummyBridgeHttp bridge = new DummyBridgeHttp();
+
public DummyBridgeHttpFactory() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
super();
- ReflectionUtils.setAttribute(BridgeHttpFactory.class, this, "csoBridgeHttp", new DummyBridgeHttpCso());
+ ReflectionUtils.setAttribute(BridgeHttpFactory.class, this, "csoBridgeHttp",
+ new DummyBridgeHttpCso(this.bridge));
}
private static class DummyBridgeHttpCso implements ComponentServiceObjects {
+ private final DummyBridgeHttp bridge;
+
+ public DummyBridgeHttpCso(DummyBridgeHttp bridge) {
+ this.bridge = bridge;
+ }
+
@Override
public BridgeHttp getService() {
- return new DummyBridgeHttp();
+ return this.bridge;
}
@Override
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java
index 4488593accc..d36835f41f8 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java
@@ -60,14 +60,20 @@ public class BridgeModbusSerialImpl extends AbstractModbusBridge
/** The configured parity. */
private Parity parity;
-
+
/** Enable internal bus termination. */
private boolean enableTermination;
-
- /** The configured delay between activating the transmitter and actually sending data in microseconds. */
+
+ /**
+ * The configured delay between activating the transmitter and actually sending
+ * data in microseconds.
+ */
private int delayBeforeTx;
-
- /** The configured delay between the end of transmitting data and deactivating transmitter in microseconds. */
+
+ /**
+ * The configured delay between the end of transmitting data and deactivating
+ * transmitter in microseconds.
+ */
private int delayAfterTx;
public BridgeModbusSerialImpl() {
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/ConfigSerial.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/ConfigSerial.java
index 5032b4f574c..0ae88b2bbdd 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/ConfigSerial.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/ConfigSerial.java
@@ -35,13 +35,13 @@
@AttributeDefinition(name = "Parity", description = "The parity - 'none', 'even', 'odd', 'mark' or 'space'")
Parity parity() default Parity.NONE;
-
+
@AttributeDefinition(name = "Enable termination", description = "Sets whether the interface shall enable internal bus termination")
boolean enableTermination() default true;
-
+
@AttributeDefinition(name = "Delay before TX [μs]", description = "Sets the delay between activating the transmitter and actually sending data. There are devices in the field requiring such a delay for start bit detection.", min = "0")
int delayBeforeTx() default 1000;
-
+
@AttributeDefinition(name = "Delay after TX [μs]", description = "Sets the delay between the end of transmitting data and deactivating the transmitter.", min = "0")
int delayAfterTx() default 0;
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 b429a7177cb..7bf6e9e249d 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
@@ -230,7 +230,7 @@ public BridgeModbus getBridgeModbus() {
* @return the {@link ModbusProtocol}
* @throws OpenemsException on error
*/
- protected ModbusProtocol getModbusProtocol() throws OpenemsException {
+ protected ModbusProtocol getModbusProtocol() {
var protocol = this.protocol;
if (protocol != null) {
return protocol;
@@ -251,7 +251,7 @@ public void retryModbusCommunication() {
* @return the ModbusProtocol
* @throws OpenemsException on error
*/
- protected abstract ModbusProtocol defineModbusProtocol() throws OpenemsException;
+ protected abstract ModbusProtocol defineModbusProtocol();
/**
* Maps an Element to one or more ModbusChannels using converters, that convert
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusProtocol.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusProtocol.java
index e4e922aead0..3b6cac58e6c 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusProtocol.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusProtocol.java
@@ -1,6 +1,5 @@
package io.openems.edge.bridge.modbus.api;
-import io.openems.common.exceptions.OpenemsException;
import io.openems.edge.bridge.modbus.api.task.Task;
import io.openems.edge.common.taskmanager.TasksManager;
@@ -21,9 +20,8 @@ public class ModbusProtocol {
*
* @param parent the {@link AbstractOpenemsModbusComponent} parent
* @param tasks the {@link Task}s
- * @throws OpenemsException on error
*/
- public ModbusProtocol(AbstractOpenemsModbusComponent parent, Task... tasks) throws OpenemsException {
+ public ModbusProtocol(AbstractOpenemsModbusComponent parent, Task... tasks) {
this.parent = parent;
this.addTasks(tasks);
}
@@ -32,9 +30,8 @@ public ModbusProtocol(AbstractOpenemsModbusComponent parent, Task... tasks) thro
* Adds Tasks to the Protocol.
*
* @param tasks the tasks
- * @throws OpenemsException on error
*/
- public synchronized void addTasks(Task... tasks) throws OpenemsException {
+ public synchronized void addTasks(Task... tasks) {
for (Task task : tasks) {
this.addTask(task);
}
@@ -44,9 +41,8 @@ public synchronized void addTasks(Task... tasks) throws OpenemsException {
* Adds a Task to the Protocol.
*
* @param task the task
- * @throws OpenemsException on plausibility error
*/
- public synchronized void addTask(Task task) throws OpenemsException {
+ public synchronized void addTask(Task task) {
// add the the parent to the Task
task.setParent(this.parent);
// fill taskManager
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java
index e2ebb682479..48dc3a52b99 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusUtils.java
@@ -1,166 +1,133 @@
package io.openems.edge.bridge.modbus.api;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
+import static io.openems.edge.bridge.modbus.api.task.Task.ExecuteState.NO_OP;
+import static java.util.Collections.emptyList;
+import static java.util.concurrent.CompletableFuture.completedFuture;
+
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.stream.Collectors;
-import java.util.stream.IntStream;
+import java.util.stream.Stream;
import com.ghgande.j2mod.modbus.procimg.InputRegister;
import com.ghgande.j2mod.modbus.procimg.Register;
-import io.openems.common.exceptions.OpenemsException;
import io.openems.edge.bridge.modbus.api.element.ModbusRegisterElement;
import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask;
import io.openems.edge.bridge.modbus.api.task.Task;
+import io.openems.edge.bridge.modbus.api.task.Task.ExecuteState;
import io.openems.edge.common.taskmanager.Priority;
public class ModbusUtils {
+ /**
+ * Predefined `retryPredicate` that triggers a retry whenever `value` is null,
+ * i.e. on any error.
+ *
+ * @param the Type of the element
+ * @param executeState the Task {@link ExecuteState}
+ * @param value the value
+ * @return true for retry
+ */
+ public static boolean retryOnNull(ExecuteState executeState, T value) {
+ return value == null;
+ }
+
+ /**
+ * Predefined `retryPredicate` that never retries.
+ *
+ * @param the Type of the element
+ * @param executeState the Task {@link ExecuteState}
+ * @param value the value
+ * @return always false
+ */
+ public static boolean doNotRetry(ExecuteState executeState, T value) {
+ return false;
+ }
+
/**
* Reads given Element once from Modbus.
*
- * @param the Type of the element
- * @param modbusProtocol the {@link ModbusProtocol}, that is linked with a
- * {@link BridgeModbus}
- * @param element the {@link ModbusRegisterElement}
- * @param tryAgainOnError if true, tries to read till it receives a value; if
- * false, stops after first try and possibly return null
+ * @param the Type of the element
+ * @param modbusProtocol the {@link ModbusProtocol}, that is linked with a
+ * {@link BridgeModbus}
+ * @param retryPredicate yield true to retry reading values; false otherwise.
+ * Parameters are the {@link ExecuteState} of the entire
+ * task and the individual element value
+ * @param element the {@link ModbusRegisterElement}
* @return a future value, e.g. a Integer or null (if tryAgainOnError is false)
- * @throws OpenemsException on error with the {@link ModbusProtocol} object
*/
- public static CompletableFuture readELementOnce(ModbusProtocol modbusProtocol,
- ModbusRegisterElement, T> element, boolean tryAgainOnError) throws OpenemsException {
- // Prepare result
- final var result = new CompletableFuture();
-
- // Activate task
- final Task task = new FC3ReadRegistersTask(element.startAddress, Priority.HIGH, element);
- modbusProtocol.addTask(task);
-
- // Register listener for element
- element.onUpdateCallback(value -> {
- if (value == null) {
- if (tryAgainOnError) {
- return;
- }
- result.complete(null);
- }
- // do not try again
- modbusProtocol.removeTask(task);
- result.complete(value);
- });
-
- return result;
+ @SuppressWarnings("unchecked")
+ public static CompletableFuture readElementOnce(ModbusProtocol modbusProtocol,
+ BiPredicate retryPredicate, ModbusRegisterElement, T> element) {
+ return readElementsOnce(modbusProtocol, retryPredicate, //
+ new ModbusRegisterElement[] { element }) //
+ .thenApply(rsr -> ((ReadElementsResult) rsr).values().get(0));
}
/**
* Reads given Elements once from Modbus.
*
- * @param the Type of the elements
- * @param modbusProtocol the {@link ModbusProtocol}, that is linked with a
- * {@link BridgeModbus}
- * @param elements the {@link ModbusRegisterElement}s
- * @param tryAgainOnError if true, tries to read till it receives a value on
- * first register; if false, stops after first try and
- * possibly return null
- * @return a future array of values, e.g. Integer[] or null (if tryAgainOnError
- * is false). If an array is returned, it is guaranteed to have the same
- * length as `elements`
- * @throws OpenemsException on error with the {@link ModbusProtocol} object
+ * @param the Type of the elements
+ * @param modbusProtocol the {@link ModbusProtocol}, that is linked with a
+ * {@link BridgeModbus}
+ * @param retryPredicate yield true to retry reading values. Parameters are the
+ * Task success state and individual element value
+ * @param elements the {@link ModbusRegisterElement}s
+ * @return a future array of values, e.g. Integer[] or null. If an array is
+ * returned, it is guaranteed to have the same length as `elements`
*/
- public static CompletableFuture> readELementsOnce(ModbusProtocol modbusProtocol,
- ModbusRegisterElement, T>[] elements, boolean tryAgainOnError) throws OpenemsException {
+ @SafeVarargs
+ public static CompletableFuture> readElementsOnce(ModbusProtocol modbusProtocol,
+ BiPredicate retryPredicate, ModbusRegisterElement, T>... elements) {
if (elements.length == 0) {
- return CompletableFuture.completedFuture(Collections.emptyList());
+ return completedFuture(new ReadElementsResult<>(NO_OP, emptyList()));
}
- // Prepare result
- final var result = new CompletableFuture>();
+ // Register listener for each element
+ final var executeState = new AtomicReference(ExecuteState.NO_OP);
// Activate task
- final Task task = new FC3ReadRegistersTask(elements[0].startAddress, Priority.HIGH, elements);
+ final Task task = new FC3ReadRegistersTask(executeState::set, //
+ elements[0].startAddress, Priority.HIGH, elements);
modbusProtocol.addTask(task);
- // Register listener for each element
- final var subResults = new ArrayList>();
- {
+ @SuppressWarnings("unchecked")
+ final var subResults = (CompletableFuture[]) new CompletableFuture>[elements.length];
+ for (var i = 0; i < elements.length; i++) {
var subResult = new CompletableFuture();
- subResults.add(subResult);
- elements[0].onUpdateCallback(value -> {
- if (value == null) {
- if (tryAgainOnError) {
- // try again
- return;
- } else {
- result.complete(null);
- }
- }
-
- // do not try again
- modbusProtocol.removeTask(task);
- subResult.complete(value);
- });
- }
-
- for (var i = 1; i < elements.length; i++) {
- var subResult = new CompletableFuture();
- subResults.add(subResult);
+ subResults[i] = subResult;
elements[i].onUpdateCallback(value -> {
- modbusProtocol.removeTask(task);
- subResult.complete(value);
+ if (retryPredicate.test(executeState.get(), value)) {
+ // try again
+ return;
+ } else {
+ // do not try again
+ subResult.complete(value);
+ }
});
}
- CompletableFuture //
- .allOf(subResults.toArray(new CompletableFuture[subResults.size()])) //
- .thenAccept(ignored -> result.complete(//
- subResults.stream() //
- .map(CompletableFuture::join) //
- .toList()));
-
- return result;
+ return CompletableFuture //
+ .allOf(subResults) //
+ .thenApply(ignore -> {
+ // remove task
+ modbusProtocol.removeTask(task);
+
+ // return combined future
+ return new ReadElementsResult<>(executeState.get(), //
+ Stream.of(subResults) //
+ .map(CompletableFuture::join) //
+ .toList());
+ });
}
- /**
- * Converts upper/lower bytes to Short.
- *
- * @param value the int value
- * @param upperBytes 1 = upper two bytes, 0 = lower two bytes
- * @return the Short
- */
- public static Short convert(int value, int upperBytes) {
- var b = ByteBuffer.allocate(4);
- b.order(ByteOrder.LITTLE_ENDIAN);
- b.putInt(value);
-
- var byte0 = b.get(upperBytes * 2);
- var byte1 = b.get(upperBytes * 2 + 1);
-
- var shortBuf = ByteBuffer.allocate(2);
- shortBuf.order(ByteOrder.LITTLE_ENDIAN);
- shortBuf.put(0, byte0);
- shortBuf.put(1, byte1);
+ public static record ReadElementsResult(ExecuteState executeState, List values) {
- return shortBuf.getShort();
- }
-
- /**
- * Converts a byte array to a String in the form "00C1 00B2".
- *
- * @param data byte array
- * @return string
- */
- public static String byteArrayToHexString(byte[] data) {
- return IntStream.range(0, data.length / 2) //
- .mapToObj(i -> String.format("%2s%2s", //
- Integer.toHexString(data[i]), Integer.toHexString(data[i + 1])).replace(' ', '0'))
- .collect(Collectors.joining(" "));
}
/**
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTask.java
index a7a6cec7edc..5a918fe1d5c 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTask.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadDigitalInputsTask.java
@@ -1,5 +1,6 @@
package io.openems.edge.bridge.modbus.api.task;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -17,9 +18,9 @@ public abstract class AbstractReadDigitalInputsTask/
RESPONSE extends ModbusResponse> //
extends AbstractReadTask {
- public AbstractReadDigitalInputsTask(String name, Class responseClazz, int startAddress,
- Priority priority, CoilElement... elements) {
- super(name, responseClazz, CoilElement.class, startAddress, priority, elements);
+ public AbstractReadDigitalInputsTask(String name, Consumer onExecute, Class responseClazz,
+ int startAddress, Priority priority, CoilElement... elements) {
+ super(name, onExecute, responseClazz, CoilElement.class, startAddress, priority, elements);
}
@Override
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadRegistersTask.java
index 936971a7464..24f691d6c1e 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadRegistersTask.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadRegistersTask.java
@@ -1,6 +1,7 @@
package io.openems.edge.bridge.modbus.api.task;
import java.util.Arrays;
+import java.util.function.Consumer;
import com.ghgande.j2mod.modbus.msg.ModbusRequest;
import com.ghgande.j2mod.modbus.msg.ModbusResponse;
@@ -17,9 +18,9 @@ public abstract class AbstractReadRegistersTask/
RESPONSE extends ModbusResponse> //
extends AbstractReadTask {
- public AbstractReadRegistersTask(String name, Class responseClazz, int startAddress, Priority priority,
- ModbusElement... elements) {
- super(name, responseClazz, ModbusRegisterElement.class, startAddress, priority, elements);
+ public AbstractReadRegistersTask(String name, Consumer onExecute, Class responseClazz,
+ int startAddress, Priority priority, ModbusElement... elements) {
+ super(name, onExecute, responseClazz, ModbusRegisterElement.class, startAddress, priority, elements);
}
@SuppressWarnings("unchecked")
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadTask.java
index 66c104f6fe8..ef5c010c268 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadTask.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractReadTask.java
@@ -2,6 +2,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
import java.util.stream.Stream;
import org.slf4j.Logger;
@@ -33,9 +34,9 @@ public abstract class AbstractReadTask/
private final Priority priority;
private final Class> elementClazz;
- public AbstractReadTask(String name, Class responseClazz, Class elementClazz, int startAddress,
- Priority priority, ModbusElement... elements) {
- super(name, responseClazz, startAddress, elements);
+ public AbstractReadTask(String name, Consumer onExecute, Class responseClazz,
+ Class elementClazz, int startAddress, Priority priority, ModbusElement... elements) {
+ super(name, onExecute, responseClazz, startAddress, elements);
this.elementClazz = elementClazz;
this.priority = priority;
}
@@ -49,19 +50,26 @@ public ExecuteState execute(AbstractModbusBridge bridge) {
try {
var result = this.parseResponse(response);
validateResponse(result, this.length);
+
+ // NOTE: onExecute has to be called before filling elements; but OK could be
+ // wrong if fillElements throws an exception.
+ this.onExecute.accept(ExecuteState.OK);
this.fillElements(result);
+ return ExecuteState.OK;
+
} catch (OpenemsException e1) {
logError(this.log, e1, "Parsing Response failed.");
throw e1;
}
- return ExecuteState.OK;
} catch (Exception e) {
+ var executeState = new ExecuteState.Error(e);
+ this.onExecute.accept(executeState);
+
// Invalidate Elements
Stream.of(this.elements).forEach(el -> el.invalidate(bridge));
-
- return ExecuteState.ERROR;
+ return executeState;
}
}
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractTask.java
index 60ac8520114..35c419f539e 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractTask.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractTask.java
@@ -3,6 +3,7 @@
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
+import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -30,6 +31,7 @@ public abstract non-sealed class AbstractTask/
RESPONSE extends ModbusResponse> implements Task {
protected final String name;
+ protected final Consumer onExecute;
protected final Class responseClazz;
protected final int startAddress;
protected final int length;
@@ -39,8 +41,10 @@ public abstract non-sealed class AbstractTask/
private AbstractOpenemsModbusComponent parent = null; // this is always set by ModbusProtocol.addTask()
- public AbstractTask(String name, Class responseClazz, int startAddress, ModbusElement... elements) {
+ public AbstractTask(String name, Consumer onExecute, Class responseClazz, int startAddress,
+ ModbusElement... elements) {
this.name = name;
+ this.onExecute = onExecute;
this.responseClazz = responseClazz;
this.startAddress = startAddress;
this.elements = elements;
@@ -87,7 +91,7 @@ public AbstractOpenemsModbusComponent getParent() {
* WriteTask.
*
* @param bridge the Modbus-Bridge
- * @return the number of executed Sub-Tasks
+ * @return the {@link ExecuteState}
*/
public abstract ExecuteState execute(AbstractModbusBridge bridge);
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractWriteTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractWriteTask.java
index 40460ca1f75..e8d2335c259 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractWriteTask.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/AbstractWriteTask.java
@@ -1,5 +1,7 @@
package io.openems.edge.bridge.modbus.api.task;
+import java.util.function.Consumer;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -16,8 +18,9 @@ public abstract class AbstractWriteTask/
RESPONSE extends ModbusResponse> //
extends AbstractTask implements WriteTask {
- public AbstractWriteTask(String name, Class responseClazz, int startAddress, ModbusElement... elements) {
- super(name, responseClazz, startAddress, elements);
+ public AbstractWriteTask(String name, Consumer onExecute, Class responseClazz,
+ int startAddress, ModbusElement... elements) {
+ super(name, onExecute, responseClazz, startAddress, elements);
}
/**
@@ -45,19 +48,26 @@ public abstract static class Single/
private final Logger log = LoggerFactory.getLogger(Single.class);
- public Single(String name, Class responseClazz, int startAddress, ELEMENT element) {
- super(name, responseClazz, startAddress, element);
+ public Single(String name, Consumer onExecute, Class responseClazz, int startAddress,
+ ELEMENT element) {
+ super(name, onExecute, responseClazz, startAddress, element);
this.element = element;
}
@Override
public final ExecuteState execute(AbstractModbusBridge bridge) {
+ var result = this._execute(bridge);
+ this.onExecute.accept(result);
+ return result;
+ }
+
+ private ExecuteState _execute(AbstractModbusBridge bridge) {
final REQUEST request;
try {
request = this.createModbusRequest();
} catch (OpenemsException e) {
logError(this.log, e, "Creating Modbus Request failed.");
- return ExecuteState.ERROR;
+ return new ExecuteState.Error(e);
}
if (request == null) {
@@ -70,7 +80,7 @@ public final ExecuteState execute(AbstractModbusBridge bridge) {
} catch (Exception e) {
// On error a log message has already been logged
- return ExecuteState.ERROR;
+ return new ExecuteState.Error(e);
}
}
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTask.java
index 0c16e83e897..7767f54674b 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTask.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC16WriteRegistersTask.java
@@ -13,6 +13,7 @@
import com.ghgande.j2mod.modbus.msg.WriteMultipleRegistersResponse;
import com.ghgande.j2mod.modbus.procimg.Register;
+import io.openems.common.utils.FunctionUtils;
import io.openems.edge.bridge.modbus.api.AbstractModbusBridge;
import io.openems.edge.bridge.modbus.api.LogVerbosity;
import io.openems.edge.bridge.modbus.api.ModbusUtils;
@@ -29,11 +30,21 @@ public class FC16WriteRegistersTask
private final Logger log = LoggerFactory.getLogger(FC16WriteRegistersTask.class);
public FC16WriteRegistersTask(int startAddress, ModbusElement... elements) {
- super("FC16WriteRegisters", WriteMultipleRegistersResponse.class, startAddress, elements);
+ this(FunctionUtils::doNothing, startAddress, elements);
+ }
+
+ public FC16WriteRegistersTask(Consumer onExecute, int startAddress, ModbusElement... elements) {
+ super("FC16WriteRegisters", onExecute, WriteMultipleRegistersResponse.class, startAddress, elements);
}
@Override
- public ExecuteState execute(AbstractModbusBridge bridge) {
+ public final ExecuteState execute(AbstractModbusBridge bridge) {
+ var result = this._execute(bridge);
+ this.onExecute.accept(result);
+ return result;
+ }
+
+ private ExecuteState _execute(AbstractModbusBridge bridge) {
var requests = mergeWriteRegisters(this.elements, message -> this.log.warn(message)).stream() //
.map(e -> new WriteMultipleRegistersRequest(e.startAddress(), e.getRegisters())) //
.toList();
@@ -42,7 +53,7 @@ public ExecuteState execute(AbstractModbusBridge bridge) {
return ExecuteState.NO_OP;
}
- boolean hasError = false;
+ Exception lastError = null;
for (var request : requests) {
try {
this.executeRequest(bridge, request);
@@ -52,12 +63,12 @@ public ExecuteState execute(AbstractModbusBridge bridge) {
// Invalidate Elements
Stream.of(this.elements).forEach(el -> el.invalidate(bridge));
- hasError = true;
+ lastError = e;
}
}
- if (hasError) {
- return ExecuteState.ERROR;
+ if (lastError != null) {
+ return new ExecuteState.Error(lastError);
} else {
return ExecuteState.OK;
}
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTask.java
index 859db8748aa..0c546569033 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTask.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC1ReadCoilsTask.java
@@ -1,9 +1,12 @@
package io.openems.edge.bridge.modbus.api.task;
+import java.util.function.Consumer;
+
import com.ghgande.j2mod.modbus.msg.ReadCoilsRequest;
import com.ghgande.j2mod.modbus.msg.ReadCoilsResponse;
import com.ghgande.j2mod.modbus.util.BitVector;
+import io.openems.common.utils.FunctionUtils;
import io.openems.edge.bridge.modbus.api.element.CoilElement;
import io.openems.edge.common.taskmanager.Priority;
@@ -14,7 +17,12 @@
public class FC1ReadCoilsTask extends AbstractReadDigitalInputsTask {
public FC1ReadCoilsTask(int startAddress, Priority priority, CoilElement... elements) {
- super("FC1ReadCoils", ReadCoilsResponse.class, startAddress, priority, elements);
+ this(FunctionUtils::doNothing, startAddress, priority, elements);
+ }
+
+ public FC1ReadCoilsTask(Consumer onExecute, int startAddress, Priority priority,
+ CoilElement... elements) {
+ super("FC1ReadCoils", onExecute, ReadCoilsResponse.class, startAddress, priority, elements);
}
@Override
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTask.java
index bd42892fa01..462dad3b5d0 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTask.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC2ReadInputsTask.java
@@ -1,9 +1,12 @@
package io.openems.edge.bridge.modbus.api.task;
+import java.util.function.Consumer;
+
import com.ghgande.j2mod.modbus.msg.ReadInputDiscretesRequest;
import com.ghgande.j2mod.modbus.msg.ReadInputDiscretesResponse;
import com.ghgande.j2mod.modbus.util.BitVector;
+import io.openems.common.utils.FunctionUtils;
import io.openems.edge.bridge.modbus.api.element.CoilElement;
import io.openems.edge.common.taskmanager.Priority;
@@ -15,7 +18,12 @@ public class FC2ReadInputsTask
extends AbstractReadDigitalInputsTask {
public FC2ReadInputsTask(int startAddress, Priority priority, CoilElement... elements) {
- super("FC2ReadCoils", ReadInputDiscretesResponse.class, startAddress, priority, elements);
+ this(FunctionUtils::doNothing, startAddress, priority, elements);
+ }
+
+ public FC2ReadInputsTask(Consumer onExecute, int startAddress, Priority priority,
+ CoilElement... elements) {
+ super("FC2ReadCoils", onExecute, ReadInputDiscretesResponse.class, startAddress, priority, elements);
}
@Override
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java
index d1d104a28fd..420c406e0c7 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC3ReadRegistersTask.java
@@ -1,10 +1,13 @@
package io.openems.edge.bridge.modbus.api.task;
+import java.util.function.Consumer;
+
import com.ghgande.j2mod.modbus.msg.ReadMultipleRegistersRequest;
import com.ghgande.j2mod.modbus.msg.ReadMultipleRegistersResponse;
import com.ghgande.j2mod.modbus.procimg.Register;
import io.openems.common.exceptions.OpenemsException;
+import io.openems.common.utils.FunctionUtils;
import io.openems.edge.bridge.modbus.api.ModbusUtils;
import io.openems.edge.bridge.modbus.api.element.ModbusElement;
import io.openems.edge.common.taskmanager.Priority;
@@ -17,7 +20,13 @@ public class FC3ReadRegistersTask
extends AbstractReadRegistersTask {
public FC3ReadRegistersTask(int startAddress, Priority priority, ModbusElement... elements) {
- super("FC3ReadHoldingRegisters", ReadMultipleRegistersResponse.class, startAddress, priority, elements);
+ this(FunctionUtils::doNothing, startAddress, priority, elements);
+ }
+
+ public FC3ReadRegistersTask(Consumer onExecute, int startAddress, Priority priority,
+ ModbusElement... elements) {
+ super("FC3ReadHoldingRegisters", onExecute, ReadMultipleRegistersResponse.class, startAddress, priority,
+ elements);
}
@Override
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java
index e05190fd759..96d383e7859 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC4ReadInputRegistersTask.java
@@ -1,5 +1,6 @@
package io.openems.edge.bridge.modbus.api.task;
+import java.util.function.Consumer;
import java.util.stream.Stream;
import com.ghgande.j2mod.modbus.msg.ReadInputRegistersRequest;
@@ -8,6 +9,7 @@
import com.ghgande.j2mod.modbus.procimg.SimpleRegister;
import io.openems.common.exceptions.OpenemsException;
+import io.openems.common.utils.FunctionUtils;
import io.openems.edge.bridge.modbus.api.ModbusUtils;
import io.openems.edge.bridge.modbus.api.element.ModbusElement;
import io.openems.edge.common.taskmanager.Priority;
@@ -20,7 +22,12 @@ public class FC4ReadInputRegistersTask
extends AbstractReadRegistersTask {
public FC4ReadInputRegistersTask(int startAddress, Priority priority, ModbusElement... elements) {
- super("FC4ReadInputRegisters", ReadInputRegistersResponse.class, startAddress, priority, elements);
+ this(FunctionUtils::doNothing, startAddress, priority, elements);
+ }
+
+ public FC4ReadInputRegistersTask(Consumer onExecute, int startAddress, Priority priority,
+ ModbusElement... elements) {
+ super("FC4ReadInputRegisters", onExecute, ReadInputRegistersResponse.class, startAddress, priority, elements);
}
@Override
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTask.java
index 25c3d3de17a..5f42a0069af 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTask.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC5WriteCoilTask.java
@@ -1,9 +1,12 @@
package io.openems.edge.bridge.modbus.api.task;
+import java.util.function.Consumer;
+
import com.ghgande.j2mod.modbus.msg.WriteCoilRequest;
import com.ghgande.j2mod.modbus.msg.WriteCoilResponse;
import io.openems.common.exceptions.OpenemsException;
+import io.openems.common.utils.FunctionUtils;
import io.openems.edge.bridge.modbus.api.element.CoilElement;
/**
@@ -13,7 +16,11 @@
public class FC5WriteCoilTask extends AbstractWriteTask.Single {
public FC5WriteCoilTask(int startAddress, CoilElement element) {
- super("FC5WriteCoil", WriteCoilResponse.class, startAddress, element);
+ this(FunctionUtils::doNothing, startAddress, element);
+ }
+
+ public FC5WriteCoilTask(Consumer onExecute, int startAddress, CoilElement element) {
+ super("FC5WriteCoil", onExecute, WriteCoilResponse.class, startAddress, element);
}
@Override
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTask.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTask.java
index c116f3907ab..ceff91159bd 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTask.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/FC6WriteRegisterTask.java
@@ -1,9 +1,12 @@
package io.openems.edge.bridge.modbus.api.task;
+import java.util.function.Consumer;
+
import com.ghgande.j2mod.modbus.msg.WriteSingleRegisterRequest;
import com.ghgande.j2mod.modbus.msg.WriteSingleRegisterResponse;
import io.openems.common.exceptions.OpenemsException;
+import io.openems.common.utils.FunctionUtils;
import io.openems.edge.bridge.modbus.api.ModbusUtils;
import io.openems.edge.bridge.modbus.api.element.AbstractSingleWordElement;
@@ -11,7 +14,12 @@ public class FC6WriteRegisterTask extends
AbstractWriteTask.Single> {
public FC6WriteRegisterTask(int startAddress, AbstractSingleWordElement, ?> element) {
- super("FC6WriteRegister", WriteSingleRegisterResponse.class, startAddress, element);
+ this(FunctionUtils::doNothing, startAddress, element);
+ }
+
+ public FC6WriteRegisterTask(Consumer onExecute, int startAddress,
+ AbstractSingleWordElement, ?> element) {
+ super("FC6WriteRegister", onExecute, WriteSingleRegisterResponse.class, startAddress, element);
}
@Override
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Task.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Task.java
index f810b548f94..2862df554ab 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Task.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/task/Task.java
@@ -58,12 +58,27 @@ public sealed interface Task extends ManagedTask permits AbstractTask, ReadTask,
*/
public ExecuteState execute(AbstractModbusBridge bridge);
- public static enum ExecuteState {
+ public static sealed interface ExecuteState {
+
+ public static final class Ok implements ExecuteState {
+ private Ok() {
+ }
+ }
+
/** Successfully executed request(s). */
- OK,
+ public static final ExecuteState.Ok OK = new ExecuteState.Ok();
+
+ public static final class NoOp implements ExecuteState {
+ private NoOp() {
+ }
+ }
+
/** No available requests -> no operation. */
- NO_OP,
+ public static final ExecuteState.NoOp NO_OP = new ExecuteState.NoOp();
+
/** Executing request(s) failed. */
- ERROR;
+ public static final record Error(Exception exception) implements ExecuteState {
+ }
+
}
}
\ No newline at end of file
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java
index f51191f8631..68aed664b5d 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java
@@ -73,23 +73,21 @@ protected void forever() throws InterruptedException {
// execute the task
var result = this.execute.apply(task);
- switch (result) {
- case OK -> {
+ // NOTE: with Java 21 LTS this can be refactored to a pattern matching switch
+ // statement
+ if (result instanceof ExecuteState.Ok) {
// no exception & at least one sub-task executed
this.markComponentAsDefective(task.getParent(), false);
- }
- case ERROR -> {
+ } else if (result instanceof ExecuteState.NoOp) {
+ // did not execute anything
+
+ } else if (result instanceof ExecuteState.Error) {
this.markComponentAsDefective(task.getParent(), true);
// invalidate elements of this task
this.invalidate.accept(task.getElements());
}
-
- case NO_OP -> {
- }
- }
-
}
/**
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java
index 5dafbd32b4d..a45c8251a35 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponent.java
@@ -1,5 +1,10 @@
package io.openems.edge.bridge.modbus.sunspec;
+import static com.ghgande.j2mod.modbus.Modbus.ILLEGAL_ADDRESS_EXCEPTION;
+import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce;
+import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementsOnce;
+import static java.util.concurrent.CompletableFuture.completedFuture;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -15,6 +20,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.ghgande.j2mod.modbus.ModbusSlaveException;
import com.google.common.collect.Lists;
import io.openems.common.exceptions.OpenemsException;
@@ -25,13 +31,12 @@
import io.openems.edge.bridge.modbus.api.ModbusUtils;
import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement;
import io.openems.edge.bridge.modbus.api.element.ModbusElement;
-import io.openems.edge.bridge.modbus.api.element.ModbusRegisterElement;
import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement;
import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement;
import io.openems.edge.bridge.modbus.api.task.AbstractTask;
import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask;
import io.openems.edge.bridge.modbus.api.task.FC3ReadRegistersTask;
-import io.openems.edge.bridge.modbus.api.task.Task;
+import io.openems.edge.bridge.modbus.api.task.Task.ExecuteState;
import io.openems.edge.common.channel.Channel;
import io.openems.edge.common.taskmanager.Priority;
@@ -70,7 +75,7 @@ public abstract class AbstractOpenemsSunSpecComponent extends AbstractOpenemsMod
*/
public AbstractOpenemsSunSpecComponent(Map activeModels,
io.openems.edge.common.channel.ChannelId[] firstInitialChannelIds,
- io.openems.edge.common.channel.ChannelId[]... furtherInitialChannelIds) throws OpenemsException {
+ io.openems.edge.common.channel.ChannelId[]... furtherInitialChannelIds) {
super(firstInitialChannelIds, furtherInitialChannelIds);
this.activeModels = activeModels;
this.modbusProtocol = new ModbusProtocol(this);
@@ -97,18 +102,10 @@ protected boolean activate(ComponentContext context, String id, String alias, bo
throw new IllegalArgumentException("This modbus device is not SunSpec!");
}
- try {
- this.readNextBlock(40_002, expectedBlocks).thenRun(() -> {
- this.isSunSpecInitializationCompleted = true;
- this.onSunSpecInitializationCompleted();
- });
-
- } catch (OpenemsException e) {
- this.logWarn(this.log, "Error while reading SunSpec identifier block: " + e.getMessage());
- e.printStackTrace();
+ this.readNextBlock(40_002, expectedBlocks).thenRun(() -> {
this.isSunSpecInitializationCompleted = true;
this.onSunSpecInitializationCompleted();
- }
+ });
});
return super.activate(context, id, alias, enabled, unitId, cm, modbusReference, modbusId);
}
@@ -128,16 +125,8 @@ protected final ModbusProtocol defineModbusProtocol() {
* @throws OpenemsException on error
*/
private CompletableFuture isSunSpec() throws OpenemsException {
- final var result = new CompletableFuture();
- ModbusUtils.readELementOnce(this.modbusProtocol, new UnsignedDoublewordElement(40_000), true)
- .thenAccept(value -> {
- if (value == 0x53756e53) {
- result.complete(true);
- } else {
- result.complete(false);
- }
- });
- return result;
+ return readElementOnce(this.modbusProtocol, ModbusUtils::retryOnNull, new UnsignedDoublewordElement(40_000)) //
+ .thenApply(v -> v == 0x53756e53);
}
/**
@@ -148,31 +137,47 @@ private CompletableFuture isSunSpec() throws OpenemsException {
* @return a future that completes once reading the block finished
* @throws OpenemsException on error
*/
- private CompletableFuture readNextBlock(int startAddress, Set remainingBlocks)
- throws OpenemsException {
- final var finished = new CompletableFuture();
-
+ private CompletableFuture readNextBlock(int startAddress, Set remainingBlocks) {
// Finish if all expected Blocks have been read
if (remainingBlocks.isEmpty()) {
- finished.complete(null);
+ return completedFuture(null);
}
/*
* Try to read block by block until all required blocks have been read or an
- * END_OF_MAP register has been found.
+ * END_OF_MAP register has been found or reading fails permanently.
*
* It may still happen that a device does not have a valid END_OF_MAP register
* and that some blocks are not read - especially when one component is used for
* multiple devices like single and three phase inverter.
*/
- this.readElementsOnceTyped(new UnsignedWordElement(startAddress), new UnsignedWordElement(startAddress + 1))
- .thenAccept(values -> {
- int blockId = values.get(0);
+ return readElementsOnce(this.modbusProtocol, //
+ // Retry if value is null and error is not "Illegal Data Address".
+ // Background: some SMA inverters do not provide an END_OF_MAP register.
+ (executeState, value) -> {
+ if (executeState instanceof ExecuteState.Error s) {
+ if (s.exception() instanceof ModbusSlaveException mse) {
+ if (mse.isType(ILLEGAL_ADDRESS_EXCEPTION)) {
+ return false; // do not retry
+ }
+ }
+ }
+ if (value != null) {
+ return false; // do not retry
+ }
+ return true;
+ }, //
+
+ new UnsignedWordElement(startAddress), // Block-ID
+ new UnsignedWordElement(startAddress + 1)) // Length of Block
+
+ .thenCompose(rer -> {
+ var values = rer.values();
+ var blockId = values.get(0);
// END_OF_MAP
- if (blockId == 0xFFFF) {
- finished.complete(null);
- return;
+ if (blockId == null || blockId == 0xFFFF) {
+ return completedFuture(null);
}
// Handle SunSpec Block
@@ -190,46 +195,23 @@ private CompletableFuture readNextBlock(int startAddress, Set rem
if (activeEntry != null) {
var sunSpecModel = activeEntry.getKey();
var priority = activeEntry.getValue();
- try {
- this.addBlock(startAddress, sunSpecModel, priority);
- remainingBlocks.remove(activeEntry.getKey().getBlockId());
- } catch (OpenemsException e) {
- this.logWarn(this.log, "Error while adding SunSpec-Model [" + blockId
- + "] starting at [" + startAddress + "]: " + e.getMessage());
- e.printStackTrace();
- }
+ this.addBlock(startAddress, sunSpecModel, priority);
+ remainingBlocks.remove(activeEntry.getKey().getBlockId());
} else {
// This block is not considered, because the Model is not active
this.logInfo(this.log,
"Ignoring SunSpec-Model [" + blockId + "] starting at [" + startAddress + "]");
}
- }
- // Stop reading if all expectedBlocks have been read
- if (remainingBlocks.isEmpty()) {
- finished.complete(null);
- return;
}
// Read next block recursively
var nextBlockStartAddress = startAddress + 2 + values.get(1);
- try {
-
- final var readNextBlockFuture = this.readNextBlock(nextBlockStartAddress, remainingBlocks);
- // Announce finished when next block (recursively) is finished
- readNextBlockFuture.thenRun(() -> {
- finished.complete(null);
- });
- } catch (OpenemsException e) {
- this.logWarn(this.log, "Error while adding SunSpec-Model [" + blockId + "] starting at ["
- + startAddress + "]: " + e.getMessage());
- e.printStackTrace();
- finished.complete(null); // announce finish immediately to not get stuck
- }
+ // Announce finished when next block (recursively) is finished
+ return this.readNextBlock(nextBlockStartAddress, remainingBlocks);
});
- return finished;
}
/**
@@ -286,9 +268,8 @@ public boolean isSunSpecInitializationCompleted() {
* @param startAddress the address to start reading from
* @param model the SunSpecModel
* @param priority the reading priority
- * @throws OpenemsException on error
*/
- protected void addBlock(int startAddress, SunSpecModel model, Priority priority) throws OpenemsException {
+ protected void addBlock(int startAddress, SunSpecModel model, Priority priority) {
this.logInfo(this.log, "Adding SunSpec-Model [" + model.getBlockId() + ":" + model.label() + "] starting at ["
+ startAddress + "]");
var readElements = new ArrayList();
@@ -424,54 +405,6 @@ protected ElementToChannelConverter generateElementToChannelConverter(SunSpecMod
}
}
- /**
- * Reads given Elements once from Modbus.
- *
- * @param the Type of the elements
- * @param elements the elements
- * @return a future list with the values, e.g. a list of integers
- * @throws OpenemsException on error
- */
- @SafeVarargs
- private final CompletableFuture> readElementsOnceTyped(ModbusRegisterElement, T>... elements)
- throws OpenemsException {
- // Register listeners for elements
- @SuppressWarnings("unchecked")
- final var subResults = (CompletableFuture[]) new CompletableFuture>[elements.length];
- for (var i = 0; i < elements.length; i++) {
- var subResult = new CompletableFuture();
- subResults[i] = subResult;
-
- var element = elements[i];
- element.onUpdateCallback(value -> {
- if (value == null) {
- // try again
- return;
- }
- subResult.complete(value);
- });
- }
-
- // Activate task
- final Task task = new FC3ReadRegistersTask(elements[0].startAddress, Priority.HIGH, elements);
- this.modbusProtocol.addTask(task);
-
- // Prepare result
- final var result = new CompletableFuture>();
- CompletableFuture.allOf(subResults).thenRun(() -> {
- // do not try again
- this.modbusProtocol.removeTask(task);
-
- // get all results and complete result
- List values = Stream.of(subResults) //
- .map(CompletableFuture::join) //
- .collect(Collectors.toCollection(ArrayList::new));
- result.complete(values);
- });
-
- return result;
- }
-
/**
* Get the Channel for the given Point.
*
@@ -524,7 +457,7 @@ protected void mapFirstPointToChannel(io.openems.edge.common.channel.ChannelId t
for (SunSpecPoint point : points) {
Optional> c = this.getSunSpecChannel(point);
if (c.isPresent()) {
- c.get().onUpdate(value -> {
+ c.get().onSetNextValue(value -> {
this.channel(targetChannel).setNextValue(converter.elementToChannel(value.get()));
});
return;
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java
index cd031cfb4cb..1d508bb9363 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java
@@ -23,6 +23,10 @@ public class DummyModbusBridge extends AbstractModbusBridge implements BridgeMod
private InetAddress ipAddress = null;
public DummyModbusBridge(String id) {
+ this(id, LogVerbosity.NONE);
+ }
+
+ public DummyModbusBridge(String id, LogVerbosity logVerbosity) {
super(//
OpenemsComponent.ChannelId.values(), //
BridgeModbus.ChannelId.values(), //
@@ -31,7 +35,7 @@ public DummyModbusBridge(String id) {
for (Channel> channel : this.channels()) {
channel.nextProcessImage();
}
- super.activate(null, id, "", true, LogVerbosity.NONE, 2);
+ super.activate(null, id, "", true, logVerbosity, 2);
}
/**
@@ -66,12 +70,12 @@ public InetAddress getIpAddress() {
@Override
public ModbusTransaction getNewModbusTransaction() throws OpenemsException {
- throw new UnsupportedOperationException("Unsupported by Dummy Class");
+ throw new UnsupportedOperationException("getNewModbusTransaction() Unsupported by Dummy Class");
}
@Override
public void closeModbusConnection() {
- throw new UnsupportedOperationException("Unsupported by Dummy Class");
+ throw new UnsupportedOperationException("closeModbusConnection() Unsupported by Dummy Class");
}
}
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java
index ca9a516b620..276dc8dcce6 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java
@@ -22,7 +22,6 @@
import io.openems.edge.common.taskmanager.Priority;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
-import io.openems.edge.common.test.DummyCycle;
import io.openems.edge.common.test.TestUtils;
public class BridgeModbusTcpImplTest {
@@ -61,7 +60,6 @@ public void test() throws Exception {
var device = new MyModbusComponent(DEVICE_ID, sut, UNIT_ID);
var test = new ComponentTest(sut) //
.addComponent(device) //
- .addReference("cycle", new DummyCycle(CYCLE_TIME)) //
.activate(MyConfigTcp.create() //
.setId(MODBUS_ID) //
.setIp("127.0.0.1") //
@@ -135,11 +133,10 @@ public Doc doc() {
}
@Override
- protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
+ protected ModbusProtocol defineModbusProtocol() {
return new ModbusProtocol(this, //
new FC3ReadRegistersTask(100, Priority.HIGH, //
- m(ChannelId.REGISTER_100, new UnsignedWordElement(100) //
- ))); //
+ m(ChannelId.REGISTER_100, new UnsignedWordElement(100)))); //
}
}
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/DummyModbusComponent.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/DummyModbusComponent.java
index f0b8ee7d3e6..a4f3dc43db3 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/DummyModbusComponent.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/DummyModbusComponent.java
@@ -50,12 +50,12 @@ public DummyModbusComponent(String id, AbstractModbusBridge bridge, int unitId,
super.activate(context, id, "", true, unitId, cm, "Modbus", bridge.id());
}
- protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
+ protected ModbusProtocol defineModbusProtocol() {
return new ModbusProtocol(this);
}
@Override
- public ModbusProtocol getModbusProtocol() throws OpenemsException {
+ public ModbusProtocol getModbusProtocol() {
return super.getModbusProtocol();
}
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/MyConfigSerial.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/MyConfigSerial.java
index 5df6db2a1d2..fbb13ca6a46 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/MyConfigSerial.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/MyConfigSerial.java
@@ -53,17 +53,17 @@ public Builder setParity(Parity parity) {
this.parity = parity;
return this;
}
-
+
public Builder setEnableTermination(boolean enableTermination) {
this.enableTermination = enableTermination;
return this;
}
-
+
public Builder setDelayBeforeTx(int delay) {
this.delayBeforeTx = delay;
return this;
}
-
+
public Builder setDelayAfterTx(int delay) {
this.delayAfterTx = delay;
return this;
@@ -124,7 +124,7 @@ public Stopbit stopbits() {
public Parity parity() {
return this.builder.parity;
}
-
+
@Override
public boolean enableTermination() {
return this.builder.enableTermination;
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/MyConfigTcp.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/MyConfigTcp.java
index 18a2114d244..1f74849a5ce 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/MyConfigTcp.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/MyConfigTcp.java
@@ -6,7 +6,7 @@
@SuppressWarnings("all")
public class MyConfigTcp extends AbstractComponentConfig implements ConfigTcp {
- protected static class Builder {
+ public static class Builder {
private String id;
private String ip;
private int port;
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/ConversionTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/ConversionTest.java
index 0e502a04d72..5e5b5777c16 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/ConversionTest.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/ConversionTest.java
@@ -10,25 +10,6 @@
public class ConversionTest {
- @Test
- public void testShortConversions() {
-
- var v1 = 0;
- var result = ModbusUtils.convert(v1, 0);
- Short expected = 0;
- assertEquals(expected, result);
-
- v1 = 1;
- result = ModbusUtils.convert(v1, 0);
- expected = 1;
- assertEquals(expected, result);
-
- v1 = 65536;
- result = ModbusUtils.convert(v1, 1);
- expected = 1;
- assertEquals(expected, result);
- }
-
@Test
public void multiplyTest() {
var value = 10;
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/ModbusUtilsTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/ModbusUtilsTest.java
new file mode 100644
index 00000000000..afca83ff9b3
--- /dev/null
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/ModbusUtilsTest.java
@@ -0,0 +1,47 @@
+package io.openems.edge.bridge.modbus.api;
+
+import static io.openems.edge.bridge.modbus.api.ModbusUtils.doNotRetry;
+import static io.openems.edge.bridge.modbus.api.ModbusUtils.intToHexString;
+import static io.openems.edge.bridge.modbus.api.ModbusUtils.registersToHexString;
+import static io.openems.edge.bridge.modbus.api.ModbusUtils.retryOnNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.ghgande.j2mod.modbus.procimg.SimpleInputRegister;
+import com.ghgande.j2mod.modbus.procimg.SimpleRegister;
+
+import io.openems.edge.bridge.modbus.api.task.Task.ExecuteState;
+
+public class ModbusUtilsTest {
+
+ @Test
+ public void testRetryOnNull() {
+ assertTrue(retryOnNull(ExecuteState.OK, null));
+ assertFalse(retryOnNull(ExecuteState.OK, 123));
+ }
+
+ @Test
+ public void testDoNotRetry() {
+ assertFalse(doNotRetry(ExecuteState.OK, null));
+ assertFalse(doNotRetry(ExecuteState.OK, 123));
+ }
+
+ @Test
+ public void testIntToHexString() {
+ assertEquals("00af", intToHexString(0xAF));
+ }
+
+ @Test
+ public void testRegistersToHexString() {
+ assertEquals("00aa 00ff", registersToHexString(new SimpleRegister(0xAA), new SimpleRegister(0xFF)));
+ }
+
+ @Test
+ public void testInputRegistersToHexString() {
+ assertEquals("00aa 00ff", registersToHexString(new SimpleInputRegister(0xAA), new SimpleInputRegister(0xFF)));
+ }
+
+}
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/AbstractDummyTask.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/AbstractDummyTask.java
index b834c62cb6c..5a61d27e0b4 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/AbstractDummyTask.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/AbstractDummyTask.java
@@ -1,11 +1,14 @@
package io.openems.edge.bridge.modbus.api.worker;
+import java.util.function.Consumer;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ghgande.j2mod.modbus.msg.ModbusRequest;
import com.ghgande.j2mod.modbus.msg.ModbusResponse;
+import io.openems.common.utils.FunctionUtils;
import io.openems.edge.bridge.modbus.api.AbstractModbusBridge;
import io.openems.edge.bridge.modbus.api.task.AbstractTask;
@@ -15,22 +18,28 @@ public abstract class AbstractDummyTask extends AbstractTask onExecute, long delay) {
+ super(name, onExecute, ModbusResponse.class, 0);
this.name = name;
this.delay = delay;
}
- public void setDefective(boolean isDefective, long delay) {
- this.isDefective = isDefective;
+ public void setDefective(Exception defect, long delay) {
+ this.defect = defect;
this.delay = delay;
}
+ protected long getDelay() {
+ return this.delay;
+ }
+
@Override
protected String payloadToString(ModbusRequest request) {
return "";
@@ -41,29 +50,16 @@ protected String payloadToString(ModbusResponse response) {
return "";
}
- /**
- * Callback on Execute.
- *
- * @param onExecuteCallback the callback {@link Runnable}
- */
- public void onExecute(Runnable onExecuteCallback) {
- this.onExecuteCallback = onExecuteCallback;
- }
-
@Override
public ExecuteState execute(AbstractModbusBridge bridge) {
- if (this.onExecuteCallback != null) {
- this.onExecuteCallback.run();
- }
-
try {
Thread.sleep(this.delay);
} catch (InterruptedException e) {
this.log.warn(e.getMessage());
}
- if (this.isDefective) {
- return ExecuteState.ERROR;
+ if (this.defect != null) {
+ return new ExecuteState.Error(this.defect);
}
return ExecuteState.OK;
}
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyReadTask.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyReadTask.java
index 63357905cfb..d8b3284429c 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyReadTask.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyReadTask.java
@@ -14,7 +14,7 @@ public DummyReadTask(String name, long delay, Priority priority) {
@Override
public String toString() {
- return "DummyReadTask [name=" + this.name + ", delay=" + this.delay + ", priority=" + this.priority + "]";
+ return "DummyReadTask [name=" + this.name + ", delay=" + this.getDelay() + ", priority=" + this.priority + "]";
}
@Override
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyWriteTask.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyWriteTask.java
index c219e0a6295..f7ff37d8c51 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyWriteTask.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/DummyWriteTask.java
@@ -11,7 +11,7 @@ public DummyWriteTask(String name, long delay) {
@Override
public String toString() {
- return "DummyWriteTask [name=" + this.name + ", delay=" + this.delay + "]";
+ return "DummyWriteTask [name=" + this.name + ", delay=" + this.getDelay() + "]";
}
@Override
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponentTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponentTest.java
index ed2041643b3..a4526ef63dd 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponentTest.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/AbstractOpenemsSunSpecComponentTest.java
@@ -1,14 +1,35 @@
package io.openems.edge.bridge.modbus.sunspec;
+import static io.openems.edge.bridge.modbus.sunspec.AbstractOpenemsSunSpecComponent.preprocessModbusElements;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import java.util.ArrayList;
+import java.util.stream.IntStream;
+import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
+import com.ghgande.j2mod.modbus.procimg.SimpleProcessImage;
+import com.ghgande.j2mod.modbus.procimg.SimpleRegister;
+import com.ghgande.j2mod.modbus.slave.ModbusSlave;
+import com.ghgande.j2mod.modbus.slave.ModbusSlaveFactory;
+import com.google.common.collect.ImmutableSortedMap;
+
import io.openems.common.exceptions.OpenemsException;
+import io.openems.edge.bridge.modbus.BridgeModbusTcpImpl;
+import io.openems.edge.bridge.modbus.MyConfigTcp;
+import io.openems.edge.bridge.modbus.api.LogVerbosity;
import io.openems.edge.bridge.modbus.api.element.ModbusElement;
import io.openems.edge.bridge.modbus.api.element.StringWordElement;
+import io.openems.edge.bridge.modbus.sunspec.dummy.MyConfig;
+import io.openems.edge.bridge.modbus.sunspec.dummy.MySunSpecComponentImpl;
+import io.openems.edge.common.test.AbstractComponentTest.TestCase;
+import io.openems.edge.common.test.ComponentTest;
+import io.openems.edge.common.test.DummyConfigurationAdmin;
+import io.openems.edge.common.test.TestUtils;
public class AbstractOpenemsSunSpecComponentTest {
@@ -22,11 +43,129 @@ public void testPreprocessModbusElements() throws OpenemsException {
elements.add(element);
}
- var sut = AbstractOpenemsSunSpecComponent.preprocessModbusElements(elements);
+ var sut = preprocessModbusElements(elements);
assertEquals(2, sut.size()); // two sublists
assertEquals(69, sut.get(0).size()); // first task
assertEquals(1, sut.get(1).size()); // second task
assertEquals(StringWordElement.class, sut.get(1).get(0).getClass()); // second task
}
+ private static final int UNIT_ID = 1;
+
+ @Before
+ public void changeLogLevel() {
+ java.lang.System.setProperty("org.ops4j.pax.logging.DefaultServiceLog.level", "INFO");
+ }
+
+ private static ImmutableSortedMap.Builder generateSunSpec() {
+ var b = ImmutableSortedMap.naturalOrder() //
+ .put(40000, 0x5375) // SunSpec identifier
+ .put(40001, 0x6e53) // SunSpec identifier
+
+ .put(40002, 1) // SunSpec Block-ID
+ .put(40003, 66); // Length of the SunSpec Block
+ IntStream.range(40004, 40070).forEach(i -> b.put(i, 0));
+ b //
+ .put(40070, 123) // SunSpec Block-ID
+ .put(40071, 24); // Length of the SunSpec Block
+ IntStream.range(40072, 40096).forEach(i -> b.put(i, 0));
+ b //
+ .put(40096, 999) // SunSpec Block-ID
+ .put(40097, 10) // Length of the SunSpec Block
+
+ .put(40108, 702) // SunSpec Block-ID
+ .put(40109, 50); // Length of the SunSpec Block
+ IntStream.range(40110, 40160).forEach(i -> b.put(i, 0));
+ return b;
+ }
+
+ // Disabled because of timing issues in CI
+ @Ignore
+ @Test
+ public void test() throws Exception {
+ var port = TestUtils.findRandomOpenPortOnAllLocalInterfaces();
+ ModbusSlave slave = null;
+ try {
+ /*
+ * Open Modbus/TCP Slave
+ */
+ slave = ModbusSlaveFactory.createTCPSlave(port, 1);
+ slave.open();
+
+ /*
+ * Instantiate Modbus-Bridge
+ */
+ var bridge = new BridgeModbusTcpImpl();
+ var testBridge = new ComponentTest(bridge) //
+ .activate(MyConfigTcp.create() //
+ .setId("modbus0") //
+ .setIp("127.0.0.1") //
+ .setPort(port) //
+ .setInvalidateElementsAfterReadErrors(1) //
+ .setLogVerbosity(LogVerbosity.READS_AND_WRITES_VERBOSE) //
+ .build());
+
+ var cmp = new MySunSpecComponentImpl();
+ var testCmp = new ComponentTest(cmp) //
+ .addReference("cm", new DummyConfigurationAdmin()) //
+ .addReference("setModbus", bridge) //
+ .activate(MyConfig.create() //
+ .setId("cmp0") //
+ .setModbusId("modbus0") //
+ .setModbusUnitId(UNIT_ID) //
+ .setReadFromModbusBlock(1) //
+ .build());
+
+ testWithEndOfMap(slave, bridge, testBridge, cmp, testCmp);
+ testWithIllegalAddress(slave, bridge, testBridge, cmp, testCmp);
+
+ assertFalse(cmp.getSunSpecChannel(DefaultSunSpecModel.S103.APH_A).isPresent());
+ assertNotNull(cmp.getSunSpecChannelOrError(DefaultSunSpecModel.S702.W_MAX_RTG));
+
+ } finally {
+ if (slave != null) {
+ slave.close();
+ }
+ }
+ }
+
+ private static void cycle(ComponentTest testBridge, ComponentTest testCmp, int count, int sleep) throws Exception {
+ for (var i = 0; i < count; i++) {
+ testBridge.next(new TestCase());
+ testCmp.next(new TestCase());
+ Thread.sleep(sleep); // TODO required?
+ }
+ }
+
+ private static void testWithEndOfMap(ModbusSlave slave, BridgeModbusTcpImpl bridge, ComponentTest testBridge,
+ MySunSpecComponentImpl cmp, ComponentTest testCmp) throws Exception {
+ var processImage = new SimpleProcessImage(UNIT_ID);
+ generateSunSpec() //
+ .put(40160, 0xFFFF) // END_OF_MAP
+ .put(40161, 0)// Behind the end
+ .build() //
+ .entrySet().stream() //
+ .forEach(e -> processImage.addRegister(e.getKey(), new SimpleRegister(e.getValue())));
+ slave.addProcessImage(UNIT_ID, processImage);
+
+ cycle(testBridge, testCmp, 5, 100);
+
+ assertEquals(58, cmp.channels().size());
+ }
+
+ private static void testWithIllegalAddress(ModbusSlave slave, BridgeModbusTcpImpl bridge, ComponentTest testBridge,
+ MySunSpecComponentImpl cmp, ComponentTest testCmp) throws Exception {
+ var processImage = new SimpleProcessImage(UNIT_ID);
+ generateSunSpec() //
+ .build() //
+ .entrySet().stream() //
+ .forEach(e -> processImage.addRegister(e.getKey(), new SimpleRegister(e.getValue())));
+ slave.addProcessImage(UNIT_ID, processImage);
+
+ cycle(testBridge, testCmp, 2, 100);
+ cycle(testBridge, testCmp, 1, 2000); // wait for defective component
+ cycle(testBridge, testCmp, 2, 100);
+
+ assertEquals(58, cmp.channels().size());
+ }
}
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/DummySunSpecComponent.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/DummySunSpecComponent.java
index adbb4e21c59..3a6d52f2fc4 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/DummySunSpecComponent.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/DummySunSpecComponent.java
@@ -16,17 +16,20 @@ public class DummySunSpecComponent extends AbstractOpenemsSunSpecComponent {
/**
* All models are active with low priority.
*/
- private static final Map ACTIVE_MODELS = Stream.of(DefaultSunSpecModel.values())
- .collect(Collectors.toMap(model -> model, model -> Priority.LOW, (a, b) -> a, TreeMap::new));
+ private static final Map ACTIVE_MODELS = Stream.of(DefaultSunSpecModel.values()) //
+ .collect(Collectors.toMap(//
+ model -> model, //
+ model -> Priority.LOW, //
+ (a, b) -> a, TreeMap::new));
- public DummySunSpecComponent() throws OpenemsException {
+ public DummySunSpecComponent() {
super(ACTIVE_MODELS, //
OpenemsComponent.ChannelId.values(), //
ModbusComponent.ChannelId.values()); //
this.addBlocks();
}
- private void addBlocks() throws OpenemsException {
+ private void addBlocks() {
var startAddress = 40000;
for (var entry : ACTIVE_MODELS.keySet()) {
this.addBlock(startAddress, entry, ACTIVE_MODELS.get(entry));
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/dummy/Config.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/dummy/Config.java
new file mode 100644
index 00000000000..24169ea7b76
--- /dev/null
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/dummy/Config.java
@@ -0,0 +1,19 @@
+package io.openems.edge.bridge.modbus.sunspec.dummy;
+
+@interface Config {
+ String id();
+
+ String alias();
+
+ boolean enabled();
+
+ boolean readOnly();
+
+ String modbus_id();
+
+ int modbusUnitId();
+
+ int readFromModbusBlock();
+
+ String Modbus_target();
+}
\ No newline at end of file
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/dummy/MyConfig.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/dummy/MyConfig.java
new file mode 100644
index 00000000000..665e48e73be
--- /dev/null
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/dummy/MyConfig.java
@@ -0,0 +1,90 @@
+package io.openems.edge.bridge.modbus.sunspec.dummy;
+
+import io.openems.common.test.AbstractComponentConfig;
+import io.openems.common.utils.ConfigUtils;
+
+@SuppressWarnings("all")
+public class MyConfig extends AbstractComponentConfig implements Config {
+
+ public static class Builder {
+ private String id = null;
+ private boolean readOnly;
+ private String modbusId = null;
+ private int modbusUnitId;
+ private int readFromModbusBlock;
+
+ private Builder() {
+ }
+
+ public Builder setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder setReadOnly(boolean readOnly) {
+ this.readOnly = readOnly;
+ return this;
+ }
+
+ public Builder setModbusId(String modbusId) {
+ this.modbusId = modbusId;
+ return this;
+ }
+
+ public Builder setModbusUnitId(int modbusUnitId) {
+ this.modbusUnitId = modbusUnitId;
+ return this;
+ }
+
+ public Builder setReadFromModbusBlock(int readFromModbusBlock) {
+ this.readFromModbusBlock = readFromModbusBlock;
+ 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 boolean readOnly() {
+ return this.builder.readOnly;
+ }
+
+ @Override
+ public String modbus_id() {
+ return this.builder.modbusId;
+ }
+
+ @Override
+ public int readFromModbusBlock() {
+ return this.builder.readFromModbusBlock;
+ }
+
+ @Override
+ public String Modbus_target() {
+ return ConfigUtils.generateReferenceTargetFilter(this.id(), this.modbus_id());
+ }
+
+ @Override
+ public int modbusUnitId() {
+ return this.builder.modbusUnitId;
+ }
+
+}
\ No newline at end of file
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/dummy/MySunSpecComponentImpl.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/dummy/MySunSpecComponentImpl.java
new file mode 100644
index 00000000000..119adf1cf99
--- /dev/null
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/sunspec/dummy/MySunSpecComponentImpl.java
@@ -0,0 +1,77 @@
+package io.openems.edge.bridge.modbus.sunspec.dummy;
+
+import java.util.Map;
+
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.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 com.google.common.collect.ImmutableMap;
+
+import io.openems.common.exceptions.OpenemsException;
+import io.openems.edge.bridge.modbus.api.BridgeModbus;
+import io.openems.edge.bridge.modbus.api.ModbusComponent;
+import io.openems.edge.bridge.modbus.api.ModbusProtocol;
+import io.openems.edge.bridge.modbus.sunspec.AbstractOpenemsSunSpecComponent;
+import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel;
+import io.openems.edge.bridge.modbus.sunspec.SunSpecModel;
+import io.openems.edge.common.component.OpenemsComponent;
+import io.openems.edge.common.taskmanager.Priority;
+
+public class MySunSpecComponentImpl extends AbstractOpenemsSunSpecComponent
+ implements ModbusComponent, OpenemsComponent {
+
+ private static final Map ACTIVE_MODELS = ImmutableMap.builder()
+ .put(DefaultSunSpecModel.S_1, Priority.LOW) //
+ .put(DefaultSunSpecModel.S_101, Priority.LOW) //
+ .put(DefaultSunSpecModel.S_103, Priority.HIGH) //
+ .put(DefaultSunSpecModel.S_701, Priority.HIGH) //
+ .put(DefaultSunSpecModel.S_702, Priority.LOW) //
+ .build();
+
+ @Reference
+ private ConfigurationAdmin cm;
+
+ @Override
+ @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY)
+ protected void setModbus(BridgeModbus modbus) {
+ super.setModbus(modbus);
+ }
+
+ public MySunSpecComponentImpl() {
+ super(//
+ ACTIVE_MODELS, //
+ OpenemsComponent.ChannelId.values(), //
+ ModbusComponent.ChannelId.values() //
+ );
+ }
+
+ @Activate
+ private void activate(ComponentContext context, Config config) throws OpenemsException {
+ if (super.activate(context, config.id(), config.alias(), true, config.modbusUnitId(), this.cm, "Modbus",
+ config.modbus_id(), config.readFromModbusBlock())) {
+ return;
+ }
+ }
+
+ @Override
+ public ModbusProtocol getModbusProtocol() {
+ return super.getModbusProtocol();
+ }
+
+ @Override
+ @Deactivate
+ protected void deactivate() {
+ super.deactivate();
+ }
+
+ @Override
+ protected void onSunSpecInitializationCompleted() {
+ }
+
+}
diff --git a/io.openems.edge.bridge.onewire/src/io/openems/edge/bridge/onewire/impl/BridgeOnewireImpl.java b/io.openems.edge.bridge.onewire/src/io/openems/edge/bridge/onewire/impl/BridgeOnewireImpl.java
index 95cadfee470..b03ef071bd0 100644
--- a/io.openems.edge.bridge.onewire/src/io/openems/edge/bridge/onewire/impl/BridgeOnewireImpl.java
+++ b/io.openems.edge.bridge.onewire/src/io/openems/edge/bridge/onewire/impl/BridgeOnewireImpl.java
@@ -1,6 +1,5 @@
package io.openems.edge.bridge.onewire.impl;
-import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import org.osgi.service.component.ComponentContext;
@@ -14,16 +13,13 @@
import com.dalsemi.onewire.adapter.DSPortAdapter;
-import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
-import io.openems.common.jsonrpc.base.JsonrpcRequest;
-import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess;
import io.openems.edge.bridge.onewire.BridgeOnewire;
import io.openems.edge.bridge.onewire.jsonrpc.GetDevicesRequest;
import io.openems.edge.common.component.AbstractOpenemsComponent;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.event.EdgeEventConstants;
-import io.openems.edge.common.jsonapi.JsonApi;
-import io.openems.edge.common.user.User;
+import io.openems.edge.common.jsonapi.ComponentJsonApi;
+import io.openems.edge.common.jsonapi.JsonApiBuilder;
@Designate(ocd = Config.class, factory = true)
@Component(//
@@ -34,7 +30,8 @@
@EventTopics({ //
EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE //
})
-public class BridgeOnewireImpl extends AbstractOpenemsComponent implements BridgeOnewire, OpenemsComponent, JsonApi {
+public class BridgeOnewireImpl extends AbstractOpenemsComponent
+ implements BridgeOnewire, OpenemsComponent, ComponentJsonApi {
private OneWireTaskWorker taskWorker = null;
@@ -84,13 +81,8 @@ protected void logError(Logger log, String message) {
}
@Override
- public CompletableFuture handleJsonrpcRequest(User user, JsonrpcRequest message)
- throws OpenemsNamedException {
- switch (message.getMethod()) {
- case GetDevicesRequest.METHOD:
- return CompletableFuture.completedFuture(this.taskWorker.handleGetDevicesRequest(message));
- }
- return null;
+ public void buildJsonApiRoutes(JsonApiBuilder builder) {
+ builder.handleRequest(GetDevicesRequest.METHOD, call -> this.taskWorker.handleGetDevicesRequest(call.getRequest()));
}
}
\ No newline at end of file
diff --git a/io.openems.edge.common/src/io/openems/edge/common/channel/calculate/CalculateAverage.java b/io.openems.edge.common/src/io/openems/edge/common/channel/calculate/CalculateAverage.java
index f6e44d207cd..e6d8ef2dc0d 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/channel/calculate/CalculateAverage.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/channel/calculate/CalculateAverage.java
@@ -26,7 +26,7 @@ public void addValue(Channel extends Number> channel) {
var value = channel.value().asOptional();
if (value.isPresent()) {
try {
- this.values.add(value.get().doubleValue());
+ this.addValue(value.get());
} catch (Exception e) {
this.log.error("Adding Channel [" + channel.address() + "] value [" + value + "] failed. "
+ e.getClass().getSimpleName() + ": " + e.getMessage());
@@ -35,6 +35,17 @@ public void addValue(Channel extends Number> channel) {
}
}
+ /**
+ * Adds a Value.
+ *
+ * @param value the value
+ */
+ public void addValue(Number value) {
+ if (value != null) {
+ this.values.add(value.doubleValue());
+ }
+ }
+
/**
* Calculates the average.
*
diff --git a/io.openems.edge.common/src/io/openems/edge/common/component/ComponentManager.java b/io.openems.edge.common/src/io/openems/edge/common/component/ComponentManager.java
index f026cf8d3fd..ea444764b63 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/component/ComponentManager.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/component/ComponentManager.java
@@ -7,18 +7,21 @@
import io.openems.common.channel.Level;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.jsonrpc.request.CreateComponentConfigRequest;
+import io.openems.common.jsonrpc.request.DeleteComponentConfigRequest;
+import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest;
import io.openems.common.types.ChannelAddress;
import io.openems.common.types.EdgeConfig;
import io.openems.edge.common.channel.Channel;
import io.openems.edge.common.channel.Doc;
import io.openems.edge.common.channel.StateChannel;
import io.openems.edge.common.channel.value.Value;
-import io.openems.edge.common.jsonapi.JsonApi;
+import io.openems.edge.common.user.User;
/**
* A Service that provides access to OpenEMS-Components.
*/
-public interface ComponentManager extends OpenemsComponent, JsonApi, ClockProvider {
+public interface ComponentManager extends OpenemsComponent, ClockProvider {
public static final String SINGLETON_SERVICE_PID = "Core.ComponentManager";
public static final String SINGLETON_COMPONENT_ID = "_componentManager";
@@ -274,4 +277,34 @@ public default > T getChannel(ChannelAddress channelAddress
*/
public EdgeConfig getEdgeConfig();
+ /**
+ * Handles a {@link CreateComponentConfigRequest}.
+ *
+ * @param user the user
+ * @param request the {@link CreateComponentConfigRequest}
+ * @throws OpenemsNamedException on error
+ */
+ public void handleCreateComponentConfigRequest(User user, CreateComponentConfigRequest request)
+ throws OpenemsNamedException;
+
+ /**
+ * Handles a {@link UpdateComponentConfigRequest}.
+ *
+ * @param user the user
+ * @param request the {@link UpdateComponentConfigRequest}
+ * @throws OpenemsNamedException on error
+ */
+ public void handleUpdateComponentConfigRequest(User user, UpdateComponentConfigRequest request)
+ throws OpenemsNamedException;
+
+ /**
+ * Handles a {@link DeleteComponentConfigRequest}.
+ *
+ * @param user the user
+ * @param request the {@link DeleteComponentConfigRequest}
+ * @throws OpenemsNamedException on error
+ */
+ public void handleDeleteComponentConfigRequest(User user, DeleteComponentConfigRequest request)
+ throws OpenemsNamedException;
+
}
\ No newline at end of file
diff --git a/io.openems.edge.common/src/io/openems/edge/common/host/DummyHost.java b/io.openems.edge.common/src/io/openems/edge/common/host/DummyHost.java
index 845c1dd479d..18378ccece7 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/host/DummyHost.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/host/DummyHost.java
@@ -1,14 +1,8 @@
package io.openems.edge.common.host;
-import java.util.concurrent.CompletableFuture;
-
-import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
-import io.openems.common.jsonrpc.base.JsonrpcRequest;
-import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.test.AbstractDummyOpenemsComponent;
import io.openems.edge.common.test.TestUtils;
-import io.openems.edge.common.user.User;
/**
* Simulates a {@link Host} for the OpenEMS Component test framework.
@@ -37,10 +31,4 @@ public DummyHost withHostname(String value) {
return this;
}
- @Override
- public CompletableFuture extends JsonrpcResponseSuccess> handleJsonrpcRequest(User user, JsonrpcRequest request)
- throws OpenemsNamedException {
- return null;
- }
-
}
\ No newline at end of file
diff --git a/io.openems.edge.common/src/io/openems/edge/common/host/Host.java b/io.openems.edge.common/src/io/openems/edge/common/host/Host.java
index da15ab467dc..27366c7d401 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/host/Host.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/host/Host.java
@@ -7,9 +7,8 @@
import io.openems.edge.common.channel.StringReadChannel;
import io.openems.edge.common.channel.value.Value;
import io.openems.edge.common.component.OpenemsComponent;
-import io.openems.edge.common.jsonapi.JsonApi;
-public interface Host extends OpenemsComponent, JsonApi {
+public interface Host extends OpenemsComponent {
public static final String SINGLETON_SERVICE_PID = "Core.Host";
public static final String SINGLETON_COMPONENT_ID = "_host";
diff --git a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/Call.java b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/Call.java
new file mode 100644
index 00000000000..f64376644a0
--- /dev/null
+++ b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/Call.java
@@ -0,0 +1,102 @@
+package io.openems.edge.common.jsonapi;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Call {
+
+ private final Logger log = LoggerFactory.getLogger(Call.class);
+
+ private final REQUEST request;
+ private RESPONSE response;
+ private Map properties;
+
+ private Call(REQUEST request, Map properties) {
+ super();
+ this.request = request;
+ this.properties = properties;
+ }
+
+ public Call(REQUEST request) {
+ this(request, new TreeMap<>());
+ }
+
+ public void setResponse(RESPONSE response) {
+ if (this.response != null) {
+ this.log.info("Request[" + this.request + "] was already fulfilled!");
+ }
+ this.response = response;
+ }
+
+ /**
+ * Gets the value to which the specific key is mapped, or null if this map
+ * contains no mapping for the key.
+ *
+ *
+ * The properties may contain special information about the current {@link Call}
+ * e. g. which user did trigger this call.
+ *
+ * @param the type of value
+ * @param key the key whose associated value is to be returned
+ * @return the value to which the specified key is mapped, or null if this map
+ * contains no mapping for the key
+ * @see Call#put(Key, Object)
+ */
+ @SuppressWarnings("unchecked")
+ public T get(Key key) {
+ return (T) this.properties.get(key.identifier());
+ }
+
+ /**
+ * Associates the specified value with the specified key in this map. If the map
+ * previously contained a mapping for the key, the old value is replaced by the
+ * specified value.
+ *
+ *
+ * With this values can be associated to the current call e. g. the user who
+ * triggered this call.
+ *
+ * @param the type of the value
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ */
+ public void put(Key key, T value) {
+ this.properties.put(key.identifier(), value);
+ }
+
+ /**
+ * Creates a new {@link Call} with the given request and applies all properties
+ * to the new {@link Call}.
+ *
+ * @param the type of the new request
+ * @param request the new request
+ * @return the new {@link Call}
+ */
+ public Call mapRequest(REQ request) {
+ return new Call<>(request, this.properties);
+ }
+
+ /**
+ * Creates a new {@link Call} with the type of the response mapped to a new
+ * type. The current request and the properties are applied to the new
+ * {@link Call}.
+ *
+ * @param the new type of the response
+ * @return the new {@link Call}
+ */
+ public Call mapResponse() {
+ return new Call<>(this.request, this.properties);
+ }
+
+ public REQUEST getRequest() {
+ return this.request;
+ }
+
+ public RESPONSE getResponse() {
+ return this.response;
+ }
+
+}
\ No newline at end of file
diff --git a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/ComponentJsonApi.java b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/ComponentJsonApi.java
new file mode 100644
index 00000000000..e12c09553fa
--- /dev/null
+++ b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/ComponentJsonApi.java
@@ -0,0 +1,12 @@
+package io.openems.edge.common.jsonapi;
+
+public interface ComponentJsonApi extends JsonApi {
+
+ /**
+ * Returns a unique ID for this OpenEMS component.
+ *
+ * @return the unique ID
+ */
+ public String id();
+
+}
diff --git a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EdgeGuards.java b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EdgeGuards.java
new file mode 100644
index 00000000000..43ce40ec96d
--- /dev/null
+++ b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EdgeGuards.java
@@ -0,0 +1,46 @@
+package io.openems.edge.common.jsonapi;
+
+import io.openems.common.session.Role;
+import io.openems.edge.common.user.User;
+
+public final class EdgeGuards {
+
+ /**
+ * Creates a {@link JsonrpcEndpointGuard} which checks if the {@link Role} of
+ * the current {@link User} is atleast the given {@link Role}.
+ *
+ * @param role the role which the user should atleast have
+ * @return the created {@link JsonrpcEndpointGuard}
+ */
+ public static JsonrpcEndpointGuard roleIsAtleast(Role role) {
+ return new JsonrpcRoleEndpointGuard(role);
+ }
+
+ /**
+ * Creates a {@link JsonrpcEndpointGuard} which checks if the request is NOT
+ * from the backend and if so checks if the {@link Role} of the current
+ * {@link User} is atleast the given {@link Role}.
+ *
+ * @param role the role which the user should atleast have
+ * @return the created {@link JsonrpcEndpointGuard}
+ */
+ public static JsonrpcEndpointGuard roleIsAtleastNotFromBackend(Role role) {
+ return new JsonrpcBackendRoleEndpointGuard(role, false);
+ }
+
+ /**
+ * Creates a {@link JsonrpcEndpointGuard} which checks if the request is from
+ * the backend and if so checks if the {@link Role} of the current {@link User}
+ * is atleast the given {@link Role}.
+ *
+ * @param role the role which the user should atleast have
+ * @return the created {@link JsonrpcEndpointGuard}
+ */
+ public static JsonrpcEndpointGuard roleIsAtleastFromBackend(Role role) {
+ return new JsonrpcBackendRoleEndpointGuard(role, true);
+ }
+
+ private EdgeGuards() {
+ }
+
+}
diff --git a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EdgeKeys.java b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EdgeKeys.java
new file mode 100644
index 00000000000..cbfb6ef940d
--- /dev/null
+++ b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EdgeKeys.java
@@ -0,0 +1,14 @@
+package io.openems.edge.common.jsonapi;
+
+import io.openems.edge.common.user.User;
+
+public final class EdgeKeys {
+
+ public static final Key USER_KEY = new Key<>("user", User.class);
+
+ public static final Key IS_FROM_BACKEND_KEY = new Key<>("isFromBackend", Boolean.class);
+
+ private EdgeKeys() {
+ }
+
+}
diff --git a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EndpointDefinitionBuilder.java b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EndpointDefinitionBuilder.java
new file mode 100644
index 00000000000..e1072f534a0
--- /dev/null
+++ b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EndpointDefinitionBuilder.java
@@ -0,0 +1,133 @@
+package io.openems.edge.common.jsonapi;
+
+import static java.util.Collections.emptyList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import io.openems.common.jsonrpc.serialization.JsonSerializer;
+
+public final class EndpointDefinitionBuilder