getEdgeId() {
}
@Override
- public String toString() {
- return "EdgeWebsocket.WsData [" //
- + "edgeId=" + this.edgeId.orElse("UNKNOWN") //
- + "]";
+ protected String toLogString() {
+ return new StringBuilder("EdgeWebsocket.WsData [edgeId=") //
+ .append(this.edgeId.orElse("UNKNOWN")) //
+ .append("]") //
+ .toString();
}
}
diff --git a/io.openems.backend.edgewebsocket/test/io/openems/backend/edgewebsocket/WsDataTest.java b/io.openems.backend.edgewebsocket/test/io/openems/backend/edgewebsocket/WsDataTest.java
new file mode 100644
index 00000000000..707168f40e3
--- /dev/null
+++ b/io.openems.backend.edgewebsocket/test/io/openems/backend/edgewebsocket/WsDataTest.java
@@ -0,0 +1,37 @@
+package io.openems.backend.edgewebsocket;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import java.util.Optional;
+
+import org.junit.Test;
+
+import com.google.gson.JsonObject;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.exceptions.OpenemsException;
+import io.openems.common.jsonrpc.base.GenericJsonrpcNotification;
+import io.openems.common.jsonrpc.base.JsonrpcMessage;
+
+public class WsDataTest {
+
+ private static final String EDGE_ID = "edge0";
+ private static final JsonrpcMessage JMSG = new GenericJsonrpcNotification("foo", new JsonObject());
+
+ @Test
+ public void test() throws OpenemsException {
+ var sut = new WsData(null);
+ assertEquals("EdgeWebsocket.WsData [edgeId=UNKNOWN]", sut.toLogString());
+ assertThrows(OpenemsNamedException.class, () -> sut.assertEdgeId(JMSG));
+ assertThrows(OpenemsNamedException.class, () -> sut.assertEdgeIdWithTimeout(JMSG, 1, MILLISECONDS));
+
+ sut.setEdgeId(EDGE_ID);
+ assertEquals("EdgeWebsocket.WsData [edgeId=edge0]", sut.toLogString());
+ sut.assertEdgeId(null);
+ sut.assertEdgeIdWithTimeout(JMSG, 1, MILLISECONDS);
+ assertEquals(Optional.of(EDGE_ID), sut.getEdgeId());
+ }
+
+}
diff --git a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java
index c60424b0d3d..b2bfdca34ef 100644
--- a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java
+++ b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java
@@ -154,14 +154,15 @@ public String assertToken() throws OpenemsNamedException {
}
@Override
- public String toString() {
- String tokenString;
- if (this.token.isPresent()) {
- tokenString = this.token.get().toString();
- } else {
- tokenString = "UNKNOWN";
- }
- return "UiWebsocket.WsData [userId=" + this.userId.orElse("UNKNOWN") + ", token=" + tokenString + "]";
+ protected String toLogString() {
+ return new StringBuilder("UiWebsocket.WsData [userId=") //
+ .append(this.userId.orElse("UNKNOWN")) //
+ .append(", token=") //
+ .append(this.token.isPresent() //
+ ? this.token.get().toString() //
+ : "UNKNOWN") //
+ .append("]") //
+ .toString();
}
/**
diff --git a/io.openems.backend.uiwebsocket/test/io/openems/backend/uiwebsocket/impl/WsDataTest.java b/io.openems.backend.uiwebsocket/test/io/openems/backend/uiwebsocket/impl/WsDataTest.java
new file mode 100644
index 00000000000..8832a3c6dfd
--- /dev/null
+++ b/io.openems.backend.uiwebsocket/test/io/openems/backend/uiwebsocket/impl/WsDataTest.java
@@ -0,0 +1,35 @@
+package io.openems.backend.uiwebsocket.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import java.util.Optional;
+
+import org.junit.Test;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+
+public class WsDataTest {
+
+ private static final String USER_ID = "user0";
+ private static final String TOKEN = "token";
+
+ @Test
+ public void test() throws OpenemsNamedException {
+ var sut = new WsData(null);
+ assertEquals(Optional.empty(), sut.getUser(null));
+ assertThrows(OpenemsNamedException.class, () -> sut.assertToken());
+ assertEquals("UiWebsocket.WsData [userId=UNKNOWN, token=UNKNOWN]", sut.toLogString());
+
+ sut.setUserId(USER_ID);
+ sut.setToken(TOKEN);
+
+ assertEquals(Optional.of(USER_ID), sut.getUserId());
+ assertEquals(Optional.of(TOKEN), sut.getToken());
+ assertEquals(TOKEN, sut.assertToken());
+ assertEquals("UiWebsocket.WsData [userId=user0, token=token]", sut.toLogString());
+
+ sut.logout();
+ }
+
+}
diff --git a/io.openems.common/resources/templates/controller/$testSrcDir$/$basePackageDir$/MyControllerTest.java b/io.openems.common/resources/templates/controller/$testSrcDir$/$basePackageDir$/MyControllerTest.java
index 2205624e13f..41b5f380e43 100644
--- a/io.openems.common/resources/templates/controller/$testSrcDir$/$basePackageDir$/MyControllerTest.java
+++ b/io.openems.common/resources/templates/controller/$testSrcDir$/$basePackageDir$/MyControllerTest.java
@@ -7,15 +7,14 @@
public class MyControllerTest {
- private static final String CTRL_ID = "ctrl0";
-
@Test
public void test() throws Exception {
new ControllerTest(new MyControllerImpl()) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .build())
- .next(new TestCase());
+ .setId("ctrl0") //
+ .build()) //
+ .next(new TestCase()) //
+ .deactivate();
}
}
diff --git a/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java b/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java
index c9961ad3a77..eb60ad3df76 100644
--- a/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java
+++ b/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java
@@ -9,19 +9,17 @@
public class MyModbusDeviceTest {
- private static final String COMPONENT_ID = "component0";
- private static final String MODBUS_ID = "modbus0";
-
@Test
public void test() throws Exception {
new ComponentTest(new MyModbusDeviceImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
.activate(MyConfig.create() //
- .setId(COMPONENT_ID) //
- .setModbusId(MODBUS_ID) //
- .build())
- .next(new TestCase());
+ .setId("component0") //
+ .setModbusId("modbus0") //
+ .build()) //
+ .next(new TestCase()) //
+ .deactivate();
}
}
diff --git a/io.openems.common/resources/templates/device/$testSrcDir$/$basePackageDir$/MyDeviceTest.java b/io.openems.common/resources/templates/device/$testSrcDir$/$basePackageDir$/MyDeviceTest.java
index d5cb74df7c9..f02eed1d7f9 100644
--- a/io.openems.common/resources/templates/device/$testSrcDir$/$basePackageDir$/MyDeviceTest.java
+++ b/io.openems.common/resources/templates/device/$testSrcDir$/$basePackageDir$/MyDeviceTest.java
@@ -7,15 +7,14 @@
public class MyDeviceTest {
- private static final String COMPONENT_ID = "component0";
-
@Test
public void test() throws Exception {
new ComponentTest(new MyDeviceImpl()) //
.activate(MyConfig.create() //
- .setId(COMPONENT_ID) //
- .build())
- .next(new TestCase());
+ .setId("component0") //
+ .build()) //
+ .next(new TestCase()) //
+ .deactivate();
}
}
diff --git a/io.openems.common/src/io/openems/common/OpenemsConstants.java b/io.openems.common/src/io/openems/common/OpenemsConstants.java
index 9b7384aae62..4ff2e3f1fab 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 = 10;
+ public static final short VERSION_MINOR = 11;
/**
* 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 466ea4fbef6..2bcc50113fd 100644
--- a/io.openems.common/src/io/openems/common/channel/Unit.java
+++ b/io.openems.common/src/io/openems/common/channel/Unit.java
@@ -284,7 +284,12 @@ public enum Unit {
/**
* Unit of Pressure [bar].
*/
- BAR("bar");
+ BAR("bar"),
+
+ /**
+ * Unit of Pressure [mbar].
+ */
+ MILLIBAR("mbar", BAR, -3);
public final String symbol;
public final Unit baseUnit;
@@ -363,7 +368,7 @@ public String format(Object value, OpenemsType type) {
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,
MILLIAMPERE_HOURS, MILLIOHM, MILLISECONDS, MINUTE, THOUSANDTH, VOLT_AMPERE_HOURS,
- VOLT_AMPERE_REACTIVE_HOURS, WATT_HOURS_BY_WATT_PEAK, CUMULATED_WATT_HOURS, BAR -> //
+ VOLT_AMPERE_REACTIVE_HOURS, WATT_HOURS_BY_WATT_PEAK, CUMULATED_WATT_HOURS, BAR, MILLIBAR -> //
value + " " + this.symbol;
case ON_OFF -> //
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java b/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java
index 6158477f132..acf64aba840 100644
--- a/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java
+++ b/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java
@@ -6,15 +6,19 @@
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.SortedMap;
import java.util.UUID;
+import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.dhatim.fastexcel.BorderSide;
+import org.dhatim.fastexcel.BorderStyle;
import org.dhatim.fastexcel.Workbook;
import org.dhatim.fastexcel.Worksheet;
@@ -22,6 +26,10 @@
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.session.Language;
+import io.openems.common.timedata.XlsxExportDetailData;
+import io.openems.common.timedata.XlsxExportDetailData.XlsxExportCategory;
+import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry;
+import io.openems.common.timedata.XlsxWorksheetWrapper;
import io.openems.common.types.ChannelAddress;
import io.openems.common.utils.JsonUtils;
@@ -60,6 +68,11 @@ protected static class Channel {
public static final ChannelAddress ESS_SOC = new ChannelAddress("_sum", "EssSoc");
}
+ private static final String BLUE = "44B3E1";
+ private static final String LIGHT_GREY = "BFBFBF";
+ private static final String DARK_GREY = "D9D9D9";
+ private static final String FONT_NAME = "Calibri";
+
/**
* All Power Channels, i.e. Channels that are exported per channel and
* timestamp.
@@ -91,21 +104,24 @@ protected static class Channel {
* While constructing, the actual Excel file is generated as payload of the
* JSON-RPC Response.
*
- * @param id the JSON-RPC ID
- * @param edgeId the Edge-ID
- * @param fromDate the start date of the export
- * @param toDate the end date of the export
- * @param historicData the power data per channel and timestamp
- * @param historicEnergy the energy data, one value per channel
- * @param language the {@link Language}
+ * @param id the JSON-RPC ID
+ * @param edgeId the Edge-ID
+ * @param fromDate the start date of the export
+ * @param toDate the end date of the export
+ * @param historicData the power data per channel and timestamp
+ * @param historicEnergy the energy data, one value per channel
+ * @param language the {@link Language}
+ * @param detailComponents the components for the detail view
* @throws IOException on error
* @throws OpenemsNamedException on error
*/
public QueryHistoricTimeseriesExportXlsxResponse(UUID id, String edgeId, ZonedDateTime fromDate,
ZonedDateTime toDate, SortedMap> historicData,
- SortedMap historicEnergy, Language language)
- throws IOException, OpenemsNamedException {
- super(id, XlsxUtils.generatePayload(edgeId, fromDate, toDate, historicData, historicEnergy, language));
+ SortedMap historicEnergy, Language language,
+ XlsxExportDetailData detailComponents) throws IOException, OpenemsNamedException {
+
+ super(id, XlsxUtils.generatePayload(edgeId, fromDate, toDate, historicData, historicEnergy, language,
+ detailComponents));
}
protected static class XlsxUtils {
@@ -118,20 +134,21 @@ protected static class XlsxUtils {
* Generates the Payload for a
* {@link QueryHistoricTimeseriesExportXlsxResponse}.
*
- * @param edgeId the Edge-Id
- * @param fromDate the start date of the export
- * @param toDate the end date of the export
- * @param powerData the power data per channel and timestamp
- * @param energyData the energy data, one value per channel
- * @param language the {@link Language}
+ * @param edgeId the Edge-Id
+ * @param fromDate the start date of the export
+ * @param toDate the end date of the export
+ * @param powerData the power data per channel and timestamp
+ * @param energyData the energy data, one value per channel
+ * @param language the {@link Language}
+ * @param detailComponents the components for the detail view
* @return the Excel file as byte-array.
* @throws IOException on error
* @throws OpenemsNamedException on error
*/
private static byte[] generatePayload(String edgeId, ZonedDateTime fromDate, ZonedDateTime toDate,
SortedMap> powerData,
- SortedMap energyData, Language language)
- throws IOException, OpenemsNamedException {
+ SortedMap energyData, Language language,
+ XlsxExportDetailData detailComponents) throws IOException, OpenemsNamedException {
byte[] payload = {};
try (//
var os = new ByteArrayOutputStream();
@@ -146,8 +163,54 @@ private static byte[] generatePayload(String edgeId, ZonedDateTime fromDate, Zon
XlsxUtils.addBasicInfo(ws, edgeId, fromDate, toDate, translationBundle);
XlsxUtils.addEnergyData(ws, energyData, translationBundle);
- XlsxUtils.addPowerData(ws, powerData, translationBundle);
+ var rowCount = XlsxUtils.addPowerData(ws, powerData, translationBundle);
+
+ final var worksheetWrapper = new XlsxWorksheetWrapper(ws);
+
+ // Box for Total Overview
+ worksheetWrapper.setForRange(9, 0, 9, 7, t -> t.style().borderStyle(BorderSide.TOP, BorderStyle.THIN));
+ worksheetWrapper.setForRange(9, 0, rowCount, 0,
+ t -> t.style().borderStyle(BorderSide.LEFT, BorderStyle.THIN));
+ worksheetWrapper.setForRange(rowCount, 0, rowCount, 7,
+ t -> t.style().borderStyle(BorderSide.BOTTOM, BorderStyle.THIN));
+ worksheetWrapper.setForRange(9, 7, rowCount, 7,
+ t -> t.style().borderStyle(BorderSide.RIGHT, BorderStyle.THIN));
+
+ if (detailComponents.data().values().stream().anyMatch(de -> !de.isEmpty())) { //
+ var rightestColumns = XlsxUtils.addDetailData(ws, powerData, detailComponents, translationBundle,
+ energyData);
+
+ final var colProd = rightestColumns.get(0) - 1;
+ final var colCons = rightestColumns.get(1) - 1;
+ final var colTou = rightestColumns.get(2) - 1;
+
+ // Set Box for detail Timerange data
+ worksheetWrapper.setForRange(9, 10, 9, colTou,
+ t -> t.style().borderStyle(BorderSide.TOP, BorderStyle.THIN));
+ worksheetWrapper.setForRange(9, 10, rowCount, 10,
+ t -> t.style().borderStyle(BorderSide.LEFT, BorderStyle.THIN));
+ worksheetWrapper.setForRange(rowCount, 10, rowCount, colTou,
+ t -> t.style().borderStyle(BorderSide.BOTTOM, BorderStyle.THIN));
+ worksheetWrapper.setForRange(9, colTou, rowCount, colTou,
+ t -> t.style().borderStyle(BorderSide.RIGHT, BorderStyle.THIN));
+
+ // Set Separators between prod, cons and tou
+ worksheetWrapper.setForRange(9, colProd, rowCount, colProd,
+ t -> t.style().borderStyle(BorderSide.RIGHT, BorderStyle.THIN));
+ worksheetWrapper.setForRange(9, colCons, rowCount, colCons,
+ t -> t.style().borderStyle(BorderSide.RIGHT, BorderStyle.THIN));
+
+ // Set "blue" Separator between detailed Overview and total Overview
+ worksheetWrapper.setForRange(0, 8, rowCount, 8, t -> t.style()//
+ .borderStyle(BorderSide.LEFT, BorderStyle.MEDIUM)//
+ .borderStyle(BorderSide.RIGHT, BorderStyle.MEDIUM)//
+ .fillColor(BLUE));
+ worksheetWrapper.getCellWrapper(0, 8).style().borderStyle(BorderSide.TOP, BorderStyle.MEDIUM);
+ worksheetWrapper.getCellWrapper(rowCount, 8).style().borderStyle(BorderSide.BOTTOM,
+ BorderStyle.MEDIUM);
+ }
+ worksheetWrapper.setAll();
wb.finish();
os.flush();
payload = os.toByteArray();
@@ -199,29 +262,35 @@ protected static void addBasicInfo(Worksheet ws, String edgeId, ZonedDateTime fr
*/
protected static void addEnergyData(Worksheet ws, SortedMap data,
ResourceBundle translationBundle) throws OpenemsNamedException {
+ ws.range(4, 1, 4, 6).merge();
+ ws.value(4, 1, translationBundle.getString("totalOverview"));
+ ws.range(4, 1, 4, 6).style().fontName(FONT_NAME).fontSize(12).horizontalAlignment("center")
+ .verticalAlignment("center").bold().fillColor(BLUE).set();
+ ws.range(5, 1, 5, 6).style().bold().fillColor(LIGHT_GREY).set();
+ ws.range(6, 1, 6, 6).style().fillColor(DARK_GREY).set();
// Grid buy energy
- XlsxUtils.addStringValueBold(ws, 4, 1, translationBundle.getString("gridBuy") + " [kWh]");
- XlsxUtils.addKwhValueIfnotNull(ws, 5, 1, data.get(Channel.GRID_BUY_ACTIVE_ENERGY), translationBundle);
+ XlsxUtils.addStringValueBold(ws, 5, 1, translationBundle.getString("gridBuy") + " [kWh]");
+ XlsxUtils.addKwhValueIfnotNull(ws, 6, 1, data.get(Channel.GRID_BUY_ACTIVE_ENERGY), translationBundle);
// Grid sell energy
- XlsxUtils.addStringValueBold(ws, 4, 2, translationBundle.getString("gridFeedIn") + " [kWh]");
- XlsxUtils.addKwhValueIfnotNull(ws, 5, 2, data.get(Channel.GRID_SELL_ACTIVE_ENERGY), translationBundle);
+ XlsxUtils.addStringValueBold(ws, 5, 2, translationBundle.getString("gridFeedIn") + " [kWh]");
+ XlsxUtils.addKwhValueIfnotNull(ws, 6, 2, data.get(Channel.GRID_SELL_ACTIVE_ENERGY), translationBundle);
// Production energy
- XlsxUtils.addStringValueBold(ws, 4, 3, translationBundle.getString("production") + " [kWh]");
- XlsxUtils.addKwhValueIfnotNull(ws, 5, 3, data.get(Channel.PRODUCTION_ACTIVE_ENERGY), translationBundle);
+ XlsxUtils.addStringValueBold(ws, 5, 3, translationBundle.getString("production") + " [kWh]");
+ XlsxUtils.addKwhValueIfnotNull(ws, 6, 3, data.get(Channel.PRODUCTION_ACTIVE_ENERGY), translationBundle);
// Charge energy
- XlsxUtils.addStringValueBold(ws, 4, 4, translationBundle.getString("storageCharging") + " [kWh]");
- XlsxUtils.addKwhValueIfnotNull(ws, 5, 4, data.get(Channel.ESS_DC_CHARGE_ENERGY), translationBundle);
+ XlsxUtils.addStringValueBold(ws, 5, 4, translationBundle.getString("storageCharging") + " [kWh]");
+ XlsxUtils.addKwhValueIfnotNull(ws, 6, 4, data.get(Channel.ESS_DC_CHARGE_ENERGY), translationBundle);
// Charge energy
- XlsxUtils.addStringValueBold(ws, 4, 5, translationBundle.getString("storageDischarging") + " [kWh]");
- XlsxUtils.addKwhValueIfnotNull(ws, 5, 5, data.get(Channel.ESS_DC_DISCHARGE_ENERGY), translationBundle);
+ XlsxUtils.addStringValueBold(ws, 5, 5, translationBundle.getString("storageDischarging") + " [kWh]");
+ XlsxUtils.addKwhValueIfnotNull(ws, 6, 5, data.get(Channel.ESS_DC_DISCHARGE_ENERGY), translationBundle);
// Consumption energy
- XlsxUtils.addStringValueBold(ws, 4, 6, translationBundle.getString("consumption") + " [kWh]");
- XlsxUtils.addKwhValueIfnotNull(ws, 5, 6, data.get(Channel.CONSUMPTION_ACTIVE_ENERGY), translationBundle);
+ XlsxUtils.addStringValueBold(ws, 5, 6, translationBundle.getString("consumption") + " [kWh]");
+ XlsxUtils.addKwhValueIfnotNull(ws, 6, 6, data.get(Channel.CONSUMPTION_ACTIVE_ENERGY), translationBundle);
}
/**
@@ -230,22 +299,24 @@ protected static void addEnergyData(Worksheet ws, SortedMap> data, ResourceBundle translationBundle)
throws OpenemsNamedException {
// Adding the headers
- XlsxUtils.addStringValueBold(ws, 7, 0, translationBundle.getString("date/time"));
- XlsxUtils.addStringValueBold(ws, 7, 1, translationBundle.getString("gridBuy") + " [W]");
- XlsxUtils.addStringValueBold(ws, 7, 2, translationBundle.getString("gridFeedIn") + " [W]");
- XlsxUtils.addStringValueBold(ws, 7, 3, translationBundle.getString("production") + " [W]");
- XlsxUtils.addStringValueBold(ws, 7, 4, translationBundle.getString("storageCharging") + " [W]");
- XlsxUtils.addStringValueBold(ws, 7, 5, translationBundle.getString("storageDischarging") + " [W]");
- XlsxUtils.addStringValueBold(ws, 7, 6, translationBundle.getString("consumption") + " [W]");
- XlsxUtils.addStringValueBold(ws, 7, 7, translationBundle.getString("stateOfCharge") + " [%]");
-
- var rowCount = 8;
+ XlsxUtils.addStringValueBold(ws, 9, 0, translationBundle.getString("date/time"));
+ XlsxUtils.addStringValueBold(ws, 9, 1, translationBundle.getString("gridBuy") + " [W]");
+ XlsxUtils.addStringValueBold(ws, 9, 2, translationBundle.getString("gridFeedIn") + " [W]");
+ XlsxUtils.addStringValueBold(ws, 9, 3, translationBundle.getString("production") + " [W]");
+ XlsxUtils.addStringValueBold(ws, 9, 4, translationBundle.getString("storageCharging") + " [W]");
+ XlsxUtils.addStringValueBold(ws, 9, 5, translationBundle.getString("storageDischarging") + " [W]");
+ XlsxUtils.addStringValueBold(ws, 9, 6, translationBundle.getString("consumption") + " [W]");
+ XlsxUtils.addStringValueBold(ws, 9, 7, translationBundle.getString("stateOfCharge") + " [%]");
+ XlsxUtils.addStringValueBold(ws, 8, 1, translationBundle.getString("generalData"));
+
+ var rowCount = 10;
for (Entry> row : data.entrySet()) {
var values = row.getValue();
@@ -267,12 +338,17 @@ protected static void addPowerData(Worksheet ws,
// Grid sell power
XlsxUtils.addFloatValue(ws, rowCount, 2, gridActivePower / -1);
}
+ } else {
+ XlsxUtils.addStringValue(ws, rowCount, 1, "-");
+ XlsxUtils.addStringValue(ws, rowCount, 2, "-");
}
// Production power
if (XlsxUtils.isNotNull(values.get(Channel.PRODUCTION_ACTIVE_POWER))) {
XlsxUtils.addFloatValue(ws, rowCount, 3,
JsonUtils.getAsFloat(values.get(Channel.PRODUCTION_ACTIVE_POWER)));
+ } else {
+ XlsxUtils.addStringValue(ws, rowCount, 3, "-");
}
if (XlsxUtils.isNotNull(values.get(Channel.ESS_DISCHARGE_POWER))) {
@@ -284,19 +360,120 @@ protected static void addPowerData(Worksheet ws,
XlsxUtils.addFloatValue(ws, rowCount, 4, essDischargePower / -1);
XlsxUtils.addFloatValue(ws, rowCount, 5, 0);
}
+ } else {
+ XlsxUtils.addStringValue(ws, rowCount, 4, "-");
+ XlsxUtils.addStringValue(ws, rowCount, 5, "-");
}
// Consumption power
if (XlsxUtils.isNotNull(values.get(Channel.CONSUMPTION_ACTIVE_POWER))) {
XlsxUtils.addFloatValue(ws, rowCount, 6,
JsonUtils.getAsFloat(values.get(Channel.CONSUMPTION_ACTIVE_POWER)));
+ } else {
+ XlsxUtils.addStringValue(ws, rowCount, 6, "-");
}
// State of charge
if (XlsxUtils.isNotNull(values.get(Channel.ESS_SOC))) {
XlsxUtils.addFloatValue(ws, rowCount, 7, JsonUtils.getAsFloat(values.get(Channel.ESS_SOC)));
+ } else {
+ XlsxUtils.addStringValue(ws, rowCount, 7, "-");
}
rowCount++;
}
+ rowCount--;
+ return rowCount;
+ }
+
+ protected static List addDetailData(Worksheet ws,
+ SortedMap> data,
+ XlsxExportDetailData detailComponents, ResourceBundle translationBundle,
+ SortedMap energyData) throws OpenemsNamedException {
+ ws.width(8, 4);
+ ws.width(9, 4);
+ ws.width(10, 25);
+ ws.width(11, 25);
+ ws.width(12, 25);
+ ws.width(13, 25);
+ ws.width(14, 25);
+ ws.width(15, 25);
+
+ ws.range(4, 10, 4, 15).merge();
+ ws.range(4, 10, 4, 15).style().fontName(FONT_NAME).fontSize(12).bold().fillColor(BLUE).set();
+ ws.value(4, 10, translationBundle.getString("detailData"));
+ ws.range(5, 10, 5, 15).merge();
+ ws.range(5, 10, 5, 15).style().fillColor(LIGHT_GREY).set();
+ ws.value(5, 10, translationBundle.getString("detailHint"));
+ var rightestColumn1 = addProductionData(ws, data, detailComponents, translationBundle);
+ var rightestColumn2 = addConsumptionData(ws, data, detailComponents, rightestColumn1, translationBundle);
+ var rightestColumn3 = addTimeOfUseTariffData(ws, data, detailComponents, rightestColumn2,
+ translationBundle);
+ ws.width(rightestColumn3, 35);
+ return List.of(rightestColumn1, rightestColumn2, rightestColumn3);
+ }
+
+ protected static int addProductionData(Worksheet ws,
+ SortedMap> data,
+ XlsxExportDetailData detailComponents, ResourceBundle translationBundle) throws OpenemsNamedException {
+ return XlsxUtils.addGenericData(ws, data, detailComponents, 10, translationBundle,
+ XlsxExportCategory.PRODUCTION, "production", (t, d) -> t.alias() + " [W]", 1);
+ }
+
+ protected static int addConsumptionData(Worksheet ws,
+ SortedMap> data,
+ XlsxExportDetailData detailComponents, int righestColumn, ResourceBundle translationBundle)
+ throws OpenemsNamedException {
+ return XlsxUtils.addGenericData(ws, data, detailComponents, righestColumn, translationBundle,
+ XlsxExportCategory.CONSUMPTION, "consumption", (t, d) -> t.alias() + " [W]", 1);
+ }
+
+ protected static int addTimeOfUseTariffData(Worksheet ws,
+ SortedMap> data,
+ XlsxExportDetailData detailComponents, int righestColumn, ResourceBundle translationBundle)
+ throws OpenemsNamedException {
+ final var unit = "[" + detailComponents.currency().getUnderPart() + "/kWh]";
+ return XlsxUtils.addGenericData(ws, data, detailComponents, righestColumn, translationBundle,
+ XlsxExportCategory.TIME_OF_USE_TARIFF, "timeOfUse", (t, d) -> t.alias() + " " + unit,
+ 1000f / detailComponents.currency().getRatio());
+ }
+
+ protected static int addGenericData(//
+ Worksheet ws, //
+ SortedMap> data, //
+ XlsxExportDetailData detailComponents, //
+ int righestColumn, //
+ ResourceBundle translationBundle, //
+ XlsxExportDetailData.XlsxExportCategory category, //
+ String translationKey, //
+ BiFunction aliasBuilder, //
+ float ratio) //
+ throws OpenemsNamedException {
+
+ final var righestColumnOld = righestColumn;
+
+ for (var item : detailComponents.data().get(category)) {
+ ws.value(8, righestColumnOld, translationBundle.getString(translationKey));
+ ws.style(8, righestColumnOld).bold().set();
+
+ ws.value(9, righestColumn, aliasBuilder.apply(item, detailComponents));
+ ws.style(9, righestColumn).bold().set();
+
+ var rowCount = 10;
+ for (final var row : data.entrySet()) {
+ final var values = row.getValue();
+ final var channelAddress = item.channel();
+
+ if (XlsxUtils.isNotNull(values.get(channelAddress))) {
+ XlsxUtils.addFloatValueNotRounded(ws, rowCount, righestColumn,
+ JsonUtils.getAsFloat(values.get(channelAddress)) / ratio);
+ } else {
+ XlsxUtils.addStringValue(ws, rowCount, righestColumn, "-");
+ }
+ rowCount++;
+ }
+ righestColumn++;
+ }
+
+ return righestColumn;
}
/**
@@ -385,6 +562,19 @@ protected static void addFloatValue(Worksheet ws, int row, int col, float value)
ws.value(row, col, Math.round(value));
}
+ /**
+ * Helper method to add the value to the excel sheet. The float value is
+ * mathematically rounded.
+ *
+ * @param ws the {@link Worksheet}
+ * @param row row number
+ * @param col column number
+ * @param value actual value in the sheet
+ */
+ protected static void addFloatValueNotRounded(Worksheet ws, int row, int col, float value) {
+ ws.value(row, col, value);
+ }
+
/**
* Simple helper method to check for null values.
*
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/response/translation_de.properties b/io.openems.common/src/io/openems/common/jsonrpc/response/translation_de.properties
index 1ac1c7f1860..4fdcc640291 100644
--- a/io.openems.common/src/io/openems/common/jsonrpc/response/translation_de.properties
+++ b/io.openems.common/src/io/openems/common/jsonrpc/response/translation_de.properties
@@ -8,4 +8,9 @@ storageDischarging = Speicher Entladung
consumption = Verbrauch
stateOfCharge = Ladezustand
date/time = Datum / Uhrzeit
-notAvailable = nicht vorhanden
\ No newline at end of file
+notAvailable = nicht vorhanden
+detailData = Detaillierte Auswertung der Erzeuger, Verbraucher und Apps
+detailHint = * Bitte beachten Sie, dass diese Werte bereits in den Leistungsdaten in Ihrer Gesamtübersicht enthalten sind.
+timeOfUse = Dynamischer Stromtarif
+totalOverview = Gesamtübersicht
+generalData = Allgemeine Daten
\ No newline at end of file
diff --git a/io.openems.common/src/io/openems/common/jsonrpc/response/translation_en.properties b/io.openems.common/src/io/openems/common/jsonrpc/response/translation_en.properties
index 4da846d76c2..40f8fc4d09a 100644
--- a/io.openems.common/src/io/openems/common/jsonrpc/response/translation_en.properties
+++ b/io.openems.common/src/io/openems/common/jsonrpc/response/translation_en.properties
@@ -8,4 +8,9 @@ storageDischarging = Storage Discharging
consumption = Consumption
stateOfCharge = State of Charge
date/time = Date / Time
-notAvailable = not available
\ No newline at end of file
+notAvailable = not available
+detailData = Detailed evaluation of the producer, consumer and apps
+detailHint = * Please note that these values are already included in the performance data in your Total Overview.
+timeOfUse = Time of Use Tariff
+totalOverview = Total Overview
+generalData = General Data
\ No newline at end of file
diff --git a/io.openems.common/src/io/openems/common/oem/DummyOpenemsEdgeOem.java b/io.openems.common/src/io/openems/common/oem/DummyOpenemsEdgeOem.java
index cd0a08830ca..7bca125287b 100644
--- a/io.openems.common/src/io/openems/common/oem/DummyOpenemsEdgeOem.java
+++ b/io.openems.common/src/io/openems/common/oem/DummyOpenemsEdgeOem.java
@@ -71,6 +71,7 @@ public SystemUpdateParams getSystemUpdateParams() {
.put("App.TimeOfUseTariff.Hassfurt", "") //
.put("App.TimeOfUseTariff.RabotCharge", "") //
.put("App.TimeOfUseTariff.Stromdao", "") //
+ .put("App.TimeOfUseTariff.Swisspower", "") //
.put("App.TimeOfUseTariff.Tibber", "") //
.put("App.Api.ModbusTcp.ReadOnly", "") //
.put("App.Api.ModbusTcp.ReadWrite", "") //
diff --git a/io.openems.common/src/io/openems/common/oem/OpenemsEdgeOem.java b/io.openems.common/src/io/openems/common/oem/OpenemsEdgeOem.java
index 5f768f896f3..84dd61fb86f 100644
--- a/io.openems.common/src/io/openems/common/oem/OpenemsEdgeOem.java
+++ b/io.openems.common/src/io/openems/common/oem/OpenemsEdgeOem.java
@@ -133,13 +133,4 @@ public default String getEntsoeToken() {
return null;
}
- /**
- * Gets the OEM Access-Key for Exchangerate.host (used by
- * TimeOfUseTariff.ENTSO-E).
- *
- * @return the value
- */
- public default String getExchangeRateAccesskey() {
- return null;
- }
}
diff --git a/io.openems.common/src/io/openems/common/test/AbstractComponentConfig.java b/io.openems.common/src/io/openems/common/test/AbstractComponentConfig.java
index af56ff73a01..c8f81ad2338 100644
--- a/io.openems.common/src/io/openems/common/test/AbstractComponentConfig.java
+++ b/io.openems.common/src/io/openems/common/test/AbstractComponentConfig.java
@@ -1,5 +1,7 @@
package io.openems.common.test;
+import static io.openems.common.utils.ReflectionUtils.invokeMethodViaReflection;
+
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -110,7 +112,7 @@ public Dictionary getAsProperties()
}
var key = method.getName().replace("_", ".");
- var value = method.invoke(this);
+ var value = invokeMethodViaReflection(this, method);
if (value == null) {
throw new IllegalArgumentException("Configuration for [" + key + "] is null");
}
diff --git a/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java b/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java
index fdebd1f183c..097f7c5aad9 100644
--- a/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java
+++ b/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java
@@ -1,6 +1,5 @@
package io.openems.common.timedata;
-import java.io.IOException;
import java.time.Period;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
@@ -10,46 +9,11 @@
import com.google.gson.JsonElement;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
-import io.openems.common.exceptions.OpenemsException;
import io.openems.common.jsonrpc.request.QueryHistoricTimeseriesDataRequest;
-import io.openems.common.jsonrpc.request.QueryHistoricTimeseriesExportXlxsRequest;
-import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesExportXlsxResponse;
-import io.openems.common.session.Language;
import io.openems.common.types.ChannelAddress;
public interface CommonTimedataService {
- /**
- * Handles a {@link QueryHistoricTimeseriesExportXlxsRequest}. Exports historic
- * data to an Excel file.
- *
- * @param edgeId the Edge-ID
- * @param request the {@link QueryHistoricTimeseriesExportXlxsRequest} request
- * @param language the {@link Language}
- * @return the {@link QueryHistoricTimeseriesExportXlsxResponse}
- * @throws OpenemsNamedException on error
- */
- public default QueryHistoricTimeseriesExportXlsxResponse handleQueryHistoricTimeseriesExportXlxsRequest(
- String edgeId, QueryHistoricTimeseriesExportXlxsRequest request, Language language)
- throws OpenemsNamedException {
- var powerData = this.queryHistoricData(edgeId, request.getFromDate(), request.getToDate(),
- QueryHistoricTimeseriesExportXlsxResponse.POWER_CHANNELS, new Resolution(15, ChronoUnit.MINUTES));
-
- var energyData = this.queryHistoricEnergy(edgeId, request.getFromDate(), request.getToDate(),
- QueryHistoricTimeseriesExportXlsxResponse.ENERGY_CHANNELS);
-
- if (powerData == null || energyData == null) {
- return null;
- }
-
- try {
- return new QueryHistoricTimeseriesExportXlsxResponse(request.getId(), edgeId, request.getFromDate(),
- request.getToDate(), powerData, energyData, language);
- } catch (IOException e) {
- throw new OpenemsException("QueryHistoricTimeseriesExportXlxsRequest failed: " + e.getMessage());
- }
- }
-
/**
* Calculates the time {@link Resolution} for the period.
*
diff --git a/io.openems.common/src/io/openems/common/timedata/Timeout.java b/io.openems.common/src/io/openems/common/timedata/Timeout.java
new file mode 100644
index 00000000000..bb489594998
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/timedata/Timeout.java
@@ -0,0 +1,55 @@
+package io.openems.common.timedata;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+
+public class Timeout {
+
+ private Instant entryTime = Instant.MIN;
+ private Duration timeout;
+
+ private Timeout(Duration duration) {
+ this.timeout = duration;
+ }
+
+ /**
+ * Get the {@link Timeout} of seconds.
+ *
+ * @param timeout the amount seconds
+ * @return the {@link Timeout}
+ */
+ public static Timeout ofSeconds(int timeout) {
+ return new Timeout(Duration.ofSeconds(timeout));
+ }
+
+ /**
+ * Get the {@link Timeout} of minutes.
+ *
+ * @param timeout the amount minutes
+ * @return the {@link Timeout}
+ */
+ public static Timeout ofMinutes(int timeout) {
+ return new Timeout(Duration.ofMinutes(timeout));
+ }
+
+ /**
+ * Sets the entry time.
+ *
+ * @param clock the {@link Clock}
+ */
+ public void start(Clock clock) {
+ this.entryTime = Instant.now(clock);
+ }
+
+ /**
+ * Checks the whether time elapsed.
+ *
+ * @param clock the {@link Clock}
+ * @return true if time is elapsed
+ */
+ public boolean elapsed(Clock clock) {
+ return Instant.now(clock).isAfter(this.entryTime.plus(this.timeout));
+ }
+
+}
diff --git a/io.openems.common/src/io/openems/common/timedata/XlsxExportDetailData.java b/io.openems.common/src/io/openems/common/timedata/XlsxExportDetailData.java
new file mode 100644
index 00000000000..199bc345846
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/timedata/XlsxExportDetailData.java
@@ -0,0 +1,37 @@
+package io.openems.common.timedata;
+
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry.HistoricTimedataSaveType;
+import io.openems.common.types.ChannelAddress;
+import io.openems.common.types.CurrencyConfig;
+
+public record XlsxExportDetailData(//
+ EnumMap> data, //
+ CurrencyConfig currency
+) {
+
+ public Map> getChannelsBySaveType() {
+ return this.data().values().stream().flatMap(List::stream).collect(Collectors.groupingBy(
+ XlsxExportDataEntry::type, Collectors.mapping(XlsxExportDataEntry::channel, Collectors.toList())));
+ }
+
+ public enum XlsxExportCategory {
+ CONSUMPTION, PRODUCTION, TIME_OF_USE_TARIFF
+ }
+
+ public record XlsxExportDataEntry(//
+ String alias, ChannelAddress channel, //
+ HistoricTimedataSaveType type //
+ ) {
+
+ public enum HistoricTimedataSaveType {
+ POWER, ENERGY
+ }
+
+ }
+
+}
diff --git a/io.openems.common/src/io/openems/common/timedata/XlsxExportUtil.java b/io.openems.common/src/io/openems/common/timedata/XlsxExportUtil.java
new file mode 100644
index 00000000000..0e3a815a464
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/timedata/XlsxExportUtil.java
@@ -0,0 +1,119 @@
+package io.openems.common.timedata;
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Set;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.timedata.XlsxExportDetailData.XlsxExportCategory;
+import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry;
+import io.openems.common.types.ChannelAddress;
+import io.openems.common.types.CurrencyConfig;
+import io.openems.common.types.EdgeConfig;
+import io.openems.common.types.MeterType;
+import io.openems.common.utils.JsonUtils;
+
+public class XlsxExportUtil {
+
+ /**
+ * Gathers the detail data for excel export.
+ *
+ * @param edge the edge
+ * @return the currency represented as a CurrencyConfig
+ * @throws OpenemsNamedException if component isnt found
+ */
+ private static CurrencyConfig getCurrency(EdgeConfig edge) throws OpenemsNamedException {
+ return edge.getComponent("_meta") //
+ .flatMap(t -> t.getProperty("currency")) //
+ .flatMap(t -> JsonUtils.getAsOptionalEnum(CurrencyConfig.class, t)) //
+ .orElse(CurrencyConfig.EUR);
+ }
+
+ /**
+ * Gathers the detail data for excel export.
+ *
+ * @param edgeConfig the {@link EdgeConfig}
+ * @return the {@link XlsxExportDetailData}
+ * @throws OpenemsNamedException if component is not found
+ */
+ public static XlsxExportDetailData getDetailData(EdgeConfig edgeConfig) throws OpenemsNamedException {
+ final var enumMap = new EnumMap>(XlsxExportCategory.class);
+ final var consumption = new ArrayList();
+ final var production = new ArrayList();
+ final var tou = new ArrayList();
+
+ enumMap.put(XlsxExportCategory.PRODUCTION, production);
+ enumMap.put(XlsxExportCategory.CONSUMPTION, consumption);
+ enumMap.put(XlsxExportCategory.TIME_OF_USE_TARIFF, tou);
+
+ for (var component : edgeConfig.getComponents().values()) {
+ final var factory = edgeConfig.getFactories().get(component.getFactoryId());
+ if (factory == null) {
+ continue;
+ }
+ for (var nature : factory.getNatureIds()) {
+ // Electricity meter
+ switch (nature) {
+ case Natures.METER -> {
+ final var props = component.getProperties();
+ var meterType = JsonUtils.getAsOptionalEnum(MeterType.class, props.get("type"))
+ .orElse(null);
+ if (meterType != null) {
+ var list = switch (meterType) {
+ case CONSUMPTION_METERED, CONSUMPTION_NOT_METERED, MANAGED_CONSUMPTION_METERED -> consumption;
+ case PRODUCTION -> production;
+ case GRID, PRODUCTION_AND_CONSUMPTION -> null;
+ };
+ if (list != null) {
+ list.add(new XlsxExportDataEntry(component.getAlias(),
+ new ChannelAddress(component.getId(), "ActivePower"),
+ XlsxExportDataEntry.HistoricTimedataSaveType.POWER));
+ }
+ continue;
+ }
+
+ final var activePowerType = getActivePowerType(component.getFactoryId());
+ if (activePowerType == null) {
+ continue;
+ }
+ enumMap.get(activePowerType)
+ .add(new XlsxExportDataEntry(component.getAlias(),
+ new ChannelAddress(component.getId(), "ActivePower"),
+ XlsxExportDataEntry.HistoricTimedataSaveType.POWER));
+ }
+ case Natures.TIME_OF_USE_TARIFF -> {
+ tou.add(new XlsxExportDataEntry(component.getAlias(), new ChannelAddress("_sum", "GridBuyPrice"),
+ XlsxExportDataEntry.HistoricTimedataSaveType.POWER));
+ }
+ }
+ }
+ }
+ return new XlsxExportDetailData(enumMap, XlsxExportUtil.getCurrency(edgeConfig));
+ }
+
+ private static XlsxExportCategory getActivePowerType(String factoryId) {
+ if (Natures.PRODUCTION_NATURES.contains(factoryId)) {
+ return XlsxExportCategory.PRODUCTION;
+ } else if (Natures.CONSUMPTION_NATURES.contains(factoryId)) {
+ return XlsxExportCategory.CONSUMPTION;
+ }
+ return null;
+ }
+
+ private static final class Natures {
+ public static final String METER = "io.openems.edge.meter.api.ElectricityMeter";
+ public static final String TIME_OF_USE_TARIFF = "io.openems.edge.timeofusetariff.api.TimeOfUseTariff";
+ public static final Set PRODUCTION_NATURES = Set.of("Simulator.PvInverter", "Fenecon.Dess.PvMeter",
+ "Fenecon.Mini.PvMeter", "Kaco.BlueplanetHybrid10.PvInverter", "PvInverter.Cluster",
+ "PV-Inverter.Fronius", "PV-Inverter.KACO.blueplanet", "PV-Inverter.SMA.SunnyTripower",
+ "PV-Inverter.Kostal", "PV-Inverter.Solarlog", "Simulator.ProductionMeter.Acting",
+ "SolarEdge.PV-Inverter");
+
+ public static final Set CONSUMPTION_NATURES = Set.of("GoodWe.EmergencyPowerMeter",
+ "Simulator.NRCMeter.Acting", "Evcs.AlpitronicHypercharger", "Evcs.Dezony", "Evcs.Goe.ChargerHome",
+ "Evcs.HardyBarth", "Evcs.Keba.KeContact", "Evcs.Ocpp.Abl", "Evcs.Ocpp.IesKeywattSingle",
+ "Evcs.Spelsberg.SMART", "Evcs.Webasto.Next", "Evcs.Webasto.Unite");
+ }
+
+}
diff --git a/io.openems.common/src/io/openems/common/timedata/XlsxWorksheetWrapper.java b/io.openems.common/src/io/openems/common/timedata/XlsxWorksheetWrapper.java
new file mode 100644
index 00000000000..faec99a684a
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/timedata/XlsxWorksheetWrapper.java
@@ -0,0 +1,46 @@
+package io.openems.common.timedata;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import org.dhatim.fastexcel.StyleSetter;
+import org.dhatim.fastexcel.Worksheet;
+
+public class XlsxWorksheetWrapper {
+ public record XlsxCellWrapper(int c, int r, StyleSetter style) {
+ }
+
+ private final Map> cellMap = new HashMap<>();
+ private final Worksheet ws;
+
+ public XlsxWorksheetWrapper(Worksheet ws) {
+ this.ws = ws;
+ }
+
+ /**
+ * Gets a CellWrapper if exists; otherwise creates a new one and returns that.
+ *
+ * @param r row of the cell
+ * @param c column of the cell
+ * @return the XlsxCellWrapper
+ */
+ public XlsxCellWrapper getCellWrapper(int r, int c) {
+ final var columns = this.cellMap.computeIfAbsent(r, row -> new HashMap<>());
+ return columns.computeIfAbsent(c, col -> new XlsxCellWrapper(r, c, this.ws.style(r, c)));
+ }
+
+ public void setAll() {
+ this.cellMap.values().stream().flatMap(map -> map.values().stream()).forEach(val -> val.style().set());
+ }
+
+ public void setForRange(int r1, int c1, int r2, int c2, Consumer styleSetterFunc) {
+ for (int row = r1; row <= r2; row++) {
+ for (int col = c1; col <= c2; col++) {
+ var cell = this.getCellWrapper(row, col);
+ styleSetterFunc.accept(cell);;
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/io.openems.common/src/io/openems/common/types/CurrencyConfig.java b/io.openems.common/src/io/openems/common/types/CurrencyConfig.java
new file mode 100644
index 00000000000..8c401aefda3
--- /dev/null
+++ b/io.openems.common/src/io/openems/common/types/CurrencyConfig.java
@@ -0,0 +1,49 @@
+package io.openems.common.types;
+
+import java.util.Currency;
+
+/**
+ * The {@link ChannelId#CURRENCY} mandates the selection of the 'currency'
+ * configuration property of this specific type. Subsequently, this selected
+ * property is transformed into the corresponding {@link Currency} type before
+ * being written through {@link Meta#_setCurrency(Currency)}.
+ */
+public enum CurrencyConfig {
+ /**
+ * Euro.
+ */
+ EUR("€", "Cent", 100f),
+ /**
+ * Swedish Krona.
+ */
+ SEK("kr", "Öre", 100f),
+ /**
+ * Swiss Francs.
+ */
+ CHF("Fr", "Rappen", 100f);
+
+ private final String symbol;
+
+ private final String underPart;
+
+ private final float ratio;
+
+ private CurrencyConfig(String symbol, String underPart, float ratio) {
+ this.symbol = symbol;
+ this.underPart = underPart;
+ this.ratio = ratio;
+ }
+
+ public String getSymbol() {
+ return this.symbol;
+ }
+
+ public String getUnderPart() {
+ return this.underPart;
+ }
+
+ public float getRatio() {
+ return this.ratio;
+ }
+
+}
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 1a7ad131b23..8014e47cae5 100644
--- a/io.openems.common/src/io/openems/common/types/EdgeConfig.java
+++ b/io.openems.common/src/io/openems/common/types/EdgeConfig.java
@@ -588,8 +588,7 @@ public static Component fromJson(String componentId, JsonElement json) throws Op
var jPropertiesOpt = JsonUtils.getAsOptionalJsonObject(json, "properties");
if (jPropertiesOpt.isPresent()) {
for (Entry entry : jPropertiesOpt.get().entrySet()) {
- if (!ignorePropertyKey(entry.getKey())
- && !ignoreComponentPropertyKey(componentId, entry.getKey())) {
+ if (!ignorePropertyKey(entry.getKey())) {
properties.put(entry.getKey(), entry.getValue());
}
}
@@ -1171,9 +1170,9 @@ public TreeMap getFactories() {
}
/**
- * Builds the {@link ActualEdgeConfig}.
+ * Builds the ActualEdgeConfig.
*
- * @return {@link ActualEdgeConfig}
+ * @return ActualEdgeConfig
*/
public ActualEdgeConfig build() {
return new ActualEdgeConfig(ImmutableSortedMap.copyOf(this.getComponents()),
@@ -1191,16 +1190,16 @@ public EdgeConfig buildEdgeConfig() {
}
/**
- * Creates an empty {@link ActualEdgeConfig}.
+ * Creates an empty ActualEdgeConfig.
*
- * @return {@link ActualEdgeConfig}
+ * @return ActualEdgeConfig
*/
public static ActualEdgeConfig empty() {
return ActualEdgeConfig.create().build();
}
/**
- * Create a {@link ActualEdgeConfig.Builder} builder.
+ * Create a ActualEdgeConfig builder.
*
* @return a {@link Builder}
*/
@@ -1248,9 +1247,9 @@ public static EdgeConfig fromJson(JsonObject json) {
private volatile JsonObject _json = null;
/**
- * Build from {@link ActualEdgeConfig}.
+ * Build from ActualEdgeConfig.
*
- * @param actual the {@link ActualEdgeConfig}
+ * @param actual the ActualEdgeConfig
*/
private EdgeConfig(ActualEdgeConfig actual) {
this._actual = actual;
@@ -1261,10 +1260,10 @@ private EdgeConfig(JsonObject json) {
}
/**
- * Gets the {@link ActualEdgeConfig}. Either by parsing it from {@link #json} or
- * by returning from cache.
+ * Gets the ActualEdgeConfig. Either by parsing it from {@link #json} or by
+ * returning from cache.
*
- * @return {@link ActualEdgeConfig}; empty on JSON parse error
+ * @return ActualEdgeConfig; empty on JSON parse error
*/
private synchronized ActualEdgeConfig getActual() {
if (this._actual != null) {
@@ -1465,25 +1464,4 @@ public static boolean ignorePropertyKey(String key) {
default -> false;
};
}
-
- /**
- * Internal Method to decide whether a configuration property should be ignored.
- *
- * @param componentId the Component-ID
- * @param key the property key
- * @return true if it should get ignored
- */
- public static boolean ignoreComponentPropertyKey(String componentId, String key) {
- return switch (componentId) {
- // Filter for _sum component
- case "_sum" -> switch (key) {
- case "productionMaxActivePower", "consumptionMaxActivePower", "gridMinActivePower", "gridMaxActivePower" ->
- true;
-
- default -> false;
- };
-
- default -> false;
- };
- }
}
diff --git a/io.openems.edge.meter.api/src/io/openems/edge/meter/api/MeterType.java b/io.openems.common/src/io/openems/common/types/MeterType.java
similarity index 69%
rename from io.openems.edge.meter.api/src/io/openems/edge/meter/api/MeterType.java
rename to io.openems.common/src/io/openems/common/types/MeterType.java
index 7809e7186d3..bb99d4bb8ae 100644
--- a/io.openems.edge.meter.api/src/io/openems/edge/meter/api/MeterType.java
+++ b/io.openems.common/src/io/openems/common/types/MeterType.java
@@ -1,14 +1,17 @@
-package io.openems.edge.meter.api;
+package io.openems.common.types;
/**
- * Defines the type of the Meter.
+ * Defines the type of an ElectricityMeter.
+ *
+ *
+ * See "io.openems.edge.meter.api" for details.
*/
public enum MeterType {
/**
* Defines a Grid-Meter, i.e. a meter that is measuring at the grid connection
* point (German: "Netzanschlusspunkt")
*/
- GRID, //
+ GRID,
/**
* Defines a Production-Meter, i.e. a meter that is measuring an electric
* producer like a photovoltaics installation
@@ -21,8 +24,12 @@ public enum MeterType {
*/
PRODUCTION_AND_CONSUMPTION,
/**
- * Defines a Consumption-Meter that is metered, i.e. a meter that is measuring
- * an electric consumer like a heating-element or electric car.
+ * Defines a Consumption-Meter that metered, i.e. a meter that is measuring an
+ * electric consumer like a heating-element or electric car.
+ *
+ *
+ * Select this {@link MeterType} if the device is not actively managed by
+ * OpenEMS - see {@link #MANAGED_CONSUMPTION_METERED} otherwise.
*
*
* Note: Consumption is generally calculated using the data from Grid-Meter,
@@ -31,6 +38,11 @@ public enum MeterType {
* expected to be already measured by the Grid-Meter.
*/
CONSUMPTION_METERED,
+ /**
+ * Defines a Consumption-Meter that is actively managed by OpenEMS and metered
+ * (See {@link #CONSUMPTION_METERED}).
+ */
+ MANAGED_CONSUMPTION_METERED,
/**
* Defines a Consumption-Meter that is NOT metered, i.e. a meter that is
* measuring an electric consumer like a heating-element or electric car.
diff --git a/io.openems.common/src/io/openems/common/utils/ReflectionUtils.java b/io.openems.common/src/io/openems/common/utils/ReflectionUtils.java
index fc88d47a09c..ef924855595 100644
--- a/io.openems.common/src/io/openems/common/utils/ReflectionUtils.java
+++ b/io.openems.common/src/io/openems/common/utils/ReflectionUtils.java
@@ -1,21 +1,134 @@
package io.openems.common.utils;
-import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import io.openems.common.function.ThrowingRunnable;
+import io.openems.common.function.ThrowingSupplier;
public class ReflectionUtils {
+ public static class ReflectionException extends RuntimeException {
+ private static final long serialVersionUID = -8001364348945297741L;
+
+ protected static ReflectionException from(Exception e) {
+ return new ReflectionException(e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+
+ public ReflectionException(String message) {
+ super(message);
+ }
+ }
+
private ReflectionUtils() {
// no instance needed
}
+ protected static void callGuarded(ThrowingRunnable runnable) throws ReflectionException {
+ try {
+ runnable.run();
+ } catch (Exception e) {
+ throw ReflectionException.from(e);
+ }
+ }
+
+ protected static T callGuarded(ThrowingSupplier supplier) throws ReflectionException {
+ try {
+ return supplier.get();
+ } catch (Exception e) {
+ throw ReflectionException.from(e);
+ }
+ }
+
+ /**
+ * Sets the value of a Field via Java Reflection.
+ *
+ * @param object the target object
+ * @param memberName the name the declared field
+ * @param value the value to be set
+ * @throws Exception on error
+ */
+ public static void setAttributeViaReflection(Object object, String memberName, Object value)
+ throws ReflectionException {
+ var field = getField(object.getClass(), memberName);
+ callGuarded(() -> field.set(object, value));
+ }
+
+ /**
+ * Sets the value of a static Field via Java Reflection.
+ *
+ * @param clazz the {@link Class}
+ * @param memberName the name the declared field
+ * @param value the value to be set
+ * @throws Exception on error
+ */
+ public static void setStaticAttributeViaReflection(Class> clazz, String memberName, Object value)
+ throws ReflectionException {
+ var field = getField(clazz, memberName);
+ callGuarded(() -> field.set(null, value));
+ }
+
+ /**
+ * Gets the value of a Field via Java Reflection.
+ *
+ * @param the type of the value
+ * @param object the target object
+ * @param memberName the name the declared field
+ * @return the value
+ * @throws Exception on error
+ */
@SuppressWarnings("unchecked")
- public static boolean setAttribute(Class extends T> clazz, T object, String memberName, Object value)
- throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+ public static T getValueViaReflection(Object object, String memberName) throws ReflectionException {
+ var field = getField(object.getClass(), memberName);
+ return (T) callGuarded(() -> field.get(object));
+ }
+
+ /**
+ * Invokes a {@link Method} that takes no arguments via Java Reflection.
+ *
+ * @param the type of the result
+ * @param object the target object
+ * @param memberName the name of the method
+ * @return the result of the method
+ * @throws Exception on error
+ */
+ public static T invokeMethodWithoutArgumentsViaReflection(Object object, String memberName)
+ throws ReflectionException {
+ var method = callGuarded(() -> object.getClass().getDeclaredMethod(memberName));
+ return invokeMethodViaReflection(object, method);
+ }
+
+ /**
+ * Invokes a {@link Method} via Java Reflection.
+ *
+ * @param the type of the result
+ * @param object the target object
+ * @param method the {@link Method}
+ * @param args the arguments to be set
+ * @return the result of the method
+ * @throws Exception on error
+ */
+ @SuppressWarnings("unchecked")
+ public static T invokeMethodViaReflection(Object object, Method method, Object... args)
+ throws ReflectionException {
+ method.setAccessible(true);
+ return (T) callGuarded(() -> method.invoke(object, args));
+ }
+
+ /**
+ * Gets the {@link Class#getDeclaredField(String)} in the given {@link Class} or
+ * any of its superclasses.
+ *
+ * @param clazz the given {@link Class}
+ * @param memberName the name of the declared field
+ * @return a {@link Field}
+ * @throws ReflectionException if there is no such field
+ */
+ public static Field getField(Class> clazz, String memberName) throws ReflectionException {
try {
var field = clazz.getDeclaredField(memberName);
field.setAccessible(true);
- field.set(object, value);
- return true;
+ return field;
} catch (NoSuchFieldException e) {
// Ignore.
}
@@ -23,9 +136,8 @@ public static boolean setAttribute(Class extends T> clazz, T object, Strin
// classes.
Class> parent = clazz.getSuperclass();
if (parent == null) {
- return false; // reached 'java.lang.Object'
+ throw new ReflectionException("Reached java.lang.Object");
}
- return setAttribute((Class) parent, object, memberName, value);
+ return getField(parent, memberName);
}
-
}
diff --git a/io.openems.common/src/io/openems/common/utils/XmlUtils.java b/io.openems.common/src/io/openems/common/utils/XmlUtils.java
index b921670e906..0a81ca749b8 100644
--- a/io.openems.common/src/io/openems/common/utils/XmlUtils.java
+++ b/io.openems.common/src/io/openems/common/utils/XmlUtils.java
@@ -1,5 +1,7 @@
package io.openems.common.utils;
+import java.io.IOException;
+import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -7,9 +9,15 @@
import java.util.stream.IntStream;
import java.util.stream.Stream;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
import org.w3c.dom.DOMException;
+import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
import io.openems.common.exceptions.OpenemsError;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
@@ -254,4 +262,26 @@ public static Stream stream(final Node node) {
var childNodes = node.getChildNodes();
return IntStream.range(0, childNodes.getLength()).boxed().map(childNodes::item);
}
+
+ /**
+ * Parses the provided XML string and returns the root {@link Element} of the
+ * XML document.
+ *
+ * @param xml the XML string to parse
+ * @return the root {@link Element} of the parsed XML document
+ * @throws ParserConfigurationException if a DocumentBuilder cannot be created
+ * which satisfies the configuration
+ * requested
+ * @throws SAXException if any parse errors occur while
+ * processing the XML
+ * @throws IOException if an I/O error occurs during parsing
+ */
+ public static Element getXmlRootDocument(String xml)
+ throws ParserConfigurationException, SAXException, IOException {
+ var dbFactory = DocumentBuilderFactory.newInstance();
+ var dBuilder = dbFactory.newDocumentBuilder();
+ var is = new InputSource(new StringReader(xml));
+ var doc = dBuilder.parse(is);
+ return doc.getDocumentElement();
+ }
}
diff --git a/io.openems.common/src/io/openems/common/websocket/AbstractWebsocket.java b/io.openems.common/src/io/openems/common/websocket/AbstractWebsocket.java
index c76b34dd560..f313c175896 100644
--- a/io.openems.common/src/io/openems/common/websocket/AbstractWebsocket.java
+++ b/io.openems.common/src/io/openems/common/websocket/AbstractWebsocket.java
@@ -128,11 +128,17 @@ protected final boolean sendMessage(WebSocket ws, JsonrpcMessage message) {
}
private void sendMessageFailedLog(WebSocket ws, JsonrpcMessage message) {
- this.logWarn(this.log, new StringBuilder() //
- .append("[").append(generateWsDataString(ws)) //
- .append("] Unable to send message: Connection is closed. ") //
- .append(toShortString(simplifyJsonrpcMessage(message), 100)) //
- .toString());
+ final var b = new StringBuilder();
+
+ var wsDataString = generateWsDataString(ws);
+ if (!wsDataString.isEmpty()) {
+ b.append("[").append(generateWsDataString(ws)).append("] ");
+ }
+
+ this.logWarn(this.log, //
+ b.append("Unable to send message: Connection is closed. ") //
+ .append(toShortString(simplifyJsonrpcMessage(message), 200)) //
+ .toString());
}
/**
diff --git a/io.openems.common/src/io/openems/common/websocket/DummyWebsocketServer.java b/io.openems.common/src/io/openems/common/websocket/DummyWebsocketServer.java
index b815deeb71c..f4b2b6cf916 100644
--- a/io.openems.common/src/io/openems/common/websocket/DummyWebsocketServer.java
+++ b/io.openems.common/src/io/openems/common/websocket/DummyWebsocketServer.java
@@ -93,12 +93,7 @@ private DummyWebsocketServer(DummyWebsocketServer.Builder builder) {
@Override
protected WsData createWsData(WebSocket ws) {
- return new WsData(ws) {
- @Override
- public String toString() {
- return "DummyWebsocketServer.WsData []";
- }
- };
+ return new WsData(ws);
}
@Override
diff --git a/io.openems.common/src/io/openems/common/websocket/MyDraft6455.java b/io.openems.common/src/io/openems/common/websocket/MyDraft6455.java
index 3f549c9cb49..4c4968114c6 100644
--- a/io.openems.common/src/io/openems/common/websocket/MyDraft6455.java
+++ b/io.openems.common/src/io/openems/common/websocket/MyDraft6455.java
@@ -23,6 +23,7 @@
import org.java_websocket.enums.Opcode;
import org.java_websocket.enums.ReadyState;
import org.java_websocket.enums.Role;
+import org.java_websocket.exceptions.IncompleteException;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.exceptions.InvalidFrameException;
import org.java_websocket.exceptions.InvalidHandshakeException;
diff --git a/io.openems.common/src/io/openems/common/websocket/WebsocketUtils.java b/io.openems.common/src/io/openems/common/websocket/WebsocketUtils.java
index e9f51ae5b04..cb80dc12b69 100644
--- a/io.openems.common/src/io/openems/common/websocket/WebsocketUtils.java
+++ b/io.openems.common/src/io/openems/common/websocket/WebsocketUtils.java
@@ -53,11 +53,11 @@ public static String parseRemoteIdentifier(WebSocket ws, Handshakedata handshake
}
/**
- * Gets the toString() content of the WsData attachment of the WebSocket; or
+ * Gets the toLogString() content of the WsData attachment of the WebSocket; or
* empty string if not available.
*
* @param ws the WebSocket
- * @return the {@link WsData#toString()} content
+ * @return the {@link WsData#toLogString()} content
*/
public static String generateWsDataString(WebSocket ws) {
if (ws == null) {
@@ -67,6 +67,10 @@ public static String generateWsDataString(WebSocket ws) {
if (wsData == null) {
return "";
}
- return wsData.toString();
+ var logString = wsData.toLogString();
+ if (logString == null) {
+ return "";
+ }
+ return logString;
}
}
diff --git a/io.openems.common/src/io/openems/common/websocket/WsData.java b/io.openems.common/src/io/openems/common/websocket/WsData.java
index b43aedad7f7..852b4290125 100644
--- a/io.openems.common/src/io/openems/common/websocket/WsData.java
+++ b/io.openems.common/src/io/openems/common/websocket/WsData.java
@@ -21,14 +21,14 @@
* Objects of this class are used to store additional data with websocket
* connections of WebSocketClient and WebSocketServer.
*/
-public abstract class WsData {
+public class WsData {
/**
* Holds the WebSocket.
*/
private final WebSocket websocket;
- protected WsData(WebSocket ws) {
+ public WsData(WebSocket ws) {
this.websocket = ws;
}
@@ -138,10 +138,11 @@ public void handleJsonrpcResponse(JsonrpcResponse response) throws OpenemsNamedE
}
/**
- * Provides a specific toString method.
+ * Provides a specific log string.
*
* @return a specific string for this instance
*/
- @Override
- public abstract String toString();
+ protected String toLogString() {
+ return "";
+ }
}
diff --git a/io.openems.common/src/io/openems/common/worker/AbstractWorker.java b/io.openems.common/src/io/openems/common/worker/AbstractWorker.java
index 29b371fd99b..c78b265fb2b 100644
--- a/io.openems.common/src/io/openems/common/worker/AbstractWorker.java
+++ b/io.openems.common/src/io/openems/common/worker/AbstractWorker.java
@@ -65,9 +65,7 @@ public void activate(String name) {
* false
*/
public void modified(String name, boolean initiallyTriggerNextRun) {
- if (!this.thread.isAlive() && !this.thread.isInterrupted() && !this.isStopped.get()) {
- this.startWorker(name, initiallyTriggerNextRun);
- }
+ this.startWorker(name, initiallyTriggerNextRun);
}
/**
@@ -79,12 +77,13 @@ public void modified(String name) {
this.modified(name, true);
}
- private void startWorker(String name, boolean autoTriggerNextRun) {
+ private synchronized void startWorker(String name, boolean autoTriggerNextRun) {
if (name != null) {
this.thread.setName(name);
}
- this.thread.start();
-
+ if (!this.thread.isAlive() && !this.thread.isInterrupted() && !this.isStopped.get()) {
+ this.thread.start();
+ }
if (autoTriggerNextRun) {
this.triggerNextRun();
}
diff --git a/io.openems.common/test/io/openems/common/jsonrpc/response/CreateXlxsTest.java b/io.openems.common/test/io/openems/common/jsonrpc/response/CreateXlxsTest.java
new file mode 100644
index 00000000000..f855b102bee
--- /dev/null
+++ b/io.openems.common/test/io/openems/common/jsonrpc/response/CreateXlxsTest.java
@@ -0,0 +1,248 @@
+package io.openems.common.jsonrpc.response;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.UUID;
+import java.util.function.Consumer;
+
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesExportXlsxResponse.Channel;
+import io.openems.common.session.Language;
+import io.openems.common.timedata.XlsxExportDetailData;
+import io.openems.common.timedata.XlsxExportDetailData.XlsxExportCategory;
+import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry;
+import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry.HistoricTimedataSaveType;
+import io.openems.common.types.ChannelAddress;
+import io.openems.common.types.CurrencyConfig;
+
+public class CreateXlxsTest {
+
+ private static SortedMap getMockedEnergyData() {
+ return ImmutableSortedMap.naturalOrder() //
+ .put(Channel.GRID_BUY_ACTIVE_ENERGY, new JsonPrimitive(500)) //
+ .put(Channel.GRID_SELL_ACTIVE_ENERGY, new JsonPrimitive(0)) //
+ .put(Channel.PRODUCTION_ACTIVE_ENERGY, new JsonPrimitive(300)) //
+ .put(Channel.CONSUMPTION_ACTIVE_ENERGY, new JsonPrimitive(700)) //
+ .put(Channel.ESS_DC_CHARGE_ENERGY, new JsonPrimitive(100)) //
+ .put(Channel.ESS_DC_DISCHARGE_ENERGY, new JsonPrimitive(80)) //
+ .build();
+ }
+
+ private static SortedMap> getMockedPowerData() {
+
+ SortedMap values = new TreeMap<>();
+ values.put(Channel.GRID_ACTIVE_POWER, new JsonPrimitive(50));
+ values.put(Channel.PRODUCTION_ACTIVE_POWER, new JsonPrimitive(50));
+ values.put(Channel.CONSUMPTION_ACTIVE_POWER, new JsonPrimitive(50));
+ values.put(Channel.ESS_DISCHARGE_POWER, new JsonPrimitive(50));
+ values.put(Channel.ESS_SOC, new JsonPrimitive(50));
+ values.put(new ChannelAddress("meter0", "ActivePower"), new JsonPrimitive(100));
+ values.put(new ChannelAddress("meter1", "ActivePower"), new JsonPrimitive(412));
+ values.put(new ChannelAddress("evcs0", "ChargePower"), new JsonPrimitive(75));
+ values.put(new ChannelAddress("meter2", "ActivePower"), new JsonPrimitive(10));
+ values.put(new ChannelAddress("_sum", "GridBuyPower"), new JsonPrimitive(292.5));
+
+ return ImmutableSortedMap.>naturalOrder()
+ .put(ZonedDateTime.of(2020, 07, 01, 0, 15, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) //
+ .put(ZonedDateTime.of(2020, 07, 01, 0, 30, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) //
+ .put(ZonedDateTime.of(2020, 07, 01, 0, 45, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) //
+ .put(ZonedDateTime.of(2020, 07, 01, 1, 0, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) //
+ .put(ZonedDateTime.of(2020, 07, 01, 1, 15, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) //
+ .put(ZonedDateTime.of(2020, 07, 01, 1, 30, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) //
+ .build();
+ }
+
+ private static XlsxExportDetailData getMockedDetailData() {
+ final var enumMap = new EnumMap>(XlsxExportCategory.class);
+ final var consumption = new ArrayList();
+ final var production = new ArrayList();
+ final var tou = new ArrayList();
+
+ enumMap.put(XlsxExportCategory.PRODUCTION, production);
+ enumMap.put(XlsxExportCategory.CONSUMPTION, consumption);
+ enumMap.put(XlsxExportCategory.TIME_OF_USE_TARIFF, tou);
+
+ production.add(new XlsxExportDataEntry("PV-Dach", new ChannelAddress("meter0", "ActivePower"),
+ HistoricTimedataSaveType.POWER));
+ production.add(new XlsxExportDataEntry("PV-Alm", new ChannelAddress("meter1", "ActivePower"),
+ HistoricTimedataSaveType.POWER));
+ consumption.add(new XlsxExportDataEntry("Consumption Meter", new ChannelAddress("meter2", "ActivePower"),
+ HistoricTimedataSaveType.POWER));
+ consumption.add(new XlsxExportDataEntry("Wallbox Garage", new ChannelAddress("evcs0", "ChargePower"),
+ HistoricTimedataSaveType.POWER));
+ tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"),
+ HistoricTimedataSaveType.POWER));
+
+ return new XlsxExportDetailData(enumMap, CurrencyConfig.EUR);
+ }
+
+ /**
+ * Main Method for creating a excel export with mocked data.
+ *
+ * @param args not used
+ * @throws IOException if file cant be written
+ * @throws OpenemsNamedException requests fails
+ */
+ public static void main(String[] args) throws IOException, OpenemsNamedException {
+ createFullXlsx();
+ createHalfXlsx();
+ createConsumptionOnlyXlsx();
+ createProductionOnlyXlsx();
+ createTouOnlyXlsx();
+ createProductionAndTouXlsx();
+ createConsumptionAndTouXlsx();
+ createnSingleOfAllXlsx();
+ }
+
+ private static void createFullXlsx() throws IOException, OpenemsNamedException {
+ var fromDate = ZonedDateTime.of(2020, 07, 01, 0, 0, 0, 0, ZoneId.systemDefault());
+ var toDate = ZonedDateTime.of(2020, 07, 02, 0, 0, 0, 0, ZoneId.systemDefault());
+
+ var powerData = CreateXlxsTest.getMockedPowerData();
+ var energyData = CreateXlxsTest.getMockedEnergyData();
+ var detailData = CreateXlxsTest.getMockedDetailData();
+
+ final var request = new QueryHistoricTimeseriesExportXlsxResponse(UUID.randomUUID(), "edge0", fromDate, toDate,
+ powerData, energyData, Language.EN, detailData);
+
+ var payload = request.getPayload();
+
+ byte[] excelData = Base64.getDecoder().decode(payload);
+
+ String filePath = ".\\..\\build\\fullTestPrint.xlsx";
+
+ try (FileOutputStream fos = new FileOutputStream(filePath)) {
+ fos.write(excelData);
+ System.out.println("Testfile created under: " + filePath);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static void createHalfXlsx() throws IOException, OpenemsNamedException {
+ createTestPrint(".\\..\\build\\emptyTestPrint.xlsx", null, null, null);
+ }
+
+ private static void createTestPrint(String filePath, Consumer> consProd,
+ Consumer> consCons, Consumer> consTou)
+ throws IOException, OpenemsNamedException {
+ final var enumMap = new EnumMap>(XlsxExportCategory.class);
+ final var consumption = new ArrayList();
+ final var production = new ArrayList();
+ final var tou = new ArrayList();
+
+ if (consProd != null) {
+ consProd.accept(production);
+ }
+
+ if (consCons != null) {
+ consCons.accept(consumption);
+ }
+
+ if (consTou != null) {
+ consTou.accept(tou);
+ }
+
+ enumMap.put(XlsxExportCategory.PRODUCTION, production);
+ enumMap.put(XlsxExportCategory.CONSUMPTION, consumption);
+ enumMap.put(XlsxExportCategory.TIME_OF_USE_TARIFF, tou);
+
+ var detailData = new XlsxExportDetailData(enumMap, CurrencyConfig.EUR);
+
+ var fromDate = ZonedDateTime.of(2020, 07, 01, 0, 0, 0, 0, ZoneId.systemDefault());
+ var toDate = ZonedDateTime.of(2020, 07, 02, 0, 0, 0, 0, ZoneId.systemDefault());
+
+ var powerData = CreateXlxsTest.getMockedPowerData();
+ var energyData = CreateXlxsTest.getMockedEnergyData();
+
+ final var request = new QueryHistoricTimeseriesExportXlsxResponse(UUID.randomUUID(), "edge0", fromDate, toDate,
+ powerData, energyData, Language.EN, detailData);
+
+ var payload = request.getPayload();
+
+ byte[] excelData = Base64.getDecoder().decode(payload);
+
+ try (FileOutputStream fos = new FileOutputStream(filePath)) {
+ fos.write(excelData);
+ System.out.println("Testfile created under: " + filePath);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static void createProductionOnlyXlsx() throws IOException, OpenemsNamedException {
+ createTestPrint(".\\..\\build\\prodTestPrint.xlsx", production -> {
+ production.add(new XlsxExportDataEntry("PV-Dach", new ChannelAddress("meter0", "ActivePower"),
+ HistoricTimedataSaveType.POWER));
+ production.add(new XlsxExportDataEntry("PV-Alm", new ChannelAddress("meter1", "ActivePower"),
+ HistoricTimedataSaveType.POWER));
+ }, null, null);
+ }
+
+ private static void createConsumptionOnlyXlsx() throws IOException, OpenemsNamedException {
+ createTestPrint(".\\..\\build\\consTestPrint.xlsx", null, consumption -> {
+ consumption.add(new XlsxExportDataEntry("Consumption Meter", new ChannelAddress("meter2", "ActivePower"),
+ HistoricTimedataSaveType.POWER));
+ consumption.add(new XlsxExportDataEntry("Wallbox Garage", new ChannelAddress("evcs0", "ChargePower"),
+ HistoricTimedataSaveType.POWER));
+ }, null);
+ }
+
+ private static void createTouOnlyXlsx() throws IOException, OpenemsNamedException {
+ createTestPrint(".\\..\\build\\touPrint.xlsx", null, null, tou -> {
+ tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"),
+ HistoricTimedataSaveType.POWER));
+ });
+ }
+
+ private static void createProductionAndTouXlsx() throws IOException, OpenemsNamedException {
+ createTestPrint(".\\..\\build\\prodAndTouPrint.xlsx", production -> {
+ production.add(new XlsxExportDataEntry("PV-Dach", new ChannelAddress("meter0", "ActivePower"),
+ HistoricTimedataSaveType.POWER));
+ production.add(new XlsxExportDataEntry("PV-Alm", new ChannelAddress("meter1", "ActivePower"),
+ HistoricTimedataSaveType.POWER));
+ }, null, tou -> {
+ tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"),
+ HistoricTimedataSaveType.POWER));
+ });
+
+ }
+
+ private static void createConsumptionAndTouXlsx() throws IOException, OpenemsNamedException {
+ createTestPrint(".\\..\\build\\consAndTouPrint.xlsx", null, consumption -> {
+ consumption.add(new XlsxExportDataEntry("Consumption Meter", new ChannelAddress("meter2", "ActivePower"),
+ HistoricTimedataSaveType.POWER));
+ consumption.add(new XlsxExportDataEntry("Wallbox Garage", new ChannelAddress("evcs0", "ChargePower"),
+ HistoricTimedataSaveType.POWER));
+ }, tou -> {
+ tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"),
+ HistoricTimedataSaveType.POWER));
+ });
+ }
+
+ private static void createnSingleOfAllXlsx() throws IOException, OpenemsNamedException {
+ createTestPrint(".\\..\\build\\singleOfAllPrint.xlsx", production -> {
+ production.add(new XlsxExportDataEntry("PV-Alm", new ChannelAddress("meter1", "ActivePower"),
+ HistoricTimedataSaveType.POWER));
+ }, consumption -> {
+ consumption.add(new XlsxExportDataEntry("Wallbox Garage", new ChannelAddress("evcs0", "ChargePower"),
+ HistoricTimedataSaveType.POWER));
+ }, tou -> {
+ tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"),
+ HistoricTimedataSaveType.POWER));
+ });
+
+ }
+}
diff --git a/io.openems.common/test/io/openems/common/timedata/TimeoutTest.java b/io.openems.common/test/io/openems/common/timedata/TimeoutTest.java
new file mode 100644
index 00000000000..15ef3d8b49d
--- /dev/null
+++ b/io.openems.common/test/io/openems/common/timedata/TimeoutTest.java
@@ -0,0 +1,27 @@
+package io.openems.common.timedata;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.time.temporal.ChronoUnit;
+
+import org.junit.Test;
+
+import io.openems.common.test.TimeLeapClock;
+
+public class TimeoutTest {
+
+ @Test
+ public void test() {
+ final var timeout = Timeout.ofSeconds(120);
+ final var timeLeap = new TimeLeapClock();
+ timeout.start(timeLeap);
+
+ timeLeap.leap(20, ChronoUnit.SECONDS);
+ assertFalse(timeout.elapsed(timeLeap));
+
+ timeLeap.leap(121, ChronoUnit.SECONDS);
+ assertTrue(timeout.elapsed(timeLeap));
+ }
+
+}
diff --git a/io.openems.common/test/io/openems/common/timedata/XlsxExportUtilTest.java b/io.openems.common/test/io/openems/common/timedata/XlsxExportUtilTest.java
new file mode 100644
index 00000000000..1328f3a4ff9
--- /dev/null
+++ b/io.openems.common/test/io/openems/common/timedata/XlsxExportUtilTest.java
@@ -0,0 +1,92 @@
+package io.openems.common.timedata;
+
+import static io.openems.common.utils.JsonUtils.toJson;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableSortedMap;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.timedata.XlsxExportDetailData.XlsxExportCategory;
+import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry.HistoricTimedataSaveType;
+import io.openems.common.types.EdgeConfig.ActualEdgeConfig;
+import io.openems.common.types.EdgeConfig.Component;
+import io.openems.common.types.EdgeConfig.Factory;
+import io.openems.common.types.EdgeConfig.Factory.Property;
+
+public class XlsxExportUtilTest {
+
+ @Test
+ public void testGetDetailData() throws OpenemsNamedException {
+ var edgeConfig = ActualEdgeConfig.create() //
+ .addComponent("meter0",
+ new Component("meter0", "My CONSUMPTION_METERED Meter", "Meter.Socomec.Threephase",
+ // Properties
+ ImmutableSortedMap.of("type", toJson("CONSUMPTION_METERED")),
+ // Channels
+ ImmutableSortedMap.of())) //
+ .addComponent("meter1",
+ new Component("meter1", "My CONSUMPTION_NOT_METERED Meter", "Meter.Socomec.Threephase",
+ // Properties
+ ImmutableSortedMap.of("type", toJson("CONSUMPTION_NOT_METERED")),
+ // Channels
+ ImmutableSortedMap.of())) //
+ .addComponent("meter2", new Component("meter2", "My PRODUCTION Meter", "Meter.Socomec.Threephase",
+ // Properties
+ ImmutableSortedMap.of("type", toJson("PRODUCTION")),
+ // Channels
+ ImmutableSortedMap.of())) //
+ .addComponent("meter3",
+ new Component("meter3", "My MANAGED_CONSUMPTION_METERED Meter", "Meter.Socomec.Threephase",
+ // Properties
+ ImmutableSortedMap.of("type", toJson("MANAGED_CONSUMPTION_METERED")),
+ // Channels
+ ImmutableSortedMap.of())) //
+
+ .addFactory("Meter.Socomec.Threephase",
+ new Factory("Meter.Socomec.Threephase", "My Name", "My Description", //
+ new Property[] {}, //
+ // Natures
+ new String[] { "io.openems.edge.meter.api.ElectricityMeter" })) //
+ .buildEdgeConfig();
+
+ final var result = XlsxExportUtil.getDetailData(edgeConfig);
+
+ var consumptions = result.data().get(XlsxExportCategory.CONSUMPTION);
+ assertEquals(3, consumptions.size());
+
+ {
+ var meter = consumptions.get(0);
+ assertEquals("My CONSUMPTION_METERED Meter", meter.alias());
+ assertEquals("meter0/ActivePower", meter.channel().toString());
+ assertEquals(HistoricTimedataSaveType.POWER, meter.type());
+ }
+ {
+ var meter = consumptions.get(1);
+ assertEquals("My CONSUMPTION_NOT_METERED Meter", meter.alias());
+ assertEquals("meter1/ActivePower", meter.channel().toString());
+ assertEquals(HistoricTimedataSaveType.POWER, meter.type());
+ }
+ {
+ var meter = consumptions.get(2);
+ assertEquals("My MANAGED_CONSUMPTION_METERED Meter", meter.alias());
+ assertEquals("meter3/ActivePower", meter.channel().toString());
+ assertEquals(HistoricTimedataSaveType.POWER, meter.type());
+ }
+
+ var productions = result.data().get(XlsxExportCategory.PRODUCTION);
+ assertEquals(1, productions.size());
+
+ {
+ var meter = productions.get(0);
+ assertEquals("My PRODUCTION Meter", meter.alias());
+ assertEquals("meter2/ActivePower", meter.channel().toString());
+ assertEquals(HistoricTimedataSaveType.POWER, meter.type());
+ }
+
+ var touts = result.data().get(XlsxExportCategory.TIME_OF_USE_TARIFF);
+ assertEquals(0, touts.size());
+ }
+
+}
diff --git a/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java b/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java
index 3eb4161bd07..62373bfe85a 100644
--- a/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java
+++ b/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java
@@ -9,28 +9,15 @@
public class ClientReconnectorWorkerTest {
- private static class MyWsData extends WsData {
-
- public MyWsData(WebSocket ws) {
- super(ws);
- }
-
- @Override
- public String toString() {
- return "";
- }
-
- }
-
- private static class MyWebsocketClient extends AbstractWebsocketClient {
+ private static class MyWebsocketClient extends AbstractWebsocketClient {
public MyWebsocketClient(String name, URI serverUri) {
super(name, serverUri);
}
@Override
- protected MyWsData createWsData(WebSocket ws) {
- return new MyWsData(ws);
+ protected WsData createWsData(WebSocket ws) {
+ return new WsData(ws);
}
@Override
diff --git a/io.openems.common/test/io/openems/common/websocket/DummyWebsocketServerTest.java b/io.openems.common/test/io/openems/common/websocket/DummyWebsocketServerTest.java
new file mode 100644
index 00000000000..172d3516be5
--- /dev/null
+++ b/io.openems.common/test/io/openems/common/websocket/DummyWebsocketServerTest.java
@@ -0,0 +1,15 @@
+package io.openems.common.websocket;
+
+import org.junit.Test;
+
+public class DummyWebsocketServerTest {
+
+ @Test
+ public void test() {
+ var sut = DummyWebsocketServer.create() //
+ .build();
+ sut.createWsData(null);
+ sut.stop();
+ }
+
+}
diff --git a/io.openems.common/test/io/openems/common/websocket/WebsocketUtilsTest.java b/io.openems.common/test/io/openems/common/websocket/WebsocketUtilsTest.java
new file mode 100644
index 00000000000..25febb897d4
--- /dev/null
+++ b/io.openems.common/test/io/openems/common/websocket/WebsocketUtilsTest.java
@@ -0,0 +1,15 @@
+package io.openems.common.websocket;
+
+import static io.openems.common.websocket.WebsocketUtils.generateWsDataString;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class WebsocketUtilsTest {
+
+ @Test
+ public void test() {
+ assertEquals("", generateWsDataString(null));
+ }
+
+}
diff --git a/io.openems.common/test/io/openems/common/websocket/WsDataTest.java b/io.openems.common/test/io/openems/common/websocket/WsDataTest.java
new file mode 100644
index 00000000000..5dd1a7aad2f
--- /dev/null
+++ b/io.openems.common/test/io/openems/common/websocket/WsDataTest.java
@@ -0,0 +1,17 @@
+package io.openems.common.websocket;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class WsDataTest {
+
+ @Test
+ public void test() {
+ var sut = new WsData(null);
+ assertEquals("", sut.toLogString());
+
+ sut.dispose();
+ }
+
+}
diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun
index 11442b68bce..218d2542807 100644
--- a/io.openems.edge.application/EdgeApp.bndrun
+++ b/io.openems.edge.application/EdgeApp.bndrun
@@ -74,6 +74,7 @@
bnd.identity;id='io.openems.edge.controller.ess.delaycharge',\
bnd.identity;id='io.openems.edge.controller.ess.delayedselltogrid',\
bnd.identity;id='io.openems.edge.controller.ess.emergencycapacityreserve',\
+ bnd.identity;id='io.openems.edge.controller.ess.fastfrequencyreserve',\
bnd.identity;id='io.openems.edge.controller.ess.fixactivepower',\
bnd.identity;id='io.openems.edge.controller.ess.fixstateofcharge',\
bnd.identity;id='io.openems.edge.controller.ess.gridoptimizedcharge',\
@@ -189,13 +190,14 @@
bnd.identity;id='io.openems.edge.timeofusetariff.groupe',\
bnd.identity;id='io.openems.edge.timeofusetariff.hassfurt',\
bnd.identity;id='io.openems.edge.timeofusetariff.rabotcharge',\
+ bnd.identity;id='io.openems.edge.timeofusetariff.swisspower',\
bnd.identity;id='io.openems.edge.timeofusetariff.tibber',\
-runbundles: \
Java-WebSocket;version='[1.5.4,1.5.5)',\
- bcpkix;version='[1.70.0,1.70.1)',\
- bcprov;version='[1.70.0,1.70.1)',\
- bcutil;version='[1.70.0,1.70.1)',\
+ bcpkix;version='[1.78.1,1.78.2)',\
+ bcprov;version='[1.78.1,1.78.2)',\
+ bcutil;version='[1.78.1,1.78.2)',\
com.fasterxml.aalto-xml;version='[1.3.3,1.3.4)',\
com.fazecast.jSerialComm;version='[2.10.4,2.10.5)',\
com.ghgande.j2mod;version='[3.2.1,3.2.2)',\
@@ -245,6 +247,7 @@
io.openems.edge.controller.ess.delaycharge;version=snapshot,\
io.openems.edge.controller.ess.delayedselltogrid;version=snapshot,\
io.openems.edge.controller.ess.emergencycapacityreserve;version=snapshot,\
+ io.openems.edge.controller.ess.fastfrequencyreserve;version=snapshot,\
io.openems.edge.controller.ess.fixactivepower;version=snapshot,\
io.openems.edge.controller.ess.fixstateofcharge;version=snapshot,\
io.openems.edge.controller.ess.gridoptimizedcharge;version=snapshot,\
@@ -371,6 +374,7 @@
io.openems.edge.timeofusetariff.groupe;version=snapshot,\
io.openems.edge.timeofusetariff.hassfurt;version=snapshot,\
io.openems.edge.timeofusetariff.rabotcharge;version=snapshot,\
+ io.openems.edge.timeofusetariff.swisspower;version=snapshot,\
io.openems.edge.timeofusetariff.tibber;version=snapshot,\
io.openems.oem.openems;version=snapshot,\
io.openems.shared.influxdb;version=snapshot,\
@@ -416,7 +420,7 @@
org.eclipse.jetty.io;version='[9.4.28,9.4.29)',\
org.eclipse.jetty.util;version='[9.4.28,9.4.29)',\
org.eclipse.paho.mqttv5.client;version='[1.2.5,1.2.6)',\
- org.jetbrains.kotlin.osgi-bundle;version='[2.0.20,2.0.21)',\
+ org.jetbrains.kotlin.osgi-bundle;version='[2.0.21,2.0.22)',\
org.jsoup;version='[1.18.1,1.18.2)',\
org.jsr-305;version='[3.0.2,3.0.3)',\
org.openmuc.jmbus;version='[3.3.0,3.3.1)',\
diff --git a/io.openems.edge.battery.api/test/io/openems/edge/battery/protection/BatteryProtectionTest.java b/io.openems.edge.battery.api/test/io/openems/edge/battery/protection/BatteryProtectionTest.java
index de029ad3ba5..74b3d0f4895 100644
--- a/io.openems.edge.battery.api/test/io/openems/edge/battery/protection/BatteryProtectionTest.java
+++ b/io.openems.edge.battery.api/test/io/openems/edge/battery/protection/BatteryProtectionTest.java
@@ -1,14 +1,24 @@
package io.openems.edge.battery.protection;
+import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT;
+import static io.openems.edge.battery.api.Battery.ChannelId.DISCHARGE_MAX_CURRENT;
+import static io.openems.edge.battery.api.Battery.ChannelId.MAX_CELL_TEMPERATURE;
+import static io.openems.edge.battery.api.Battery.ChannelId.MAX_CELL_VOLTAGE;
+import static io.openems.edge.battery.api.Battery.ChannelId.MIN_CELL_TEMPERATURE;
+import static io.openems.edge.battery.api.Battery.ChannelId.MIN_CELL_VOLTAGE;
+import static io.openems.edge.battery.protection.BatteryProtection.ChannelId.BP_CHARGE_BMS;
+import static io.openems.edge.battery.protection.BatteryProtection.ChannelId.BP_DISCHARGE_BMS;
+import static io.openems.edge.common.startstop.StartStoppable.ChannelId.START_STOP;
+import static java.time.temporal.ChronoUnit.MINUTES;
+import static java.time.temporal.ChronoUnit.SECONDS;
+
import java.time.Instant;
import java.time.ZoneOffset;
-import java.time.temporal.ChronoUnit;
import org.junit.Test;
import io.openems.common.channel.Unit;
import io.openems.common.test.TimeLeapClock;
-import io.openems.common.types.ChannelAddress;
import io.openems.common.types.OpenemsType;
import io.openems.edge.battery.protection.currenthandler.ChargeMaxCurrentHandler;
import io.openems.edge.battery.protection.currenthandler.DischargeMaxCurrentHandler;
@@ -18,7 +28,6 @@
import io.openems.edge.common.channel.Doc;
import io.openems.edge.common.linecharacteristic.PolyLine;
import io.openems.edge.common.startstop.StartStop;
-import io.openems.edge.common.startstop.StartStoppable;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.common.test.DummyComponentManager;
@@ -102,22 +111,6 @@ public Doc doc() {
private static final String BATTERY_ID = "battery0";
- private static final ChannelAddress BATTERY_START_STOP = new ChannelAddress(BATTERY_ID,
- StartStoppable.ChannelId.START_STOP.id());
- private static final ChannelAddress BATTERY_BP_CHARGE_BMS = new ChannelAddress(BATTERY_ID,
- BatteryProtection.ChannelId.BP_CHARGE_BMS.id());
- private static final ChannelAddress BATTERY_BP_DISCHARGE_BMS = new ChannelAddress(BATTERY_ID,
- BatteryProtection.ChannelId.BP_DISCHARGE_BMS.id());
- private static final ChannelAddress BATTERY_MIN_CELL_VOLTAGE = new ChannelAddress(BATTERY_ID, "MinCellVoltage");
- private static final ChannelAddress BATTERY_MAX_CELL_VOLTAGE = new ChannelAddress(BATTERY_ID, "MaxCellVoltage");
- private static final ChannelAddress BATTERY_MIN_CELL_TEMPERATURE = new ChannelAddress(BATTERY_ID,
- "MinCellTemperature");
- private static final ChannelAddress BATTERY_MAX_CELL_TEMPERATURE = new ChannelAddress(BATTERY_ID,
- "MaxCellTemperature");
- private static final ChannelAddress BATTERY_CHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, "ChargeMaxCurrent");
- private static final ChannelAddress BATTERY_DISCHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID,
- "DischargeMaxCurrent");
-
@Test
public void test() throws Exception {
final var battery = new DummyBattery(BATTERY_ID);
@@ -137,169 +130,168 @@ public void test() throws Exception {
.setForceCharge(FORCE_CHARGE) //
.build()) //
.build();
- new ComponentTest(new DummyBattery(BATTERY_ID)) //
- .addComponent(battery) //
+ new ComponentTest(battery) //
.next(new TestCase() //
- .input(BATTERY_START_STOP, StartStop.START) //
- .input(BATTERY_BP_CHARGE_BMS, 80) //
- .input(BATTERY_BP_DISCHARGE_BMS, 80) //
- .input(BATTERY_MIN_CELL_VOLTAGE, 2950) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3300) //
- .input(BATTERY_MIN_CELL_TEMPERATURE, 16) //
- .input(BATTERY_MAX_CELL_TEMPERATURE, 17) //
+ .input(START_STOP, StartStop.START) //
+ .input(BP_CHARGE_BMS, 80) //
+ .input(BP_DISCHARGE_BMS, 80) //
+ .input(MIN_CELL_VOLTAGE, 2950) //
+ .input(MAX_CELL_VOLTAGE, 3300) //
+ .input(MIN_CELL_TEMPERATURE, 16) //
+ .input(MAX_CELL_TEMPERATURE, 17) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 0) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 0)) //
+ .output(CHARGE_MAX_CURRENT, 0) //
+ .output(DISCHARGE_MAX_CURRENT, 0)) //
.next(new TestCase("open, but maxIncreaseAmpereLimit") //
- .timeleap(clock, 2, ChronoUnit.SECONDS) //
- .input(BATTERY_MIN_CELL_VOLTAGE, 3000) //
+ .timeleap(clock, 2, SECONDS) //
+ .input(MIN_CELL_VOLTAGE, 3000) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 1) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 1)) //
+ .output(CHARGE_MAX_CURRENT, 1) //
+ .output(DISCHARGE_MAX_CURRENT, 1)) //
.next(new TestCase() //
- .timeleap(clock, 2, ChronoUnit.SECONDS) //
- .input(BATTERY_MIN_CELL_VOLTAGE, 3050) //
+ .timeleap(clock, 2, SECONDS) //
+ .input(MIN_CELL_VOLTAGE, 3050) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 2) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 2)) //
+ .output(CHARGE_MAX_CURRENT, 2) //
+ .output(DISCHARGE_MAX_CURRENT, 2)) //
.next(new TestCase() //
- .timeleap(clock, 10, ChronoUnit.SECONDS) //
+ .timeleap(clock, 10, SECONDS) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 7) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 7)) //
+ .output(CHARGE_MAX_CURRENT, 7) //
+ .output(DISCHARGE_MAX_CURRENT, 7)) //
.next(new TestCase() //
- .timeleap(clock, 10, ChronoUnit.MINUTES) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3300) //
+ .timeleap(clock, 10, MINUTES) //
+ .input(MAX_CELL_VOLTAGE, 3300) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 80) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, 80) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase() //
- .timeleap(clock, 10, ChronoUnit.MINUTES) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3499) //
+ .timeleap(clock, 10, MINUTES) //
+ .input(MAX_CELL_VOLTAGE, 3499) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 54) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, 54) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase() //
- .timeleap(clock, 10, ChronoUnit.MINUTES) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3649) //
+ .timeleap(clock, 10, MINUTES) //
+ .input(MAX_CELL_VOLTAGE, 3649) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 2) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, 2) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase() //
- .timeleap(clock, 10, ChronoUnit.MINUTES) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3649) //
+ .timeleap(clock, 10, MINUTES) //
+ .input(MAX_CELL_VOLTAGE, 3649) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 2) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, 2) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase() //
- .timeleap(clock, 10, ChronoUnit.MINUTES) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3650) //
+ .timeleap(clock, 10, MINUTES) //
+ .input(MAX_CELL_VOLTAGE, 3650) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 0) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, 0) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Start Force-Discharge: wait 60 seconds") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3660) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3660) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 0) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, 0) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Start Force-Discharge") //
- .timeleap(clock, 60, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3660) //
+ .timeleap(clock, 60, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3660) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, -2) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, -2) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Force-Discharge") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3640) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3640) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, -2) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, -2) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Block Charge #1") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3639) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3639) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, -1) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, -1) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Block Charge #1 still reduce by 1") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3638) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3638) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, -1) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, -1) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Block Charge #1 still reduce by 1") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3610) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3610) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 0) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, 0) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Block Charge #2") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3600) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3600) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 0) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, 0) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Start Force-Discharge again") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3660) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3660) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, -2) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, -2) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Force-Discharge") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3640) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3640) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, -2) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, -2) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Block Charge #1") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3639) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3639) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, -1) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, -1) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Block Charge #1 still reduce by 1") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3638) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3638) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, -1) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, -1) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Block Charge #1 still reduce by 1") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3637) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3637) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 0) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, 0) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Block Charge #2") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3600) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3600) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 0) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, 0) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Block Charge #3") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3450) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3450) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 0) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, 0) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Finish Force-Discharge") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3449) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3449) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 0) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, 0) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase() //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3400) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3400) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 0) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, 0) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
.next(new TestCase("Allow Charge") //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(BATTERY_MAX_CELL_VOLTAGE, 3350) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(MAX_CELL_VOLTAGE, 3350) //
.onAfterProcessImage(() -> sut.apply()) //
- .output(BATTERY_CHARGE_MAX_CURRENT, 1) //
- .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) //
+ .output(CHARGE_MAX_CURRENT, 1) //
+ .output(DISCHARGE_MAX_CURRENT, 80)) //
;
}
diff --git a/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/BmwBatteryImplTest.java b/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/BmwBatteryImplTest.java
index fd5aa8847db..8ded6b2514c 100644
--- a/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/BmwBatteryImplTest.java
+++ b/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/BmwBatteryImplTest.java
@@ -1,33 +1,33 @@
package io.openems.edge.battery.bmw;
+import static io.openems.edge.battery.bmw.enums.BatteryState.DEFAULT;
+
import org.junit.Test;
-import io.openems.edge.battery.bmw.enums.BatteryState;
import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
+import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.common.test.DummyConfigurationAdmin;
public class BmwBatteryImplTest {
- private static final String BATTERY_ID = "battery0";
- private static final String MODBUS_ID = "modbus0";
-
@Test
public void test() throws Exception {
new ComponentTest(new BmwBatteryImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
- .setBatteryState(BatteryState.DEFAULT) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
+ .setBatteryState(DEFAULT) //
.setErrorDelay(0) //
.setMaxStartAttempts(0) //
.setMaxStartTime(0) //
.setPendingTolerance(0) //
.setStartUnsuccessfulDelay(0) //
.build()) //
- ;
+ .next(new TestCase()) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130.java b/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130.java
index 2dedfc9f26a..72d31daa444 100644
--- a/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130.java
+++ b/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130.java
@@ -787,27 +787,27 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
LEVEL1_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) //
.text("Cluster 2 Charge Current High Alarm Level 2")), //
- LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.WARNING) //
.text("Cluster 3 Cell Voltage High Alarm Level 3")), //
- LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.FAULT) //
+ LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.WARNING) //
.text("Cluster 3 Cell Voltage Low Alarm Level 3")), //
- LEVEL2_CELL_VOLTAGE_DIFF_TOO_BIG(Doc.of(Level.FAULT) //
+ LEVEL2_CELL_VOLTAGE_DIFF_TOO_BIG(Doc.of(Level.WARNING) //
.text("Alarm Level 3 Battery Cells Unbalanced")), //
- LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.WARNING) //
.text("Cluster 3 Cell Discharge Temperature High Alarm Level 3")), //
- LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.FAULT) //
+ LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.WARNING) //
.text("Cluster 3 Cell Discharge Temperature Low Alarm Level 3")), //
- LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.WARNING) //
.text("Cluster 3 Cell Charge Temperature High Alarm Level 3")), //
- LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.FAULT) //
+ LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.WARNING) //
.text("Cluster 3 Cell Charge Temperature Low Alarm Level 3")), //
- LEVEL2_TEMP_DIFF_TOO_BIG(Doc.of(Level.FAULT) //
+ LEVEL2_TEMP_DIFF_TOO_BIG(Doc.of(Level.WARNING) //
.text("Cluster 3 Cell Temperature Diff High Alarm Level 3")), //
- LEVEL2_POWER_POLE_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_POWER_POLE_HIGH(Doc.of(Level.WARNING) //
.text("Cluster 3 Cell Temperature High Alarm Level 3")), //
- LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) //
.text("Cluster 3 Discharge Current High Alarm Level 3")), //
- LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) //
.text("Cluster 3 Charge Current High Alarm Level 3")), //
ALARM_LEVEL_1_TOTAL_VOLTAGE_DIFF_HIGH(Doc.of(Level.WARNING) //
@@ -822,69 +822,69 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
.text("Cluster 1 Total Voltage Low Alarm Level 1")), //
ALARM_LEVEL_1_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) //
.text("Cluster 1 Total Voltage High Alarm Level 1")), //
- ALARM_FUSE(Doc.of(Level.FAULT) //
+ ALARM_FUSE(Doc.of(Level.WARNING) //
.text(" Fuse Alarm")), //
SHIELDED_SWITCH_STATE(Doc.of(Level.WARNING) //
.text("Shielded switch state")), //
ALARM_BAU_COMMUNICATION(Doc.of(Level.WARNING) //
.text("BAU Communication Alarm")), //
- ALARM_INSULATION_CHECK(Doc.of(Level.FAULT) //
+ ALARM_INSULATION_CHECK(Doc.of(Level.WARNING) //
.text("Inuslation Resistance Alarm")), //
ALARM_CURRENT_SENSOR(Doc.of(Level.WARNING) //
.text("Current Sensor Alarm")), //
ALARM_BCU_BMU_COMMUNICATION(Doc.of(Level.WARNING) //
.text("BCU BMU Communication Alarm")), //
- ALARM_CONTACTOR_ADHESION(Doc.of(Level.FAULT)//
+ ALARM_CONTACTOR_ADHESION(Doc.of(Level.WARNING)//
.text("Contactor Adhesion Alarm ")), //
ALARM_BCU_NTC(Doc.of(Level.WARNING) //
.text("BCU NTC Alarm")), //
ALARM_SLAVE_CONTROL_SUMMARY(Doc.of(Level.WARNING) //
.text("Slave Control Summary Alarm")), //
- FAILURE_INITIALIZATION(Doc.of(Level.FAULT) //
+ FAILURE_INITIALIZATION(Doc.of(Level.WARNING) //
.text("Initialization failure")), //
- FAILURE_EEPROM(Doc.of(Level.FAULT) //
+ FAILURE_EEPROM(Doc.of(Level.WARNING) //
.text("EEPROM fault")), //
- FAILURE_EEPROM2(Doc.of(Level.FAULT) //
+ FAILURE_EEPROM2(Doc.of(Level.WARNING) //
.text("EEPROM2 fault")), //
- FAILURE_INTRANET_COMMUNICATION(Doc.of(Level.FAULT) //
+ FAILURE_INTRANET_COMMUNICATION(Doc.of(Level.WARNING) //
.text("Intranet communication fault")), //
- FAILURE_TEMP_SAMPLING_LINE(Doc.of(Level.FAULT) //
+ FAILURE_TEMP_SAMPLING_LINE(Doc.of(Level.WARNING) //
.text("Temperature sampling line fault")), //
- FAILURE_BALANCING_MODULE(Doc.of(Level.FAULT) //
+ FAILURE_BALANCING_MODULE(Doc.of(Level.WARNING) //
.text("Balancing module fault")), //
- FAILURE_TEMP_SENSOR(Doc.of(Level.FAULT) //
+ FAILURE_TEMP_SENSOR(Doc.of(Level.WARNING) //
.text("Temperature sensor fault")), //
- FAILURE_TEMP_SAMPLING(Doc.of(Level.FAULT) //
+ FAILURE_TEMP_SAMPLING(Doc.of(Level.WARNING) //
.text("Temperature sampling fault")), //
- FAILURE_VOLTAGE_SAMPLING(Doc.of(Level.FAULT) //
+ FAILURE_VOLTAGE_SAMPLING(Doc.of(Level.WARNING) //
.text("Voltage sampling fault")), //
- FAILURE_VOLTAGE_SAMPLING_LINE(Doc.of(Level.FAULT) //
+ FAILURE_VOLTAGE_SAMPLING_LINE(Doc.of(Level.WARNING) //
.text("Voltage sampling Line fault")), //
- FAILURE_SLAVE_UNIT_INITIALIZATION(Doc.of(Level.FAULT) //
+ FAILURE_SLAVE_UNIT_INITIALIZATION(Doc.of(Level.WARNING) //
.text("Failure Slave Unit Initialization")),
- FAILURE_CONNECTING_LINE(Doc.of(Level.FAULT) //
+ FAILURE_CONNECTING_LINE(Doc.of(Level.WARNING) //
.text("Connecting Line Failure")), //
- FAILURE_SAMPLING_CHIP(Doc.of(Level.FAULT) //
+ FAILURE_SAMPLING_CHIP(Doc.of(Level.WARNING) //
.text("Sampling Chip Failure")), //
- FAILURE_CONTACTOR(Doc.of(Level.FAULT) //
+ FAILURE_CONTACTOR(Doc.of(Level.WARNING) //
.text("Contactor Failure")), //
- FAILURE_PASSIVE_BALANCE(Doc.of(Level.FAULT) //
+ FAILURE_PASSIVE_BALANCE(Doc.of(Level.WARNING) //
.text("Passive Balance Failure")), //
- FAILURE_PASSIVE_BALANCE_TEMP(Doc.of(Level.FAULT) //
+ FAILURE_PASSIVE_BALANCE_TEMP(Doc.of(Level.WARNING) //
.text("Passive Balance Temp Failure")), //
- FAILURE_ACTIVE_BALANCE(Doc.of(Level.FAULT) //
+ FAILURE_ACTIVE_BALANCE(Doc.of(Level.WARNING) //
.text("Active Balance Failure")), //
- FAILURE_LTC6803(Doc.of(Level.FAULT) //
+ FAILURE_LTC6803(Doc.of(Level.WARNING) //
.text("LTC6803 sfault")), //
- FAILURE_CONNECTOR_WIRE(Doc.of(Level.FAULT) //
+ FAILURE_CONNECTOR_WIRE(Doc.of(Level.WARNING) //
.text("connector wire fault")), //
- FAILURE_SAMPLING_WIRE(Doc.of(Level.FAULT) //
+ FAILURE_SAMPLING_WIRE(Doc.of(Level.WARNING) //
.text("sampling wire fault")), //
- PRECHARGE_TAKING_TOO_LONG(Doc.of(Level.FAULT) //
+ PRECHARGE_TAKING_TOO_LONG(Doc.of(Level.WARNING) //
.text("precharge time was too long")), //
NEED_CHARGE(Doc.of(Level.WARNING) //
.text("Battery Need Charge")), //
- FAULT(Doc.of(Level.FAULT) //
+ FAULT(Doc.of(Level.WARNING) //
.text("battery fault state")), //
STATE_MACHINE(Doc.of(State.values()) //
.text("Current State of State-Machine")), //
@@ -928,76 +928,76 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
.text("ALARM LEVEL 2 SOH LOWER")), //
LEVEL1_PACK_TEMP_HIGH(Doc.of(Level.WARNING) //
.text("ALARM LEVEL 2 PACK TEMP HIGH")), //
- LEVEL2_SYSTEM_VOLTAGE_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_SYSTEM_VOLTAGE_HIGH(Doc.of(Level.WARNING) //
.text("ALARM LEVEL 3 SYSTEM VOLTAGE HIGH")), //
- LEVEL2_SYSTEM_VOLTAGE_LOW(Doc.of(Level.FAULT) //
+ LEVEL2_SYSTEM_VOLTAGE_LOW(Doc.of(Level.WARNING) //
.text("ALARM LEVEL 3 SYSTEM VOLTAGE LOW")), //
- LEVEL2_SYSTEM_VOLTAGE_UNBALANCED(Doc.of(Level.FAULT) //
+ LEVEL2_SYSTEM_VOLTAGE_UNBALANCED(Doc.of(Level.WARNING) //
.text("ALARM LEVEL 3 SYSTEM VOLTAGE UNBALANCED")), //
- LEVEL2_INSULATION_RESISTANCE_LOWER(Doc.of(Level.FAULT) //
+ LEVEL2_INSULATION_RESISTANCE_LOWER(Doc.of(Level.WARNING) //
.text("ALARM LEVEL 3 INSULATION RESISTANCE LOWER")), //
- LEVEL2_POS_INSULATION_RESISTANCE_LOWER(Doc.of(Level.FAULT) //
+ LEVEL2_POS_INSULATION_RESISTANCE_LOWER(Doc.of(Level.WARNING) //
.text("ALARM LEVEL 3 POS INSULATION RESISTANCE LOWER")), //
- LEVEL2_NEG_INSULATION_RESISTANCE_LOWER(Doc.of(Level.FAULT) //
+ LEVEL2_NEG_INSULATION_RESISTANCE_LOWER(Doc.of(Level.WARNING) //
.text("ALARM LEVEL 3 NEG INSULATION RESISTANCE LOWER")), //
- LEVEL2_SYSTEM_SOC_LOWER(Doc.of(Level.FAULT) //
+ LEVEL2_SYSTEM_SOC_LOWER(Doc.of(Level.WARNING) //
.text("ALARM LEVEL 3 SYSTEM SOC LOWER")), //
- LEVEL2_SYSTEM_SOC_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_SYSTEM_SOC_HIGH(Doc.of(Level.WARNING) //
.text("ALARM LEVEL 3 SYSTEM SOC HIGH")), //
LEVEL2_SOH_LOWER(Doc.of(Level.WARNING) //
.text("ALARM LEVEL 3 SOH LOWER")), //
- LEVEL2_PACK_TEMP_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_PACK_TEMP_HIGH(Doc.of(Level.WARNING) //
.text("ALARM LEVEL 3 PACK TEMP HIGH")), //
- SLAVE_11_COMMUNICATION_ERROR(Doc.of(Level.FAULT)//
+ SLAVE_11_COMMUNICATION_ERROR(Doc.of(Level.WARNING)//
.text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_11")), //
- SLAVE_12_COMMUNICATION_ERROR(Doc.of(Level.FAULT)//
+ SLAVE_12_COMMUNICATION_ERROR(Doc.of(Level.WARNING)//
.text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_12")), //
- SLAVE_13_COMMUNICATION_ERROR(Doc.of(Level.FAULT)//
+ SLAVE_13_COMMUNICATION_ERROR(Doc.of(Level.WARNING)//
.text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_13")), //
- SLAVE_14_COMMUNICATION_ERROR(Doc.of(Level.FAULT)//
+ SLAVE_14_COMMUNICATION_ERROR(Doc.of(Level.WARNING)//
.text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_14")), //
- SLAVE_15_COMMUNICATION_ERROR(Doc.of(Level.FAULT)//
+ SLAVE_15_COMMUNICATION_ERROR(Doc.of(Level.WARNING)//
.text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_15")), //
- SLAVE_16_COMMUNICATION_ERROR(Doc.of(Level.FAULT)//
+ SLAVE_16_COMMUNICATION_ERROR(Doc.of(Level.WARNING)//
.text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_16")), //
- SLAVE_17_COMMUNICATION_ERROR(Doc.of(Level.FAULT)//
+ SLAVE_17_COMMUNICATION_ERROR(Doc.of(Level.WARNING)//
.text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_17")), //
- SLAVE_18_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_18_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_18")), //
- SLAVE_19_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_19_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_19")), //
- SLAVE_20_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_20_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_20")), //
- SLAVE_21_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_21_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_21")), //
- SLAVE_22_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_22_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_22")), //
- SLAVE_23_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_23_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_23")), //
- SLAVE_24_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_24_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_24")), //
- SLAVE_25_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_25_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_25")), //
- SLAVE_26_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_26_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_26")), //
- SLAVE_27_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_27_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_27")), //
- SLAVE_28_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_28_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_28")), //
- SLAVE_29_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_29_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_29")), //
- SLAVE_30_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_30_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_30")), //
- SLAVE_31_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_31_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_31")), //
- SLAVE_32_COMMUNICATION_ERROR(Doc.of(Level.FAULT) //
+ SLAVE_32_COMMUNICATION_ERROR(Doc.of(Level.WARNING) //
.text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_31")), //
// OpenEMS Faults
- RUN_FAILED(Doc.of(Level.FAULT) //
+ RUN_FAILED(Doc.of(Level.WARNING) //
.text("Running the Logic failed")), //
- MAX_START_ATTEMPTS(Doc.of(Level.FAULT) //
+ MAX_START_ATTEMPTS(Doc.of(Level.WARNING) //
.text("The maximum number of start attempts failed")), //
- MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) //
+ MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) //
.text("The maximum number of stop attempts failed")), //
;
diff --git a/io.openems.edge.battery.bydcommercial/test/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130ImplTest.java b/io.openems.edge.battery.bydcommercial/test/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130ImplTest.java
index 91d0b85e1c1..1b87571c888 100644
--- a/io.openems.edge.battery.bydcommercial/test/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130ImplTest.java
+++ b/io.openems.edge.battery.bydcommercial/test/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130ImplTest.java
@@ -1,28 +1,30 @@
package io.openems.edge.battery.bydcommercial;
+import static io.openems.edge.common.startstop.StartStopConfig.AUTO;
+
import org.junit.Test;
import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
-import io.openems.edge.common.startstop.StartStopConfig;
+import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
+import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.common.test.DummyConfigurationAdmin;
public class BydBatteryBoxCommercialC130ImplTest {
- private static final String BATTERY_ID = "battery0";
- private static final String MODBUS_ID = "modbus0";
-
@Test
public void test() throws Exception {
new ComponentTest(new BydBatteryBoxCommercialC130Impl()) //
+ .addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
- .setStartStop(StartStopConfig.AUTO) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
+ .setStartStop(AUTO) //
.build()) //
- ;
+ .next(new TestCase()) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImplTest.java b/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImplTest.java
index 1226ccc9ffc..8fec9839f16 100644
--- a/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImplTest.java
+++ b/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImplTest.java
@@ -1,17 +1,20 @@
package io.openems.edge.battery.fenecon.commercial;
-import java.time.Instant;
-import java.time.ZoneOffset;
+import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT;
+import static io.openems.edge.battery.api.Battery.ChannelId.DISCHARGE_MAX_CURRENT;
+import static io.openems.edge.battery.api.Battery.ChannelId.SOC;
+import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.BATTERY_SOC;
+import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.RUNNING;
+import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.STATE_MACHINE;
+import static io.openems.edge.common.startstop.StartStoppable.ChannelId.START_STOP;
+import static io.openems.edge.common.test.TestUtils.createDummyClock;
+import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT7;
import org.junit.Test;
-import io.openems.common.test.TimeLeapClock;
-import io.openems.common.types.ChannelAddress;
-import io.openems.edge.battery.api.Battery;
import io.openems.edge.battery.fenecon.commercial.statemachine.StateMachine;
import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
import io.openems.edge.common.startstop.StartStopConfig;
-import io.openems.edge.common.startstop.StartStoppable;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.common.test.DummyComponentManager;
@@ -20,42 +23,23 @@
public class BatteryFeneconCommercialImplTest {
- private static final String BATTERY_ID = "battery0";
- private static final String MODBUS_ID = "modbus0";
- private static final String IO_ID = "io0";
-
- private static final ChannelAddress STATE_MACHINE = new ChannelAddress(BATTERY_ID,
- BatteryFeneconCommercial.ChannelId.STATE_MACHINE.id());
- private static final ChannelAddress RUNNING = new ChannelAddress(BATTERY_ID,
- BatteryFeneconCommercial.ChannelId.RUNNING.id());
- private static final ChannelAddress BATTERY_RELAY = new ChannelAddress(IO_ID, "InputOutput7");
- private static final ChannelAddress START_STOP = new ChannelAddress(BATTERY_ID,
- StartStoppable.ChannelId.START_STOP.id());
- private static final ChannelAddress BATTERY_SOC = new ChannelAddress(BATTERY_ID,
- BatteryFeneconCommercial.ChannelId.BATTERY_SOC.id());
- private static final ChannelAddress BATTERY_MAX_DISCHARGE_CURRENT = new ChannelAddress(BATTERY_ID,
- Battery.ChannelId.DISCHARGE_MAX_CURRENT.id());
- private static final ChannelAddress BATTERY_MAX_CHARGE_CURRENT = new ChannelAddress(BATTERY_ID,
- Battery.ChannelId.CHARGE_MAX_CURRENT.id());
- private static final ChannelAddress SOC = new ChannelAddress(BATTERY_ID, Battery.ChannelId.SOC.id());
-
@Test
public void startBattery() throws Exception {
- final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC);
+ final var clock = createDummyClock();
new ComponentTest(new BatteryFeneconCommercialImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
- .addComponent(new DummyInputOutput(IO_ID))//
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
+ .addComponent(new DummyInputOutput("io0"))//
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(1) //
.setStartStop(StartStopConfig.START) //
.setBatteryStartStopRelay("io0/InputOutput7")//
.build())//
.next(new TestCase("Battery Relay false, starting") //
- .input(BATTERY_RELAY, false)//
+ .input("io0", INPUT_OUTPUT7, false)//
.input(RUNNING, false)// Switched Off
.output(STATE_MACHINE, StateMachine.State.UNDEFINED))//
.next(new TestCase() //
@@ -63,18 +47,18 @@ public void startBattery() throws Exception {
.next(new TestCase()//
.output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
.next(new TestCase()//
- .input(BATTERY_RELAY, true))//
+ .input("io0", INPUT_OUTPUT7, true))//
.next(new TestCase() //
.output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
.next(new TestCase()//
.input(RUNNING, true)//
- .input(BATTERY_RELAY, false))//
+ .input("io0", INPUT_OUTPUT7, false))//
.next(new TestCase() //
.output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
.next(new TestCase("Battery Running")//
.output(STATE_MACHINE, StateMachine.State.RUNNING))//
.next(new TestCase("Battery Running")//
- .output(BATTERY_RELAY, false)//
+ .output("io0", INPUT_OUTPUT7, false)//
.output(RUNNING, true) //
.output(STATE_MACHINE, StateMachine.State.RUNNING))//
@@ -83,21 +67,21 @@ public void startBattery() throws Exception {
@Test
public void stopBattery() throws Exception {
- final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC);
+ final var clock = createDummyClock();
new ComponentTest(new BatteryFeneconCommercialImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
- .addComponent(new DummyInputOutput(IO_ID))//
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
+ .addComponent(new DummyInputOutput("io0"))//
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(1) //
.setStartStop(StartStopConfig.STOP) //
.setBatteryStartStopRelay("io0/InputOutput7")//
.build())//
.next(new TestCase("Battery Running")//
- .input(BATTERY_RELAY, false)//
+ .input("io0", INPUT_OUTPUT7, false)//
.input(RUNNING, true) //
.input(STATE_MACHINE, StateMachine.State.RUNNING))//
.next(new TestCase("Stopping") //
@@ -105,7 +89,7 @@ public void stopBattery() throws Exception {
.next(new TestCase()//
.output(STATE_MACHINE, StateMachine.State.GO_STOPPED))//
.next(new TestCase()//
- .input(BATTERY_RELAY, true)) //
+ .input("io0", INPUT_OUTPUT7, true)) //
.next(new TestCase()//
.output(STATE_MACHINE, StateMachine.State.STOPPED))//
@@ -114,15 +98,15 @@ public void stopBattery() throws Exception {
@Test
public void socManipulationMin() throws Exception {
- final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC);
+ final var clock = createDummyClock();
new ComponentTest(new BatteryFeneconCommercialImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
- .addComponent(new DummyInputOutput(IO_ID))//
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
+ .addComponent(new DummyInputOutput("io0"))//
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(1) //
.setStartStop(StartStopConfig.START) //
.setBatteryStartStopRelay("io0/InputOutput7")//
@@ -130,22 +114,22 @@ public void socManipulationMin() throws Exception {
.next(new TestCase("Soc")//
.input(RUNNING, true) //
.input(BATTERY_SOC, 10) //
- .input(BATTERY_MAX_DISCHARGE_CURRENT, 0) //
- .input(BATTERY_MAX_CHARGE_CURRENT, 10000) //
+ .input(DISCHARGE_MAX_CURRENT, 0) //
+ .input(CHARGE_MAX_CURRENT, 10000) //
.output(SOC, 10));
}
@Test
public void socManipulationMax() throws Exception {
- final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC);
+ final var clock = createDummyClock();
new ComponentTest(new BatteryFeneconCommercialImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
- .addComponent(new DummyInputOutput(IO_ID))//
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
+ .addComponent(new DummyInputOutput("io0"))//
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(1) //
.setStartStop(StartStopConfig.START) //
.setBatteryStartStopRelay("io0/InputOutput7")//
@@ -153,8 +137,8 @@ public void socManipulationMax() throws Exception {
.next(new TestCase("Soc")//
.input(RUNNING, true) //
.input(BATTERY_SOC, 98) //
- .input(BATTERY_MAX_DISCHARGE_CURRENT, 100000) //
- .input(BATTERY_MAX_CHARGE_CURRENT, 0) //
+ .input(DISCHARGE_MAX_CURRENT, 100000) //
+ .input(CHARGE_MAX_CURRENT, 0) //
.output(SOC, 100));
}
}
diff --git a/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java b/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java
index b28b1513542..a9db6249dc3 100644
--- a/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java
+++ b/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java
@@ -1,10 +1,14 @@
package io.openems.edge.battery.fenecon.commercial;
+import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.MASTER_MCU_HARDWARE_VERSION;
+import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.NUMBER_OF_CELLS_PER_MODULE;
+import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.NUMBER_OF_MODULES_PER_TOWER;
+import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.NUMBER_OF_TOWERS;
+import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercialImpl.VERSION_CONVERTER;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.battery.api.Battery;
import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
import io.openems.edge.common.startstop.StartStopConfig;
@@ -16,24 +20,10 @@
public class DynamicChannelsAndSerialNumbersTest {
- private static final String BATTERY_ID = "battery0";
- private static final String MODBUS_ID = "modbus0";
- private static final String IO_ID = "io0";
-
private static final int TOWERS = 1;
private static final int MODULES = 10;
private static final int CELLS = 120;/* Read from register as cells*modules */
- private static final ChannelAddress NUMBER_OF_MODULES_PER_TOWER = new ChannelAddress(BATTERY_ID,
- "NumberOfModulesPerTower");
- private static final ChannelAddress NUMBER_OF_TOWERS = new ChannelAddress(BATTERY_ID, "NumberOfTowers");
- private static final ChannelAddress NUMBER_OF_CELLS_PER_MODULE = new ChannelAddress(BATTERY_ID,
- "NumberOfCellsPerModule");
- private static final ChannelAddress SUB_MASTER_HARDWARE_VERSION = new ChannelAddress(BATTERY_ID,
- "Tower0SubMasterHardwareVersion");
- private static final ChannelAddress MASTER_MCU_HARDWARE_VERSION = new ChannelAddress(BATTERY_ID,
- "MasterMcuHardwareVersion");
-
@Test
public void testSerialNum() throws Exception {
var battery = new BatteryFeneconCommercialImpl();
@@ -41,11 +31,11 @@ public void testSerialNum() throws Exception {
var componentTest = new ComponentTest(battery) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager()) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
- .addComponent(new DummyInputOutput(IO_ID))//
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
+ .addComponent(new DummyInputOutput("io0"))//
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(0) //
.setBatteryStartStopRelay("io0/InputOutput0")//
.setStartStop(StartStopConfig.AUTO) //
@@ -56,10 +46,10 @@ public void testSerialNum() throws Exception {
.input(NUMBER_OF_TOWERS, TOWERS) //
.input(NUMBER_OF_MODULES_PER_TOWER, MODULES) //
.input(NUMBER_OF_CELLS_PER_MODULE, CELLS) //
- .input(SUB_MASTER_HARDWARE_VERSION, "109101BM60"));
+ .input("battery0", "Tower0SubMasterHardwareVersion", "109101BM60"));
checkDynamicChannels(battery, TOWERS, MODULES, CELLS / MODULES);
- assertEquals("011910MB06", BatteryFeneconCommercialImpl.VERSION_CONVERTER.elementToChannel("109101BM60"));
+ assertEquals("011910MB06", VERSION_CONVERTER.elementToChannel("109101BM60"));
componentTest.next(new TestCase());
componentTest.next(new TestCase());
@@ -72,7 +62,7 @@ public void testSerialNum() throws Exception {
.input(NUMBER_OF_CELLS_PER_MODULE, CELLS) //
.input(MASTER_MCU_HARDWARE_VERSION, "100201MS50"));
- assertEquals("012010SM05", BatteryFeneconCommercialImpl.VERSION_CONVERTER.elementToChannel("100201MS50"));
+ assertEquals("012010SM05", VERSION_CONVERTER.elementToChannel("100201MS50"));
}
/**
diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java
index 0ea7cc798ae..d4298dde150 100644
--- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java
+++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java
@@ -673,7 +673,7 @@ public static 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) //
+ RUN_FAILED(Doc.of(Level.WARNING) //
.text("Running the Logic failed")), //
LOW_MIN_VOLTAGE_WARNING(Doc.of(Level.WARNING) //
.text("Low min voltage warning "
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 efa68f7d43d..e9d933bc5e0 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
@@ -117,17 +117,12 @@ public BatteryFeneconHomeImpl() {
BatteryProtection.ChannelId.values(), //
BatteryFeneconHome.ChannelId.values() //
);
- this.updateHardwareType(BatteryFeneconHomeHardwareType.DEFAULT);
}
@Activate
private void activate(ComponentContext context, Config config) throws OpenemsException {
this.config = config;
-
- // Predefine BatteryProtection. Later adapted to the hardware type.
- this.batteryProtection = BatteryProtection.create(this) //
- .applyBatteryProtectionDefinition(new FeneconHomeBatteryProtection52(), this.componentManager) //
- .build();
+ this.updateHardwareType(BatteryFeneconHomeHardwareType.DEFAULT); // initialize to default
if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm,
"Modbus", config.modbus_id())) {
@@ -194,7 +189,7 @@ private void handleStateMachine() {
@Override
protected ModbusProtocol defineModbusProtocol() {
return new ModbusProtocol(this, //
- new FC3ReadRegistersTask(500, Priority.LOW, //
+ new FC3ReadRegistersTask(500, Priority.HIGH, //
m(new BitsWordElement(500, this) //
.bit(0, BatteryFeneconHome.ChannelId.RACK_PRE_ALARM_CELL_OVER_VOLTAGE) //
.bit(1, BatteryFeneconHome.ChannelId.RACK_PRE_ALARM_CELL_UNDER_VOLTAGE) //
@@ -271,10 +266,7 @@ protected ModbusProtocol defineModbusProtocol() {
.bit(6, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_7) //
.bit(7, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_8) //
.bit(8, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_9) //
- .bit(9, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_10))//
- ), //
-
- new FC3ReadRegistersTask(506, Priority.LOW, //
+ .bit(9, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_10)), //
m(Battery.ChannelId.VOLTAGE, new UnsignedWordElement(506), SCALE_FACTOR_MINUS_1), // [V]
m(Battery.ChannelId.CURRENT, new SignedWordElement(507), SCALE_FACTOR_MINUS_1), // [A]
m(Battery.ChannelId.SOC, new UnsignedWordElement(508), SCALE_FACTOR_MINUS_1), // [%]
@@ -1066,7 +1058,7 @@ public BridgeModbus getModbus() {
}
@Override
- public ModbusProtocol getDefinedModbusProtocol() throws OpenemsException {
+ public ModbusProtocol getDefinedModbusProtocol() {
return this.getModbusProtocol();
}
diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java
index bbad1e67bde..65dec26fc83 100644
--- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java
+++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java
@@ -25,8 +25,8 @@ public PolyLine getChargeVoltageToPercent() {
.addPoint(Math.nextUp(3000), 1) //
.addPoint(3450, 1) //
.addPoint(3540, 0.08) //
- .addPoint(Math.nextDown(3550), 0.08) //
- .addPoint(3550, 0) //
+ .addPoint(Math.nextDown(3580), 0.08) //
+ .addPoint(3580, 0) //
.build();
}
@@ -70,7 +70,7 @@ public PolyLine getDischargeSocToPercent() {
@Override
public ForceDischarge.Params getForceDischargeParams() {
- return new ForceDischarge.Params(3600, 3540, 3450);
+ return new ForceDischarge.Params(3630, 3540, 3450);
}
@Override
diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/ModbusHelper.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/ModbusHelper.java
index b072d1ebdc4..4f95aa64a66 100644
--- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/ModbusHelper.java
+++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/ModbusHelper.java
@@ -1,24 +1,22 @@
package io.openems.edge.battery.fenecon.home;
-import io.openems.common.exceptions.OpenemsException;
import io.openems.edge.bridge.modbus.api.BridgeModbus;
import io.openems.edge.bridge.modbus.api.ModbusProtocol;
public interface ModbusHelper {
/**
- * Get modbus bridge.
+ * Get the {@link BridgeModbus}.
*
- * @return modbus bridge.
+ * @return the {@link BridgeModbus}
*/
public BridgeModbus getModbus();
/**
- * Get defined modbus protocol.
+ * Get defined {@link ModbusProtocol}.
*
- * @return modbus protocol
- * @throws OpenemsException on error
+ * @return the {@link ModbusProtocol}
*/
- public ModbusProtocol getDefinedModbusProtocol() throws OpenemsException;
+ public ModbusProtocol getDefinedModbusProtocol();
}
diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java
index a2828143f76..6cb49490bb6 100644
--- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java
+++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java
@@ -32,8 +32,4 @@ public Context(BatteryFeneconHome parent, Clock clock, Boolean batteryStartUpRel
this.modbusCommunicationFailed = modbusCommunicationFailed;
this.retryModbusCommunication = retryModbusCommunication;
}
-
- protected void retryModbusCommunication() {
- this.getParent().retryModbusCommunication();
- }
}
\ No newline at end of file
diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoStoppedHandler.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoStoppedHandler.java
index a41a0bf4837..d539b27df30 100644
--- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoStoppedHandler.java
+++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoStoppedHandler.java
@@ -12,26 +12,25 @@ public class GoStoppedHandler extends StateHandler {
private static int TIMEOUT = 2100; // [35 minutes in seconds]
private Instant timeAtEntry = Instant.MIN;
- private boolean didProtocolAdd = false;
+ private boolean isProtocolAdded = false;
@Override
- protected void onEntry(Context context) throws OpenemsNamedException {
- final var battery = context.getParent();
- final var modbus = battery.getModbus();
- modbus.removeProtocol(battery.id());
- this.didProtocolAdd = false;
+ protected void onEntry(Context context) {
+ // Remove the protocol to trigger BMS timeout
+ this.removeProtocol(context);
+
this.timeAtEntry = Instant.now(context.clock);
}
@Override
public State runAndGetNextState(Context context) throws OpenemsException {
- final var battery = context.getParent();
var now = Instant.now(context.clock);
- if (Duration.between(this.timeAtEntry, now).getSeconds() > TIMEOUT && !this.didProtocolAdd) {
- this.addAndRetryModbusProtocol(context);
+ if (Duration.between(this.timeAtEntry, now).getSeconds() > TIMEOUT && !this.isProtocolAdded) {
+ this.addProtocol(context);
return State.GO_STOPPED;
}
+ final var battery = context.getParent();
if (battery.getModbusCommunicationFailed()) {
return State.STOPPED;
}
@@ -40,12 +39,25 @@ public State runAndGetNextState(Context context) throws OpenemsException {
return State.GO_STOPPED;
}
- private void addAndRetryModbusProtocol(Context context) throws OpenemsException {
+ @Override
+ protected void onExit(Context context) throws OpenemsNamedException {
+ // Make sure to leave this GoStoppedHandler with added protocol
+ if (!this.isProtocolAdded) {
+ this.addProtocol(context);
+ }
+ }
+
+ private void addProtocol(Context context) {
final var battery = context.getParent();
final var modbus = battery.getModbus();
+ this.isProtocolAdded = true;
modbus.addProtocol(battery.id(), battery.getDefinedModbusProtocol());
- modbus.retryModbusCommunication(battery.id());
- this.didProtocolAdd = true;
}
+ private void removeProtocol(Context context) {
+ final var battery = context.getParent();
+ final var modbus = battery.getModbus();
+ this.isProtocolAdded = false;
+ modbus.removeProtocol(battery.id());
+ }
}
diff --git a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java
index 432eccdcb6e..b0029262635 100644
--- a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java
+++ b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java
@@ -1,21 +1,38 @@
package io.openems.edge.battery.fenecon.home;
+import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT;
+import static io.openems.edge.battery.api.Battery.ChannelId.CURRENT;
+import static io.openems.edge.battery.api.Battery.ChannelId.MAX_CELL_VOLTAGE;
+import static io.openems.edge.battery.api.Battery.ChannelId.MIN_CELL_VOLTAGE;
+import static io.openems.edge.battery.api.Battery.ChannelId.SOC;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.BMS_CONTROL;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_FAULT;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_WARNING;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.NUMBER_OF_MODULES_PER_TOWER;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.NUMBER_OF_TOWERS;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.STATE_MACHINE;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_0_BMS_SOFTWARE_VERSION;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_1_BMS_SOFTWARE_VERSION;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_2_BMS_SOFTWARE_VERSION;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_3_BMS_SOFTWARE_VERSION;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_4_BMS_SOFTWARE_VERSION;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHomeImpl.TIMEOUT;
+import static io.openems.edge.battery.protection.BatteryProtection.ChannelId.BP_CHARGE_BMS;
+import static io.openems.edge.battery.protection.BatteryProtection.ChannelId.BP_CHARGE_MAX_SOC;
+import static io.openems.edge.bridge.modbus.api.ModbusComponent.ChannelId.MODBUS_COMMUNICATION_FAILED;
+import static io.openems.edge.common.test.TestUtils.createDummyClock;
+import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT4;
+import static java.lang.Math.round;
+import static java.time.temporal.ChronoUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
-import java.time.Instant;
-import java.time.ZoneOffset;
-import java.time.temporal.ChronoUnit;
-
import org.junit.Test;
import io.openems.common.function.ThrowingRunnable;
-import io.openems.common.test.TimeLeapClock;
-import io.openems.common.types.ChannelAddress;
-import io.openems.edge.battery.api.Battery;
-import io.openems.edge.battery.fenecon.home.statemachine.StateMachine;
-import io.openems.edge.battery.protection.BatteryProtection;
-import io.openems.edge.bridge.modbus.api.ModbusComponent;
+import io.openems.edge.battery.fenecon.home.statemachine.StateMachine.State;
import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
import io.openems.edge.common.startstop.StartStopConfig;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
@@ -26,51 +43,6 @@
public class BatteryFeneconHomeImplTest {
- private static final String BATTERY_ID = "battery0";
- private static final String MODBUS_ID = "modbus0";
- private static final String IO_ID = "io0";
-
- private static final ChannelAddress STATE_MACHINE = new ChannelAddress(BATTERY_ID,
- BatteryFeneconHome.ChannelId.STATE_MACHINE.id());
- private static final ChannelAddress LOW_MIN_VOLTAGE_WARNING = new ChannelAddress(BATTERY_ID,
- BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_WARNING.id());
- private static final ChannelAddress LOW_MIN_VOLTAGE_FAULT = new ChannelAddress(BATTERY_ID,
- BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_FAULT.id());
- private static final ChannelAddress LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED = new ChannelAddress(BATTERY_ID,
- BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED.id());
- private static final ChannelAddress MODBUS_COMMUNICATION_FAILED = new ChannelAddress(BATTERY_ID,
- ModbusComponent.ChannelId.MODBUS_COMMUNICATION_FAILED.id());
- private static final ChannelAddress BMS_CONTROL = new ChannelAddress(BATTERY_ID,
- BatteryFeneconHome.ChannelId.BMS_CONTROL.id());
- private static final ChannelAddress BP_CHARGE_BMS = new ChannelAddress(BATTERY_ID,
- BatteryProtection.ChannelId.BP_CHARGE_BMS.id());
- private static final ChannelAddress MAX_CELL_VOLTAGE = new ChannelAddress(BATTERY_ID,
- Battery.ChannelId.MAX_CELL_VOLTAGE.id());
- private static final ChannelAddress CHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID,
- Battery.ChannelId.CHARGE_MAX_CURRENT.id());
- private static final ChannelAddress CURRENT = new ChannelAddress(BATTERY_ID, Battery.ChannelId.CURRENT.id());
- private static final ChannelAddress MIN_CELL_VOLTAGE = new ChannelAddress(BATTERY_ID,
- Battery.ChannelId.MIN_CELL_VOLTAGE.id());
- private static final ChannelAddress TOWER_0_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID,
- BatteryFeneconHome.ChannelId.TOWER_0_BMS_SOFTWARE_VERSION.id());
- private static final ChannelAddress TOWER_1_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID,
- BatteryFeneconHome.ChannelId.TOWER_1_BMS_SOFTWARE_VERSION.id());
- private static final ChannelAddress TOWER_2_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID,
- BatteryFeneconHome.ChannelId.TOWER_2_BMS_SOFTWARE_VERSION.id());
- private static final ChannelAddress TOWER_3_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID,
- BatteryFeneconHome.ChannelId.TOWER_3_BMS_SOFTWARE_VERSION.id());
- private static final ChannelAddress TOWER_4_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID,
- BatteryFeneconHome.ChannelId.TOWER_4_BMS_SOFTWARE_VERSION.id());
- private static final ChannelAddress NUMBER_OF_TOWERS = new ChannelAddress(BATTERY_ID,
- BatteryFeneconHome.ChannelId.NUMBER_OF_TOWERS.id());
- private static final ChannelAddress NUMBER_OF_MODULES_PER_TOWER = new ChannelAddress(BATTERY_ID,
- BatteryFeneconHome.ChannelId.NUMBER_OF_MODULES_PER_TOWER.id());
- private static final ChannelAddress BP_CHARGE_MAX_SOC = new ChannelAddress(BATTERY_ID,
- BatteryProtection.ChannelId.BP_CHARGE_MAX_SOC.id());
- private static final ChannelAddress SOC = new ChannelAddress(BATTERY_ID, Battery.ChannelId.SOC.id());
-
- private static final ChannelAddress BATTERY_RELAY = new ChannelAddress(IO_ID, "InputOutput4");
-
private static ThrowingRunnable assertLog(BatteryFeneconHomeImpl sut, String message) {
return () -> assertEquals(message, sut.stateMachine.debugLog());
}
@@ -82,41 +54,41 @@ private static ThrowingRunnable assertLog(BatteryFeneconHomeImpl sut,
*/
@Test
public void test() throws Exception {
- final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC);
+ final var clock = createDummyClock();
var sut = new BatteryFeneconHomeImpl();
new ComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
- .addComponent(new DummyInputOutput(IO_ID))//
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
+ .addComponent(new DummyInputOutput("io0"))//
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(0) //
.setStartStop(StartStopConfig.START) //
.setBatteryStartUpRelay("io0/InputOutput4")//
.build())//
.next(new TestCase() //
.inputForce(MODBUS_COMMUNICATION_FAILED, true) //
- .input(BATTERY_RELAY, false) // Switch OFF
+ .input("io0", INPUT_OUTPUT4, false) // Switch OFF
.input(BMS_CONTROL, false) // Switched OFF
.onBeforeProcessImage(assertLog(sut, "Undefined")) //
- .output(STATE_MACHINE, StateMachine.State.UNDEFINED)) //
+ .output(STATE_MACHINE, State.UNDEFINED)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase()//
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn"))) //
.next(new TestCase()//
- .input(BATTERY_RELAY, true) // Switch ON
+ .input("io0", INPUT_OUTPUT4, true) // Switch ON
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn"))) //
.next(new TestCase()//
- .input(BATTERY_RELAY, true) // Switch ON
+ .input("io0", INPUT_OUTPUT4, true) // Switch ON
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayHold"))
- .onAfterProcessImage(() -> clock.leap(11, ChronoUnit.SECONDS))) //
+ .onAfterProcessImage(() -> clock.leap(11, SECONDS))) //
.next(new TestCase() //
- .input(BATTERY_RELAY, false) // Switch OFF
+ .input("io0", INPUT_OUTPUT4, false) // Switch OFF
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff"))) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication"))) //
@@ -129,17 +101,17 @@ public void test() throws Exception {
.next(new TestCase()//
.onBeforeProcessImage(assertLog(sut, "Running")) //
- .output(STATE_MACHINE, StateMachine.State.RUNNING)) //
+ .output(STATE_MACHINE, State.RUNNING)) //
// Ramp-Up ChargeMaxCurrent (0.1 A / Second)
.next(new TestCase() //
.input(BP_CHARGE_BMS, 40) //
.input(MAX_CELL_VOLTAGE, 3000)) //
.next(new TestCase("Ramp up") //
- .timeleap(clock, 100, ChronoUnit.SECONDS) //
+ .timeleap(clock, 100, SECONDS) //
.output(CHARGE_MAX_CURRENT, 10)) //
.next(new TestCase() //
- .timeleap(clock, 300, ChronoUnit.SECONDS) //
+ .timeleap(clock, 300, SECONDS) //
.output(CHARGE_MAX_CURRENT, 40))
// Full Battery
@@ -151,7 +123,7 @@ public void test() throws Exception {
.next(new TestCase() //
.input(BP_CHARGE_BMS, 40)) //
.next(new TestCase() //
- .timeleap(clock, 100, ChronoUnit.SECONDS) //
+ .timeleap(clock, 100, SECONDS) //
.output(CHARGE_MAX_CURRENT, 25)) //
;
}
@@ -163,54 +135,54 @@ public void test() throws Exception {
*/
@Test
public void test2() throws Exception {
- final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC);
+ final var clock = createDummyClock();
var sut = new BatteryFeneconHomeImpl();
new ComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
- .addComponent(new DummyInputOutput(IO_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
+ .addComponent(new DummyInputOutput("io0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(0) //
.setStartStop(StartStopConfig.START) //
.setBatteryStartUpRelay("io0/InputOutput4")//
.build())//
.next(new TestCase()//
- .input(BATTERY_RELAY, true) //
+ .input("io0", INPUT_OUTPUT4, true) //
.input(BMS_CONTROL, false) // Switched Off
- .output(STATE_MACHINE, StateMachine.State.UNDEFINED))//
+ .output(STATE_MACHINE, State.UNDEFINED))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
+ .output(STATE_MACHINE, State.GO_RUNNING))//
.next(new TestCase()//
- .input(BATTERY_RELAY, true) // Switch ON
+ .input("io0", INPUT_OUTPUT4, true) // Switch ON
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayHold"))
- .onAfterProcessImage(() -> clock.leap(11, ChronoUnit.SECONDS))) //
+ .onAfterProcessImage(() -> clock.leap(11, SECONDS))) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) //
.input(BMS_CONTROL, true) // Switched On
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) //
- .input(BATTERY_RELAY, false) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .input("io0", INPUT_OUTPUT4, false) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
- .output(STATE_MACHINE, StateMachine.State.RUNNING));
+ .output(STATE_MACHINE, State.RUNNING));
}
/**
@@ -225,37 +197,37 @@ public void test3() throws Exception {
new ComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager()) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
- .addComponent(new DummyInputOutput(IO_ID))//
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
+ .addComponent(new DummyInputOutput("io0"))//
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(0) //
.setStartStop(StartStopConfig.START) //
.setBatteryStartUpRelay("io0/InputOutput4")//
.build()) //
.next(new TestCase() //
- .input(BATTERY_RELAY, false) //
+ .input("io0", INPUT_OUTPUT4, false) //
.input(BMS_CONTROL, true) // Switched On
- .output(STATE_MACHINE, StateMachine.State.UNDEFINED))//
+ .output(STATE_MACHINE, State.UNDEFINED))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
+ .output(STATE_MACHINE, State.GO_RUNNING))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
+ .output(STATE_MACHINE, State.GO_RUNNING))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
- .output(STATE_MACHINE, StateMachine.State.RUNNING));
+ .output(STATE_MACHINE, State.RUNNING));
}
/**
@@ -265,53 +237,53 @@ public void test3() throws Exception {
*/
@Test
public void test4() throws Exception {
- final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC);
+ final var clock = createDummyClock();
var sut = new BatteryFeneconHomeImpl();
new ComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
- .addComponent(new DummyInputOutput(IO_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
+ .addComponent(new DummyInputOutput("io0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(0) //
.setStartStop(StartStopConfig.START) //
.setBatteryStartUpRelay("io0/InputOutput4") //
.build()) //
.next(new TestCase() //
- .input(BATTERY_RELAY, false) //
+ .input("io0", INPUT_OUTPUT4, false) //
.input(BMS_CONTROL, false) // Switched Off
- .output(STATE_MACHINE, StateMachine.State.UNDEFINED)) //
+ .output(STATE_MACHINE, State.UNDEFINED)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
// Ex; after long time if hard switch turned on....
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) //
.input(BMS_CONTROL, true) // Switched On
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) //
- .input(BATTERY_RELAY, true) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .input("io0", INPUT_OUTPUT4, true) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase()//
- .input(BATTERY_RELAY, true) // Switch ON
+ .input("io0", INPUT_OUTPUT4, true) // Switch ON
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayHold"))
- .onAfterProcessImage(() -> clock.leap(11, ChronoUnit.SECONDS))) //
+ .onAfterProcessImage(() -> clock.leap(11, SECONDS))) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
+ .output(STATE_MACHINE, State.GO_RUNNING))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)); //
+ .output(STATE_MACHINE, State.GO_RUNNING)); //
}
@Test
@@ -334,224 +306,221 @@ public void testGetHardwareTypeFromRegisterValue() {
@Test
public void testMinVoltageGoStopped() throws Exception {
- final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC);
+ final var clock = createDummyClock();
var sut = new BatteryFeneconHomeImpl();
new ComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
- .addComponent(new DummyInputOutput(IO_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
+ .addComponent(new DummyInputOutput("io0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(0) //
.setStartStop(StartStopConfig.START) //
.setBatteryStartUpRelay("io0/InputOutput4")//
.build())//
.next(new TestCase() //
- .input(BATTERY_RELAY, false) //
+ .input("io0", INPUT_OUTPUT4, false) //
.input(BMS_CONTROL, true) // Switched On
- .output(STATE_MACHINE, StateMachine.State.UNDEFINED))//
+ .output(STATE_MACHINE, State.UNDEFINED))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
+ .output(STATE_MACHINE, State.GO_RUNNING))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
+ .output(STATE_MACHINE, State.GO_RUNNING))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
- .output(STATE_MACHINE, StateMachine.State.RUNNING))
+ .output(STATE_MACHINE, State.RUNNING))
/*
* Critical min voltage
*/
.next(new TestCase("MinCellVoltage below critical value") //
- .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
+ .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
.input(CURRENT, 0) //
.output(LOW_MIN_VOLTAGE_WARNING, true) //
.output(LOW_MIN_VOLTAGE_FAULT, false) //
- .output(STATE_MACHINE, StateMachine.State.RUNNING) //
+ .output(STATE_MACHINE, State.RUNNING) //
.output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) //
- .onAfterControllersCallbacks(
- () -> clock.leap(BatteryFeneconHomeImpl.TIMEOUT - 10, ChronoUnit.SECONDS))) //
+ .onAfterControllersCallbacks(() -> clock.leap(TIMEOUT - 10, SECONDS))) //
.next(new TestCase("MinCellVoltage below critical value - charging resets time") //
- .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
+ .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
.input(CURRENT, -300) //
.output(LOW_MIN_VOLTAGE_WARNING, true) //
.output(LOW_MIN_VOLTAGE_FAULT, false) //
.output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false)) //
.next(new TestCase("MinCellVoltage below critical value - timer starts again") //
.input(CURRENT, 0) //
- .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
+ .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
.output(LOW_MIN_VOLTAGE_WARNING, true) //
.output(LOW_MIN_VOLTAGE_FAULT, false) //
.output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) //
- .onAfterControllersCallbacks(
- () -> clock.leap(BatteryFeneconHomeImpl.TIMEOUT - 10, ChronoUnit.SECONDS))) //
+ .onAfterControllersCallbacks(() -> clock.leap(TIMEOUT - 10, SECONDS))) //
.next(new TestCase("MinCellVoltage below critical value - time not passed") //
.input(CURRENT, 0) //
- .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
+ .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
.output(LOW_MIN_VOLTAGE_WARNING, true) //
.output(LOW_MIN_VOLTAGE_FAULT, false) //
.output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) //
- .onAfterControllersCallbacks(() -> clock.leap(15, ChronoUnit.SECONDS))) //
+ .onAfterControllersCallbacks(() -> clock.leap(15, SECONDS))) //
.next(new TestCase("MinCellVoltage below critical value - time passed") //
.input(CURRENT, 0) //
- .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
+ .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
.output(LOW_MIN_VOLTAGE_FAULT, true) //
.output(LOW_MIN_VOLTAGE_WARNING, false) //
.output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) //
- .output(STATE_MACHINE, StateMachine.State.RUNNING)) //
+ .output(STATE_MACHINE, State.RUNNING)) //
.next(new TestCase("MinCellVoltage below critical value - error") //
.input(LOW_MIN_VOLTAGE_FAULT, true) //
.input(CURRENT, 0) //
- .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
+ .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
.output(LOW_MIN_VOLTAGE_FAULT, true) //
.output(LOW_MIN_VOLTAGE_WARNING, false) //
.output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false)) //
.next(new TestCase() //
- .output(STATE_MACHINE, StateMachine.State.ERROR)) //
+ .output(STATE_MACHINE, State.ERROR)) //
.next(new TestCase("MinCellVoltage below critical value - go stopped") //
.input(LOW_MIN_VOLTAGE_FAULT, true) //
.input(CURRENT, 0) //
// MinCellVoltage would be null, but there is not DummyTimedata for not to test
// "getPastValues"
- .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
+ .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
.output(LOW_MIN_VOLTAGE_FAULT, true) //
.output(LOW_MIN_VOLTAGE_WARNING, false) //
.output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) //
- .output(STATE_MACHINE, StateMachine.State.GO_STOPPED) //
- .onAfterControllersCallbacks(() -> clock.leap(2_100, ChronoUnit.SECONDS))) // 35 minutes
+ .output(STATE_MACHINE, State.GO_STOPPED) //
+ .onAfterControllersCallbacks(() -> clock.leap(2_100, SECONDS))) // 35 minutes
.next(new TestCase() //
.input(MODBUS_COMMUNICATION_FAILED, true) //
) //
.next(new TestCase("MinCellVoltage below critical value - stopped") //
.input(CURRENT, 0) //
.input(MODBUS_COMMUNICATION_FAILED, true) //
- .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
+ .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
.output(LOW_MIN_VOLTAGE_WARNING, false) //
.output(LOW_MIN_VOLTAGE_FAULT, false) //
.output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, true) //
- .output(STATE_MACHINE, StateMachine.State.STOPPED) //
+ .output(STATE_MACHINE, State.STOPPED) //
);
}
@Test
public void testMinVoltageCharging() throws Exception {
- final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC);
+ final var clock = createDummyClock();
var sut = new BatteryFeneconHomeImpl();
new ComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
- .addComponent(new DummyInputOutput(IO_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
+ .addComponent(new DummyInputOutput("io0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(0) //
.setStartStop(StartStopConfig.START) //
.setBatteryStartUpRelay("io0/InputOutput4")//
.build())//
.next(new TestCase() //
- .input(BATTERY_RELAY, false) //
+ .input("io0", INPUT_OUTPUT4, false) //
.input(BMS_CONTROL, true) // Switched On
- .output(STATE_MACHINE, StateMachine.State.UNDEFINED))//
+ .output(STATE_MACHINE, State.UNDEFINED))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
+ .output(STATE_MACHINE, State.GO_RUNNING))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
+ .output(STATE_MACHINE, State.GO_RUNNING))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
- .output(STATE_MACHINE, StateMachine.State.RUNNING))
+ .output(STATE_MACHINE, State.RUNNING))
/*
* Critical min voltage
*/
.next(new TestCase("MinCellVoltage below critical value") //
- .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
+ .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
.input(CURRENT, 0) //
.output(LOW_MIN_VOLTAGE_WARNING, true) //
.output(LOW_MIN_VOLTAGE_FAULT, false) //
- .output(STATE_MACHINE, StateMachine.State.RUNNING) //
+ .output(STATE_MACHINE, State.RUNNING) //
.output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) //
- .onAfterControllersCallbacks(
- () -> clock.leap(BatteryFeneconHomeImpl.TIMEOUT - 10, ChronoUnit.SECONDS))) //
+ .onAfterControllersCallbacks(() -> clock.leap(TIMEOUT - 10, SECONDS))) //
.next(new TestCase("MinCellVoltage below critical value - charging resets time") //
- .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
+ .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) //
.input(CURRENT, -300) //
.output(LOW_MIN_VOLTAGE_WARNING, true) //
.output(LOW_MIN_VOLTAGE_FAULT, false) //
- .output(STATE_MACHINE, StateMachine.State.RUNNING) //
+ .output(STATE_MACHINE, State.RUNNING) //
.output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false)) //
.next(new TestCase("MinCellVoltage below critical value - charging") //
.input(CURRENT, -2000) //
- .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE + 50)) //
+ .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE + 50)) //
.output(LOW_MIN_VOLTAGE_WARNING, false) //
.output(LOW_MIN_VOLTAGE_FAULT, false) //
- .output(STATE_MACHINE, StateMachine.State.RUNNING) //
+ .output(STATE_MACHINE, State.RUNNING) //
.output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) //
);
}
@Test
public void testNumberOfTowers() throws Exception {
- final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC);
+ final var clock = createDummyClock();
var sut = new BatteryFeneconHomeImpl();
new ComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
- .addComponent(new DummyInputOutput(IO_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
+ .addComponent(new DummyInputOutput("io0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(0) //
.setStartStop(StartStopConfig.START) //
- .setBatteryStartUpRelay("io0/InputOutput4")//
- .build())//
+ .setBatteryStartUpRelay("io0/InputOutput4") //
+ .build()) //
.next(new TestCase() //
- .input(BATTERY_RELAY, false) //
+ .input("io0", INPUT_OUTPUT4, false) //
.input(BMS_CONTROL, true) // Switched On
- .output(STATE_MACHINE, StateMachine.State.UNDEFINED))//
+ .output(STATE_MACHINE, State.UNDEFINED))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
+ .output(STATE_MACHINE, State.GO_RUNNING))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
+ .output(STATE_MACHINE, State.GO_RUNNING))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
- .output(STATE_MACHINE, StateMachine.State.RUNNING))
+ .output(STATE_MACHINE, State.RUNNING))
.next(new TestCase() //
.output(NUMBER_OF_TOWERS, null))
.next(new TestCase() //
@@ -610,37 +579,37 @@ public void testBatteryProtectionSocLimitations() throws Exception {
new ComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager()) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
- .addComponent(new DummyInputOutput(IO_ID))//
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
+ .addComponent(new DummyInputOutput("io0"))//
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(0) //
.setStartStop(StartStopConfig.START) //
.setBatteryStartUpRelay("io0/InputOutput4")//
.build()) //
.next(new TestCase() //
- .input(BATTERY_RELAY, false) //
+ .input("io0", INPUT_OUTPUT4, false) //
.input(BMS_CONTROL, true) // Switched On
- .output(STATE_MACHINE, StateMachine.State.UNDEFINED))//
+ .output(STATE_MACHINE, State.UNDEFINED))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
+ .output(STATE_MACHINE, State.GO_RUNNING))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))//
+ .output(STATE_MACHINE, State.GO_RUNNING))//
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
.onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) //
- .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) //
+ .output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
- .output(STATE_MACHINE, StateMachine.State.RUNNING)) //
+ .output(STATE_MACHINE, State.RUNNING)) //
.next(new TestCase() //
.output(BP_CHARGE_MAX_SOC, 40)) //
@@ -648,23 +617,45 @@ public void testBatteryProtectionSocLimitations() throws Exception {
.input(SOC, 97) //
.output(SOC, 97)) //
.next(new TestCase() //
- .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.625))) //
+ .output(BP_CHARGE_MAX_SOC, round(40 * 0.625F))) //
.next(new TestCase() //
.input(SOC, 98)) //
.next(new TestCase() //
- .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.4))) //
+ .output(BP_CHARGE_MAX_SOC, round(40 * 0.4F))) //
.next(new TestCase() //
.input(SOC, 99)) //
.next(new TestCase() //
- .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.2))) //
+ .output(BP_CHARGE_MAX_SOC, round(40 * 0.2F))) //
.next(new TestCase() //
.input(SOC, 100)) //
.next(new TestCase() //
- .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.05))) //
+ .output(BP_CHARGE_MAX_SOC, round(40 * 0.05F))) //
.next(new TestCase() //
.input(SOC, 99)) //
.next(new TestCase() //
- .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.2)) //
+ .output(BP_CHARGE_MAX_SOC, round(40 * 0.2F)) //
);
}
+
+ @Test
+ public void testReadModbus() throws Exception {
+ var sut = new BatteryFeneconHomeImpl();
+ new ComponentTest(sut) //
+ .addReference("cm", new DummyConfigurationAdmin()) //
+ .addReference("componentManager", new DummyComponentManager()) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0") //
+ .withRegister(18000, (byte) 0x00, (byte) 0x00)) // TOWER_4_BMS_SOFTWARE_VERSION
+ .activate(MyConfig.create() //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
+ .setModbusUnitId(0) //
+ .setStartStop(StartStopConfig.START) //
+ .setBatteryStartUpRelay("io0/InputOutput4")//
+ .build()) //
+
+ .next(new TestCase() //
+ .output(BatteryFeneconHome.ChannelId.TOWER_4_BMS_SOFTWARE_VERSION, 0)) //
+
+ .deactivate();
+ }
}
diff --git a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java
index ff8b7353731..9fff171c345 100644
--- a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java
+++ b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java
@@ -1,11 +1,15 @@
package io.openems.edge.battery.fenecon.home;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.BATTERY_HARDWARE_TYPE;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.NUMBER_OF_MODULES_PER_TOWER;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_0_BMS_SOFTWARE_VERSION;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_1_BMS_SOFTWARE_VERSION;
+import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_2_BMS_SOFTWARE_VERSION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.junit.Test;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.battery.api.Battery;
import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
import io.openems.edge.common.channel.ChannelId;
@@ -17,19 +21,6 @@
public class TowersAndModulesTest {
- private static final String BATTERY_ID = "battery0";
- private static final String MODBUS_ID = "modbus0";
-
- private static final ChannelAddress NUMBER_OF_MODULES_PER_TOWER = new ChannelAddress(BATTERY_ID,
- "NumberOfModulesPerTower");
- private static final ChannelAddress TOWER_0_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID,
- "Tower0BmsSoftwareVersion");
- private static final ChannelAddress TOWER_1_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID,
- "Tower1BmsSoftwareVersion");
- private static final ChannelAddress TOWER_2_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID,
- "Tower2BmsSoftwareVersion");
- private static final ChannelAddress BATTERY_HARDWARE_TYPE = new ChannelAddress(BATTERY_ID, "BatteryHardwareType");
-
private static final int TOWERS = 1;
private static final int MODULES = 5;
private static final int CELLS = 14;
@@ -40,10 +31,10 @@ public void testChannelsCreatedDynamically() throws Exception {
var componentTest = new ComponentTest(battery) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager()) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(0) //
.setBatteryStartUpRelay("io0/Relay4") //
.setStartStop(StartStopConfig.AUTO) //
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionB.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionB.java
index 1276244de00..867c5b99a9f 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionB.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionB.java
@@ -52,80 +52,80 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
.accessMode(AccessMode.READ_WRITE)), //
// StateChannels
- MASTER_ALARM_COMMUNICATION_ERROR_WITH_SUBMASTER(Doc.of(Level.FAULT) //
+ MASTER_ALARM_COMMUNICATION_ERROR_WITH_SUBMASTER(Doc.of(Level.WARNING) //
.text("Communication error with submaster")),
- MASTER_ALARM_PCS_EMS_COMMUNICATION_FAILURE(Doc.of(Level.FAULT) //
+ MASTER_ALARM_PCS_EMS_COMMUNICATION_FAILURE(Doc.of(Level.WARNING) //
.text("PCS/EMS communication failure alarm")),
- MASTER_ALARM_PCS_EMS_CONTROL_FAIL(Doc.of(Level.FAULT) //
+ MASTER_ALARM_PCS_EMS_CONTROL_FAIL(Doc.of(Level.WARNING) //
.text("PCS/EMS control fail alarm")),
MASTER_ALARM_LEVEL_1_INSULATION(Doc.of(Level.WARNING) //
.text("System insulation alarm level 1")),
- MASTER_ALARM_LEVEL_2_INSULATION(Doc.of(Level.FAULT) //
+ MASTER_ALARM_LEVEL_2_INSULATION(Doc.of(Level.WARNING) //
.text("System insulation alarm level 2")),
- RACK_1_LEVEL_2_ALARM(Doc.of(Level.FAULT) //
+ RACK_1_LEVEL_2_ALARM(Doc.of(Level.WARNING) //
.text("Rack 1 Level 2 Alarm")),
- RACK_1_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) //
+ RACK_1_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) //
.text("Rack 1 PCS control fault")),
- RACK_1_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) //
+ RACK_1_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) //
.text("Rack 1 Communication with master error")),
- RACK_1_DEVICE_ERROR(Doc.of(Level.FAULT) //
+ RACK_1_DEVICE_ERROR(Doc.of(Level.WARNING) //
.text("Rack 1 Device error")),
- RACK_1_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) //
+ RACK_1_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) //
.text("Rack 1 Cycle over current")),
- RACK_1_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) //
+ RACK_1_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) //
.text("Rack 1 Voltage difference")),
- RACK_2_LEVEL_2_ALARM(Doc.of(Level.FAULT) //
+ RACK_2_LEVEL_2_ALARM(Doc.of(Level.WARNING) //
.text("Rack 2 Level 2 Alarm")),
- RACK_2_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) //
+ RACK_2_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) //
.text("Rack 2 PCS control fault")),
- RACK_2_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) //
+ RACK_2_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) //
.text("Rack 2 Communication with master error")),
- RACK_2_DEVICE_ERROR(Doc.of(Level.FAULT) //
+ RACK_2_DEVICE_ERROR(Doc.of(Level.WARNING) //
.text("Rack 2 Device error")),
- RACK_2_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) //
+ RACK_2_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) //
.text("Rack 1 Cycle over current")),
- RACK_2_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) //
+ RACK_2_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) //
.text("Rack 1 Voltage difference")),
- RACK_3_LEVEL_2_ALARM(Doc.of(Level.FAULT) //
+ RACK_3_LEVEL_2_ALARM(Doc.of(Level.WARNING) //
.text("Rack 3 Level 2 Alarm")),
- RACK_3_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) //
+ RACK_3_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) //
.text("Rack 3 PCS control fault")),
- RACK_3_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) //
+ RACK_3_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) //
.text("Rack 3 Communication with master error")),
- RACK_3_DEVICE_ERROR(Doc.of(Level.FAULT) //
+ RACK_3_DEVICE_ERROR(Doc.of(Level.WARNING) //
.text("Rack 3 Device error")),
- RACK_3_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) //
+ RACK_3_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) //
.text("Rack 1 Cycle over current")),
- RACK_3_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) //
+ RACK_3_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) //
.text("Rack 1 Voltage difference")),
- RACK_4_LEVEL_2_ALARM(Doc.of(Level.FAULT) //
+ RACK_4_LEVEL_2_ALARM(Doc.of(Level.WARNING) //
.text("Rack 4 Level 2 Alarm")),
- RACK_4_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) //
+ RACK_4_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) //
.text("Rack 4 PCS control fault")),
- RACK_4_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) //
+ RACK_4_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) //
.text("Rack 4 Communication with master error")),
- RACK_4_DEVICE_ERROR(Doc.of(Level.FAULT) //
+ RACK_4_DEVICE_ERROR(Doc.of(Level.WARNING) //
.text("Rack 4 Device error")),
- RACK_4_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) //
+ RACK_4_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) //
.text("Rack 1 Cycle over current")),
- RACK_4_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) //
+ RACK_4_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) //
.text("Rack 1 Voltage difference")),
- RACK_5_LEVEL_2_ALARM(Doc.of(Level.FAULT) //
+ RACK_5_LEVEL_2_ALARM(Doc.of(Level.WARNING) //
.text("Rack 5 Level 2 Alarm")),
- RACK_5_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) //
+ RACK_5_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) //
.text("Rack 5 PCS control fault")),
- RACK_5_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) //
+ RACK_5_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) //
.text("Rack 5 Communication with master error")),
- RACK_5_DEVICE_ERROR(Doc.of(Level.FAULT) //
+ RACK_5_DEVICE_ERROR(Doc.of(Level.WARNING) //
.text("Rack 5 Device error")),
- RACK_5_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) //
+ RACK_5_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) //
.text("Rack 1 Cycle over current")),
- RACK_5_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) //
+ RACK_5_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) //
.text("Rack 1 Voltage difference")),;
private final Doc doc;
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java
index 45d65c88cfb..02835194b33 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java
@@ -439,28 +439,28 @@ private Map createChannelIdMap() {
this.addEntry(map, KEY_MAX_CELL_TEMPERATURE, new IntegerDoc().unit(Unit.DEZIDEGREE_CELSIUS));
this.addEntry(map, KEY_MIN_CELL_TEMPERATURE_ID, new IntegerDoc().unit(Unit.NONE));
this.addEntry(map, KEY_MIN_CELL_TEMPERATURE, new IntegerDoc().unit(Unit.DEZIDEGREE_CELSIUS));
- this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_DISCHA_TEMP_LOW, Doc.of(Level.FAULT)
+ this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_DISCHA_TEMP_LOW, Doc.of(Level.WARNING)
.text("Rack" + this.rackNumber + " Cell Discharge Temperature Low Alarm Level 2")); /* Bit 15 */
- this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_DISCHA_TEMP_HIGH, Doc.of(Level.FAULT)
+ this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_DISCHA_TEMP_HIGH, Doc.of(Level.WARNING)
.text("Rack" + this.rackNumber + " Cell Discharge Temperature High Alarm Level 2")); /* Bit 14 */
this.addEntry(map, KEY_ALARM_LEVEL_2_GR_TEMPERATURE_HIGH,
- Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " GR Temperature High Alarm Level 2")); /* Bit 10 */
- this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_CHA_TEMP_LOW, Doc.of(Level.FAULT)
+ Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " GR Temperature High Alarm Level 2")); /* Bit 10 */
+ this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_CHA_TEMP_LOW, Doc.of(Level.WARNING)
.text("Rack" + this.rackNumber + " Cell Charge Temperature Low Alarm Level 2")); /* Bit 7 */
- this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_CHA_TEMP_HIGH, Doc.of(Level.FAULT)
+ this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_CHA_TEMP_HIGH, Doc.of(Level.WARNING)
.text("Rack" + this.rackNumber + " Cell Charge Temperature High Alarm Level 2")); /* Bit 6 */
- this.addEntry(map, KEY_ALARM_LEVEL_2_DISCHA_CURRENT_HIGH, Doc.of(Level.FAULT)
+ this.addEntry(map, KEY_ALARM_LEVEL_2_DISCHA_CURRENT_HIGH, Doc.of(Level.WARNING)
.text("Rack" + this.rackNumber + " Discharge Current High Alarm Level 2")); /* Bit 5 */
this.addEntry(map, KEY_ALARM_LEVEL_2_TOTAL_VOLTAGE_LOW,
- Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Total Voltage Low Alarm Level 2")); /* Bit 4 */
+ Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Total Voltage Low Alarm Level 2")); /* Bit 4 */
this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_VOLTAGE_LOW,
- Doc.of(Level.FAULT).text("Cluster 1 Cell Voltage Low Alarm Level 2")); /* Bit 3 */
+ Doc.of(Level.WARNING).text("Cluster 1 Cell Voltage Low Alarm Level 2")); /* Bit 3 */
this.addEntry(map, KEY_ALARM_LEVEL_2_CHA_CURRENT_HIGH,
- Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Charge Current High Alarm Level 2")); /* Bit 2 */
+ Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Charge Current High Alarm Level 2")); /* Bit 2 */
this.addEntry(map, KEY_ALARM_LEVEL_2_TOTAL_VOLTAGE_HIGH,
- Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Total Voltage High Alarm Level 2")); /* Bit 1 */
+ Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Total Voltage High Alarm Level 2")); /* Bit 1 */
this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_VOLTAGE_HIGH,
- Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Cell Voltage High Alarm Level 2")); /* Bit 0 */
+ Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell Voltage High Alarm Level 2")); /* Bit 0 */
this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_DISCHA_TEMP_LOW, Doc.of(Level.WARNING)
.text("Rack" + this.rackNumber + " Cell Discharge Temperature Low Alarm Level 1")); /* Bit 15 */
this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_DISCHA_TEMP_HIGH, Doc.of(Level.WARNING)
@@ -492,23 +492,23 @@ private Map createChannelIdMap() {
this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_VOLTAGE_HIGH,
Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell Voltage High Alarm Level 1")); /* Bit 0 */
this.addEntry(map, KEY_RUN_STATE, Doc.of(Enums.ClusterRunState.values())); //
- this.addEntry(map, KEY_FAILURE_INITIALIZATION, Doc.of(Level.FAULT).text("Initialization failure")); /* Bit */
- this.addEntry(map, KEY_FAILURE_EEPROM, Doc.of(Level.FAULT).text("EEPROM fault")); /* Bit 11 */
+ this.addEntry(map, KEY_FAILURE_INITIALIZATION, Doc.of(Level.WARNING).text("Initialization failure")); /* Bit */
+ this.addEntry(map, KEY_FAILURE_EEPROM, Doc.of(Level.WARNING).text("EEPROM fault")); /* Bit 11 */
this.addEntry(map, KEY_FAILURE_INTRANET_COMMUNICATION,
- Doc.of(Level.FAULT).text("Internal communication fault")); /* Bit 10 */
+ Doc.of(Level.WARNING).text("Internal communication fault")); /* Bit 10 */
this.addEntry(map, KEY_FAILURE_TEMPERATURE_SENSOR_CABLE,
- Doc.of(Level.FAULT).text("Temperature sensor cable fault")); /* Bit 9 */
+ Doc.of(Level.WARNING).text("Temperature sensor cable fault")); /* Bit 9 */
this.addEntry(map, KEY_FAILURE_BALANCING_MODULE, Doc.of(Level.OK).text("Balancing module fault")); /* Bit 8 */
- this.addEntry(map, KEY_FAILURE_TEMPERATURE_PCB, Doc.of(Level.FAULT).text("Temperature PCB error")); /* Bit 7 */
- this.addEntry(map, KEY_FAILURE_GR_TEMPERATURE, Doc.of(Level.FAULT).text("GR Temperature error")); /* Bit 6 */
- this.addEntry(map, KEY_FAILURE_TEMP_SENSOR, Doc.of(Level.FAULT).text("Temperature sensor fault")); /* Bit 5 */
+ this.addEntry(map, KEY_FAILURE_TEMPERATURE_PCB, Doc.of(Level.WARNING).text("Temperature PCB error")); /* Bit 7 */
+ this.addEntry(map, KEY_FAILURE_GR_TEMPERATURE, Doc.of(Level.WARNING).text("GR Temperature error")); /* Bit 6 */
+ this.addEntry(map, KEY_FAILURE_TEMP_SENSOR, Doc.of(Level.WARNING).text("Temperature sensor fault")); /* Bit 5 */
this.addEntry(map, KEY_FAILURE_TEMP_SAMPLING,
- Doc.of(Level.FAULT).text("Temperature sampling fault")); /* Bit 4 */
+ Doc.of(Level.WARNING).text("Temperature sampling fault")); /* Bit 4 */
this.addEntry(map, KEY_FAILURE_VOLTAGE_SAMPLING,
- Doc.of(Level.FAULT).text("Voltage sampling fault")); /* Bit 3 */
- this.addEntry(map, KEY_FAILURE_LTC6803, Doc.of(Level.FAULT).text("LTC6803 fault")); /* Bit 2 */
- this.addEntry(map, KEY_FAILURE_CONNECTOR_WIRE, Doc.of(Level.FAULT).text("connector wire fault")); /* Bit 1 */
- this.addEntry(map, KEY_FAILURE_SAMPLING_WIRE, Doc.of(Level.FAULT).text("sampling wire fault")); /* Bit 0 */
+ Doc.of(Level.WARNING).text("Voltage sampling fault")); /* Bit 3 */
+ this.addEntry(map, KEY_FAILURE_LTC6803, Doc.of(Level.WARNING).text("LTC6803 fault")); /* Bit 2 */
+ this.addEntry(map, KEY_FAILURE_CONNECTOR_WIRE, Doc.of(Level.WARNING).text("connector wire fault")); /* Bit 1 */
+ this.addEntry(map, KEY_FAILURE_SAMPLING_WIRE, Doc.of(Level.WARNING).text("sampling wire fault")); /* Bit 0 */
this.addEntry(map, KEY_SLEEP, Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_WRITE));
this.addEntry(map, KEY_RESET, Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_WRITE));
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java
index 99f8fea4c9f..cec303bdbf7 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java
@@ -265,82 +265,82 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId
// Master BMS Alarm Registers
MASTER_EMS_COMMUNICATION_FAILURE(Doc.of(Level.WARNING) //
.text("Master EMS Communication Failure")),
- MASTER_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) //
+ MASTER_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) //
.text("Master PCS Control Failure")),
- MASTER_PCS_COMMUNICATION_FAILURE(Doc.of(Level.FAULT) //
+ MASTER_PCS_COMMUNICATION_FAILURE(Doc.of(Level.WARNING) //
.text("Master PCS Communication Failure")),
// Rack #1 cannot be paralleled to DC Bus reasons
- RACK_1_LEVEL_2_ALARM(Doc.of(Level.FAULT) //
+ RACK_1_LEVEL_2_ALARM(Doc.of(Level.WARNING) //
.text("Rack 1 Level 2 Alarm")),
- RACK_1_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) //
+ RACK_1_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 1 PCS Control Failure")),
- RACK_1_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) //
+ RACK_1_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 1 Communication to Master BMS Failure")),
- RACK_1_HARDWARE_FAILURE(Doc.of(Level.FAULT) //
+ RACK_1_HARDWARE_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 1 Hardware Failure")),
- RACK_1_OVER_CURRENT(Doc.of(Level.FAULT) //
+ RACK_1_OVER_CURRENT(Doc.of(Level.WARNING) //
.text("Rack 1 Too big circulating Current among clusters (>4A)")),
- RACK_1_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) //
+ RACK_1_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) //
.text("Rack 1 Too big boltage difference among clusters (>50V)")),
// Rack #2 cannot be paralleled to DC Bus reasons
- RACK_2_LEVEL_2_ALARM(Doc.of(Level.FAULT) //
+ RACK_2_LEVEL_2_ALARM(Doc.of(Level.WARNING) //
.text("Rack 2 Level 2 Alarm")),
- RACK_2_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) //
+ RACK_2_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 2 PCS Control Failure")),
- RACK_2_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) //
+ RACK_2_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 2 Communication to Master BMS Failure")),
- RACK_2_HARDWARE_FAILURE(Doc.of(Level.FAULT) //
+ RACK_2_HARDWARE_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 2 Hardware Failure")),
- RACK_2_OVER_CURRENT(Doc.of(Level.FAULT) //
+ RACK_2_OVER_CURRENT(Doc.of(Level.WARNING) //
.text("Rack 2 Too big circulating Current among clusters (>4A)")),
- RACK_2_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) //
+ RACK_2_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) //
.text("Rack 2 Too big boltage difference among clusters (>50V)")),
// Rack #3 cannot be paralleled to DC Bus reasons
- RACK_3_LEVEL_2_ALARM(Doc.of(Level.FAULT) //
+ RACK_3_LEVEL_2_ALARM(Doc.of(Level.WARNING) //
.text("Rack 3 Level 2 Alarm")),
- RACK_3_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) //
+ RACK_3_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 3 PCS Control Failure")),
- RACK_3_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) //
+ RACK_3_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 3 Communication to Master BMS Failure")),
- RACK_3_HARDWARE_FAILURE(Doc.of(Level.FAULT) //
+ RACK_3_HARDWARE_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 3 Hardware Failure")),
- RACK_3_OVER_CURRENT(Doc.of(Level.FAULT) //
+ RACK_3_OVER_CURRENT(Doc.of(Level.WARNING) //
.text("Rack 3 Too big circulating Current among clusters (>4A)")),
- RACK_3_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) //
+ RACK_3_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) //
.text("Rack 3 Too big boltage difference among clusters (>50V)")),
// Rack #4 cannot be paralleled to DC Bus reasons
- RACK_4_LEVEL_2_ALARM(Doc.of(Level.FAULT) //
+ RACK_4_LEVEL_2_ALARM(Doc.of(Level.WARNING) //
.text("Rack 4 Level 2 Alarm")),
- RACK_4_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) //
+ RACK_4_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 4 PCS Control Failure")),
- RACK_4_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) //
+ RACK_4_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 4 Communication to Master BMS Failure")),
- RACK_4_HARDWARE_FAILURE(Doc.of(Level.FAULT) //
+ RACK_4_HARDWARE_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 4 Hardware Failure")),
- RACK_4_OVER_CURRENT(Doc.of(Level.FAULT) //
+ RACK_4_OVER_CURRENT(Doc.of(Level.WARNING) //
.text("Rack 4 Too big circulating Current among clusters (>4A)")),
- RACK_4_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) //
+ RACK_4_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) //
.text("Rack 4 Too big boltage difference among clusters (>50V)")),
// Rack #5 cannot be paralleled to DC Bus reasons
- RACK_5_LEVEL_2_ALARM(Doc.of(Level.FAULT) //
+ RACK_5_LEVEL_2_ALARM(Doc.of(Level.WARNING) //
.text("Rack 5 Level 2 Alarm")),
- RACK_5_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) //
+ RACK_5_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 5 PCS Control Failure")),
- RACK_5_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) //
+ RACK_5_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 5 Communication to Master BMS Failure")),
- RACK_5_HARDWARE_FAILURE(Doc.of(Level.FAULT) //
+ RACK_5_HARDWARE_FAILURE(Doc.of(Level.WARNING) //
.text("Rack 5 Hardware Failure")),
- RACK_5_OVER_CURRENT(Doc.of(Level.FAULT) //
+ RACK_5_OVER_CURRENT(Doc.of(Level.WARNING) //
.text("Rack 5 Too big circulating Current among clusters (>4A)")),
- RACK_5_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) //
+ RACK_5_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) //
.text("Rack 5 Too big boltage difference among clusters (>50V)")),
// OpenEMS Faults
- RUN_FAILED(Doc.of(Level.FAULT) //
+ RUN_FAILED(Doc.of(Level.WARNING) //
.text("Running the Logic failed")), //
- MAX_START_ATTEMPTS(Doc.of(Level.FAULT) //
+ MAX_START_ATTEMPTS(Doc.of(Level.WARNING) //
.text("The maximum number of start attempts failed")), //
- MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) //
+ MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) //
.text("The maximum number of stop attempts failed")), //
NUMBER_OF_MODULES_PER_TOWER(Doc.of(OpenemsType.INTEGER) //
.persistencePriority(PersistencePriority.HIGH) //
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java
index ec13258ac7c..daee5253fa6 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java
@@ -463,27 +463,27 @@ public enum RackChannel {
LEVEL1_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) //
.text("Total Voltage High Alarm Level 1")), //
// Alarm Level 2
- LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.FAULT) //
+ LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.WARNING) //
.text("Discharge Temperature Low Alarm Level 2")), //
- LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.WARNING) //
.text("Discharge Temperature High Alarm Level 2")), //
- LEVEL2_INSULATION_VALUE(Doc.of(Level.FAULT) //
+ LEVEL2_INSULATION_VALUE(Doc.of(Level.WARNING) //
.text("Insulation Value Failure Alarm Level 2")), //
- LEVEL2_POWER_POLE_TEMP_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_POWER_POLE_TEMP_HIGH(Doc.of(Level.WARNING) //
.text("Power Pole temperature too high Alarm Level 2")), //
- LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.FAULT) //
+ LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.WARNING) //
.text("Cell Charge Temperature Low Alarm Level 2")), //
- LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.WARNING) //
.text("Charge Temperature High Alarm Level 2")), //
- LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) //
.text("Discharge Current High Alarm Level 2")), //
- LEVEL2_TOTAL_VOLTAGE_LOW(Doc.of(Level.FAULT) //
+ LEVEL2_TOTAL_VOLTAGE_LOW(Doc.of(Level.WARNING) //
.text("Total Voltage Low Alarm Level 2")), //
- LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.FAULT) //
+ LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.WARNING) //
.text("Cell Voltage Low Alarm Level 2")), //
- LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) //
.text("Charge Current High Alarm Level 2")), //
- LEVEL2_TOTAL_VOLTAGE_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) //
.text("Total Voltage High Alarm Level 2")), //
LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.INFO) //
.text("Cell Voltage High Alarm Level 2")), //
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionA.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionA.java
index 9ee4f1c6abf..7d65c2aacd1 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionA.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionA.java
@@ -681,29 +681,29 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
.text("Cluster 1 Total Voltage High Alarm Level 1")), //
ALARM_LEVEL_1_CELL_VOLTAGE_HIGH(Doc.of(Level.WARNING) //
.text("Cluster 1 Cell Voltage High Alarm Level 1")), //
- FAILURE_INITIALIZATION(Doc.of(Level.FAULT) //
+ FAILURE_INITIALIZATION(Doc.of(Level.WARNING) //
.text("Initialization failure")), //
- FAILURE_EEPROM(Doc.of(Level.FAULT) //
+ FAILURE_EEPROM(Doc.of(Level.WARNING) //
.text("EEPROM fault")), //
- FAILURE_INTRANET_COMMUNICATION(Doc.of(Level.FAULT) //
+ FAILURE_INTRANET_COMMUNICATION(Doc.of(Level.WARNING) //
.text("Intranet communication fault")), //
- FAILURE_TEMP_SAMPLING_LINE(Doc.of(Level.FAULT) //
+ FAILURE_TEMP_SAMPLING_LINE(Doc.of(Level.WARNING) //
.text("Temperature sampling line fault")), //
- FAILURE_BALANCING_MODULE(Doc.of(Level.FAULT) //
+ FAILURE_BALANCING_MODULE(Doc.of(Level.WARNING) //
.text("Balancing module fault")), //
- FAILURE_TEMP_SENSOR(Doc.of(Level.FAULT) //
+ FAILURE_TEMP_SENSOR(Doc.of(Level.WARNING) //
.text("Temperature sensor fault")), //
- FAILURE_TEMP_SAMPLING(Doc.of(Level.FAULT) //
+ FAILURE_TEMP_SAMPLING(Doc.of(Level.WARNING) //
.text("Temperature sampling fault")), //
- FAILURE_VOLTAGE_SAMPLING(Doc.of(Level.FAULT) //
+ FAILURE_VOLTAGE_SAMPLING(Doc.of(Level.WARNING) //
.text("Voltage sampling fault")), //
- FAILURE_LTC6803(Doc.of(Level.FAULT) //
+ FAILURE_LTC6803(Doc.of(Level.WARNING) //
.text("LTC6803 fault")), //
FAILURE_CONNECTOR_WIRE(Doc.of(Level.WARNING) //
.text("connector wire fault")), //
- FAILURE_SAMPLING_WIRE(Doc.of(Level.FAULT) //
+ FAILURE_SAMPLING_WIRE(Doc.of(Level.WARNING) //
.text("sampling wire fault")), //
- PRECHARGE_TAKING_TOO_LONG(Doc.of(Level.FAULT) //
+ PRECHARGE_TAKING_TOO_LONG(Doc.of(Level.WARNING) //
.text("precharge time was too long")), //
STATE_MACHINE(Doc.of(State.values()) //
.text("Current State of State-Machine")), //
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionB.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionB.java
index ecc741e2cc2..8434481d85c 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionB.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionB.java
@@ -995,11 +995,11 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId
.text("precharge time was too long")),
// OpenEMS Faults
- RUN_FAILED(Doc.of(Level.FAULT) //
+ RUN_FAILED(Doc.of(Level.WARNING) //
.text("Running the Logic failed")), //
- MAX_START_ATTEMPTS(Doc.of(Level.FAULT) //
+ MAX_START_ATTEMPTS(Doc.of(Level.WARNING) //
.text("The maximum number of start attempts failed")), //
- MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) //
+ MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) //
.text("The maximum number of stop attempts failed")), //
;
diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java
index 28a54d5b9af..1f57a137c60 100644
--- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java
+++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java
@@ -514,29 +514,29 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId
// Faults and warnings
// Alarm Level 2
- LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.FAULT) //
+ LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.WARNING) //
.text("Discharge Temperature Low Alarm Level 2")), //
- LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.WARNING) //
.text("Discharge Temperature High Alarm Level 2")), //
- LEVEL2_INSULATION_VALUE(Doc.of(Level.FAULT) //
+ LEVEL2_INSULATION_VALUE(Doc.of(Level.WARNING) //
.text("Insulation Value Failure Alarm Level 2")), //
- LEVEL2_POWER_POLE_TEMP_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_POWER_POLE_TEMP_HIGH(Doc.of(Level.WARNING) //
.text("Power Pole temperature too high Alarm Level 2")), //
- LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.FAULT) //
+ LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.WARNING) //
.text("Cell Charge Temperature Low Alarm Level 2")), //
- LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.WARNING) //
.text("Charge Temperature High Alarm Level 2")), //
- LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) //
.text("Discharge Current High Alarm Level 2")), //
- LEVEL2_TOTAL_VOLTAGE_LOW(Doc.of(Level.FAULT) //
+ LEVEL2_TOTAL_VOLTAGE_LOW(Doc.of(Level.WARNING) //
.text("Total Voltage Low Alarm Level 2")), //
- LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.FAULT) //
+ LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.WARNING) //
.text("Cell Voltage Low Alarm Level 2")), //
- LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) //
.text("Charge Current High Alarm Level 2")), //
- LEVEL2_TOTAL_VOLTAGE_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) //
.text("Total Voltage High Alarm Level 2")), //
- LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.FAULT) //
+ LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.WARNING) //
.text("Cell Voltage High Alarm Level 2")), //
// Alarm Level 1
@@ -672,11 +672,11 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId
.text("Slave 20 communication error")), //
// OpenEMS Faults
- RUN_FAILED(Doc.of(Level.FAULT) //
+ RUN_FAILED(Doc.of(Level.WARNING) //
.text("Running the Logic failed")), //
- MAX_START_ATTEMPTS(Doc.of(Level.FAULT) //
+ MAX_START_ATTEMPTS(Doc.of(Level.WARNING) //
.text("The maximum number of start attempts failed")), //
- MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) //
+ MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) //
.text("The maximum number of stop attempts failed")), //
;
diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImplTest.java
index eda7ce07667..be7f6db76ce 100644
--- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImplTest.java
+++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImplTest.java
@@ -1,9 +1,13 @@
package io.openems.edge.battery.soltaro.cluster.versionb;
+import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_1_COMMUNICATION_FAILURE;
+import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_2_COMMUNICATION_FAILURE;
+import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_3_COMMUNICATION_FAILURE;
+import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_4_COMMUNICATION_FAILURE;
+import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_5_COMMUNICATION_FAILURE;
+
import org.junit.Test;
-import io.openems.common.types.ChannelAddress;
-import io.openems.edge.battery.soltaro.cluster.SoltaroCluster;
import io.openems.edge.battery.soltaro.common.enums.BatteryState;
import io.openems.edge.battery.soltaro.common.enums.ModuleType;
import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
@@ -14,30 +18,16 @@
public class BatterySoltaroClusterVersionBImplTest {
- private static final String BATTERY_ID = "battery0";
- private static final String MODBUS_ID = "modbus0";
-
- private static final ChannelAddress SUB_MASTER_1_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID,
- SoltaroCluster.ChannelId.SUB_MASTER_1_COMMUNICATION_FAILURE.id());
- private static final ChannelAddress SUB_MASTER_2_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID,
- SoltaroCluster.ChannelId.SUB_MASTER_2_COMMUNICATION_FAILURE.id());
- private static final ChannelAddress SUB_MASTER_3_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID,
- SoltaroCluster.ChannelId.SUB_MASTER_3_COMMUNICATION_FAILURE.id());
- private static final ChannelAddress SUB_MASTER_4_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID,
- SoltaroCluster.ChannelId.SUB_MASTER_4_COMMUNICATION_FAILURE.id());
- private static final ChannelAddress SUB_MASTER_5_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID,
- SoltaroCluster.ChannelId.SUB_MASTER_5_COMMUNICATION_FAILURE.id());
-
@Test
public void test() throws Exception {
var sut = new BatterySoltaroClusterVersionBImpl();
new ComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager())
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(0) //
.setNumberOfSlaves(0) //
.setModuleType(ModuleType.MODULE_3_5_KWH) //
diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImplTest.java
index aaffbc2cc9e..ebcb21122ce 100644
--- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImplTest.java
+++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImplTest.java
@@ -9,17 +9,14 @@
public class BatterySoltaroClusterVersionCImplTest {
- private static final String BATTERY_ID = "battery0";
- private static final String MODBUS_ID = "modbus0";
-
@Test
public void test() throws Exception {
new ComponentTest(new BatterySoltaroClusterVersionCImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setStartStop(StartStopConfig.AUTO) //
.build()) //
;
diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImplTest.java
index 443dbce24c9..daccfa35f66 100644
--- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImplTest.java
+++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImplTest.java
@@ -9,17 +9,14 @@
public class BatterySoltaroSingleRackVersionAImplTest {
- private static final String BATTERY_ID = "battery0";
- private static final String MODBUS_ID = "modbus0";
-
@Test
public void test() throws Exception {
new ComponentTest(new BatterySoltaroSingleRackVersionAImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(0) //
.setErrorLevel2Delay(0) //
.setMaxStartTime(0) //
diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImplTest.java
index b2b33ff92fb..3fce33da342 100644
--- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImplTest.java
+++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImplTest.java
@@ -11,18 +11,15 @@
public class BatterySoltaroSingleRackVersionBImplTest {
- private static final String BATTERY_ID = "battery0";
- private static final String MODBUS_ID = "modbus0";
-
@Test
public void test() throws Exception {
new ComponentTest(new BatterySoltaroSingleRackVersionBImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager()) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(0) //
.setErrorLevel2Delay(0) //
.setMaxStartTime(0) //
diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImplTest.java
index 82e9a8c75d9..504d8d66b2a 100644
--- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImplTest.java
+++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImplTest.java
@@ -9,17 +9,14 @@
public class BatterySoltaroSingleRackVersionCImplTest {
- private static final String BATTERY_ID = "battery0";
- private static final String MODBUS_ID = "modbus0";
-
@Test
public void test() throws Exception {
new ComponentTest(new BatterySoltaroSingleRackVersionCImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("battery0") //
+ .setModbusId("modbus0") //
.setModbusUnitId(0) //
.setStartStop(StartStopConfig.AUTO) //
.build()) //
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 d9124765b01..b32ba021cd3 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
@@ -33,29 +33,40 @@ public interface BatteryInverterKacoBlueplanetGridsave extends ManagedSymmetricB
public static final int WATCHDOG_TRIGGER_SECONDS = 10;
public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
+
+ /*
+ * Whenever one of these states would be Level.FAULT, the EssGeneric will stop
+ * the battery and the inverter. If this is necessary, it must be specifically
+ * mentioned and the state should have a proper description of the fault.
+ */
+
STATE_MACHINE(Doc.of(State.values()) //
.text("Current State of State-Machine")), //
- RUN_FAILED(Doc.of(Level.FAULT) //
+ RUN_FAILED(Doc.of(Level.WARNING) //
.text("Running the Logic failed")), //
- MAX_START_TIMEOUT(Doc.of(Level.FAULT) //
+ MAX_START_TIMEOUT(Doc.of(Level.WARNING) //
.text("Max start time is exceeded")), //
- MAX_STOP_TIMEOUT(Doc.of(Level.FAULT) //
+ MAX_STOP_TIMEOUT(Doc.of(Level.WARNING) //
.text("Max stop time is exceeded")), //
- INVERTER_CURRENT_STATE_FAULT(Doc.of(Level.FAULT) //
+
+ /**
+ * Internal StateMachine from KACO.
+ */
+ INVERTER_CURRENT_STATE_FAULT(Doc.of(Level.WARNING) //
.text("The 'CurrentState' is invalid")), //
- GRID_DISCONNECTION(Doc.of(Level.FAULT) //
+ GRID_DISCONNECTION(Doc.of(Level.WARNING) //
.text("External grid protection disconnection (17)")), //
- GRID_FAILURE_LINE_TO_LINE(Doc.of(Level.FAULT) //
+ GRID_FAILURE_LINE_TO_LINE(Doc.of(Level.WARNING) //
.text("Grid failure phase-to-phase voltage (47)")), //
- LINE_FAILURE_UNDER_FREQ(Doc.of(Level.FAULT) //
+ LINE_FAILURE_UNDER_FREQ(Doc.of(Level.WARNING) //
.text("Line failure: Grid frequency is too low (48)")), //
- LINE_FAILURE_OVER_FREQ(Doc.of(Level.FAULT) //
+ LINE_FAILURE_OVER_FREQ(Doc.of(Level.WARNING) //
.text("Line failure: Grid frequency is too high (49)")), //
- PROTECTION_SHUTDOWN_LINE_1(Doc.of(Level.FAULT) //
+ PROTECTION_SHUTDOWN_LINE_1(Doc.of(Level.WARNING) //
.text("Grid Failure: grid voltage L1 protection (81)")), //
- PROTECTION_SHUTDOWN_LINE_2(Doc.of(Level.FAULT) //
+ PROTECTION_SHUTDOWN_LINE_2(Doc.of(Level.WARNING) //
.text("Grid Failure: grid voltage L2 protection (82)")), //
- PROTECTION_SHUTDOWN_LINE_3(Doc.of(Level.FAULT) //
+ PROTECTION_SHUTDOWN_LINE_3(Doc.of(Level.WARNING) //
.text("Grid Failure: grid voltage L3 protection (83)")), //
;
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 84ec0153305..9b5a3cefef3 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
@@ -28,6 +28,7 @@
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
+import io.openems.common.channel.AccessMode;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.exceptions.OpenemsException;
import io.openems.common.types.OptionsEnum;
@@ -57,6 +58,9 @@
import io.openems.edge.common.channel.value.Value;
import io.openems.edge.common.component.ComponentManager;
import io.openems.edge.common.component.OpenemsComponent;
+import io.openems.edge.common.modbusslave.ModbusSlave;
+import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable;
+import io.openems.edge.common.modbusslave.ModbusSlaveTable;
import io.openems.edge.common.startstop.StartStop;
import io.openems.edge.common.startstop.StartStoppable;
import io.openems.edge.common.taskmanager.Priority;
@@ -75,7 +79,7 @@
)
public class BatteryInverterKacoBlueplanetGridsaveImpl extends AbstractSunSpecBatteryInverter
implements BatteryInverterKacoBlueplanetGridsave, ManagedSymmetricBatteryInverter, SymmetricBatteryInverter,
- ModbusComponent, OpenemsComponent, TimedataProvider, StartStoppable {
+ ModbusComponent, ModbusSlave, OpenemsComponent, TimedataProvider, StartStoppable {
private static final int UNIT_ID = 1;
private static final int READ_FROM_MODBUS_BLOCK = 1;
@@ -140,7 +144,6 @@ protected void setModbus(BridgeModbus modbus) {
// .put(SunSpecModel.S_136, Priority.LOW) //
// .put(SunSpecModel.S_160, Priority.LOW) //
- @Activate
public BatteryInverterKacoBlueplanetGridsaveImpl() {
super(//
ACTIVE_MODELS, //
@@ -158,11 +161,11 @@ public BatteryInverterKacoBlueplanetGridsaveImpl() {
@Activate
private void activate(ComponentContext context, Config config) throws OpenemsException {
+ this.config = config;
if (super.activate(context, config.id(), config.alias(), config.enabled(), UNIT_ID, this.cm, "Modbus",
config.modbus_id(), READ_FROM_MODBUS_BLOCK)) {
return;
}
- this.config = config;
}
@Override
@@ -279,6 +282,16 @@ private void handleGridDisconnection() {
});
}
+ @Override
+ public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) {
+ return new ModbusSlaveTable(//
+ OpenemsComponent.getModbusSlaveNatureTable(accessMode), //
+ SymmetricBatteryInverter.getModbusSlaveNatureTable(accessMode), //
+ ManagedSymmetricBatteryInverter.getModbusSlaveNatureTable(accessMode), //
+ ModbusSlaveNatureTable.of(BatteryInverterKacoBlueplanetGridsave.class, accessMode, 100) //
+ .build());
+ }
+
@Override
public BatteryInverterConstraint[] getStaticConstraints() throws OpenemsException {
if (this.stateMachine.getCurrentState() == State.RUNNING) {
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 2757dbf6616..88974aeedf0 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
@@ -106,7 +106,8 @@ public static enum S64201 implements SunSpecPoint {
V_AR(new ScaledValuePoint("S64201_V_AR", "AC Reactive Power", "", //
ValuePoint.Type.INT16, true, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "V_AR_SF")), //
HZ(new ScaledValuePoint("S64201_HZ", "Line Frequency", "", //
- ValuePoint.Type.INT16, true, AccessMode.READ_ONLY, Unit.MILLIHERTZ, "mHZ_SF")), //
+ ValuePoint.Type.UINT16, true, AccessMode.READ_ONLY, Unit.HERTZ, "Hz_SF")
+ ), //
RESERVED_36(new ReservedPoint("S64201_RESERVED_36")), //
RESERVED_37(new ReservedPoint("S64201_RESERVED_37")), //
RESERVED_38(new ReservedPoint("S64201_RESERVED_38")), //
@@ -271,6 +272,7 @@ public static enum S64201StVnd implements OptionsEnum {
LINE_FAILURE_OVERVOLTAGE_3(46,
"Line failure overvoltage L3 The voltage of a grid phase is too low; the grid cannot be fed into. The phase experiencing failure is displayed."), //
GRID_FAILURE_PHASETOPHASE(47, "Grid failure phase-to-phase voltage"), //
+
LINE_FAILURE_UNDERFREQ(48,
"Line failure: underfreq. Grid frequency is too low. This fault may be gridrelated."), //
LINE_FAILURE_OVERFREQ(49,
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 8dc242a57b6..1424879da21 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
@@ -14,11 +14,18 @@ protected void onEntry(Context context) throws OpenemsNamedException {
}
@Override
- public State runAndGetNextState(Context context) throws OpenemsNamedException {
+ public State runAndGetNextState(Context context) {
final var inverter = context.getParent();
if (!inverter.hasFailure()) {
- return State.GO_STOPPED;
+ return State.UNDEFINED;
}
return State.ERROR;
}
+
+ @Override
+ protected void onExit(Context context) {
+ final var inverter = context.getParent();
+ inverter._setMaxStartTimeout(false);
+ inverter._setMaxStopTimeout(false);
+ }
}
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 4bc316e97c5..fc68eda27ec 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
@@ -1,15 +1,18 @@
package io.openems.edge.batteryinverter.kaco.blueplanetgridsave;
-import java.time.Instant;
-import java.time.ZoneOffset;
-import java.time.temporal.ChronoUnit;
+import static io.openems.edge.batteryinverter.api.SymmetricBatteryInverter.ChannelId.MAX_APPARENT_POWER;
+import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TIMEOUT_SECONDS;
+import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TRIGGER_SECONDS;
+import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.BatteryInverterKacoBlueplanetGridsave.ChannelId.STATE_MACHINE;
+import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.CURRENT_STATE;
+import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.WATCHDOG;
+import static java.time.temporal.ChronoUnit.SECONDS;
import org.junit.Before;
import org.junit.Test;
import io.openems.common.exceptions.OpenemsException;
import io.openems.common.test.TimeLeapClock;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.battery.api.Battery;
import io.openems.edge.battery.test.DummyBattery;
import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.S64201CurrentState;
@@ -24,25 +27,13 @@
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.common.test.DummyConfigurationAdmin;
+import io.openems.edge.common.test.TestUtils;
public class BatteryInverterKacoBlueplanetGridsaveImplTest {
- private static final String BATTERY_INVERTER_ID = "batteryInverter0";
- private static final String BATTERY_ID = "battery0";
- private static final String MODBUS_ID = "modbus0";
-
- private static final ChannelAddress STATE_MACHINE = new ChannelAddress(BATTERY_INVERTER_ID, "StateMachine");
-
- private static final ChannelAddress MAX_APPARENT_POWER = new ChannelAddress(BATTERY_INVERTER_ID,
- "MaxApparentPower");
- private static final ChannelAddress CURRENT_STATE = new ChannelAddress(BATTERY_INVERTER_ID,
- KacoSunSpecModel.S64201.CURRENT_STATE.getChannelId().id());
- private static final ChannelAddress WATCHDOG = new ChannelAddress(BATTERY_INVERTER_ID,
- KacoSunSpecModel.S64201.WATCHDOG.getChannelId().id());
-
private static class MyComponentTest extends ComponentTest {
- private final Battery battery = new DummyBattery(BATTERY_ID);
+ private final Battery battery = new DummyBattery("battery0");
public MyComponentTest(OpenemsComponent sut) throws OpenemsException {
super(sut);
@@ -63,15 +54,13 @@ protected void handleEvent(String topic) throws Exception {
@Before
public void prepareTest() throws Exception {
- final var start = 1577836800L;
- clock = new TimeLeapClock(Instant.ofEpochSecond(start) /* starts at 1. January 2020 00:00:00 */,
- ZoneOffset.UTC);
+ clock = TestUtils.createDummyClock();
var sut = new BatteryInverterKacoBlueplanetGridsaveImpl();
test = new MyComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID));
+ .addReference("setModbus", new DummyModbusBridge("modbus0"));
// TODO implement proper Dummy-Modbus-Bridge with SunSpec support. Till then...
test.addReference("isSunSpecInitializationCompleted", true); //
@@ -86,16 +75,16 @@ public void prepareTest() throws Exception {
addChannel.invoke(sut, KacoSunSpecModel.S64202.CHA_MAX_A_0.getChannelId());
addChannel.invoke(sut, KacoSunSpecModel.S64202.EN_LIMIT_0.getChannelId());
addChannel.invoke(sut, KacoSunSpecModel.S64201.REQUESTED_STATE.getChannelId());
- addChannel.invoke(sut, KacoSunSpecModel.S64201.CURRENT_STATE.getChannelId());
- addChannel.invoke(sut, KacoSunSpecModel.S64201.WATCHDOG.getChannelId());
+ addChannel.invoke(sut, CURRENT_STATE.getChannelId());
+ addChannel.invoke(sut, 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) //
+ .setId("batteryInverter0") //
.setStartStopConfig(StartStopConfig.START) //
- .setModbusId(MODBUS_ID) //
+ .setModbusId("modbus0") //
.setActivateWatchdog(true) //
.build()); //
}
@@ -104,16 +93,16 @@ public void prepareTest() throws Exception {
public void testStart() throws Exception {
test //
.next(new TestCase() //
- .input(CURRENT_STATE, S64201CurrentState.STANDBY) //
+ .input(CURRENT_STATE.getChannelId(), S64201CurrentState.STANDBY) //
.input(MAX_APPARENT_POWER, 50_000) //
.output(STATE_MACHINE, State.UNDEFINED)) //
.next(new TestCase() //
- .timeleap(clock, 4, ChronoUnit.SECONDS) //
+ .timeleap(clock, 4, SECONDS) //
.output(STATE_MACHINE, State.GO_RUNNING)) //
.next(new TestCase() //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .input(CURRENT_STATE, S64201CurrentState.GRID_CONNECTED) //
- .output(WATCHDOG, BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TIMEOUT_SECONDS)) //
+ .timeleap(clock, 1, SECONDS) //
+ .input(CURRENT_STATE.getChannelId(), S64201CurrentState.GRID_CONNECTED) //
+ .output(WATCHDOG.getChannelId(), WATCHDOG_TIMEOUT_SECONDS)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.RUNNING)) //
;
@@ -123,14 +112,13 @@ public void testStart() throws Exception {
public void testWatchdog() throws Exception {
test //
.next(new TestCase() //
- .output(WATCHDOG, BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TIMEOUT_SECONDS)) //
+ .output(WATCHDOG.getChannelId(), WATCHDOG_TIMEOUT_SECONDS)) //
.next(new TestCase() //
- .timeleap(clock, BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TRIGGER_SECONDS - 1,
- ChronoUnit.SECONDS) //
- .output(WATCHDOG, null /* waiting till next watchdog trigger */)) //
+ .timeleap(clock, WATCHDOG_TRIGGER_SECONDS - 1, SECONDS) //
+ .output(WATCHDOG.getChannelId(), null /* waiting till next watchdog trigger */)) //
.next(new TestCase() //
- .timeleap(clock, 1, ChronoUnit.SECONDS) //
- .output(WATCHDOG, BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TIMEOUT_SECONDS)) //
+ .timeleap(clock, 1, SECONDS) //
+ .output(WATCHDOG.getChannelId(), WATCHDOG_TIMEOUT_SECONDS)) //
;
}
}
diff --git a/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88k.java b/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88k.java
index 2a9e69ceb53..64a0473325d 100644
--- a/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88k.java
+++ b/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88k.java
@@ -43,16 +43,22 @@ public interface BatteryInverterRefuStore88k
*/
public static int RETRY_COMMAND_MAX_ATTEMPTS = 30;
+ /*
+ * Whenever one of these states would be Level.FAULT, the EssGeneric will stop
+ * the battery and the inverter. If this is necessary, it must be specifically
+ * mentioned and the state should have a proper description of the fault.
+ */
+
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) //
+ RUN_FAILED(Doc.of(Level.WARNING) //
.text("Running the Logic failed")), //
- MAX_START_ATTEMPTS(Doc.of(Level.FAULT) //
+ MAX_START_ATTEMPTS(Doc.of(Level.WARNING) //
.text("The maximum number of start attempts failed")), //
- MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) //
+ MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) //
.text("The maximum number of stop attempts failed")), //
- INVERTER_CURRENT_STATE_FAULT(Doc.of(Level.FAULT) //
+ INVERTER_CURRENT_STATE_FAULT(Doc.of(Level.WARNING) //
.text("The 'CurrentState' is invalid")), //
/*
@@ -106,41 +112,41 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
ST(Doc.of(OperatingState.values())), //
ST_VND(Doc.of(VendorOperatingState.values())), //
// Evt1 Alarms and Warnings
- GROUND_FAULT(Doc.of(Level.FAULT) //
+ GROUND_FAULT(Doc.of(Level.WARNING) //
.text("Ground fault")), //
- DC_OVER_VOLTAGE(Doc.of(Level.FAULT) //
+ DC_OVER_VOLTAGE(Doc.of(Level.WARNING) //
.text("Dc over voltage")), //
- AC_DISCONNECT(Doc.of(Level.FAULT) //
+ AC_DISCONNECT(Doc.of(Level.WARNING) //
.text("AC disconnect open")), //
- DC_DISCONNECT(Doc.of(Level.FAULT) //
+ DC_DISCONNECT(Doc.of(Level.WARNING) //
.text("DC disconnect open")), //
- GRID_DISCONNECT(Doc.of(Level.FAULT) //
+ GRID_DISCONNECT(Doc.of(Level.WARNING) //
.text("Grid shutdown")), //
- CABINET_OPEN(Doc.of(Level.FAULT) //
+ CABINET_OPEN(Doc.of(Level.WARNING) //
.text("Cabinet open")), //
- MANUAL_SHUTDOWN(Doc.of(Level.FAULT) //
+ MANUAL_SHUTDOWN(Doc.of(Level.WARNING) //
.text("Manual shutdown")), //
- OVER_TEMP(Doc.of(Level.FAULT) //
+ OVER_TEMP(Doc.of(Level.WARNING) //
.text("Over temperature")), //
- OVER_FREQUENCY(Doc.of(Level.FAULT) //
+ OVER_FREQUENCY(Doc.of(Level.WARNING) //
.text("Frequency above limit")), //
- UNDER_FREQUENCY(Doc.of(Level.FAULT) //
+ UNDER_FREQUENCY(Doc.of(Level.WARNING) //
.text("Frequency under limit")), //
- AC_OVER_VOLT(Doc.of(Level.FAULT) //
+ AC_OVER_VOLT(Doc.of(Level.WARNING) //
.text("AC Voltage above limit")), //
- AC_UNDER_VOLT(Doc.of(Level.FAULT) //
+ AC_UNDER_VOLT(Doc.of(Level.WARNING) //
.text("AC Voltage under limit")), //
- BLOWN_STRING_FUSE(Doc.of(Level.FAULT) //
+ BLOWN_STRING_FUSE(Doc.of(Level.WARNING) //
.text("Blown String fuse on input")), //
- UNDER_TEMP(Doc.of(Level.FAULT) //
+ UNDER_TEMP(Doc.of(Level.WARNING) //
.text("Under temperature")), //
- MEMORY_LOSS(Doc.of(Level.FAULT) //
+ MEMORY_LOSS(Doc.of(Level.WARNING) //
.text("Generic Memory or Communication error (internal)")), //
- HW_TEST_FAILURE(Doc.of(Level.FAULT) //
+ HW_TEST_FAILURE(Doc.of(Level.WARNING) //
.text("Hardware test failure")), //
- OTHER_ALARM(Doc.of(Level.FAULT) //
+ OTHER_ALARM(Doc.of(Level.WARNING) //
.text("Other alarm")), //
- OTHER_WARNING(Doc.of(Level.FAULT) //
+ OTHER_WARNING(Doc.of(Level.WARNING) //
.text("Other warning")), //
EVT_2(Doc.of(OpenemsType.INTEGER).unit(Unit.NONE).accessMode(AccessMode.READ_ONLY)), //
EVT_VND_1(Doc.of(OpenemsType.INTEGER).unit(Unit.NONE).accessMode(AccessMode.READ_ONLY)), //
diff --git a/io.openems.edge.batteryinverter.refu88k/test/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImplTest.java b/io.openems.edge.batteryinverter.refu88k/test/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImplTest.java
index 08316f856cf..560aa3bb4f3 100644
--- a/io.openems.edge.batteryinverter.refu88k/test/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImplTest.java
+++ b/io.openems.edge.batteryinverter.refu88k/test/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImplTest.java
@@ -9,17 +9,14 @@
public class BatteryInverterRefuStore88kImplTest {
- private static final String BATTERY_INVERTER_ID = "batteryInverter0";
- private static final String MODBUS_ID = "modbus0";
-
@Test
public void test() throws Exception {
new ComponentTest(new BatteryInverterRefuStore88kImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_INVERTER_ID) //
- .setModbusId(MODBUS_ID) //
+ .setId("batteryInverter0") //
+ .setModbusId("modbus0") //
.setStartStop(StartStopConfig.AUTO) //
.setTimeLimitNoPower(0) //
.setWatchdoginterval(0) //
diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java
index daa20ac5029..d54e8bbe2d1 100644
--- a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java
+++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java
@@ -44,7 +44,7 @@ public interface BatteryInverterSinexcel extends OffGridBatteryInverter, Managed
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) //
+ RUN_FAILED(Doc.of(Level.WARNING) //
.text("Running the Logic failed")), //
SET_ACTIVE_POWER(Doc.of(OpenemsType.INTEGER) //
.accessMode(AccessMode.READ_WRITE)//
@@ -96,7 +96,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
SERIAL_NUMBER(Doc.of(OpenemsType.STRING) //
.persistencePriority(PersistencePriority.HIGH) //
.accessMode(AccessMode.READ_ONLY)), //
- FAULT_STATUS(Doc.of(Level.FAULT) //
+ FAULT_STATUS(Doc.of(Level.WARNING) //
.accessMode(AccessMode.READ_ONLY)), //
ALERT_STATUS(Doc.of(Level.WARNING) //
.accessMode(AccessMode.READ_ONLY)), //
diff --git a/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImplTest.java b/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImplTest.java
index 6092ebde7d6..abf144c927e 100644
--- a/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImplTest.java
+++ b/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImplTest.java
@@ -1,5 +1,11 @@
package io.openems.edge.batteryinverter.sinexcel;
+import static io.openems.edge.batteryinverter.api.OffGridBatteryInverter.ChannelId.INVERTER_STATE;
+import static io.openems.edge.batteryinverter.api.SymmetricBatteryInverter.ChannelId.MAX_APPARENT_POWER;
+import static io.openems.edge.batteryinverter.sinexcel.BatteryInverterSinexcel.ChannelId.SET_OFF_GRID_MODE;
+import static io.openems.edge.batteryinverter.sinexcel.BatteryInverterSinexcel.ChannelId.SET_ON_GRID_MODE;
+import static io.openems.edge.batteryinverter.sinexcel.BatteryInverterSinexcel.ChannelId.STATE_MACHINE;
+
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
@@ -8,10 +14,8 @@
import io.openems.common.exceptions.OpenemsException;
import io.openems.common.test.TimeLeapClock;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.battery.api.Battery;
import io.openems.edge.battery.test.DummyBattery;
-import io.openems.edge.batteryinverter.api.OffGridBatteryInverter;
import io.openems.edge.batteryinverter.api.OffGridBatteryInverter.TargetGridMode;
import io.openems.edge.batteryinverter.sinexcel.enums.CountryCode;
import io.openems.edge.batteryinverter.sinexcel.enums.EnableDisable;
@@ -27,21 +31,9 @@
public class BatteryInverterSinexcelImplTest {
- private static final String BATTERY_INVERTER_ID = "batteryInverter0";
- private static final String BATTERY_ID = "battery0";
- private static final String MODBUS_ID = "modbus0";
- private static final ChannelAddress STATE_MACHINE = new ChannelAddress(BATTERY_INVERTER_ID, "StateMachine");
- private static final ChannelAddress SET_ON_GRID_MODE = new ChannelAddress(BATTERY_INVERTER_ID, "SetOnGridMode");
- private static final ChannelAddress SET_OFF_GRID_MODE = new ChannelAddress(BATTERY_INVERTER_ID, "SetOffGridMode");
- private static final ChannelAddress MAX_APPARENT_POWER = new ChannelAddress(BATTERY_INVERTER_ID, //
- "MaxApparentPower");
-
- private static final ChannelAddress INVERTER_STATE = new ChannelAddress(BATTERY_INVERTER_ID, //
- OffGridBatteryInverter.ChannelId.INVERTER_STATE.id());
-
private static class MyComponentTest extends ComponentTest {
- private final Battery battery = new DummyBattery(BATTERY_ID);
+ private final Battery battery = new DummyBattery("battery0");
public MyComponentTest(OpenemsComponent sut) throws OpenemsException {
super(sut);
@@ -64,11 +56,11 @@ public void testStart() throws Exception {
new MyComponentTest(new BatteryInverterSinexcelImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_INVERTER_ID) //
+ .setId("batteryInverter0") //
.setStartStopConfig(StartStopConfig.START) //
- .setModbusId(MODBUS_ID) //
+ .setModbusId("modbus0") //
.setCountryCode(CountryCode.GERMANY)//
.setEmergencyPower(EnableDisable.DISABLE)//
.build()) //
@@ -100,11 +92,11 @@ public void testOffGrid() throws Exception {
new MyComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0")) //
.activate(MyConfig.create() //
- .setId(BATTERY_INVERTER_ID) //
+ .setId("batteryInverter0") //
.setStartStopConfig(StartStopConfig.START) //
- .setModbusId(MODBUS_ID) //
+ .setModbusId("modbus0") //
.setCountryCode(CountryCode.GERMANY)//
.setEmergencyPower(EnableDisable.DISABLE)//
.build()) //
diff --git a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterImpl.java b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterImpl.java
index 74c10e22aad..03f72961afa 100644
--- a/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterImpl.java
+++ b/io.openems.edge.bosch.bpts5hybrid/src/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterImpl.java
@@ -17,13 +17,13 @@
import org.osgi.service.metatype.annotations.Designate;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.types.MeterType;
import io.openems.edge.bosch.bpts5hybrid.core.BoschBpts5HybridCore;
import io.openems.edge.common.channel.Doc;
import io.openems.edge.common.component.AbstractOpenemsComponent;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.event.EdgeEventConstants;
import io.openems.edge.meter.api.ElectricityMeter;
-import io.openems.edge.meter.api.MeterType;
@Designate(ocd = Config.class, factory = true)
@Component(//
diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCoreImplTest.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCoreImplTest.java
index b7f597fbc35..e13cd08a7ab 100644
--- a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCoreImplTest.java
+++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/core/BoschBpts5HybridCoreImplTest.java
@@ -2,6 +2,7 @@
import org.junit.Test;
+import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.common.test.DummyConfigurationAdmin;
@@ -21,6 +22,7 @@ public void test() throws Exception {
.setIpaddress("127.0.0.1") //
.setInterval(2) //
.build()) //
- ;
+ .next(new TestCase()) //
+ .deactivate();
}
}
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/ess/BoschBpts5HybridEssTest.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/ess/BoschBpts5HybridEssTest.java
index c45058b22e5..0a2d9d77811 100644
--- a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/ess/BoschBpts5HybridEssTest.java
+++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/ess/BoschBpts5HybridEssTest.java
@@ -3,6 +3,7 @@
import org.junit.Test;
import io.openems.edge.bosch.bpts5hybrid.core.BoschBpts5HybridCoreImpl;
+import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.common.test.DummyConfigurationAdmin;
@@ -30,6 +31,7 @@ public void test() throws Exception {
.setId(ESS_ID) //
.setCoreId(CORE_ID) //
.build()) //
- ;
+ .next(new TestCase()) //
+ .deactivate();
}
}
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterTest.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterTest.java
index 476489b3ef7..1d3af580ac8 100644
--- a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterTest.java
+++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/meter/BoschBpts5HybridMeterTest.java
@@ -3,6 +3,7 @@
import org.junit.Test;
import io.openems.edge.bosch.bpts5hybrid.core.BoschBpts5HybridCoreImpl;
+import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.common.test.DummyConfigurationAdmin;
@@ -30,6 +31,7 @@ public void test() throws Exception {
.setId(METER_ID) //
.setCoreId(CORE_ID) //
.build()) //
- ;
+ .next(new TestCase()) //
+ .deactivate();
}
}
\ No newline at end of file
diff --git a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/pv/BoschBpts5HybridPvTest.java b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/pv/BoschBpts5HybridPvTest.java
index 5d0041227f6..8a411cf25e0 100644
--- a/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/pv/BoschBpts5HybridPvTest.java
+++ b/io.openems.edge.bosch.bpts5hybrid/test/io/openems/edge/bosch/bpts5hybrid/pv/BoschBpts5HybridPvTest.java
@@ -3,6 +3,7 @@
import org.junit.Test;
import io.openems.edge.bosch.bpts5hybrid.core.BoschBpts5HybridCoreImpl;
+import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.common.test.DummyConfigurationAdmin;
@@ -30,6 +31,7 @@ public void test() throws Exception {
.setId(CHARGER_ID) //
.setCoreId(CORE_ID) //
.build()) //
- ;
+ .next(new TestCase()) //
+ .deactivate();
}
}
\ No newline at end of file
diff --git a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/api/UrlBuilder.java b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/api/UrlBuilder.java
new file mode 100644
index 00000000000..095b1fe2c24
--- /dev/null
+++ b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/api/UrlBuilder.java
@@ -0,0 +1,246 @@
+package io.openems.edge.bridge.http.api;
+
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toUnmodifiableMap;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+
+/**
+ * Simple URL Builder class to build URLs with correctly encoded query
+ * parameter. This class is immutable.
+ *
+ *
+ * Example Usage:
+ *
+ *
+ *
+ * final var url = UrlBuilder.create() //
+ .withScheme("https") //
+ .withHost("openems.io") //
+ .withPort(443) //
+ .withPath("/path/to") //
+ .withQueryParam("key", "value") //
+ .withFragment("fragment") //
+ // get final result
+ .toUri() // to get a URI Object
+ .toUrl() // to get a URL Object
+ .toEncodedString() // to get the URL as a encoded String
+ *
+ * or parse from a string
+ *
+ * final var url = UrlBuilder.from("https://openems.io:443/path?key=value#fragment");
+ *
+ *
+ *
+ *
+ * URL-Schema:
+ * scheme://host:port/path?queryParams#fragment
+ *
+ */
+public final class UrlBuilder {
+
+ /**
+ * Parses a raw uri string to the url parts.
+ *
+ * @param uriString the raw string to parse
+ * @return the {@link UrlBuilder} with the parts of th uri
+ */
+ public static UrlBuilder parse(String uriString) {
+ final var uri = URI.create(uriString);
+
+ final var query = uri.getQuery();
+ final var queryParams = query == null ? Collections.emptyMap()
+ : Stream.of(uri.getQuery().split("&")) //
+ .map(t -> t.split("=")) //
+ .collect(toUnmodifiableMap(t -> t[0], t -> t[1]));
+ return new UrlBuilder(//
+ uri.getScheme(), //
+ uri.getHost(), //
+ uri.getPort(), //
+ uri.getPath(), //
+ queryParams, //
+ uri.getFragment() //
+ );
+ }
+
+ /**
+ * Creates a new {@link UrlBuilder}.
+ *
+ * @return the new {@link UrlBuilder} instance
+ */
+ public static UrlBuilder create() {
+ return new UrlBuilder(null, null, null, null, Collections.emptyMap(), null);
+ }
+
+ private final String scheme;
+ private final String host;
+ private final Integer port;
+ private final String path;
+ private final Map queryParams;
+ private final String fragment;
+
+ private UrlBuilder(//
+ String scheme, //
+ String host, //
+ Integer port, //
+ String path, //
+ Map queryParams, //
+ String fragment //
+ ) {
+ this.scheme = scheme;
+ this.host = host;
+ this.port = port;
+ this.path = path;
+ this.queryParams = queryParams;
+ this.fragment = fragment;
+ }
+
+ /**
+ * Creates a copy of the current {@link UrlBuilder} with the new scheme.
+ *
+ * @param scheme the new scheme
+ * @return the copy of the {@link UrlBuilder} with the new scheme
+ */
+ public UrlBuilder withScheme(String scheme) {
+ return new UrlBuilder(scheme, this.host, this.port, this.path, this.queryParams, this.fragment);
+ }
+
+ /**
+ * Creates a copy of the current {@link UrlBuilder} with the new host.
+ *
+ * @param host the new host
+ * @return the copy of the {@link UrlBuilder} with the new host
+ */
+ public UrlBuilder withHost(String host) {
+ return new UrlBuilder(this.scheme, host, this.port, this.path, this.queryParams, this.fragment);
+ }
+
+ /**
+ * Creates a copy of the current {@link UrlBuilder} with the new port.
+ *
+ * @param port the new port
+ * @return the copy of the {@link UrlBuilder} with the new port
+ */
+ public UrlBuilder withPort(int port) {
+ if (port < 0) {
+ throw new IllegalArgumentException("Property 'port' must not be smaller than '0'.");
+ }
+ return new UrlBuilder(this.scheme, this.host, port, this.path, this.queryParams, this.fragment);
+ }
+
+ /**
+ * Creates a copy of the current {@link UrlBuilder} with the new path.
+ *
+ * @param path the new path
+ * @return the copy of the {@link UrlBuilder} with the new path
+ */
+ public UrlBuilder withPath(String path) {
+ return new UrlBuilder(this.scheme, this.host, this.port, path, this.queryParams, this.fragment);
+ }
+
+ /**
+ * Creates a copy of the current {@link UrlBuilder} with the new query parameter
+ * added.
+ *
+ * @param key the key of the new query parameter
+ * @param value the value of the new query parameter
+ * @return the copy of the {@link UrlBuilder} with the new query parameter added
+ */
+ public UrlBuilder withQueryParam(String key, String value) {
+ Map newQueryParams = new HashMap<>(this.queryParams);
+ newQueryParams.put(key, value);
+ return new UrlBuilder(this.scheme, this.host, this.port, this.path, Collections.unmodifiableMap(newQueryParams),
+ this.fragment);
+ }
+
+ /**
+ * Creates a copy of the current {@link UrlBuilder} with the new fragment.
+ *
+ * @param fragment the new fragment
+ * @return the copy of the {@link UrlBuilder} with the new fragment
+ */
+ public UrlBuilder withFragment(String fragment) {
+ return new UrlBuilder(this.scheme, this.host, this.port, this.path, this.queryParams, fragment);
+ }
+
+ /**
+ * Creates a {@link URI} from this object.
+ *
+ * @return the {@link URI}
+ */
+ public URI toUri() {
+ return URI.create(this.toEncodedString());
+ }
+
+ /**
+ * Creates a {@link URI} from this object.
+ *
+ * @return the {@link URI}
+ * @throws MalformedURLException If a protocol handler for the URL could not be
+ * found, or if some other error occurred while
+ * constructing the URL
+ */
+ public URL toUrl() throws MalformedURLException {
+ return this.toUri().toURL();
+ }
+
+ /**
+ * Creates an encoded string url from this object.
+ *
+ *
+ * Note: does not check if the url is valid. To Check if it is valid use
+ * {@link #toUrl()}
+ *
+ * @return the encoded url
+ */
+ public String toEncodedString() {
+ final var url = new StringBuilder();
+
+ url.append(this.scheme);
+ url.append("://");
+ url.append(this.host);
+
+ if (this.port != null) {
+ url.append(":");
+ url.append(this.port);
+ }
+
+ if (this.path != null && !this.path.isEmpty()) {
+ if (!this.path.startsWith("/")) {
+ url.append("/");
+ }
+ url.append(this.path);
+ }
+
+ if (!this.queryParams.isEmpty()) {
+ var query = this.queryParams.entrySet().stream() //
+ .map(t -> encode(t.getKey()) + "=" + encode(t.getValue())) //
+ .collect(joining("&", "?", ""));
+ url.append(query);
+ }
+
+ if (this.fragment != null && !this.fragment.isEmpty()) {
+ if (!this.fragment.startsWith("#")) {
+ url.append("#");
+ }
+ url.append(this.fragment);
+ }
+
+ return url.toString();
+ }
+
+ // Helper method to URL-encode values
+ private static String encode(String value) {
+ return URLEncoder.encode(value, StandardCharsets.UTF_8) //
+ .replace("+", "%20") // " " => "+" => "%20"
+ ;
+ }
+}
diff --git a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpBundle.java b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpBundle.java
index befb0e1ebca..fcd0d24b56d 100644
--- a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpBundle.java
+++ b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpBundle.java
@@ -1,5 +1,6 @@
package io.openems.edge.bridge.http.dummy;
+import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.dummyEndpointFetcher;
import static java.util.Collections.emptyMap;
import java.util.concurrent.CompletableFuture;
@@ -7,7 +8,6 @@
import org.osgi.service.event.Event;
-import io.openems.common.test.TimeLeapClock;
import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint;
import io.openems.edge.bridge.http.api.BridgeHttpFactory;
import io.openems.edge.bridge.http.api.CycleSubscriber;
@@ -17,9 +17,8 @@
public class DummyBridgeHttpBundle {
- private final DummyEndpointFetcher fetcher = DummyBridgeHttpFactory.dummyEndpointFetcher();
- private final DummyBridgeHttpExecutor pool = DummyBridgeHttpFactory.dummyBridgeHttpExecutor(new TimeLeapClock(),
- true);
+ private final DummyEndpointFetcher fetcher = dummyEndpointFetcher();
+ private final DummyBridgeHttpExecutor pool = DummyBridgeHttpFactory.dummyBridgeHttpExecutor(true);
private final CycleSubscriber cycleSubscriber = DummyBridgeHttpFactory.cycleSubscriber();
private final DummyBridgeHttpFactory bridgeFactory = DummyBridgeHttpFactory.ofBridgeImpl(() -> this.cycleSubscriber,
() -> this.fetcher, () -> this.pool);
diff --git a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpExecutor.java b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpExecutor.java
index 2a74f1c7ea0..cf1d07df092 100644
--- a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpExecutor.java
+++ b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpExecutor.java
@@ -1,5 +1,7 @@
package io.openems.edge.bridge.http.dummy;
+import static io.openems.edge.common.test.TestUtils.createDummyClock;
+
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
@@ -110,10 +112,18 @@ public DummyBridgeHttpExecutor(Clock clock, boolean handleTasksImmediately) {
this.taskExecutor = handleTasksImmediately ? new ImmediateTaskExecutor() : new DelayedTaskExecutor();
}
+ public DummyBridgeHttpExecutor(boolean handleTasksImmediately) {
+ this(createDummyClock(), handleTasksImmediately);
+ }
+
public DummyBridgeHttpExecutor(Clock clock) {
this(clock, false);
}
+ public DummyBridgeHttpExecutor() {
+ this(false);
+ }
+
@Override
public ScheduledFuture> schedule(Runnable task, Delay.DurationDelay durationDelay) {
if (this.isShutdown()) {
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 635b56ad885..01f1f169e83 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
@@ -124,6 +124,21 @@ public static DummyBridgeHttpExecutor dummyBridgeHttpExecutor(//
return new DummyBridgeHttpExecutor(clock, handleTasksImmediately);
}
+ /**
+ * Creates a {@link DummyBridgeHttpExecutor} to handle the execution of the
+ * requests to fetch an {@link Endpoint}.
+ *
+ * @param handleTasksImmediately true if all tasks which are not scheduled
+ * should be executed immediately in the same
+ * thread; false if only executed during the
+ * {@link DummyBridgeHttpExecutor#update()}
+ * method.
+ * @return the created {@link DummyBridgeHttpExecutor}
+ */
+ public static DummyBridgeHttpExecutor dummyBridgeHttpExecutor(boolean handleTasksImmediately) {
+ return new DummyBridgeHttpExecutor(handleTasksImmediately);
+ }
+
/**
* Creates a {@link DummyBridgeHttpExecutor} to handle the execution of the
* requests to fetch an {@link Endpoint}.
@@ -136,6 +151,16 @@ public static DummyBridgeHttpExecutor dummyBridgeHttpExecutor(Clock clock) {
return new DummyBridgeHttpExecutor(clock);
}
+ /**
+ * Creates a {@link DummyBridgeHttpExecutor} to handle the execution of the
+ * requests to fetch an {@link Endpoint}.
+ *
+ * @return the created {@link DummyBridgeHttpExecutor}
+ */
+ public static DummyBridgeHttpExecutor dummyBridgeHttpExecutor() {
+ return new DummyBridgeHttpExecutor();
+ }
+
private DummyBridgeHttpFactory(Supplier supplier) {
super(new DummyBridgeHttpCso(supplier));
}
diff --git a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpCycleTest.java b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpCycleTest.java
index 0e4ca875adc..ca2a7d82130 100644
--- a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpCycleTest.java
+++ b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpCycleTest.java
@@ -13,7 +13,6 @@
import org.junit.Test;
import org.osgi.service.event.Event;
-import io.openems.common.test.TimeLeapClock;
import io.openems.common.utils.FunctionUtils;
import io.openems.edge.bridge.http.BridgeHttpImpl;
import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint;
@@ -33,7 +32,7 @@ public class BridgeHttpCycleTest {
public void before() throws Exception {
this.cycleSubscriber = new CycleSubscriber();
this.fetcher = new DummyEndpointFetcher();
- this.pool = new DummyBridgeHttpExecutor(new TimeLeapClock());
+ this.pool = new DummyBridgeHttpExecutor();
this.bridgeHttp = new BridgeHttpImpl(//
this.cycleSubscriber, //
this.fetcher, //
diff --git a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTest.java b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTest.java
index 013e2ddc067..253668672e1 100644
--- a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTest.java
+++ b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTest.java
@@ -1,5 +1,6 @@
package io.openems.edge.bridge.http.api;
+import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.dummyBridgeHttpExecutor;
import static java.util.Collections.emptyMap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -14,7 +15,6 @@
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.function.ThrowingFunction;
-import io.openems.common.test.TimeLeapClock;
import io.openems.common.utils.JsonUtils;
import io.openems.edge.bridge.http.BridgeHttpImpl;
import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint;
@@ -31,8 +31,7 @@ public class BridgeHttpTest {
public void before() throws Exception {
this.cycleSubscriber = DummyBridgeHttpFactory.cycleSubscriber();
this.fetcher = DummyBridgeHttpFactory.dummyEndpointFetcher();
- this.bridgeHttp = new BridgeHttpImpl(this.cycleSubscriber, this.fetcher,
- DummyBridgeHttpFactory.dummyBridgeHttpExecutor(new TimeLeapClock(), true));
+ this.bridgeHttp = new BridgeHttpImpl(this.cycleSubscriber, this.fetcher, dummyBridgeHttpExecutor(true));
}
@After
diff --git a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTimeTest.java b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTimeTest.java
index 74f89ade961..db098dab702 100644
--- a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTimeTest.java
+++ b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTimeTest.java
@@ -1,6 +1,7 @@
package io.openems.edge.bridge.http.api;
import static io.openems.edge.bridge.http.time.DelayTimeProviderChain.fixedDelay;
+import static io.openems.edge.common.test.TestUtils.createDummyClock;
import static org.junit.Assert.assertEquals;
import java.time.Duration;
@@ -35,7 +36,7 @@ public void before() throws Exception {
};
});
- this.pool = new DummyBridgeHttpExecutor(this.clock = new TimeLeapClock());
+ this.pool = new DummyBridgeHttpExecutor(this.clock = createDummyClock());
this.bridgeHttp = new BridgeHttpImpl(cycleSubscriber, fetcher, this.pool);
}
diff --git a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/UrlBuilderTest.java b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/UrlBuilderTest.java
new file mode 100644
index 00000000000..7bfbc193476
--- /dev/null
+++ b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/UrlBuilderTest.java
@@ -0,0 +1,117 @@
+package io.openems.edge.bridge.http.api;
+
+import static org.junit.Assert.assertEquals;
+
+import java.net.URI;
+
+import org.junit.Test;
+
+public class UrlBuilderTest {
+
+ @Test
+ public void testParse() {
+ final var rawUrl = "https://openems.io:443/path?key=value#fragment";
+ final var parsedUrl = UrlBuilder.parse(rawUrl);
+ assertEquals(rawUrl, parsedUrl.toEncodedString());
+ }
+
+ @Test
+ public void testParseNoQueryParams() {
+ final var rawUrl = "https://openems.io:443/path#fragment";
+ final var parsedUrl = UrlBuilder.parse(rawUrl);
+ assertEquals(rawUrl, parsedUrl.toEncodedString());
+ }
+
+ @Test
+ public void testScheme() {
+ final var url = UrlBuilder.create() //
+ .withScheme("https") //
+ .withHost("openems.io");
+
+ assertEquals("https://openems.io", url.toEncodedString());
+ assertEquals("http://openems.io", url.withScheme("http").toEncodedString());
+ }
+
+ @Test
+ public void testHost() {
+ final var url = UrlBuilder.create() //
+ .withScheme("https") //
+ .withHost("openems.io");
+
+ assertEquals("https://openems.io", url.toEncodedString());
+ assertEquals("https://better.openems.io", url.withHost("better.openems.io").toEncodedString());
+ }
+
+ @Test
+ public void testPort() {
+ final var url = UrlBuilder.create() //
+ .withScheme("https") //
+ .withHost("openems.io") //
+ .withPort(443);
+
+ assertEquals("https://openems.io:443", url.toEncodedString());
+ assertEquals("https://openems.io:445", url.withPort(445).toEncodedString());
+ }
+
+ @Test
+ public void testPath() {
+ final var url = UrlBuilder.create() //
+ .withScheme("https") //
+ .withHost("openems.io") //
+ .withPath("/path");
+
+ assertEquals("https://openems.io/path", url.toEncodedString());
+ assertEquals("https://openems.io/path/abc", url.withPath("/path/abc").toEncodedString());
+ assertEquals("https://openems.io/withoutslash", url.withPath("withoutslash").toEncodedString());
+ }
+
+ @Test
+ public void testQueryParameter() {
+ final var url = UrlBuilder.create() //
+ .withScheme("https") //
+ .withHost("openems.io") //
+ .withQueryParam("key", "value");
+
+ assertEquals("https://openems.io?key=value", url.toEncodedString());
+ assertEquals("https://openems.io?key=otherValue", url.withQueryParam("key", "otherValue").toEncodedString());
+ }
+
+ @Test
+ public void testFragment() {
+ final var url = UrlBuilder.create() //
+ .withScheme("https") //
+ .withHost("openems.io") //
+ .withFragment("myFragment");
+
+ assertEquals("https://openems.io#myFragment", url.toEncodedString());
+ assertEquals("https://openems.io#myOtherFragment", url.withFragment("myOtherFragment").toEncodedString());
+ assertEquals("https://openems.io#with", url.withFragment("#with").toEncodedString());
+ }
+
+ @Test
+ public void testToUri() {
+ final var url = UrlBuilder.create() //
+ .withScheme("https") //
+ .withHost("openems.io") //
+ .withPort(443) //
+ .withPath("/path") //
+ .withQueryParam("key", "value") //
+ .withFragment("fragment");
+
+ assertEquals(URI.create("https://openems.io:443/path?key=value#fragment"), url.toUri());
+ }
+
+ @Test
+ public void testToEncodedString() {
+ final var url = UrlBuilder.create() //
+ .withScheme("https") //
+ .withHost("openems.io") //
+ .withPort(443) //
+ .withPath("/path") //
+ .withQueryParam("key", "va lu+e") //
+ .withFragment("fragment");
+
+ assertEquals("https://openems.io:443/path?key=va%20lu%2Be#fragment", url.toEncodedString());
+ }
+
+}
diff --git a/io.openems.edge.bridge.mbus/test/io/openems/edge/bridge/mbus/BridgeMbusImplTest.java b/io.openems.edge.bridge.mbus/test/io/openems/edge/bridge/mbus/BridgeMbusImplTest.java
index 799f89f4971..79e1f813886 100644
--- a/io.openems.edge.bridge.mbus/test/io/openems/edge/bridge/mbus/BridgeMbusImplTest.java
+++ b/io.openems.edge.bridge.mbus/test/io/openems/edge/bridge/mbus/BridgeMbusImplTest.java
@@ -2,22 +2,18 @@
import org.junit.Test;
-import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
public class BridgeMbusImplTest {
- private static final String COMPONENT_ID = "mbus0";
-
@Test
public void test() throws Exception {
new ComponentTest(new BridgeMbusImpl()) //
.activate(MyConfig.create() //
- .setId(COMPONENT_ID) //
+ .setId("mbus0") //
.setPortName("/dev/ttyUSB0") //
.setBaudrate(2400) //
- .build()) //
- .next(new TestCase()); //
+ .build()); //
}
}
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 d36835f41f8..3c5c5d64c19 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
@@ -24,6 +24,7 @@
import io.openems.edge.bridge.modbus.api.AbstractModbusBridge;
import io.openems.edge.bridge.modbus.api.BridgeModbus;
import io.openems.edge.bridge.modbus.api.BridgeModbusSerial;
+import io.openems.edge.bridge.modbus.api.Config;
import io.openems.edge.bridge.modbus.api.Parity;
import io.openems.edge.bridge.modbus.api.Stopbit;
import io.openems.edge.common.component.OpenemsComponent;
@@ -86,15 +87,15 @@ public BridgeModbusSerialImpl() {
@Activate
private void activate(ComponentContext context, ConfigSerial config) {
- super.activate(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(),
- config.invalidateElementsAfterReadErrors());
+ super.activate(context, new Config(config.id(), config.alias(), config.enabled(), config.logVerbosity(),
+ config.invalidateElementsAfterReadErrors()));
this.applyConfig(config);
}
@Modified
private void modified(ComponentContext context, ConfigSerial config) {
- super.modified(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(),
- config.invalidateElementsAfterReadErrors());
+ super.modified(context, new Config(config.id(), config.alias(), config.enabled(), config.logVerbosity(),
+ config.invalidateElementsAfterReadErrors()));
this.applyConfig(config);
this.closeModbusConnection();
}
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java
index 9cd9cd3dff3..aacd72d89fd 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java
@@ -22,6 +22,7 @@
import io.openems.edge.bridge.modbus.api.AbstractModbusBridge;
import io.openems.edge.bridge.modbus.api.BridgeModbus;
import io.openems.edge.bridge.modbus.api.BridgeModbusTcp;
+import io.openems.edge.bridge.modbus.api.Config;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.event.EdgeEventConstants;
@@ -56,15 +57,15 @@ public BridgeModbusTcpImpl() {
@Activate
private void activate(ComponentContext context, ConfigTcp config) throws UnknownHostException {
- super.activate(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(),
- config.invalidateElementsAfterReadErrors());
+ super.activate(context, new Config(config.id(), config.alias(), config.enabled(), config.logVerbosity(),
+ config.invalidateElementsAfterReadErrors()));
this.applyConfig(config);
}
@Modified
private void modified(ComponentContext context, ConfigTcp config) throws UnknownHostException {
- super.modified(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(),
- config.invalidateElementsAfterReadErrors());
+ super.modified(context, new Config(config.id(), config.alias(), config.enabled(), config.logVerbosity(),
+ config.invalidateElementsAfterReadErrors()));
this.applyConfig(config);
this.closeModbusConnection();
}
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java
index 52087943576..2410fbaf055 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java
@@ -1,6 +1,5 @@
package io.openems.edge.bridge.modbus.api;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import org.osgi.service.component.ComponentContext;
@@ -35,8 +34,7 @@ public abstract class AbstractModbusBridge extends AbstractOpenemsComponent impl
*/
protected static final int DEFAULT_RETRIES = 1;
- private final AtomicReference logVerbosity = new AtomicReference<>(LogVerbosity.NONE);
- private int invalidateElementsAfterReadErrors = 1;
+ private Config config = null;
protected final ModbusWorker worker = new ModbusWorker(
// Execute Task
@@ -47,8 +45,8 @@ public abstract class AbstractModbusBridge extends AbstractOpenemsComponent impl
state -> this._setCycleTimeIsTooShort(state),
// Set ChannelId.CYCLE_DELAY
cycleDelay -> this._setCycleDelay(cycleDelay),
- // LogVerbosity
- this.logVerbosity //
+ // LogHandler
+ () -> this.config.log //
);
protected AbstractModbusBridge(io.openems.edge.common.channel.ChannelId[] firstInitialChannelIds,
@@ -62,12 +60,11 @@ protected void activate(ComponentContext context, String id, String alias, boole
throw new IllegalArgumentException("Use the other activate() method.");
}
- protected void activate(ComponentContext context, String id, String alias, boolean enabled,
- LogVerbosity logVerbosity, int invalidateElementsAfterReadErrors) {
- super.activate(context, id, alias, enabled);
- this.applyConfig(logVerbosity, invalidateElementsAfterReadErrors);
- if (enabled) {
- this.worker.activate(id);
+ protected void activate(ComponentContext context, Config config) {
+ super.activate(context, config.id, config.alias, config.enabled);
+ this.applyConfig(config);
+ if (config.enabled) {
+ this.worker.activate(config.id);
}
}
@@ -84,20 +81,18 @@ protected void modified(ComponentContext context, String id, String alias, boole
throw new IllegalArgumentException("Use the other modified() method.");
}
- protected void modified(ComponentContext context, String id, String alias, boolean enabled,
- LogVerbosity logVerbosity, int invalidateElementsAfterReadErrors) {
- super.modified(context, id, alias, enabled);
- this.applyConfig(logVerbosity, invalidateElementsAfterReadErrors);
- if (enabled) {
- this.worker.modified(id);
+ protected void modified(ComponentContext context, Config config) {
+ super.modified(context, config.id, config.alias, config.enabled);
+ this.applyConfig(config);
+ if (config.enabled) {
+ this.worker.modified(config.id);
} else {
this.worker.deactivate();
}
}
- private void applyConfig(LogVerbosity logVerbosity, int invalidateElementsAfterReadErrors) {
- this.logVerbosity.set(logVerbosity);
- this.invalidateElementsAfterReadErrors = invalidateElementsAfterReadErrors;
+ private void applyConfig(Config config) {
+ this.config = config;
}
/**
@@ -124,22 +119,24 @@ public void removeProtocol(String sourceId) {
@Override
public void handleEvent(Event event) {
- if (!this.isEnabled()) {
+ if (this.config == null || !this.isEnabled()) {
return;
}
switch (event.getTopic()) {
- case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE:
- this.worker.onBeforeProcessImage();
- break;
- case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE:
- this.worker.onExecuteWrite();
- break;
+ case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE //
+ -> this.worker.onBeforeProcessImage();
+
+ case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE //
+ -> this.worker.onExecuteWrite();
}
}
@Override
public String debugLog() {
- return switch (this.logVerbosity.get()) {
+ if (this.config == null) {
+ return null;
+ }
+ return switch (this.config.log.verbosity) {
case NONE -> //
null;
case DEBUG_LOG, READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE,
@@ -167,7 +164,7 @@ public String debugLog() {
* @return {@link LogVerbosity}
*/
public LogVerbosity getLogVerbosity() {
- return this.logVerbosity.get();
+ return this.config.log.verbosity;
}
/**
@@ -177,7 +174,7 @@ public LogVerbosity getLogVerbosity() {
* @return value
*/
public int invalidateElementsAfterReadErrors() {
- return this.invalidateElementsAfterReadErrors;
+ return this.config.invalidateElementsAfterReadErrors;
}
@Override
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 7bf6e9e249d..80c29c5f234 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
@@ -110,17 +110,7 @@ protected void activate(String id) {
protected boolean activate(ComponentContext context, String id, String alias, boolean enabled, int unitId,
ConfigurationAdmin cm, String modbusReference, String modbusId) throws OpenemsException {
super.activate(context, id, alias, enabled);
- // update filter for 'Modbus'
- if (OpenemsComponent.updateReferenceFilter(cm, this.servicePid(), "Modbus", modbusId)) {
- return true;
- }
- this.unitId = unitId;
- var modbus = this.modbus.get();
- if (this.isEnabled() && modbus != null) {
- modbus.addProtocol(this.id(), this.getModbusProtocol());
- modbus.retryModbusCommunication(this.id());
- }
- return false;
+ return this.activateOrModified(unitId, cm, modbusReference, modbusId);
}
@Override
@@ -147,19 +137,41 @@ protected void activate(ComponentContext context, String id, String alias, boole
* @param modbusId The ID of the Modbus bridge. Typically
* 'config.modbus_id()'
* @return true if the target filter was updated. You may use it to abort the
- * activate() method.
+ * modified() method.
* @throws OpenemsException on error
*/
protected boolean modified(ComponentContext context, String id, String alias, boolean enabled, int unitId,
ConfigurationAdmin cm, String modbusReference, String modbusId) throws OpenemsException {
super.modified(context, id, alias, enabled);
+ return this.activateOrModified(unitId, cm, modbusReference, modbusId);
+ }
+
+ @Override
+ protected void modified(ComponentContext context, String id, String alias, boolean enabled) {
+ throw new IllegalArgumentException("Use the other modified() for Modbus components!");
+ }
+
+ /**
+ * Common tasks for @Activate and @Modified.
+ *
+ * @param unitId Unit-ID of the Modbus target
+ * @param cm An instance of ConfigurationAdmin. Receive it
+ * using @Reference
+ * @param modbusReference The name of the @Reference setter method for the
+ * Modbus bridge - e.g. 'Modbus' if you have a
+ * setModbus()-method
+ * @param modbusId The ID of the Modbus bridge. Typically
+ * 'config.modbus_id()'
+ * @return true if the target filter was updated. You may use it to abort the
+ * activate() or modified() method.
+ */
+ private boolean activateOrModified(int unitId, ConfigurationAdmin cm, String modbusReference, String modbusId) {
// update filter for 'Modbus'
- if (OpenemsComponent.updateReferenceFilter(cm, this.servicePid(), "Modbus", modbusId)) {
+ if (OpenemsComponent.updateReferenceFilter(cm, this.servicePid(), modbusReference, modbusId)) {
return true;
}
this.unitId = unitId;
var modbus = this.modbus.get();
- modbus.removeProtocol(this.id());
if (this.isEnabled() && modbus != null) {
modbus.addProtocol(this.id(), this.getModbusProtocol());
modbus.retryModbusCommunication(this.id());
@@ -167,11 +179,6 @@ protected boolean modified(ComponentContext context, String id, String alias, bo
return false;
}
- @Override
- protected void modified(ComponentContext context, String id, String alias, boolean enabled) {
- throw new IllegalArgumentException("Use the other activate() for Modbus components!");
- }
-
@Override
protected void deactivate() {
super.deactivate();
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/Config.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/Config.java
new file mode 100644
index 00000000000..9f6e98b6d45
--- /dev/null
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/Config.java
@@ -0,0 +1,60 @@
+package io.openems.edge.bridge.modbus.api;
+
+import java.util.function.Supplier;
+
+import org.slf4j.Logger;
+
+public class Config {
+
+ public final String id;
+ public final String alias;
+ public final boolean enabled;
+ public final int invalidateElementsAfterReadErrors;
+ public final LogHandler log;
+
+ public Config(String id, String alias, boolean enabled, LogVerbosity logVerbosity,
+ int invalidateElementsAfterReadErrors) {
+ this.id = id;
+ this.alias = alias;
+ this.enabled = enabled;
+ this.invalidateElementsAfterReadErrors = invalidateElementsAfterReadErrors;
+ this.log = new LogHandler(this, logVerbosity);
+ }
+
+ public static class LogHandler {
+ public final LogVerbosity verbosity;
+
+ private final Config config;
+
+ private LogHandler(Config config, LogVerbosity logVerbosity) {
+ this.config = config;
+ this.verbosity = logVerbosity;
+ }
+
+ /**
+ * Logs messages for
+ * {@link LogVerbosity#READS_AND_WRITES_DURATION_TRACE_EVENTS}.
+ *
+ * @param logger the {@link Logger}
+ * @param message the String message
+ */
+ public void trace(Logger logger, Supplier message) {
+ if (this.isTrace()) {
+ logger.info("[" + this.config.id + "] " + message.get());
+ }
+ }
+
+ /**
+ * Return true if {@link LogVerbosity#READS_AND_WRITES_DURATION_TRACE_EVENTS} is
+ * active.
+ *
+ * @return true for trace-log
+ */
+ public boolean isTrace() {
+ return switch (this.verbosity) {
+ case NONE, DEBUG_LOG, READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE -> false;
+ case READS_AND_WRITES_DURATION_TRACE_EVENTS -> true;
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java
index ace3a2c4da4..c2b284dd80f 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java
@@ -20,7 +20,13 @@
public interface ModbusComponent extends OpenemsComponent {
public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
- MODBUS_COMMUNICATION_FAILED(Doc.of(Level.FAULT) //
+
+ /*
+ * If ModbusCommunicationFault would be a FaultState, check it explicitly in
+ * Generic Ess ErrorHandler, as the battery could still have a communication
+ * fault while starting the battery
+ */
+ MODBUS_COMMUNICATION_FAILED(Doc.of(Level.WARNING) //
.debounce(10, Debounce.SAME_VALUES_IN_A_ROW_TO_CHANGE) //
.text("Modbus Communication failed")) //
;
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 68aed664b5d..2558852fc4d 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
@@ -1,12 +1,12 @@
package io.openems.edge.bridge.modbus.api.worker;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Supplier;
import io.openems.common.worker.AbstractImmediateWorker;
import io.openems.edge.bridge.modbus.api.BridgeModbus;
-import io.openems.edge.bridge.modbus.api.LogVerbosity;
+import io.openems.edge.bridge.modbus.api.Config.LogHandler;
import io.openems.edge.bridge.modbus.api.ModbusComponent;
import io.openems.edge.bridge.modbus.api.ModbusProtocol;
import io.openems.edge.bridge.modbus.api.element.ModbusElement;
@@ -52,18 +52,19 @@ public class ModbusWorker extends AbstractImmediateWorker {
* @param cycleDelayChannel sets the
* {@link BridgeModbus.ChannelId#CYCLE_DELAY}
* channel
- * @param logVerbosity the configured {@link LogVerbosity}
+ * @param logHandler a {@link Supplier} for the
+ * {@link LogHandler}
*/
public ModbusWorker(Function execute, Consumer invalidate,
Consumer cycleTimeIsTooShortChannel, Consumer cycleDelayChannel,
- AtomicReference logVerbosity) {
+ Supplier logHandler) {
this.execute = execute;
this.invalidate = invalidate;
- this.defectiveComponents = new DefectiveComponents(logVerbosity);
- this.tasksSupplier = new TasksSupplierImpl();
+ this.defectiveComponents = new DefectiveComponents(logHandler);
+ this.tasksSupplier = new TasksSupplierImpl(logHandler);
this.cycleTasksManager = new CycleTasksManager(this.tasksSupplier, this.defectiveComponents,
- cycleTimeIsTooShortChannel, cycleDelayChannel, logVerbosity);
+ cycleTimeIsTooShortChannel, cycleDelayChannel, logHandler);
}
@Override
@@ -123,7 +124,7 @@ private void markComponentAsDefective(ModbusComponent component, boolean isDefec
* @param protocol the ModbusProtocol
*/
public void addProtocol(String sourceId, ModbusProtocol protocol) {
- this.tasksSupplier.addProtocol(sourceId, protocol);
+ this.tasksSupplier.addProtocol(sourceId, protocol, this.invalidate);
this.defectiveComponents.remove(sourceId); // Cleanup
}
@@ -133,7 +134,7 @@ public void addProtocol(String sourceId, ModbusProtocol protocol) {
* @param sourceId Component-ID of the source
*/
public void removeProtocol(String sourceId) {
- this.tasksSupplier.removeProtocol(sourceId);
+ this.tasksSupplier.removeProtocol(sourceId, this.invalidate);
this.defectiveComponents.remove(sourceId); // Cleanup
}
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java
index fbee14394ac..03222a82a04 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java
@@ -1,12 +1,12 @@
package io.openems.edge.bridge.modbus.api.worker.internal;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
+import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import io.openems.edge.bridge.modbus.api.LogVerbosity;
+import io.openems.edge.bridge.modbus.api.Config.LogHandler;
import io.openems.edge.bridge.modbus.api.task.Task;
import io.openems.edge.bridge.modbus.api.task.WaitTask;
import io.openems.edge.bridge.modbus.api.worker.ModbusWorker;
@@ -26,7 +26,7 @@ public class CycleTasksManager {
private final TasksSupplier tasksSupplier;
private final DefectiveComponents defectiveComponents;
private final Consumer cycleTimeIsTooShortChannel;
- private final AtomicReference logVerbosity;
+ private final Supplier logHandler;
private final WaitDelayHandler waitDelayHandler;
private final WaitTask.Mutex waitMutexTask = new WaitTask.Mutex();
@@ -35,22 +35,15 @@ public class CycleTasksManager {
public CycleTasksManager(TasksSupplier tasksSupplier, DefectiveComponents defectiveComponents,
Consumer cycleTimeIsTooShortChannel, Consumer cycleDelayChannel,
- AtomicReference logVerbosity) {
+ Supplier logHandler) {
this.tasksSupplier = tasksSupplier;
this.defectiveComponents = defectiveComponents;
this.cycleTimeIsTooShortChannel = cycleTimeIsTooShortChannel;
- this.logVerbosity = logVerbosity;
-
+ this.logHandler = logHandler;
this.waitDelayHandler = new WaitDelayHandler(() -> this.onWaitDelayTaskFinished(), cycleDelayChannel);
}
- protected CycleTasksManager(TasksSupplier tasksSupplier, DefectiveComponents defectiveComponents,
- Consumer cycleTimeIsTooShortChannel, Consumer cycleDelayChannel) {
- this(tasksSupplier, defectiveComponents, cycleTimeIsTooShortChannel, cycleDelayChannel,
- new AtomicReference<>(LogVerbosity.NONE));
- }
-
- private static enum StateMachine {
+ protected static enum StateMachine {
INITIAL_WAIT, //
READ_BEFORE_WRITE, //
WAIT_FOR_WRITE, //
@@ -62,24 +55,31 @@ private static enum StateMachine {
private StateMachine state = StateMachine.FINISHED;
+ /**
+ * Gets the current state.
+ *
+ * @return the {@link StateMachine}
+ */
+ protected StateMachine getState() {
+ return this.state;
+ }
+
/**
* Called on BEFORE_PROCESS_IMAGE event.
*/
public synchronized void onBeforeProcessImage() {
// Calculate Delay
- var waitDelayHandlerLog = this.waitDelayHandler.onBeforeProcessImage(this.isTraceLog());
+ final var waitDelayHandlerLog = this.waitDelayHandler.onBeforeProcessImage(this.logHandler.get().isTrace());
// Evaluate Cycle-Time-Is-Too-Short, invalidate time measurement and stop early
var cycleTimeIsTooShort = this.state != StateMachine.FINISHED;
this.cycleTimeIsTooShortChannel.accept(cycleTimeIsTooShort);
if (cycleTimeIsTooShort) {
this.waitDelayHandler.timeIsInvalid();
- if (this.isTraceLog()) {
- this.log.info("State: " + this.state + " unchanged" //
- + " (in onBeforeProcessImage)" //
- + " Delay [" + this.waitDelayHandler.getWaitDelayTask().initialDelay + "] " //
- + waitDelayHandlerLog);
- }
+ this.traceLog(() -> "State: " + this.state + " unchanged" //
+ + " (in onBeforeProcessImage)" //
+ + " Delay [" + this.waitDelayHandler.getWaitDelayTask().initialDelay + "] " //
+ + waitDelayHandlerLog);
return;
}
@@ -90,18 +90,19 @@ public synchronized void onBeforeProcessImage() {
this.cycleTasks = this.tasksSupplier.getCycleTasks(this.defectiveComponents);
// On defectiveComponents invalidate time measurement
- if (this.cycleTasks.containsDefectiveComponent(this.defectiveComponents)) {
+ final var containsDefectiveComponents = this.cycleTasks.containsDefectiveComponent(this.defectiveComponents);
+ if (containsDefectiveComponents) {
this.waitDelayHandler.timeIsInvalid();
- waitDelayHandlerLog += " DEFECTIVE_COMPONENT";
}
// Initialize next Cycle
- if (this.isTraceLog()) {
- this.log.info("State: " + this.state + " -> " + StateMachine.INITIAL_WAIT //
- + " (in onBeforeProcessImage)" //
- + " Delay [" + this.waitDelayHandler.getWaitDelayTask().initialDelay + "] " //
- + waitDelayHandlerLog);
- }
+ this.traceLog(() -> "State: " + this.state + " -> " + StateMachine.INITIAL_WAIT //
+ + " (in onBeforeProcessImage)" //
+ + " Delay [" + this.waitDelayHandler.getWaitDelayTask().initialDelay + "] " //
+ + waitDelayHandlerLog //
+ + (containsDefectiveComponents //
+ ? " DEFECTIVE_COMPONENT"
+ : ""));
this.state = StateMachine.INITIAL_WAIT;
// Interrupt wait
@@ -112,9 +113,7 @@ public synchronized void onBeforeProcessImage() {
* Called on EXECUTE_WRITE event.
*/
public synchronized void onExecuteWrite() {
- if (this.isTraceLog()) {
- this.log.info("State: " + this.state + " -> " + StateMachine.WRITE + " (onExecuteWrite)");
- }
+ this.traceLog(() -> "State: " + this.state + " -> " + StateMachine.WRITE + " (onExecuteWrite)");
this.state = StateMachine.WRITE;
this.waitMutexTask.release();
@@ -128,7 +127,8 @@ public synchronized void onExecuteWrite() {
*/
public Task getNextTask() {
if (this.cycleTasks == null) {
- return this.waitMutexTask;
+ // Fallback to avoid NPE on race condition
+ this.cycleTasks = this.tasksSupplier.getCycleTasks(this.defectiveComponents);
}
var previousState = this.state; // drop before release
@@ -187,8 +187,8 @@ public Task getNextTask() {
}
};
- if (this.state != previousState && this.isTraceLog()) {
- this.log.info("State: " + previousState + " -> " + this.state + " (getNextTask)");
+ if (this.state != previousState) {
+ this.traceLog(() -> "State: " + previousState + " -> " + this.state + " (getNextTask)");
}
return nextTask;
}
@@ -207,16 +207,11 @@ private synchronized void onWaitDelayTaskFinished() {
};
if (this.state != previousState) {
- if (this.isTraceLog()) {
- this.log.info("State: " + previousState + " -> " + this.state + " (onWaitDelayTaskFinished)");
- }
+ this.traceLog(() -> "State: " + previousState + " -> " + this.state + " (onWaitDelayTaskFinished)");
}
}
- private boolean isTraceLog() {
- return switch (this.logVerbosity.get()) {
- case READS_AND_WRITES_DURATION_TRACE_EVENTS -> true;
- case NONE, DEBUG_LOG, READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE -> false;
- };
+ private void traceLog(Supplier message) {
+ this.logHandler.get().trace(this.log, message);
}
}
\ No newline at end of file
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java
index 6fa218f805f..80cb95937df 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java
@@ -4,12 +4,12 @@
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import io.openems.edge.bridge.modbus.api.LogVerbosity;
+import io.openems.edge.bridge.modbus.api.Config.LogHandler;
import io.openems.edge.common.type.TypeUtils;
public class DefectiveComponents {
@@ -23,24 +23,16 @@ private static record NextTry(Instant timestamp, int count) {
}
private final Clock clock;
- private final AtomicReference logVerbosity;
+ private final Supplier logHandler;
private final Map nextTries = new HashMap<>();
- public DefectiveComponents(AtomicReference logVerbosity) {
- this(Clock.systemDefaultZone(), logVerbosity);
+ public DefectiveComponents(Supplier logHandler) {
+ this(Clock.systemDefaultZone(), logHandler);
}
- protected DefectiveComponents() {
- this(Clock.systemDefaultZone(), new AtomicReference<>(LogVerbosity.READS_AND_WRITES_DURATION_TRACE_EVENTS));
- }
-
- protected DefectiveComponents(Clock clock) {
- this(clock, new AtomicReference<>(LogVerbosity.READS_AND_WRITES_DURATION_TRACE_EVENTS));
- }
-
- protected DefectiveComponents(Clock clock, AtomicReference logVerbosity) {
+ protected DefectiveComponents(Clock clock, Supplier logHandler) {
this.clock = clock;
- this.logVerbosity = logVerbosity;
+ this.logHandler = logHandler;
}
/**
@@ -54,15 +46,11 @@ public synchronized void add(String componentId) {
this.nextTries.compute(componentId, (k, v) -> {
var count = (v == null) ? 1 : v.count + 1;
var wait = Math.min(INCREASE_WAIT_SECONDS * count, MAX_WAIT_SECONDS);
- if (this.isTraceLog()) {
- final String log;
- if (count == 1) {
- log = "Add [" + componentId + "] to defective Components.";
- } else {
- log = "Increase wait for defective Component [" + componentId + "].";
- }
- this.log.info(log + " Wait [" + wait + "s]" + " Count [" + count + "]");
- }
+ this.traceLog(() -> //
+ (count == 1 //
+ ? "Add [" + componentId + "] to defective Components." //
+ : "Increase wait for defective Component [" + componentId + "].") + " Wait [" + wait + "s]"
+ + " Count [" + count + "]");
return new NextTry(Instant.now(this.clock).plusSeconds(wait), count);
});
}
@@ -74,8 +62,8 @@ public synchronized void add(String componentId) {
*/
public synchronized void remove(String componentId) {
TypeUtils.assertNull("DefectiveComponents remove() takes no null values", componentId);
- if (this.nextTries.remove(componentId) != null && this.isTraceLog()) {
- this.log.info("Remove [" + componentId + "] from defective Components.");
+ if (this.nextTries.remove(componentId) != null) {
+ this.traceLog(() -> "Remove [" + componentId + "] from defective Components.");
}
}
@@ -105,12 +93,7 @@ public synchronized Boolean isDueForNextTry(String componentId) {
return now.isAfter(nextTry.timestamp);
}
- private boolean isTraceLog() {
- return switch (this.logVerbosity.get()) {
- case READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE,
- READS_AND_WRITES_DURATION_TRACE_EVENTS ->
- true;
- case NONE, DEBUG_LOG -> false;
- };
+ private void traceLog(Supplier message) {
+ this.logHandler.get().trace(this.log, message);
}
}
\ No newline at end of file
diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java
index e7c6a4bcb02..2f836ee0c14 100644
--- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java
+++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java
@@ -5,9 +5,16 @@
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.openems.edge.bridge.modbus.api.Config.LogHandler;
import io.openems.edge.bridge.modbus.api.ModbusProtocol;
+import io.openems.edge.bridge.modbus.api.element.ModbusElement;
import io.openems.edge.bridge.modbus.api.task.ReadTask;
import io.openems.edge.bridge.modbus.api.task.Task;
import io.openems.edge.bridge.modbus.api.task.WriteTask;
@@ -20,6 +27,13 @@
*/
public class TasksSupplierImpl implements TasksSupplier {
+ private final Logger log = LoggerFactory.getLogger(TasksSupplierImpl.class);
+ private final Supplier logHandler;
+
+ public TasksSupplierImpl(Supplier logHandler) {
+ this.logHandler = logHandler;
+ }
+
/**
* Source-ID -> TasksManager for {@link Task}s.
*/
@@ -31,22 +45,45 @@ public class TasksSupplierImpl implements TasksSupplier {
private final Queue> nextLowPriorityTasks = new LinkedList<>();
/**
- * Adds the protocol.
- *
- * @param sourceId Component-ID of the source
- * @param protocol the ModbusProtocol
+ * Adds (or replaces) the protocol identified by its sourceId.
+ *
+ *
+ * If a protocol with the same sourceId existed before,
+ * {@link #removeProtocol(String, Consumer)} is called internally first.
+ *
+ * @param sourceId Component-ID of the source
+ * @param protocol the ModbusProtocol
+ * @param invalidate invalidates the given {@link ModbusElement}s after read
+ * errors
*/
- public synchronized void addProtocol(String sourceId, ModbusProtocol protocol) {
+ public synchronized void addProtocol(String sourceId, ModbusProtocol protocol,
+ Consumer invalidate) {
+ this.removeProtocol(sourceId, invalidate); // remove if sourceId exists
+
+ this.traceLog(() -> "Add Protocol for " //
+ + "[" + sourceId + "] with " //
+ + "[" + protocol.getTaskManager().countTasks() + "] tasks");
this.taskManagers.put(sourceId, protocol.getTaskManager());
}
/**
- * Removes the protocol.
+ * Removes the protocol and invalidates all {@link ModbusElement}s.
*
- * @param sourceId Component-ID of the source
+ * @param sourceId Component-ID of the source
+ * @param invalidate invalidates the given {@link ModbusElement}s after read
+ * errors
*/
- public synchronized void removeProtocol(String sourceId) {
- this.taskManagers.remove(sourceId);
+ public synchronized void removeProtocol(String sourceId, Consumer invalidate) {
+ var taskManager = this.taskManagers.remove(sourceId);
+ if (taskManager == null) {
+ return;
+ }
+
+ this.traceLog(() -> "Remove Protocol for " //
+ + "[" + sourceId + "] with " //
+ + "[" + taskManager.countTasks() + "] tasks");
+ taskManager.getTasks() //
+ .forEach(t -> invalidate.accept(t.getElements()));
this.nextLowPriorityTasks.removeIf(t -> t.a() == sourceId);
}
@@ -84,7 +121,7 @@ public synchronized CycleTasks getCycleTasks(DefectiveComponents defectiveCompon
componentTasks.clear();
}
});
- return new CycleTasks(//
+ var result = new CycleTasks(//
tasks.values().stream().flatMap(LinkedList::stream) //
.filter(ReadTask.class::isInstance).map(ReadTask.class::cast) //
// Sort HIGH priority to the end
@@ -93,6 +130,11 @@ public synchronized CycleTasks getCycleTasks(DefectiveComponents defectiveCompon
tasks.values().stream().flatMap(LinkedList::stream) //
.filter(WriteTask.class::isInstance).map(WriteTask.class::cast) //
.collect(Collectors.toCollection(LinkedList::new)));
+
+ this.traceLog(() -> "Getting " //
+ + "[" + result.reads().size() + "] read and " //
+ + "[" + result.writes().size() + "] write tasks for this Cycle");
+ return result;
}
/**
@@ -128,4 +170,8 @@ public synchronized int getTotalNumberOfTasks() {
.mapToInt(m -> m.countTasks()) //
.sum();
}
+
+ private void traceLog(Supplier message) {
+ this.logHandler.get().trace(this.log, message);
+ }
}
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 1d508bb9363..e41bae3848c 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
@@ -1,25 +1,60 @@
package io.openems.edge.bridge.modbus.test;
+import static io.openems.common.utils.ReflectionUtils.getValueViaReflection;
+import static io.openems.edge.common.event.EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE;
+
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.function.Consumer;
+
+import org.osgi.service.event.Event;
+import com.ghgande.j2mod.modbus.ModbusException;
import com.ghgande.j2mod.modbus.io.ModbusTransaction;
+import com.ghgande.j2mod.modbus.msg.ModbusRequest;
+import com.ghgande.j2mod.modbus.msg.ModbusResponse;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.procimg.ProcessImage;
+import com.ghgande.j2mod.modbus.procimg.SimpleProcessImage;
+import com.ghgande.j2mod.modbus.procimg.SimpleRegister;
import io.openems.common.exceptions.OpenemsException;
import io.openems.edge.bridge.modbus.api.AbstractModbusBridge;
import io.openems.edge.bridge.modbus.api.BridgeModbus;
import io.openems.edge.bridge.modbus.api.BridgeModbusTcp;
+import io.openems.edge.bridge.modbus.api.Config;
import io.openems.edge.bridge.modbus.api.LogVerbosity;
-import io.openems.edge.bridge.modbus.api.ModbusProtocol;
-import io.openems.edge.common.channel.Channel;
+import io.openems.edge.bridge.modbus.api.worker.internal.DefectiveComponents;
+import io.openems.edge.bridge.modbus.api.worker.internal.TasksSupplier;
import io.openems.edge.common.component.OpenemsComponent;
public class DummyModbusBridge extends AbstractModbusBridge implements BridgeModbusTcp, BridgeModbus, OpenemsComponent {
- private final Map protocols = new HashMap<>();
+ private final ModbusTransaction modbusTransaction = new ModbusTransaction() {
+ @Override
+ public void execute() throws ModbusException {
+ this.response = DummyModbusBridge.this.executeModbusRequest(this.request);
+ }
+ };
+ private final AbstractModbusListener modbusListener = new AbstractModbusListener() {
+ @Override
+ public ProcessImage getProcessImage(int unitId) {
+ return DummyModbusBridge.this.processImage;
+ }
+ @Override
+ public void run() {
+ }
+
+ @Override
+ public void stop() {
+ }
+
+ };
+ private final TasksSupplier tasksSupplier;
+ private final DefectiveComponents defectiveComponents;
+
+ private SimpleProcessImage processImage = null;
private InetAddress ipAddress = null;
public DummyModbusBridge(String id) {
@@ -32,10 +67,20 @@ public DummyModbusBridge(String id, LogVerbosity logVerbosity) {
BridgeModbus.ChannelId.values(), //
BridgeModbusTcp.ChannelId.values() //
);
- for (Channel> channel : this.channels()) {
+ for (var channel : this.channels()) {
channel.nextProcessImage();
}
- super.activate(null, id, "", true, logVerbosity, 2);
+ super.activate(null, new Config(id, "", false, logVerbosity, 2));
+ this.tasksSupplier = getValueViaReflection(this.worker, "tasksSupplier");
+ this.defectiveComponents = getValueViaReflection(this.worker, "defectiveComponents");
+ }
+
+ private synchronized DummyModbusBridge withProcessImage(Consumer callback) {
+ if (this.processImage == null) {
+ this.processImage = new SimpleProcessImage();
+ }
+ callback.accept(this.processImage);
+ return this;
}
/**
@@ -50,14 +95,82 @@ public DummyModbusBridge withIpAddress(String ipAddress) throws UnknownHostExcep
return this;
}
- @Override
- public void addProtocol(String sourceId, ModbusProtocol protocol) {
- this.protocols.put(sourceId, protocol);
+ /**
+ * Sets the value of a Register.
+ *
+ * @param address the Register address
+ * @param b1 first byte
+ * @param b2 second byte
+ * @return myself
+ */
+ public DummyModbusBridge withRegister(int address, byte b1, byte b2) {
+ return this.withProcessImage(pi -> pi.addRegister(address, new SimpleRegister(b1, b2)));
+ }
+
+ /**
+ * Sets the value of a Register.
+ *
+ * @param address the Register address
+ * @param value the value
+ * @return myself
+ */
+ public DummyModbusBridge withRegister(int address, int value) {
+ return this.withProcessImage(pi -> pi.addRegister(address, new SimpleRegister(value)));
}
+ /**
+ * Sets the values of Registers.
+ *
+ * @param startAddress the start Register address
+ * @param values the values
+ * @return myself
+ */
+ public DummyModbusBridge withRegisters(int startAddress, int... values) {
+ for (var value : values) {
+ this.withRegister(startAddress++, value);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the values of Registers.
+ *
+ * @param startAddress the start Register address
+ * @param values the values
+ * @return myself
+ */
+ public DummyModbusBridge withRegisters(int startAddress, int[]... values) {
+ for (var a : values) {
+ for (var b : a) {
+ this.withRegister(startAddress++, b);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * NOTE: {@link DummyModbusBridge} does not call parent handleEvent().
+ */
@Override
- public void removeProtocol(String sourceId) {
- this.protocols.remove(sourceId);
+ public synchronized void handleEvent(Event event) {
+ // NOTE: TOPIC_CYCLE_EXECUTE_WRITE is not implemented (yet)
+ if (this.processImage == null) {
+ return;
+ }
+ switch (event.getTopic()) {
+ case TOPIC_CYCLE_BEFORE_PROCESS_IMAGE -> this.onBeforeProcessImage();
+ }
+ }
+
+ private ModbusResponse executeModbusRequest(ModbusRequest request) {
+ return request.createResponse(this.modbusListener);
+ }
+
+ private void onBeforeProcessImage() {
+ var cycleTasks = this.tasksSupplier.getCycleTasks(this.defectiveComponents);
+ for (var readTask : cycleTasks.reads()) {
+ readTask.execute(this);
+ }
}
@Override
@@ -70,12 +183,11 @@ public InetAddress getIpAddress() {
@Override
public ModbusTransaction getNewModbusTransaction() throws OpenemsException {
- throw new UnsupportedOperationException("getNewModbusTransaction() Unsupported by Dummy Class");
+ return this.modbusTransaction;
}
@Override
public void closeModbusConnection() {
- throw new UnsupportedOperationException("closeModbusConnection() Unsupported by Dummy Class");
}
}
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusSerialImplTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusSerialImplTest.java
index 3645d2d6fdf..1d6d1e780ba 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusSerialImplTest.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusSerialImplTest.java
@@ -9,13 +9,11 @@
public class BridgeModbusSerialImplTest {
- private static final String MODBUS_ID = "modbus0";
-
@Test
public void test() throws Exception {
new ComponentTest(new BridgeModbusSerialImpl()) //
.activate(MyConfigSerial.create() //
- .setId(MODBUS_ID) //
+ .setId("modbus0") //
.setPortName("/etc/ttyUSB0") //
.setBaudRate(9600) //
.setDatabits(8) //
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 276dc8dcce6..33c3496c644 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
@@ -1,6 +1,7 @@
package io.openems.edge.bridge.modbus;
-import org.junit.Ignore;
+import static io.openems.edge.bridge.modbus.api.ModbusComponent.ChannelId.MODBUS_COMMUNICATION_FAILED;
+
import org.junit.Test;
import com.ghgande.j2mod.modbus.procimg.Register;
@@ -11,7 +12,6 @@
import io.openems.common.exceptions.OpenemsException;
import io.openems.common.function.ThrowingRunnable;
-import io.openems.common.types.ChannelAddress;
import io.openems.common.types.OpenemsType;
import io.openems.edge.bridge.modbus.api.AbstractModbusBridge;
import io.openems.edge.bridge.modbus.api.LogVerbosity;
@@ -26,16 +26,9 @@
public class BridgeModbusTcpImplTest {
- private static final String MODBUS_ID = "modbus0";
- private static final String DEVICE_ID = "device0";
private static final int UNIT_ID = 1;
private static final int CYCLE_TIME = 100;
- private static final ChannelAddress REGISTER_100 = new ChannelAddress(DEVICE_ID, "Register100");
- private static final ChannelAddress MODBUS_COMMUNICATION_FAILED = new ChannelAddress(DEVICE_ID,
- "ModbusCommunicationFailed");
-
- @Ignore
@Test
public void test() throws Exception {
final ThrowingRunnable sleep = () -> Thread.sleep(CYCLE_TIME);
@@ -57,16 +50,15 @@ public void test() throws Exception {
* Instantiate Modbus-Bridge
*/
var sut = new BridgeModbusTcpImpl();
- var device = new MyModbusComponent(DEVICE_ID, sut, UNIT_ID);
var test = new ComponentTest(sut) //
- .addComponent(device) //
.activate(MyConfigTcp.create() //
- .setId(MODBUS_ID) //
+ .setId("modbus0") //
.setIp("127.0.0.1") //
.setPort(port) //
.setInvalidateElementsAfterReadErrors(1) //
.setLogVerbosity(LogVerbosity.NONE) //
.build());
+ test.addComponent(new MyModbusComponent("device0", sut, UNIT_ID));
/*
* Successfully read Register
@@ -76,34 +68,22 @@ public void test() throws Exception {
.onAfterProcessImage(sleep)) //
.next(new TestCase() //
.onAfterProcessImage(sleep) //
- .output(REGISTER_100, 123) //
- .output(MODBUS_COMMUNICATION_FAILED, false)); //
+ .output("device0", MyModbusComponent.ChannelId.REGISTER_100, 123) //
+ .output("device0", MODBUS_COMMUNICATION_FAILED, false)); //
/*
- * Reading Register fails after debounce of 10
+ * Remove Protocol and unset channel values
*/
- processImage.removeRegister(register100);
- for (var i = 0; i < 9; i++) {
- test.next(new TestCase() //
- .onAfterProcessImage(sleep));
- }
+ sut.removeProtocol("device0");
+
test //
.next(new TestCase() //
- .onAfterProcessImage(sleep) //
- .output(MODBUS_COMMUNICATION_FAILED, false)) //
+ .onAfterProcessImage(sleep)) //
.next(new TestCase() //
.onAfterProcessImage(sleep) //
- .output(MODBUS_COMMUNICATION_FAILED, true));
+ .output("device0", MyModbusComponent.ChannelId.REGISTER_100, null) //
+ .output("device0", MODBUS_COMMUNICATION_FAILED, false)); //
- /*
- * Successfully read Register
- */
- processImage.addRegister(100, register100);
- test //
- .next(new TestCase() //
- .onAfterProcessImage(sleep) //
- .output(REGISTER_100, 123) //
- .output(MODBUS_COMMUNICATION_FAILED, false)); //
} finally {
if (slave != null) {
slave.close();
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/BitsWordElementTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/BitsWordElementTest.java
index 65b0e885a56..d3f068826da 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/BitsWordElementTest.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/element/BitsWordElementTest.java
@@ -3,6 +3,7 @@
import static io.openems.common.channel.AccessMode.READ_WRITE;
import static io.openems.common.types.OpenemsType.BOOLEAN;
import static io.openems.common.types.OpenemsType.INTEGER;
+import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -129,12 +130,7 @@ public void testNotBoolean() throws Exception {
private static ModbusTest.FC3ReadRegisters generateSut() throws IllegalArgumentException,
IllegalAccessException, OpenemsException, NoSuchFieldException, SecurityException {
var sut = new ModbusTest.FC3ReadRegisters<>(new BitsWordElement(0, null), INTEGER);
-
- // Some Reflection to properly initialize the BitsWordElement
- var field = BitsWordElement.class.getDeclaredField("component");
- field.setAccessible(true);
- field.set(sut.element, sut);
-
+ setAttributeViaReflection(sut.element, "component", sut);
return sut;
}
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java
index 0d764fdecef..e57a4b32917 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java
@@ -1,15 +1,21 @@
package io.openems.edge.bridge.modbus.api.worker.internal;
+import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManager.StateMachine.FINISHED;
+import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManager.StateMachine.WAIT_BEFORE_READ;
+import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManager.StateMachine.WRITE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.function.Consumer;
+import java.util.function.Supplier;
import org.junit.Before;
import org.junit.Test;
import io.openems.common.exceptions.OpenemsException;
import io.openems.edge.bridge.modbus.DummyModbusComponent;
+import io.openems.edge.bridge.modbus.api.Config;
+import io.openems.edge.bridge.modbus.api.LogVerbosity;
import io.openems.edge.bridge.modbus.api.task.WaitTask;
import io.openems.edge.bridge.modbus.api.worker.DummyReadTask;
import io.openems.edge.bridge.modbus.api.worker.DummyWriteTask;
@@ -21,6 +27,8 @@ public class CycleTasksManagerTest {
};
public static final Consumer CYCLE_DELAY = (cycleDelay) -> {
};
+ private static final Config CONFIG = new Config("foo", "bar", true, LogVerbosity.NONE, 1);
+ public static final Supplier LOG_HANDLER = () -> CONFIG.log;
private static DummyReadTask RT_H_1;
private static DummyReadTask RT_H_2;
@@ -48,9 +56,10 @@ public void testIdealConditions() throws OpenemsException, InterruptedException
.writes(WT_1) //
.build();
var tasksSupplier = new DummyTasksSupplier(cycle1, cycle2);
- var defectiveComponents = new DefectiveComponents();
+ var defectiveComponents = new DefectiveComponents(LOG_HANDLER);
- var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY);
+ var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, //
+ CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY, LOG_HANDLER);
// Cycle 1
sut.onBeforeProcessImage();
@@ -117,6 +126,32 @@ public void testIdealConditions() throws OpenemsException, InterruptedException
// task.execute(null); -> this would block in single-threaded JUnit test
}
+ @Test
+ public void testExecuteWriteBeforeNextProcessImage() throws OpenemsException, InterruptedException {
+ var cycle1 = CycleTasks.create() //
+ .reads(RT_L_1, RT_H_1, RT_H_2) //
+ .writes(WT_1) //
+ .build();
+ var cycle2 = CycleTasks.create() //
+ .reads(RT_L_2, RT_H_1, RT_H_2) //
+ .writes(WT_1) //
+ .build();
+ var tasksSupplier = new DummyTasksSupplier(cycle1, cycle2);
+ var defectiveComponents = new DefectiveComponents(LOG_HANDLER);
+
+ var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, //
+ CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY, LOG_HANDLER);
+
+ sut.getNextTask();
+ assertEquals(FINISHED, sut.getState());
+ sut.onExecuteWrite();
+ sut.getNextTask();
+ assertEquals(WRITE, sut.getState());
+ sut.onBeforeProcessImage();
+ sut.getNextTask();
+ assertEquals(WAIT_BEFORE_READ, sut.getState());
+ }
+
@Test
public void testDefective() throws OpenemsException, InterruptedException {
var component = new DummyModbusComponent();
@@ -131,10 +166,11 @@ public void testDefective() throws OpenemsException, InterruptedException {
.writes(WT_1) //
.build();
var tasksSupplier = new DummyTasksSupplier(cycle1, cycle2);
- var defectiveComponents = new DefectiveComponents();
+ var defectiveComponents = new DefectiveComponents(LOG_HANDLER);
defectiveComponents.add(component.id());
- var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY);
+ var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, //
+ CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY, LOG_HANDLER);
// Cycle 1
sut.onBeforeProcessImage();
@@ -162,9 +198,10 @@ public void testNoTasks() throws OpenemsException, InterruptedException {
var cycle1 = CycleTasks.create() //
.build();
var tasksSupplier = new DummyTasksSupplier(cycle1);
- var defectiveComponents = new DefectiveComponents();
+ var defectiveComponents = new DefectiveComponents(LOG_HANDLER);
- var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY);
+ var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, //
+ CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY, LOG_HANDLER);
// Cycle 1
sut.onBeforeProcessImage();
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java
index 3f8d57ea014..ce0b9f97068 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java
@@ -1,5 +1,7 @@
package io.openems.edge.bridge.modbus.api.worker.internal;
+import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManagerTest.LOG_HANDLER;
+import static io.openems.edge.common.test.TestUtils.createDummyClock;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -8,16 +10,14 @@
import org.junit.Test;
-import io.openems.common.test.TimeLeapClock;
-
public class DefectiveComponentsTest {
private static final String CMP = "foo";
@Test
public void testIsDueForNextTry() {
- var clock = new TimeLeapClock();
- var sut = new DefectiveComponents(clock);
+ final var clock = createDummyClock();
+ var sut = new DefectiveComponents(clock, LOG_HANDLER);
assertNull(sut.isDueForNextTry(CMP));
sut.add(CMP);
@@ -28,8 +28,8 @@ public void testIsDueForNextTry() {
@Test
public void testAddRemove() {
- var clock = new TimeLeapClock();
- var sut = new DefectiveComponents(clock);
+ final var clock = createDummyClock();
+ var sut = new DefectiveComponents(clock, LOG_HANDLER);
sut.add(CMP);
clock.leap(30_001, ChronoUnit.MILLIS);
@@ -40,7 +40,7 @@ public void testAddRemove() {
@Test
public void testIsKnownw() {
- var sut = new DefectiveComponents();
+ var sut = new DefectiveComponents(LOG_HANDLER);
sut.add(CMP);
assertTrue(sut.isKnown(CMP));
diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java
index 0af815bda47..77e3d8b8f3f 100644
--- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java
+++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java
@@ -1,5 +1,7 @@
package io.openems.edge.bridge.modbus.api.worker.internal;
+import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManagerTest.LOG_HANDLER;
+import static io.openems.edge.common.test.TestUtils.createDummyClock;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -11,6 +13,7 @@
import io.openems.common.exceptions.OpenemsException;
import io.openems.common.test.TimeLeapClock;
+import io.openems.common.utils.FunctionUtils;
import io.openems.edge.bridge.modbus.DummyModbusComponent;
import io.openems.edge.bridge.modbus.api.worker.DummyReadTask;
import io.openems.edge.bridge.modbus.api.worker.DummyWriteTask;
@@ -35,14 +38,14 @@ public void before() {
@Test
public void testFull() throws OpenemsException {
- var clock = new TimeLeapClock();
- var defectiveComponents = new DefectiveComponents(clock);
- var sut = new TasksSupplierImpl();
+ final var clock = createDummyClock();
+ var defectiveComponents = new DefectiveComponents(clock, LOG_HANDLER);
+ var sut = new TasksSupplierImpl(LOG_HANDLER);
var component = new DummyModbusComponent();
var protocol = component.getModbusProtocol();
protocol.addTasks(RT_H_1, RT_H_2, RT_L_1, RT_L_2, WT_1);
- sut.addProtocol(component.id(), protocol);
+ sut.addProtocol(component.id(), protocol, FunctionUtils::doNothing);
// 1st Cycle
var tasks = sut.getCycleTasks(defectiveComponents);
@@ -82,19 +85,19 @@ public void testFull() throws OpenemsException {
assertEquals(4, tasks.reads().size() + tasks.writes().size());
// Finish
- sut.removeProtocol(component.id());
+ sut.removeProtocol(component.id(), FunctionUtils::doNothing);
}
@Test
public void testHighOnly() throws OpenemsException {
var clock = new TimeLeapClock();
- var defectiveComponents = new DefectiveComponents(clock);
- var sut = new TasksSupplierImpl();
+ var defectiveComponents = new DefectiveComponents(clock, LOG_HANDLER);
+ var sut = new TasksSupplierImpl(LOG_HANDLER);
var component = new DummyModbusComponent();
var protocol = component.getModbusProtocol();
protocol.addTasks(RT_H_1, RT_H_2, WT_1);
- sut.addProtocol(component.id(), protocol);
+ sut.addProtocol(component.id(), protocol, FunctionUtils::doNothing);
var tasks = sut.getCycleTasks(defectiveComponents);
assertEquals(3, tasks.reads().size() + tasks.writes().size());
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 2ce0c196ec0..2e877e2cb42 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,12 +1,12 @@
package io.openems.edge.bridge.modbus.sunspec;
import static io.openems.edge.bridge.modbus.sunspec.AbstractOpenemsSunSpecComponent.preprocessModbusElements;
+import static java.util.stream.IntStream.range;
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;
@@ -24,9 +24,16 @@
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.DefaultSunSpecModel.S1;
+import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel.S101;
+import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel.S103;
+import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel.S701;
+import io.openems.edge.bridge.modbus.sunspec.DefaultSunSpecModel.S701_ACType;
import io.openems.edge.bridge.modbus.sunspec.Point.ModbusElementPoint;
import io.openems.edge.bridge.modbus.sunspec.dummy.MyConfig;
import io.openems.edge.bridge.modbus.sunspec.dummy.MySunSpecComponentImpl;
+import io.openems.edge.bridge.modbus.test.DummyModbusBridge;
+import io.openems.edge.common.channel.ChannelId;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.common.test.DummyConfigurationAdmin;
@@ -60,6 +67,76 @@ public void changeLogLevel() {
java.lang.System.setProperty("org.ops4j.pax.logging.DefaultServiceLog.level", "INFO");
}
+ @Test
+ public void testReadFromModbus() throws Exception {
+ var sut = new MySunSpecComponentImpl();
+ new ComponentTest(sut) //
+ .addReference("cm", new DummyConfigurationAdmin()) //
+ .addReference("setModbus", new DummyModbusBridge("modbus0") //
+ .withRegisters(40000, 0x5375, 0x6e53) // isSunSpec
+ .withRegisters(40002, 1, 66) // Block 1
+ .withRegisters(40004, //
+ new int[] { 0x4D79, 0x204D, 0x616E, 0x7566, 0x6163, 0x7475, 0x7265, 0x7200, 0, 0, 0, 0,
+ 0, 0, 0, 0 }, // S1_MN
+ new int[] { 0x4D79, 0x204D, 0x6F64, 0x656C, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // S1_MD
+ range(0, 43).map(i -> 0).toArray()) //
+ .withRegisters(40070, 101, 50) // Block 101
+ .withRegisters(40072, //
+ new int[] { 123, 234, 345, 456, 1 }, //
+ range(0, 45).map(i -> 0).toArray()) //
+ .withRegisters(40122, 103, 50) // Block 103
+ .withRegisters(40124, //
+ new int[] { 124, 235, 346, 457, 1 }, //
+ range(0, 45).map(i -> 0).toArray()) //
+ .withRegisters(40174, 701, 121) // Block 701
+ .withRegisters(40176, //
+ new int[] { 1, }, //
+ range(0, 120).map(i -> 0).toArray()) //
+ .withRegisters(40297, 702, 50) // Block 702
+ .withRegisters(40299, //
+ new int[] { 1, }, //
+ range(0, 49).map(i -> 0).toArray()) //
+ .withRegisters(40375, 0xFFFF) // END_OF_MAP
+ ) //
+ .activate(MyConfig.create() //
+ .setId("cmp0") //
+ .setModbusId("modbus0") //
+ .setModbusUnitId(UNIT_ID) //
+ .setReadFromModbusBlock(1) //
+ .build())
+
+ .next(new TestCase()) //
+ .next(new TestCase() //
+ .output(c(S1.MN), null) //
+ .output(c(S1.MD), null))
+
+ .next(new TestCase() //
+ .output(c(S1.MN), "My Manufacturer") //
+ .output(c(S1.MD), "My Model"))
+
+ .next(new TestCase() //
+ .output(c(S101.A), null) //
+ .output(c(S101.APH_A), null) //
+ .output(c(S103.A), null) //
+ .output(c(S103.APH_A), null)) //
+
+ .next(new TestCase() //
+ .output(c(S101.A), 1230F) //
+ .output(c(S101.APH_A), 2340F) //
+ .output(c(S701.A_C_TYPE), S701_ACType.UNDEFINED)) //
+
+ .next(new TestCase() //
+ .output(c(S103.A), 1240F) //
+ .output(c(S103.APH_A), 2350F) //
+ .output(c(S701.A_C_TYPE), S701_ACType.SPLIT_PHASE)) //
+
+ .deactivate();
+ }
+
+ private static ChannelId c(SunSpecPoint point) {
+ return point.getChannelId();
+ }
+
private static ImmutableSortedMap.Builder generateSunSpec() {
var b = ImmutableSortedMap.naturalOrder() //
.put(40000, 0x5375) // SunSpec identifier
@@ -67,18 +144,18 @@ private static ImmutableSortedMap.Builder generateSunSpec() {
.put(40002, 1) // SunSpec Block-ID
.put(40003, 66); // Length of the SunSpec Block
- IntStream.range(40004, 40070).forEach(i -> b.put(i, 0));
+ range(40004, 40070).forEach(i -> b.put(i, 0));
b //
.put(40070, 103) // SunSpec Block-ID
.put(40071, 24); // Length of the SunSpec Block
- IntStream.range(40072, 40096).forEach(i -> b.put(i, 0));
+ 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));
+ range(40110, 40160).forEach(i -> b.put(i, 0));
return b;
}
diff --git a/io.openems.edge.bridge.onewire/test/io/openems/edge/bridge/onewire/impl/BridgeOnewireImplTest.java b/io.openems.edge.bridge.onewire/test/io/openems/edge/bridge/onewire/impl/BridgeOnewireImplTest.java
index 744192de7c8..c81a5ca33c2 100644
--- a/io.openems.edge.bridge.onewire/test/io/openems/edge/bridge/onewire/impl/BridgeOnewireImplTest.java
+++ b/io.openems.edge.bridge.onewire/test/io/openems/edge/bridge/onewire/impl/BridgeOnewireImplTest.java
@@ -6,13 +6,11 @@
public class BridgeOnewireImplTest {
- private static final String BRIDGE_ID = "onewire0";
-
@Test
public void test() throws Exception {
new ComponentTest(new BridgeOnewireImpl()) //
.activate(MyConfig.create() //
- .setId(BRIDGE_ID) //
+ .setId("onewire0") //
.setPort("USB1") //
.build()) //
;
diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java b/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java
index 1aaa95601f7..52337d0c78f 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java
@@ -1,5 +1,6 @@
package io.openems.edge.common.currency;
+import io.openems.common.types.CurrencyConfig;
import io.openems.common.types.OptionsEnum;
public enum Currency implements OptionsEnum {
@@ -30,4 +31,18 @@ public OptionsEnum getUndefined() {
return Currency.UNDEFINED;
}
+ /**
+ * Converts the {@link CurrencyConfig} to the {@link Currency}.
+ *
+ * @param config currencyConfig to be transformed
+ * @return The {@link Currency}.
+ */
+ public static Currency fromCurrencyConfig(CurrencyConfig config) {
+ return switch (config) {
+ case EUR -> Currency.EUR;
+ case SEK -> Currency.SEK;
+ case CHF -> Currency.CHF;
+ };
+ }
+
}
diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java
deleted file mode 100644
index 2448ff5b004..00000000000
--- a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package io.openems.edge.common.currency;
-
-import io.openems.edge.common.meta.Meta;
-import io.openems.edge.common.meta.Meta.ChannelId;
-
-/**
- * The {@link ChannelId#CURRENCY} mandates the selection of the 'currency'
- * configuration property of this specific type. Subsequently, this selected
- * property is transformed into the corresponding {@link Currency} type before
- * being written through {@link Meta#_setCurrency(Currency)}.
- */
-public enum CurrencyConfig {
- /**
- * Euro.
- */
- EUR,
- /**
- * Swedish Krona.
- */
- SEK,
- /**
- * Swiss Francs.
- */
- CHF;
-
- /**
- * Converts the {@link CurrencyConfig} to the {@link Currency}.
- *
- * @return The {@link Currency}.
- */
- public Currency toCurrency() {
- return switch (this) {
- case EUR -> Currency.EUR;
- case SEK -> Currency.SEK;
- case CHF -> Currency.CHF;
- };
- }
-}
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 27366c7d401..85a4b328ec2 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
@@ -17,6 +17,15 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
DISK_IS_FULL(Doc.of(Level.INFO) //
.text("Disk is full")), //
HOSTNAME(Doc.of(OpenemsType.STRING)), //
+
+ /**
+ * Operating System Version.
+ *
+ *
+ * e. g. 'Raspbian GNU/Linux 11 (bullseye)' or 'Windows 11'
+ */
+ OS_VERSION(Doc.of(OpenemsType.STRING) //
+ .text("Operating system version")), //
;
private final Doc doc;
@@ -69,7 +78,7 @@ public default StringReadChannel getHostnameChannel() {
}
/**
- * Gets the Disk is Full Warning State. See {@link ChannelId#HOSTNAME}.
+ * Gets the hostname. See {@link ChannelId#HOSTNAME}.
*
* @return the Channel {@link Value}
*/
@@ -86,4 +95,32 @@ public default void _setHostname(String value) {
this.getHostnameChannel().setNextValue(value);
}
+ /**
+ * Gets the Channel for {@link ChannelId#OS_VERSION}.
+ *
+ * @return the Channel
+ */
+ public default StringReadChannel getOsVersionChannel() {
+ return this.channel(ChannelId.OS_VERSION);
+ }
+
+ /**
+ * Gets the operating system version. See {@link ChannelId#OS_VERSION}.
+ *
+ * @return the Channel {@link Value}
+ */
+ public default Value getOsVersion() {
+ return this.getOsVersionChannel().value();
+ }
+
+ /**
+ * Internal method to set the 'nextValue' on {@link ChannelId#OS_VERSION}
+ * Channel.
+ *
+ * @param value the next value
+ */
+ public default void _setOsVersion(String value) {
+ this.getOsVersionChannel().setNextValue(value);
+ }
+
}
diff --git a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java
index c10ee0d08f2..aa31e104d1f 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java
@@ -3,6 +3,7 @@
import io.openems.common.OpenemsConstants;
import io.openems.common.channel.AccessMode;
import io.openems.common.channel.PersistencePriority;
+import io.openems.common.channel.Unit;
import io.openems.common.oem.OpenemsEdgeOem;
import io.openems.common.types.OpenemsType;
import io.openems.edge.common.channel.Doc;
@@ -12,6 +13,7 @@
import io.openems.edge.common.modbusslave.ModbusSlave;
import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable;
import io.openems.edge.common.modbusslave.ModbusSlaveTable;
+import io.openems.edge.common.modbusslave.ModbusType;
public interface Meta extends ModbusSlave {
@@ -29,7 +31,18 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
*/
VERSION(Doc.of(OpenemsType.STRING) //
.persistencePriority(PersistencePriority.HIGH)),
-
+ /**
+ * System Time: seconds since 1st January 1970 00:00:00 UTC.
+ *
+ *
+ * - Interface: Meta
+ *
- Type: Long
+ *
+ */
+ SYSTEM_TIME_UTC(Doc.of(OpenemsType.LONG) //
+ .unit(Unit.SECONDS) //
+ .text("System Time: seconds since 1st January 1970 00:00:00 UTC") //
+ .persistencePriority(PersistencePriority.VERY_LOW)),
/**
* Edge currency.
*
@@ -73,6 +86,7 @@ public static ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode, Openem
.string16(51, "Manufacturer Version", oem.getManufacturerVersion()) //
.string16(67, "Manufacturer Serial Number", oem.getManufacturerSerialNumber()) //
.string16(83, "Manufacturer EMS Serial Number", oem.getManufacturerEmsSerialNumber()) //
+ .channel(99, ChannelId.SYSTEM_TIME_UTC, ModbusType.UINT64) //
.build());
}
diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java
index 670a58a3ebc..0a8f7239042 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java
@@ -41,6 +41,7 @@ public ModbusRecordChannel(int offset, ModbusType type, ChannelId channelId, Acc
case STRING16 -> ModbusRecordString16.BYTE_LENGTH;
case ENUM16, UINT16 -> ModbusRecordUint16.BYTE_LENGTH;
case UINT32 -> ModbusRecordUint32.BYTE_LENGTH;
+ case UINT64 -> ModbusRecordUint64.BYTE_LENGTH;
};
this.writeValueBuffer = new Byte[byteLength];
}
@@ -140,6 +141,11 @@ public byte[] getValue(OpenemsComponent component) {
case READ_ONLY, READ_WRITE -> ModbusRecordUint32.toByteArray(value);
case WRITE_ONLY -> ModbusRecordUint32.UNDEFINED_VALUE;
};
+ case UINT64 -> //
+ switch (this.accessMode) {
+ case READ_ONLY, READ_WRITE -> ModbusRecordUint64.toByteArray(value);
+ case WRITE_ONLY -> ModbusRecordUint64.UNDEFINED_VALUE;
+ };
};
}
@@ -190,6 +196,7 @@ public void writeValue(int index, byte byte1, byte byte2) {
case STRING16 -> ""; // TODO implement String conversion
case ENUM16, UINT16 -> buff.getShort();
case UINT32 -> buff.getInt();
+ case UINT64 -> buff.getLong();
};
// Forward Value to ApiWorker
diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordConstant.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordConstant.java
index 7158dd50e88..9a5b376f6c3 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordConstant.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordConstant.java
@@ -1,5 +1,8 @@
package io.openems.edge.common.modbusslave;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,4 +52,44 @@ public AccessMode getAccessMode() {
return AccessMode.READ_ONLY;
}
+ /**
+ * Generates a common toString() method for implementations of
+ * {@link ModbusRecordConstant}.
+ *
+ * @param the type of the value
+ * @param name the name of the implementation class
+ * @param callback a {@link StringBuilder} callback
+ * @param value the actual value
+ * @param toHexString the toHexString() method
+ * @return a {@link String}
+ */
+ protected String generateToString(String name, Consumer callback, T value,
+ Function toHexString) {
+ var b = new StringBuilder() //
+ .append(name) //
+ .append(" [");
+ if (callback != null) {
+ callback.accept(b);
+ }
+ b.append("value=");
+ if (value != null) {
+ b.append(value);
+ if (toHexString != null) {
+ b.append("/0x").append(toHexString.apply(value));
+ }
+ } else {
+ b.append("UNDEFINED");
+ }
+ return b.append(", type=").append(this.getType()) //
+ .append("]") //
+ .toString();
+ }
+
+ protected String generateToString(String name, T value, Function toHexString) {
+ return this.generateToString(name, null, value, toHexString);
+ }
+
+ protected String generateToString(String name, T value) {
+ return this.generateToString(name, null, value, null);
+ }
}
diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordCycleValue.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordCycleValue.java
index 11a846349de..47cfe30253d 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordCycleValue.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordCycleValue.java
@@ -64,21 +64,14 @@ public void updateValue(T component) {
@Override
public byte[] getValue(OpenemsComponent component) {
- switch (this.getType()) {
- case FLOAT32:
- return ModbusRecordFloat32.toByteArray(this.value);
- case FLOAT64:
- return ModbusRecordFloat64.toByteArray(this.value);
- case STRING16:
- return ModbusRecordString16.toByteArray(this.value);
- case ENUM16:
- case UINT16:
- return ModbusRecordUint16.toByteArray(this.value);
- case UINT32:
- return ModbusRecordUint32.toByteArray(this.value);
- }
- assert true;
- return new byte[0];
+ return switch (this.getType()) {
+ case FLOAT32 -> ModbusRecordFloat32.toByteArray(this.value);
+ case FLOAT64 -> ModbusRecordFloat64.toByteArray(this.value);
+ case STRING16 -> ModbusRecordString16.toByteArray(this.value);
+ case ENUM16, UINT16 -> ModbusRecordUint16.toByteArray(this.value);
+ case UINT32 -> ModbusRecordUint32.toByteArray(this.value);
+ case UINT64 -> ModbusRecordUint64.toByteArray(this.value);
+ };
}
@Override
diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat32.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat32.java
index 591bfbd7d9f..61cdce90776 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat32.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat32.java
@@ -20,7 +20,7 @@ public ModbusRecordFloat32(int offset, String name, Float value) {
@Override
public String toString() {
- return "ModbusRecordFloat32 [value=" + this.value + ", type=" + this.getType() + "]";
+ return this.generateToString("ModbusRecordFloat32", this.value);
}
/**
diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat64.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat64.java
index 6cb32c747cf..34613a120c0 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat64.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat64.java
@@ -21,7 +21,7 @@ public ModbusRecordFloat64(int offset, String name, Double value) {
@Override
public String toString() {
- return "ModbusRecordFloat64 [value=" + this.value + ", type=" + this.getType() + "]";
+ return this.generateToString("ModbusRecordFloat64", this.value);
}
/**
diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordString16.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordString16.java
index 532e9e07c28..f5cdfb6466b 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordString16.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordString16.java
@@ -20,7 +20,7 @@ public ModbusRecordString16(int offset, String name, String value) {
@Override
public String toString() {
- return "ModbusRecordString16 [value=" + this.value + ", type=" + this.getType() + "]";
+ return this.generateToString("ModbusRecordString16", this.value);
}
/**
@@ -30,6 +30,9 @@ public String toString() {
* @return the byte array
*/
public static byte[] toByteArray(String value) {
+ if (value == null) {
+ return UNDEFINED_VALUE;
+ }
var result = new byte[BYTE_LENGTH];
var converted = value.getBytes(StandardCharsets.US_ASCII);
System.arraycopy(converted, 0, result, 0, Math.min(BYTE_LENGTH, converted.length));
diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16.java
index dc92046984c..3e2e751cde0 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16.java
@@ -20,8 +20,7 @@ public ModbusRecordUint16(int offset, String name, Short value) {
@Override
public String toString() {
- return "ModbusRecordUInt16 [value=" + this.value + "/0x" + Integer.toHexString(this.value) + ", type="
- + this.getType() + "]";
+ return generateToString("ModbusRecordUInt16", this.value, v -> Integer.toHexString(v));
}
/**
diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16BlockLength.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16BlockLength.java
index 03fb4a11ecf..e66ceb2641e 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16BlockLength.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16BlockLength.java
@@ -11,8 +11,9 @@ public ModbusRecordUint16BlockLength(int offset, String blockName, short length)
@Override
public String toString() {
- return "ModbusRecordUint16BlockLength [blockName=" + this.blockName + ", value=" + this.value + "/0x"
- + Integer.toHexString(this.value) + ", type=" + this.getType() + "]";
+ return generateToString("ModbusRecordUint16BlockLength",
+ b -> b.append("blockName=").append(this.blockName).append(", "), this.value,
+ v -> Integer.toHexString(v));
}
}
diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16Hash.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16Hash.java
index 14780d4fc35..e1e093c1280 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16Hash.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16Hash.java
@@ -11,8 +11,8 @@ public ModbusRecordUint16Hash(int offset, String text) {
@Override
public String toString() {
- return "ModbusRecordUint16Hash [text=" + this.text + ", value=" + this.value + "/0x"
- + Integer.toHexString(this.value) + ", type=" + this.getType() + "]";
+ return generateToString("ModbusRecordUint16Hash", b -> b.append("text=").append(this.text).append(", "),
+ this.value, v -> Integer.toHexString(v & 0xffff));
}
@Override
diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint32.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint32.java
index 0e0a9b05e4a..23bae440c32 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint32.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint32.java
@@ -20,8 +20,7 @@ public ModbusRecordUint32(int offset, String name, Integer value) {
@Override
public String toString() {
- return "ModbusRecordUInt32 [value=" + this.value + "/0x" + Integer.toHexString(this.value) + ", type="
- + this.getType() + "]";
+ return generateToString("ModbusRecordUInt32", this.value, Integer::toHexString);
}
/**
diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64.java
new file mode 100644
index 00000000000..87ccc8e779d
--- /dev/null
+++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64.java
@@ -0,0 +1,59 @@
+package io.openems.edge.common.modbusslave;
+
+import java.nio.ByteBuffer;
+
+import io.openems.common.types.OpenemsType;
+import io.openems.edge.common.type.TypeUtils;
+
+public class ModbusRecordUint64 extends ModbusRecordConstant {
+
+ public static final byte[] UNDEFINED_VALUE = { //
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, //
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF };
+
+ public static final int BYTE_LENGTH = 8;
+
+ protected final Long value;
+
+ public ModbusRecordUint64(int offset, String name, Long value) {
+ super(offset, name, ModbusType.UINT64, toByteArray(value));
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return this.generateToString("ModbusRecordUInt64", this.value, Long::toHexString);
+ }
+
+ /**
+ * Convert to byte array.
+ *
+ * @param value the value
+ * @return the byte array
+ */
+ public static byte[] toByteArray(long value) {
+ return ByteBuffer.allocate(BYTE_LENGTH).putLong(value).array();
+ }
+
+ /**
+ * Convert to byte array.
+ *
+ * @param value the value
+ * @return the byte array
+ */
+ public static byte[] toByteArray(Object value) {
+ if (value == null || value instanceof io.openems.common.types.OptionsEnum
+ && ((io.openems.common.types.OptionsEnum) value).isUndefined()) {
+ return UNDEFINED_VALUE;
+ }
+ return toByteArray((long) TypeUtils.getAsType(OpenemsType.LONG, value));
+ }
+
+ @Override
+ public String getValueDescription() {
+ return this.value != null //
+ ? "\"" + Long.toString(this.value) + "\"" //
+ : "";
+ }
+
+}
diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64Reserved.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64Reserved.java
new file mode 100644
index 00000000000..d6018217403
--- /dev/null
+++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64Reserved.java
@@ -0,0 +1,14 @@
+package io.openems.edge.common.modbusslave;
+
+public class ModbusRecordUint64Reserved extends ModbusRecordUint64 {
+
+ public ModbusRecordUint64Reserved(int offset) {
+ super(offset, "Reserved", null);
+ }
+
+ @Override
+ public String toString() {
+ return "ModbusRecordUint64Reserved [type=" + this.getType() + "]";
+ }
+
+}
diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusSlaveNatureTable.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusSlaveNatureTable.java
index da1d57c03af..f358ad8511b 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusSlaveNatureTable.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusSlaveNatureTable.java
@@ -63,22 +63,12 @@ public Builder channel(int offset, ChannelId channelId, ModbusType type) {
} else {
// Channel did not pass filter -> show as Reserved
switch (type) {
- case FLOAT32:
- this.float32Reserved(offset);
- break;
- case FLOAT64:
- this.float64Reserved(offset);
- break;
- case STRING16:
- this.string16Reserved(offset);
- break;
- case ENUM16:
- case UINT16:
- this.uint16Reserved(offset);
- break;
- case UINT32:
- this.uint32Reserved(offset);
- break;
+ case FLOAT32 -> this.float32Reserved(offset);
+ case FLOAT64 -> this.float64Reserved(offset);
+ case STRING16 -> this.string16Reserved(offset);
+ case ENUM16, UINT16 -> this.uint16Reserved(offset);
+ case UINT32 -> this.uint32Reserved(offset);
+ case UINT64 -> this.uint64Reserved(offset);
}
}
return this;
@@ -158,6 +148,18 @@ public Builder uint32Reserved(int offset) {
return this;
}
+ /**
+ * Add a Unsigned Int 64 Reserved value to the {@link ModbusSlaveNatureTable}
+ * {@link Builder}.
+ *
+ * @param offset the address offset
+ * @return myself
+ */
+ public Builder uint64Reserved(int offset) {
+ this.add(new ModbusRecordUint64Reserved(offset));
+ return this;
+ }
+
/**
* Add a Float 32 value to the {@link ModbusSlaveNatureTable} {@link Builder}.
*
diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusType.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusType.java
index 5da35991871..df071d14fdc 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusType.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusType.java
@@ -4,6 +4,7 @@ public enum ModbusType {
ENUM16(1, "enum16"), //
UINT16(1, "uint16"), //
UINT32(2, "uint32"), //
+ UINT64(4, "uint64"), //
FLOAT32(2, "float32"), //
FLOAT64(4, "float64"), //
STRING16(16, "string16");
diff --git a/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java b/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java
index 41a246d6b33..2a2b9bd7568 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java
@@ -49,4 +49,48 @@ public DummySum withGridActivePower(int value) {
return this.self();
}
+ /**
+ * Set {@link Sum.ChannelId#ESS_CAPACITY}.
+ *
+ * @param value the value
+ * @return myself
+ */
+ public DummySum withEssCapacity(int value) {
+ withValue(this, Sum.ChannelId.ESS_CAPACITY, value);
+ return this.self();
+ }
+
+ /**
+ * Set {@link Sum.ChannelId#ESS_SOC}.
+ *
+ * @param value the value
+ * @return myself
+ */
+ public DummySum withEssSoc(int value) {
+ withValue(this, Sum.ChannelId.ESS_SOC, value);
+ return this.self();
+ }
+
+ /**
+ * Set {@link Sum.ChannelId#ESS_MIN_DISCHARGE_POWER}.
+ *
+ * @param value the value
+ * @return myself
+ */
+ public DummySum withEssMinDischargePower(int value) {
+ withValue(this, Sum.ChannelId.ESS_MIN_DISCHARGE_POWER, value);
+ return this.self();
+ }
+
+ /**
+ * Set {@link Sum.ChannelId#ESS_MAX_DISCHARGE_POWER}.
+ *
+ * @param value the value
+ * @return myself
+ */
+ public DummySum withEssMaxDischargePower(int value) {
+ withValue(this, Sum.ChannelId.ESS_MAX_DISCHARGE_POWER, value);
+ return this.self();
+ }
+
}
diff --git a/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java b/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java
index c6a903df67c..b065cb50c18 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java
@@ -629,6 +629,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
CONSUMPTION_ACTIVE_ENERGY(Doc.of(OpenemsType.LONG) //
.unit(Unit.CUMULATED_WATT_HOURS) //
.persistencePriority(PersistencePriority.VERY_HIGH)), //
+
/**
* Is there any Component Info/Warning/Fault that is getting ignored/hidden
* because of the 'ignoreStateComponents' configuration setting?.
@@ -716,6 +717,7 @@ public static ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode access
.channel(113, ChannelId.ESS_DISCHARGE_POWER, ModbusType.FLOAT32) //
.channel(115, ChannelId.GRID_MODE, ModbusType.ENUM16) //
.channel(116, ChannelId.GRID_MODE_OFF_GRID_TIME, ModbusType.FLOAT32) //
+ .channel(118, ChannelId.ESS_CAPACITY, ModbusType.FLOAT32) //
.build();
}
diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java b/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java
index f714fb70658..3f949bce31e 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java
@@ -1,5 +1,9 @@
package io.openems.edge.common.test;
+import static io.openems.common.utils.ReflectionUtils.invokeMethodViaReflection;
+import static io.openems.common.utils.ReflectionUtils.invokeMethodWithoutArgumentsViaReflection;
+import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection;
+
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -34,6 +38,7 @@
import io.openems.common.types.ChannelAddress;
import io.openems.common.types.OpenemsType;
import io.openems.common.types.OptionsEnum;
+import io.openems.common.utils.ReflectionUtils.ReflectionException;
import io.openems.edge.common.channel.Channel;
import io.openems.edge.common.channel.ChannelId;
import io.openems.edge.common.channel.EnumDoc;
@@ -42,6 +47,11 @@
import io.openems.edge.common.component.ComponentManager;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.event.EdgeEventConstants;
+import io.openems.edge.common.sum.Sum;
+import io.openems.edge.common.test.AbstractComponentTest.ChannelValue.ChannelAddressValue;
+import io.openems.edge.common.test.AbstractComponentTest.ChannelValue.ChannelIdValue;
+import io.openems.edge.common.test.AbstractComponentTest.ChannelValue.ChannelNameValue;
+import io.openems.edge.common.test.AbstractComponentTest.ChannelValue.ComponentChannelIdValue;
import io.openems.edge.common.type.TypeUtils;
/**
@@ -49,10 +59,49 @@
*/
public abstract class AbstractComponentTest, SUT extends OpenemsComponent> {
- public record ChannelValue(ChannelAddress address, Object value, boolean force) {
- @Override
- public String toString() {
- return this.address.toString() + ":" + this.value;
+ public sealed interface ChannelValue {
+
+ /**
+ * Gets the value.
+ *
+ * @return the value
+ */
+ public Object value();
+
+ /**
+ * Is the value enforced?.
+ *
+ * @return true for force
+ */
+ public boolean force();
+
+ public record ChannelAddressValue(ChannelAddress address, Object value, boolean force) implements ChannelValue {
+ @Override
+ public String toString() {
+ return this.address.toString() + ":" + this.value;
+ }
+ }
+
+ public record ChannelIdValue(ChannelId channelId, Object value, boolean force) implements ChannelValue {
+ @Override
+ public String toString() {
+ return this.channelId.id() + ":" + this.value;
+ }
+ }
+
+ public record ChannelNameValue(String channelName, Object value, boolean force) implements ChannelValue {
+ @Override
+ public String toString() {
+ return this.channelName + ":" + this.value;
+ }
+ }
+
+ public record ComponentChannelIdValue(String componentId, ChannelId channelId, Object value, boolean force)
+ implements ChannelValue {
+ @Override
+ public String toString() {
+ return this.componentId + "/" + this.channelId.id() + ":" + this.value;
+ }
}
}
@@ -109,19 +158,71 @@ public TestCase(String description) {
}
/**
- * Adds an input value for a Channel.
+ * Adds an input value for a {@link ChannelAddress}.
*
* @param address the {@link ChannelAddress}
* @param value the value {@link Object}
* @return myself
*/
public TestCase input(ChannelAddress address, Object value) {
- this.inputs.add(new ChannelValue(address, value, false));
+ this.inputs.add(new ChannelAddressValue(address, value, false));
+ return this;
+ }
+
+ /**
+ * Adds an input value for a ChannelId of the given Component.
+ *
+ * @param componentId the Component-ID
+ * @param channelId the Channel-ID in CamelCase
+ * @param value the value {@link Object}
+ * @return myself
+ */
+ public TestCase input(String componentId, String channelId, Object value) {
+ return this.input(new ChannelAddress(componentId, channelId), value);
+ }
+
+ /**
+ * Adds an input value for a {@link ChannelId} of the given Component.
+ *
+ * @param componentId the Component-ID
+ * @param channelId the {@link ChannelId}
+ * @param value the value {@link Object}
+ * @return myself
+ */
+ public TestCase input(String componentId, ChannelId channelId, Object value) {
+ this.inputs.add(new ComponentChannelIdValue(componentId, channelId, value, false));
+ return this;
+ }
+
+ /**
+ * Adds an input value for a {@link ChannelId} of the system-under-test.
+ *
+ * @param channelId the {@link ChannelId}
+ * @param value the value {@link Object}
+ * @return myself
+ */
+ public TestCase input(ChannelId channelId, Object value) {
+ if (channelId instanceof Sum.ChannelId) {
+ return this.input("_sum", channelId, value);
+ }
+ this.inputs.add(new ChannelIdValue(channelId, value, false));
+ return this;
+ }
+
+ /**
+ * Adds an input value for a ChannelId of the system-under-test.
+ *
+ * @param channelName the Channel
+ * @param value the value {@link Object}
+ * @return myself
+ */
+ public TestCase input(String channelName, Object value) {
+ this.inputs.add(new ChannelNameValue(channelName, value, false));
return this;
}
/**
- * Enforces an input value for a Channel.
+ * Enforces an input value for a {@link ChannelAddress}.
*
*
* Use this method if you want to be sure, that the Channel actually applies the
@@ -132,19 +233,130 @@ public TestCase input(ChannelAddress address, Object value) {
* @return myself
*/
public TestCase inputForce(ChannelAddress address, Object value) {
- this.inputs.add(new ChannelValue(address, value, true));
+ this.inputs.add(new ChannelAddressValue(address, value, true));
+ return this;
+ }
+
+ /**
+ * Enforces an input value for a {@link ChannelAddress}.
+ *
+ *
+ * Use this method if you want to be sure, that the Channel actually applies the
+ * value, e.g. to override a {@link Debounce} setting.
+ *
+ * @param componentId the Component-ID
+ * @param channelId the Channel-ID
+ * @param value the value {@link Object}
+ * @return myself
+ */
+ public TestCase inputForce(String componentId, String channelId, Object value) {
+ return this.inputForce(new ChannelAddress(componentId, channelId), value);
+ }
+
+ /**
+ * Enforces an input value for a {@link ChannelId} of the given Component.
+ *
+ * @param componentId the Component-ID
+ * @param channelId the {@link ChannelId}
+ * @param value the value {@link Object}
+ * @return myself
+ */
+ public TestCase inputForce(String componentId, ChannelId channelId, Object value) {
+ this.inputs.add(new ComponentChannelIdValue(componentId, channelId, value, true));
return this;
}
/**
- * Adds an expected output value for a Channel.
+ * Enforces an input value for a {@link ChannelId} of the system-under-test.
+ *
+ *
+ * Use this method if you want to be sure, that the Channel actually applies the
+ * value, e.g. to override a {@link Debounce} setting.
+ *
+ * @param channelId the {@link ChannelId}
+ * @param value the value {@link Object}
+ * @return myself
+ */
+ public TestCase inputForce(ChannelId channelId, Object value) {
+ this.inputs.add(new ChannelIdValue(channelId, value, true));
+ return this;
+ }
+
+ /**
+ * Enforces an input value for a Channel of the system-under-test.
+ *
+ *
+ * Use this method if you want to be sure, that the Channel actually applies the
+ * value, e.g. to override a {@link Debounce} setting.
+ *
+ * @param channelName the Channel
+ * @param value the value {@link Object}
+ * @return myself
+ */
+ public TestCase inputForce(String channelName, Object value) {
+ this.inputs.add(new ChannelNameValue(channelName, value, true));
+ return this;
+ }
+
+ /**
+ * Adds an expected output value for a {@link ChannelAddress}.
*
* @param address the {@link ChannelAddress}
* @param value the value {@link Object}
* @return myself
*/
public TestCase output(ChannelAddress address, Object value) {
- this.outputs.add(new ChannelValue(address, value, false));
+ this.outputs.add(new ChannelAddressValue(address, value, false));
+ return this;
+ }
+
+ /**
+ * Adds an expected output value for a {@link ChannelAddress}.
+ *
+ * @param componentId the Component-ID
+ * @param channelId the Channel-ID in CamelCase
+ * @param value the value {@link Object}
+ * @return myself
+ */
+ public TestCase output(String componentId, String channelId, Object value) {
+ return this.output(new ChannelAddress(componentId, channelId), value);
+ }
+
+ /**
+ * Adds an expected output value for a {@link ChannelId} of the given Component.
+ *
+ * @param componentId the Component-ID
+ * @param channelId the {@link ChannelId}
+ * @param value the value {@link Object}
+ * @return myself
+ */
+ public TestCase output(String componentId, ChannelId channelId, Object value) {
+ this.outputs.add(new ComponentChannelIdValue(componentId, channelId, value, true));
+ return this;
+ }
+
+ /**
+ * Adds an expected output value for a {@link ChannelId} of the
+ * system-under-test.
+ *
+ * @param channelId the {@link ChannelId}
+ * @param value the value {@link Object}
+ * @return myself
+ */
+ public TestCase output(ChannelId channelId, Object value) {
+ this.outputs.add(new ChannelIdValue(channelId, value, false));
+ return this;
+ }
+
+ /**
+ * Adds an expected output value for a Channel of the system-under-test.
+ *
+ * @param channelName the Channel
+ * @param value the value {@link Object}
+ * @return myself
+ */
+ public TestCase output(String channelName, Object value) {
+ this.outputs.add(new ChannelNameValue(channelName, value, false));
return this;
}
@@ -286,20 +498,14 @@ public void applyTimeLeap() {
/**
* Applies the values for input channels.
*
- * @param components Referenced components
+ * @param act the {@link AbstractComponentTest}
* @throws OpenemsNamedException on error
* @throws IllegalArgumentException on error
*/
- protected void applyInputs(Map components)
+ protected void applyInputs(AbstractComponentTest, ?> act)
throws IllegalArgumentException, OpenemsNamedException {
for (var input : this.inputs) {
- var component = components.get(input.address.getComponentId());
- if (component == null) {
- throw new IllegalArgumentException("On TestCase [" + this.description + "]: " //
- + "the component [" + input.address.getComponentId() + "] " //
- + "was not added to the OpenEMS Component test framework!");
- }
- var channel = component.channel(input.address.getChannelId());
+ final Channel> channel = this.getChannel(act, input);
// (Force) set the Read-Value
do {
@@ -317,38 +523,76 @@ protected void applyInputs(Map components)
/**
* Validates the output values.
*
- * @param components Referenced components
+ * @param act the {@link AbstractComponentTest}
* @throws Exception on validation failure
*/
- protected void validateOutputs(Map components) throws Exception {
+ @SuppressWarnings("unchecked")
+ protected void validateOutputs(AbstractComponentTest, ?> act) throws Exception {
for (var output : this.outputs) {
- var expected = output.value;
- var channel = components.get(output.address.getComponentId()).channel(output.address.getChannelId());
+ final Channel> channel = this.getChannel(act, output);
+
Object got;
- if (channel instanceof WriteChannel) {
- got = ((WriteChannel>) channel).getNextWriteValueAndReset().orElse(null);
+ if (channel instanceof WriteChannel wc) {
+ got = wc.getNextWriteValueAndReset().orElse(null);
} else {
var value = channel.getNextValue();
got = value.orElse(null);
}
-
// Try to parse an Enum
if (channel.channelDoc() instanceof EnumDoc) {
var enumDoc = (EnumDoc) channel.channelDoc();
var intGot = TypeUtils.getAsType(OpenemsType.INTEGER, got);
got = enumDoc.getOption(intGot);
}
- if (!Objects.equals(expected, got)) {
+ if (!Objects.equals(output.value(), got)) {
throw new Exception("On TestCase [" + this.description + "]: " //
- + "expected [" + output.value + "] " //
+ + "expected [" + output.value() + "] " //
+ "got [" + got + "] " //
- + "for Channel [" + output.address.toString() + "] " //
+ + "for Channel [" + output.toString() + "] " //
+ "on Inputs [" + this.inputs + "]");
}
}
}
+
+ private OpenemsComponent getComponent(Map components, String componentId) {
+ var component = components.get(componentId);
+ if (component != null) {
+ return component;
+ }
+ throw new IllegalArgumentException("On TestCase [" + this.description + "]: " //
+ + "the component [" + componentId + "] " //
+ + "was not added to the OpenEMS Component test framework!");
+ }
+
+ private Channel> getChannel(AbstractComponentTest, ?> act, ChannelValue cv)
+ throws IllegalArgumentException {
+ if (cv instanceof ChannelAddressValue cav) {
+ var component = this.getComponent(act.components, cav.address.getComponentId());
+ return component.channel(cav.address.getChannelId());
+ }
+
+ if (cv instanceof ChannelIdValue civ) {
+ return act.sut.channel(civ.channelId);
+ }
+
+ if (cv instanceof ChannelNameValue civ2) {
+ return act.sut.channel(civ2.channelName);
+ }
+
+ if (cv instanceof ComponentChannelIdValue cciv) {
+ var component = this.getComponent(act.components, cciv.componentId());
+ return component.channel(cciv.channelId());
+ }
+
+ throw new IllegalArgumentException("Unhandled subtype of ChannelValue");
+ }
}
+ /**
+ * The {@link OpenemsComponent} to be tested. "sut" is for system-under-test.
+ */
+ public final SUT sut;
+
/**
* References added by {@link #addReference()}.
*/
@@ -359,11 +603,6 @@ protected void validateOutputs(Map components) throws
*/
private final Map components = new HashMap<>();
- /**
- * The {@link OpenemsComponent} to be tested. "sut" is for system-under-test.
- */
- private final SUT sut;
-
/**
* Constructs the Component-Test and validates the implemented Channel-IDs.
*
@@ -469,11 +708,9 @@ public SELF addReference(String memberName, Object object) throws Exception {
private boolean addReference(Class> clazz, String memberName, Object object)
throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
try {
- var field = clazz.getDeclaredField(memberName);
- field.setAccessible(true);
- field.set(this.sut, object);
+ setAttributeViaReflection(this.sut, memberName, object);
return true;
- } catch (NoSuchFieldException e) {
+ } catch (ReflectionException e) {
// Ignore. Try method.
if (this.invokeSingleArgMethod(clazz, memberName, object)) {
return true;
@@ -603,8 +840,7 @@ private boolean callActivateOrModified(String methodName, AbstractComponentConfi
}
args[i] = arg;
}
- method.setAccessible(true);
- method.invoke(this.sut, args);
+ invokeMethodViaReflection(this.sut, method, args);
return true;
}
return false;
@@ -624,14 +860,10 @@ private void callModified(AbstractComponentConfig config) throws Exception {
private void callDeactivate() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,
NoSuchMethodException, SecurityException {
- Class> clazz = this.sut.getClass();
- var method = clazz.getDeclaredMethod("deactivate");
- method.setAccessible(true);
- method.invoke(this.sut);
+ invokeMethodWithoutArgumentsViaReflection(this.sut, "deactivate");
}
- private boolean invokeSingleArgMethod(Class> clazz, String methodName, Object arg)
- throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+ private boolean invokeSingleArgMethod(Class> clazz, String methodName, Object arg) throws ReflectionException {
var methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (!method.getName().equals(methodName)) {
@@ -646,8 +878,7 @@ private boolean invokeSingleArgMethod(Class> clazz, String methodName, Object
continue;
}
- method.setAccessible(true);
- method.invoke(this.sut, arg);
+ invokeMethodViaReflection(this.sut, method, arg);
return true;
}
@@ -670,7 +901,7 @@ public SELF next(TestCase testCase) throws Exception {
for (Channel> channel : this.getSut().channels()) {
channel.nextProcessImage();
}
- testCase.applyInputs(this.components);
+ testCase.applyInputs(this);
this.onAfterProcessImage();
executeCallbacks(testCase.onAfterProcessImageCallbacks);
this.handleEvent(EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE);
@@ -691,7 +922,7 @@ public SELF next(TestCase testCase) throws Exception {
this.onAfterWrite();
executeCallbacks(testCase.onAfterWriteCallbacks);
this.handleEvent(EdgeEventConstants.TOPIC_CYCLE_AFTER_WRITE);
- testCase.validateOutputs(this.components);
+ testCase.validateOutputs(this);
return this.self();
}
@@ -725,9 +956,11 @@ private static void executeCallbacks(List> callbacks
*
*/
protected void handleEvent(String topic) throws Exception {
- if (this.sut instanceof EventHandler) {
- var event = new Event(topic, new HashMap());
- ((EventHandler) this.sut).handleEvent(event);
+ var event = new Event(topic, new HashMap());
+ for (var component : this.components.values()) {
+ if (component instanceof EventHandler eh) {
+ eh.handleEvent(event);
+ }
}
}
@@ -802,7 +1035,5 @@ protected void onExecuteWrite() throws OpenemsNamedException {
* @throws OpenemsNamedException on error
*/
protected void onAfterWrite() throws OpenemsNamedException {
-
}
-
}
diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java b/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java
index a3a43bbe07f..db5ebb96825 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java
@@ -1,5 +1,7 @@
package io.openems.edge.common.test;
+import static io.openems.edge.common.test.TestUtils.createDummyClock;
+
import java.io.IOException;
import java.time.Clock;
import java.util.ArrayList;
@@ -47,7 +49,7 @@ public class DummyComponentManager implements ComponentManager, ComponentJsonApi
private ConfigurationAdmin configurationAdmin = null;
public DummyComponentManager() {
- this(Clock.systemDefaultZone());
+ this(createDummyClock());
}
public DummyComponentManager(Clock clock) {
diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java b/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java
index d58c7dbf484..4cb54a3f37e 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java
@@ -2,9 +2,14 @@
import java.io.IOException;
import java.net.ServerSocket;
+import java.time.Instant;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import io.openems.common.test.TimeLeapClock;
import io.openems.edge.common.channel.Channel;
import io.openems.edge.common.channel.ChannelId;
+import io.openems.edge.common.channel.value.Value;
import io.openems.edge.common.component.OpenemsComponent;
public class TestUtils {
@@ -13,6 +18,15 @@ private TestUtils() {
}
+ /**
+ * Creates a {@link TimeLeapClock} for 1st January 2000 00:00.
+ *
+ * @return the {@link TimeLeapClock}
+ */
+ public static TimeLeapClock createDummyClock() {
+ return new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */);
+ }
+
/**
* Finds and returns an open port.
*
@@ -69,4 +83,24 @@ public static void withValue(Channel> channel, Object value) {
channel.setNextValue(value);
channel.nextProcessImage();
}
+
+ /**
+ * Helper to test a {@link #withValue(Channel, Object)} method in a JUnit test.
+ *
+ * @param the type of the {@link AbstractDummyOpenemsComponent}
+ * @param sut the actual system-under-test
+ * @param setter the getChannel getter method
+ * @param getter the withChannel setter method
+ */
+ public static void testWithValue(T sut, BiFunction setter, Function> getter) {
+ var before = getter.apply(sut).get();
+ if (before != null) {
+ throw new IllegalArgumentException("TestUtils.testWithValue() expected [null] got [" + before + "]");
+ }
+ setter.apply(sut, 123);
+ var after = getter.apply(sut).get().intValue();
+ if (after != 123) {
+ throw new IllegalArgumentException("TestUtils.testWithValue() expected [123] got [" + after + "]");
+ }
+ }
}
diff --git a/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java b/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java
index babada99a81..d43a8bc6716 100644
--- a/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java
+++ b/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java
@@ -364,23 +364,15 @@ public static JsonElement getAsJson(OpenemsType type, Object originalValue) {
return JsonNull.INSTANCE;
}
var value = TypeUtils.getAsType(type, originalValue);
- switch (type) {
- case BOOLEAN:
- return new JsonPrimitive((Boolean) value ? 1 : 0);
- case SHORT:
- return new JsonPrimitive((Short) value);
- case INTEGER:
- return new JsonPrimitive((Integer) value);
- case LONG:
- return new JsonPrimitive((Long) value);
- case FLOAT:
- return new JsonPrimitive((Float) value);
- case DOUBLE:
- return new JsonPrimitive((Double) value);
- case STRING:
- return new JsonPrimitive((String) value);
- }
- throw new IllegalArgumentException("Converter for value [" + value + "] to JSON is not implemented.");
+ return switch (type) {
+ case BOOLEAN -> new JsonPrimitive((Boolean) value ? 1 : 0);
+ case SHORT -> new JsonPrimitive((Short) value);
+ case INTEGER -> new JsonPrimitive((Integer) value);
+ case LONG -> new JsonPrimitive((Long) value);
+ case FLOAT -> new JsonPrimitive((Float) value);
+ case DOUBLE -> new JsonPrimitive((Double) value);
+ case STRING -> new JsonPrimitive((String) value);
+ };
}
/**
@@ -427,6 +419,28 @@ public static Integer sum(Integer... values) {
return result;
}
+ /**
+ * Safely add Floats. If one of them is null it is considered '0'. If all of
+ * them are null, 'null' is returned.
+ *
+ * @param values the {@link Float} values
+ * @return the sum
+ */
+ public static Float sum(Float... values) {
+ Float result = null;
+ for (Float value : values) {
+ if (value == null) {
+ continue;
+ }
+ if (result == null) {
+ result = value;
+ } else {
+ result += value;
+ }
+ }
+ return result;
+ }
+
/**
* Safely add Longs. If one of them is null it is considered '0'. If all of them
* are null, 'null' is returned.
diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat32Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat32Test.java
new file mode 100644
index 00000000000..197fbff8dcf
--- /dev/null
+++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat32Test.java
@@ -0,0 +1,30 @@
+package io.openems.edge.common.modbusslave;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class ModbusRecordFloat32Test {
+
+ @Test
+ public void testValue() {
+ var sut = new ModbusRecordFloat32(0, "foo", 1234567.89F);
+ assertEquals("ModbusRecordFloat32 [value=1234567.9, type=float32]", sut.toString());
+ assertEquals("\"1234567.9\"", sut.getValueDescription());
+ }
+
+ @Test
+ public void testNull() {
+ var sut = new ModbusRecordFloat32(0, "bar", null);
+ assertEquals("ModbusRecordFloat32 [value=UNDEFINED, type=float32]", sut.toString());
+ assertEquals("", sut.getValueDescription());
+ }
+
+ @Test
+ public void testReserved() {
+ var sut = new ModbusRecordFloat32Reserved(0);
+ assertEquals("ModbusRecordFloat32Reserved [type=float32]", sut.toString());
+ assertEquals("", sut.getValueDescription());
+ }
+
+}
diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat64Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat64Test.java
new file mode 100644
index 00000000000..70a21900022
--- /dev/null
+++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat64Test.java
@@ -0,0 +1,30 @@
+package io.openems.edge.common.modbusslave;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class ModbusRecordFloat64Test {
+
+ @Test
+ public void testValue() {
+ var sut = new ModbusRecordFloat64(0, "foo", 1234567.89);
+ assertEquals("ModbusRecordFloat64 [value=1234567.89, type=float64]", sut.toString());
+ assertEquals("\"1234567.89\"", sut.getValueDescription());
+ }
+
+ @Test
+ public void testNull() {
+ var sut = new ModbusRecordFloat64(0, "bar", null);
+ assertEquals("ModbusRecordFloat64 [value=UNDEFINED, type=float64]", sut.toString());
+ assertEquals("", sut.getValueDescription());
+ }
+
+ @Test
+ public void testReserved() {
+ var sut = new ModbusRecordFloat64Reserved(0);
+ assertEquals("ModbusRecordFloat64Reserved [type=float64]", sut.toString());
+ assertEquals("", sut.getValueDescription());
+ }
+
+}
diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordString16Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordString16Test.java
new file mode 100644
index 00000000000..d6bea0edd02
--- /dev/null
+++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordString16Test.java
@@ -0,0 +1,42 @@
+package io.openems.edge.common.modbusslave;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+public class ModbusRecordString16Test {
+
+ @Test
+ public void testValue() {
+ var sut = new ModbusRecordString16(0, "foo", "bar");
+ assertEquals("ModbusRecordString16 [value=bar, type=string16]", sut.toString());
+ assertEquals("\"bar\"", sut.getValueDescription());
+ }
+
+ @Test
+ public void testNull() {
+ var sut = new ModbusRecordString16(0, "bar", null);
+ assertEquals("ModbusRecordString16 [value=UNDEFINED, type=string16]", sut.toString());
+ assertEquals("", sut.getValueDescription());
+ }
+
+ @Test
+ public void testReserved() {
+ var sut = new ModbusRecordString16Reserved(0);
+ assertEquals("ModbusRecordString16Reserved [type=string16]", sut.toString());
+ assertEquals("", sut.getValueDescription());
+ }
+
+ @Test
+ public void testToByteArray() {
+ assertEquals("[72, 101, 108, 108, 111, "//
+ + "32, " //
+ + "87, 111, 114, 108, 100, " //
+ + "0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]",
+ Arrays.toString(ModbusRecordString16.toByteArray((Object) "Hello World")));
+ assertEquals("[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]",
+ Arrays.toString(ModbusRecordString16.toByteArray((Object) null)));
+ }
+}
diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint16Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint16Test.java
new file mode 100644
index 00000000000..7752f124cd2
--- /dev/null
+++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint16Test.java
@@ -0,0 +1,52 @@
+package io.openems.edge.common.modbusslave;
+
+import static io.openems.common.test.DummyOptionsEnum.UNDEFINED;
+import static io.openems.common.test.DummyOptionsEnum.VALUE_1;
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+public class ModbusRecordUint16Test {
+
+ @Test
+ public void testValue() {
+ var sut = new ModbusRecordUint16(0, "foo", (short) 12345);
+ assertEquals("ModbusRecordUInt16 [value=12345/0x3039, type=uint16]", sut.toString());
+ assertEquals("\"12345\"", sut.getValueDescription());
+ }
+
+ @Test
+ public void testNull() {
+ var sut = new ModbusRecordUint16(0, "bar", null);
+ assertEquals("ModbusRecordUInt16 [value=UNDEFINED, type=uint16]", sut.toString());
+ assertEquals("", sut.getValueDescription());
+ }
+
+ @Test
+ public void testOptionsEnum() {
+ assertEquals("[-1, -1]", Arrays.toString(ModbusRecordUint16.toByteArray(UNDEFINED)));
+ assertEquals("[0, 1]", Arrays.toString(ModbusRecordUint16.toByteArray(VALUE_1)));
+ }
+
+ @Test
+ public void testReserved() {
+ var sut = new ModbusRecordUint16Reserved(0);
+ assertEquals("ModbusRecordUint16Reserved [type=uint16]", sut.toString());
+ assertEquals("", sut.getValueDescription());
+ }
+
+ @Test
+ public void testBlockLength() {
+ assertEquals("ModbusRecordUint16BlockLength [blockName=block, value=12345/0x3039, type=uint16]",
+ new ModbusRecordUint16BlockLength(0, "block", (short) 12345).toString());
+ }
+
+ @Test
+ public void testHash() {
+ var sut = new ModbusRecordUint16Hash(0, "hash");
+ assertEquals("ModbusRecordUint16Hash [text=hash, value=-16114/0xc10e, type=uint16]", sut.toString());
+ assertEquals("\"0xc10e\"", sut.getValueDescription());
+ }
+}
diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint32Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint32Test.java
new file mode 100644
index 00000000000..0f5504bf857
--- /dev/null
+++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint32Test.java
@@ -0,0 +1,40 @@
+package io.openems.edge.common.modbusslave;
+
+import static io.openems.common.test.DummyOptionsEnum.UNDEFINED;
+import static io.openems.common.test.DummyOptionsEnum.VALUE_1;
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+public class ModbusRecordUint32Test {
+
+ @Test
+ public void testValue() {
+ var sut = new ModbusRecordUint32(0, "foo", 123456789);
+ assertEquals("ModbusRecordUInt32 [value=123456789/0x75bcd15, type=uint32]", sut.toString());
+ assertEquals("\"123456789\"", sut.getValueDescription());
+ }
+
+ @Test
+ public void testNull() {
+ var sut = new ModbusRecordUint32(0, "bar", null);
+ assertEquals("ModbusRecordUInt32 [value=UNDEFINED, type=uint32]", sut.toString());
+ assertEquals("", sut.getValueDescription());
+ }
+
+ @Test
+ public void testOptionsEnum() {
+ assertEquals("[-1, -1, -1, -1]", Arrays.toString(ModbusRecordUint32.toByteArray(UNDEFINED)));
+ assertEquals("[0, 0, 0, 1]", Arrays.toString(ModbusRecordUint32.toByteArray(VALUE_1)));
+ }
+
+ @Test
+ public void testReserved() {
+ var sut = new ModbusRecordUint32Reserved(0);
+ assertEquals("ModbusRecordUint32Reserved [type=uint32]", sut.toString());
+ assertEquals("", sut.getValueDescription());
+ }
+
+}
diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint64Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint64Test.java
new file mode 100644
index 00000000000..903a5139236
--- /dev/null
+++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint64Test.java
@@ -0,0 +1,40 @@
+package io.openems.edge.common.modbusslave;
+
+import static io.openems.common.test.DummyOptionsEnum.UNDEFINED;
+import static io.openems.common.test.DummyOptionsEnum.VALUE_1;
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+public class ModbusRecordUint64Test {
+
+ @Test
+ public void testValue() {
+ var sut = new ModbusRecordUint64(0, "foo", 123456789L);
+ assertEquals("ModbusRecordUInt64 [value=123456789/0x75bcd15, type=uint64]", sut.toString());
+ assertEquals("\"123456789\"", sut.getValueDescription());
+ }
+
+ @Test
+ public void testNull() {
+ var sut = new ModbusRecordUint64(0, "bar", null);
+ assertEquals("ModbusRecordUInt64 [value=UNDEFINED, type=uint64]", sut.toString());
+ assertEquals("", sut.getValueDescription());
+ }
+
+ @Test
+ public void testOptionsEnum() {
+ assertEquals("[-1, -1, -1, -1, -1, -1, -1, -1]", Arrays.toString(ModbusRecordUint64.toByteArray(UNDEFINED)));
+ assertEquals("[0, 0, 0, 0, 0, 0, 0, 1]", Arrays.toString(ModbusRecordUint64.toByteArray(VALUE_1)));
+ }
+
+ @Test
+ public void testReserved() {
+ var sut = new ModbusRecordUint64Reserved(0);
+ assertEquals("ModbusRecordUint64Reserved [type=uint64]", sut.toString());
+ assertEquals("", sut.getValueDescription());
+ }
+
+}
diff --git a/io.openems.edge.common/test/io/openems/edge/common/sum/DummySumTest.java b/io.openems.edge.common/test/io/openems/edge/common/sum/DummySumTest.java
new file mode 100644
index 00000000000..8cb89d3a6d2
--- /dev/null
+++ b/io.openems.edge.common/test/io/openems/edge/common/sum/DummySumTest.java
@@ -0,0 +1,22 @@
+package io.openems.edge.common.sum;
+
+import static io.openems.edge.common.test.TestUtils.testWithValue;
+
+import org.junit.Test;
+
+import io.openems.common.exceptions.OpenemsException;
+
+public class DummySumTest {
+
+ @Test
+ public void test() throws OpenemsException {
+ final var sut = new DummySum();
+
+ testWithValue(sut, DummySum::withProductionAcActivePower, Sum::getProductionAcActivePower);
+ testWithValue(sut, DummySum::withGridActivePower, Sum::getGridActivePower);
+ testWithValue(sut, DummySum::withEssCapacity, Sum::getEssCapacity);
+ testWithValue(sut, DummySum::withEssSoc, Sum::getEssSoc);
+ testWithValue(sut, DummySum::withEssMinDischargePower, Sum::getEssMinDischargePower);
+ testWithValue(sut, DummySum::withEssMaxDischargePower, Sum::getEssMaxDischargePower);
+ }
+}
diff --git a/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java b/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java
index 83c76584a1c..f5ff84b47f3 100644
--- a/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java
+++ b/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java
@@ -1,5 +1,15 @@
package io.openems.edge.common.type;
+import static com.google.gson.JsonNull.INSTANCE;
+import static io.openems.common.types.OpenemsType.BOOLEAN;
+import static io.openems.common.types.OpenemsType.DOUBLE;
+import static io.openems.common.types.OpenemsType.FLOAT;
+import static io.openems.common.types.OpenemsType.INTEGER;
+import static io.openems.common.types.OpenemsType.LONG;
+import static io.openems.common.types.OpenemsType.SHORT;
+import static io.openems.common.types.OpenemsType.STRING;
+import static io.openems.edge.common.type.TypeUtils.getAsJson;
+import static io.openems.edge.common.type.TypeUtils.sum;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -7,8 +17,9 @@
import org.junit.Test;
+import com.google.gson.JsonPrimitive;
+
import io.openems.common.function.ThrowingRunnable;
-import io.openems.common.types.OpenemsType;
import io.openems.common.types.OptionsEnum;
import io.openems.edge.common.channel.value.Value;
@@ -81,8 +92,8 @@ public void testMin() {
@Test
public void testSumDouble() {
- assertNull(TypeUtils.sum((Double) null, null));
- assertEquals(4.0, TypeUtils.sum(1.5, 2.5), 0.1);
+ assertNull(sum((Double) null, null));
+ assertEquals(4.0, sum(1.5, 2.5), 0.1);
}
@Test
@@ -269,6 +280,47 @@ public void testGetAsType() {
}
}
+ @Test
+ public void testGetAsJson() {
+ assertEquals(INSTANCE, getAsJson(INTEGER, null));
+ assertEquals(new JsonPrimitive(0), getAsJson(BOOLEAN, false));
+ assertEquals(new JsonPrimitive(1), getAsJson(BOOLEAN, true));
+ assertEquals(new JsonPrimitive(123), getAsJson(SHORT, 123));
+ assertEquals(new JsonPrimitive(234), getAsJson(INTEGER, 234));
+ assertEquals(new JsonPrimitive(345), getAsJson(LONG, 345));
+ assertEquals(new JsonPrimitive(45.6F), getAsJson(FLOAT, 45.6F));
+ assertEquals(new JsonPrimitive(56.7), getAsJson(DOUBLE, 56.7));
+ assertEquals(new JsonPrimitive("678"), getAsJson(STRING, "678"));
+ }
+
+ @Test
+ public void sumInteger() {
+ assertEquals(6, sum(1, 2, 3).intValue());
+ assertNull(sum((Integer) null));
+ assertEquals(6, sum(1, null, 2, 3).intValue());
+ }
+
+ @Test
+ public void sumFloat() {
+ assertEquals(6F, sum(1F, 2F, 3F).floatValue(), 0.001F);
+ assertNull(sum((Float) null));
+ assertEquals(6F, sum(1F, null, 2F, 3F).floatValue(), 0.001F);
+ }
+
+ @Test
+ public void sumLong() {
+ assertEquals(6L, sum(1L, 2L, 3L).longValue());
+ assertNull(sum((Long) null));
+ assertEquals(6L, sum(1L, null, 2L, 3L).longValue());
+ }
+
+ @Test
+ public void sumDouble() {
+ assertEquals(6., sum(1., 2., 3.).doubleValue(), 0.001);
+ assertNull(sum((Double) null));
+ assertEquals(6., sum(1., null, 2., 3.).doubleValue(), 0.001);
+ }
+
private static void assertException(ThrowingRunnable runnable) {
try {
runnable.run();
@@ -279,31 +331,31 @@ private static void assertException(ThrowingRunnable runnable) {
}
private static Boolean getAsBoolean(Object value) {
- return TypeUtils.getAsType(OpenemsType.BOOLEAN, value);
+ return TypeUtils.getAsType(BOOLEAN, value);
}
private static Short getAsShort(Object value) {
- return TypeUtils.getAsType(OpenemsType.SHORT, value);
+ return TypeUtils.getAsType(SHORT, value);
}
private static Integer getAsInteger(Object value) {
- return TypeUtils.getAsType(OpenemsType.INTEGER, value);
+ return TypeUtils.getAsType(INTEGER, value);
}
private static Long getAsLong(Object value) {
- return TypeUtils.getAsType(OpenemsType.LONG, value);
+ return TypeUtils.getAsType(LONG, value);
}
private static Float getAsFloat(Object value) {
- return TypeUtils.getAsType(OpenemsType.FLOAT, value);
+ return TypeUtils.getAsType(FLOAT, value);
}
private static Double getAsDouble(Object value) {
- return TypeUtils.getAsType(OpenemsType.DOUBLE, value);
+ return TypeUtils.getAsType(DOUBLE, value);
}
private static String getAsString(Object value) {
- return TypeUtils.getAsType(OpenemsType.STRING, value);
+ return TypeUtils.getAsType(STRING, value);
}
private static enum MyOptionsEnum implements OptionsEnum {
diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java
index aeb2e73a5ba..10b7a380eab 100644
--- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java
+++ b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java
@@ -13,6 +13,7 @@
import io.openems.common.websocket.AbstractWebsocketClient;
import io.openems.common.websocket.OnClose;
+import io.openems.common.websocket.WsData;
public class WebsocketClient extends AbstractWebsocketClient {
diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WsData.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WsData.java
deleted file mode 100644
index 2dfdd355c5b..00000000000
--- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WsData.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package io.openems.edge.controller.api.backend;
-
-import org.java_websocket.WebSocket;
-
-public class WsData extends io.openems.common.websocket.WsData {
-
- public WsData(WebSocket ws) {
- super(ws);
- }
-
- @Override
- public String toString() {
- return "BackendApi.WsData []";
- }
-
-}
diff --git a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java
index 9d594615157..d99ef35e3bf 100644
--- a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java
+++ b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java
@@ -17,8 +17,6 @@
public class ControllerApiBackendImplTest {
- private static final String CTRL_ID = "ctrl0";
-
@Test
public void test() throws Exception {
@@ -44,7 +42,7 @@ public void test() throws Exception {
.addReference("oem", new DummyOpenemsEdgeOem()) //
.addComponent(new DummySum()) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
+ .setId("ctrl0") //
.setUri("ws://localhost:" + port) //
.setApikey("12345") //
.setProxyType(Type.DIRECT) //
diff --git a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyBackendOnRequestFactory.java b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyBackendOnRequestFactory.java
index 05aed052db3..bb10ff7fd07 100644
--- a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyBackendOnRequestFactory.java
+++ b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyBackendOnRequestFactory.java
@@ -1,21 +1,20 @@
package io.openems.edge.controller.api.backend;
-import java.lang.reflect.InvocationTargetException;
+import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentServiceObjects;
-import io.openems.common.utils.ReflectionUtils;
+import io.openems.common.utils.ReflectionUtils.ReflectionException;
import io.openems.edge.controller.api.backend.handler.BindingRoutesJsonApiHandler;
import io.openems.edge.controller.api.backend.handler.RootRequestHandler;
import io.openems.edge.controller.api.common.handler.RoutesJsonApiHandler;
public class DummyBackendOnRequestFactory extends BackendOnRequest.Factory {
- public DummyBackendOnRequestFactory()
- throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+ public DummyBackendOnRequestFactory() throws ReflectionException {
super();
- ReflectionUtils.setAttribute(BackendOnRequest.Factory.class, this, "cso", new DummyBackendOnRequestCso());
+ setAttributeViaReflection(this, "cso", new DummyBackendOnRequestCso());
}
private static class DummyBackendOnRequestCso implements ComponentServiceObjects {
diff --git a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyResendHistoricDataWorkerFactory.java b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyResendHistoricDataWorkerFactory.java
index 91aff08460e..55e5e7f439c 100644
--- a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyResendHistoricDataWorkerFactory.java
+++ b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/DummyResendHistoricDataWorkerFactory.java
@@ -1,19 +1,17 @@
package io.openems.edge.controller.api.backend;
-import java.lang.reflect.InvocationTargetException;
+import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentServiceObjects;
-import io.openems.common.utils.ReflectionUtils;
+import io.openems.common.utils.ReflectionUtils.ReflectionException;
public class DummyResendHistoricDataWorkerFactory extends ResendHistoricDataWorkerFactory {
- public DummyResendHistoricDataWorkerFactory()
- throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+ public DummyResendHistoricDataWorkerFactory() throws ReflectionException {
super();
- ReflectionUtils.setAttribute(ResendHistoricDataWorkerFactory.class, this, "cso",
- new DummyResendHistoricDataWorkerCso());
+ setAttributeViaReflection(this, "cso", new DummyResendHistoricDataWorkerCso());
}
private static class DummyResendHistoricDataWorkerCso implements ComponentServiceObjects {
diff --git a/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/handler/QueryRequestHandler.java b/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/handler/QueryRequestHandler.java
index 9bed397860b..64496f773cc 100644
--- a/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/handler/QueryRequestHandler.java
+++ b/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/handler/QueryRequestHandler.java
@@ -1,11 +1,17 @@
package io.openems.edge.controller.api.common.handler;
+import java.io.IOException;
+import java.time.temporal.ChronoUnit;
+import java.util.Collections;
+import java.util.TreeSet;
+
import org.osgi.service.component.annotations.Component;
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 io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.exceptions.OpenemsException;
import io.openems.common.jsonrpc.request.QueryHistoricTimeseriesDataRequest;
import io.openems.common.jsonrpc.request.QueryHistoricTimeseriesEnergyPerPeriodRequest;
@@ -14,6 +20,13 @@
import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesDataResponse;
import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesEnergyPerPeriodResponse;
import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesEnergyResponse;
+import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesExportXlsxResponse;
+import io.openems.common.session.Language;
+import io.openems.common.timedata.Resolution;
+import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry.HistoricTimedataSaveType;
+import io.openems.common.timedata.XlsxExportUtil;
+import io.openems.common.types.ChannelAddress;
+import io.openems.edge.common.component.ComponentManager;
import io.openems.edge.common.jsonapi.EdgeKeys;
import io.openems.edge.common.jsonapi.JsonApi;
import io.openems.edge.common.jsonapi.JsonApiBuilder;
@@ -29,6 +42,9 @@ public class QueryRequestHandler implements JsonApi {
)
private volatile Timedata timedata;
+ @Reference
+ private ComponentManager componentManager;
+
@Override
public void buildJsonApiRoutes(JsonApiBuilder builder) {
builder.handleRequest(QueryHistoricTimeseriesDataRequest.METHOD, call -> {
@@ -54,14 +70,39 @@ public void buildJsonApiRoutes(JsonApiBuilder builder) {
request.getFromDate(), request.getToDate(), request.getChannels(), request.getResolution());
return new QueryHistoricTimeseriesEnergyPerPeriodResponse(request.getId(), data);
});
-
builder.handleRequest(QueryHistoricTimeseriesExportXlxsRequest.METHOD, call -> {
final var request = QueryHistoricTimeseriesExportXlxsRequest.from(call.getRequest());
- return this.getTimedata().handleQueryHistoricTimeseriesExportXlxsRequest(null /* ignore Edge-ID */, request,
+ return this.handleQueryHistoricTimeseriesExportXlxsRequest(request,
call.get(EdgeKeys.USER_KEY).getLanguage());
});
}
+ private QueryHistoricTimeseriesExportXlsxResponse handleQueryHistoricTimeseriesExportXlxsRequest(
+ QueryHistoricTimeseriesExportXlxsRequest request, Language language) throws OpenemsNamedException {
+ final var powerChannels = new TreeSet(QueryHistoricTimeseriesExportXlsxResponse.POWER_CHANNELS);
+ final var energyChannels = new TreeSet(
+ QueryHistoricTimeseriesExportXlsxResponse.ENERGY_CHANNELS);
+ final var detailData = XlsxExportUtil.getDetailData(this.componentManager.getEdgeConfig());
+ final var channelsByType = detailData.getChannelsBySaveType();
+ powerChannels.addAll(channelsByType.getOrDefault(HistoricTimedataSaveType.POWER, Collections.emptyList()));
+ energyChannels.addAll(channelsByType.getOrDefault(HistoricTimedataSaveType.ENERGY, Collections.emptyList()));
+ var powerData = this.timedata.queryHistoricData(null, request.getFromDate(), request.getToDate(),
+ powerChannels, new Resolution(15, ChronoUnit.MINUTES));
+
+ var energyData = this.timedata.queryHistoricEnergy(null, request.getFromDate(), request.getToDate(),
+ energyChannels);
+ if (powerData == null || energyData == null) {
+ return null;
+ }
+ try {
+ return new QueryHistoricTimeseriesExportXlsxResponse(request.getId(), null, request.getFromDate(),
+ request.getToDate(), powerData, energyData, language, detailData);
+
+ } catch (IOException e) {
+ throw new OpenemsException("QueryHistoricTimeseriesExportXlxsRequest failed: " + e.getMessage());
+ }
+ }
+
private final Timedata getTimedata() throws OpenemsException {
final var currentTimedata = this.timedata;
if (currentTimedata == null) {
diff --git a/io.openems.edge.controller.api.modbus/bnd.bnd b/io.openems.edge.controller.api.modbus/bnd.bnd
index 420b544a143..87fd5d5a4a8 100644
--- a/io.openems.edge.controller.api.modbus/bnd.bnd
+++ b/io.openems.edge.controller.api.modbus/bnd.bnd
@@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp}
io.openems.edge.common,\
io.openems.edge.controller.api,\
io.openems.edge.controller.api.common,\
+ io.openems.edge.ess.api,\
io.openems.edge.timedata.api,\
io.openems.wrapper.fastexcel
diff --git a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/AbstractModbusTcpApi.java b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/AbstractModbusTcpApi.java
index 52e5f67deae..435021672eb 100644
--- a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/AbstractModbusTcpApi.java
+++ b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/AbstractModbusTcpApi.java
@@ -529,6 +529,6 @@ public boolean equals(Object other) {
* @return component_channelId as String
*/
public static String formatChannelName(WriteChannel> channel) {
- return channel.getComponent().alias() + "_" + channel.channelId().name();
+ return channel.getComponent().id() + "_" + channel.channelId().name();
}
}
diff --git a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolExportXlsxResponse.java b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolExportXlsxResponse.java
index 21f2447965c..b86d0a983bd 100644
--- a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolExportXlsxResponse.java
+++ b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolExportXlsxResponse.java
@@ -19,6 +19,7 @@
import io.openems.edge.common.modbusslave.ModbusRecordString16;
import io.openems.edge.common.modbusslave.ModbusRecordUint16;
import io.openems.edge.common.modbusslave.ModbusRecordUint32;
+import io.openems.edge.common.modbusslave.ModbusRecordUint64;
import io.openems.edge.common.modbusslave.ModbusType;
/**
@@ -148,25 +149,14 @@ private static void addUndefinedSheet(Workbook wb) {
var nextRow = 2;
for (ModbusType modbusType : ModbusType.values()) {
- byte[] value = {};
- switch (modbusType) {
- case FLOAT32:
- value = ModbusRecordFloat32.UNDEFINED_VALUE;
- break;
- case FLOAT64:
- value = ModbusRecordFloat64.UNDEFINED_VALUE;
- break;
- case STRING16:
- value = ModbusRecordString16.UNDEFINED_VALUE;
- break;
- case ENUM16:
- case UINT16:
- value = ModbusRecordUint16.UNDEFINED_VALUE;
- break;
- case UINT32:
- value = ModbusRecordUint32.UNDEFINED_VALUE;
- break;
- }
+ byte[] value = switch (modbusType) {
+ case FLOAT32 -> ModbusRecordFloat32.UNDEFINED_VALUE;
+ case FLOAT64 -> ModbusRecordFloat64.UNDEFINED_VALUE;
+ case STRING16 -> ModbusRecordString16.UNDEFINED_VALUE;
+ case ENUM16, UINT16 -> ModbusRecordUint16.UNDEFINED_VALUE;
+ case UINT32 -> ModbusRecordUint32.UNDEFINED_VALUE;
+ case UINT64 -> ModbusRecordUint64.UNDEFINED_VALUE;
+ };
nextRow++;
ws.value(nextRow, 0, modbusType.toString());
ws.value(nextRow, 1, byteArrayToString(value));
diff --git a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImpl.java b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImpl.java
index 74055d005b1..6f1a47ad49c 100644
--- a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImpl.java
+++ b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImpl.java
@@ -1,5 +1,8 @@
package io.openems.edge.controller.api.modbus.readwrite;
+import static io.openems.edge.common.channel.ChannelId.channelIdCamelToUpper;
+import static io.openems.edge.common.channel.ChannelId.channelIdUpperToCamel;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -27,20 +30,26 @@
import io.openems.common.channel.AccessMode;
import io.openems.common.channel.PersistencePriority;
+import io.openems.common.channel.Unit;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.exceptions.OpenemsException;
-import io.openems.common.types.OpenemsType;
import io.openems.edge.common.channel.ChannelId.ChannelIdImpl;
import io.openems.edge.common.channel.Doc;
+import io.openems.edge.common.channel.IntegerReadChannel;
import io.openems.edge.common.channel.WriteChannel;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.jsonapi.ComponentJsonApi;
import io.openems.edge.common.meta.Meta;
+import io.openems.edge.common.modbusslave.ModbusSlave;
+import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable;
+import io.openems.edge.common.modbusslave.ModbusSlaveTable;
+import io.openems.edge.common.modbusslave.ModbusType;
import io.openems.edge.controller.api.Controller;
import io.openems.edge.controller.api.common.Status;
import io.openems.edge.controller.api.common.WriteObject;
import io.openems.edge.controller.api.modbus.AbstractModbusTcpApi;
import io.openems.edge.controller.api.modbus.ModbusTcpApi;
+import io.openems.edge.ess.api.ManagedSymmetricEss;
import io.openems.edge.timedata.api.Timedata;
import io.openems.edge.timedata.api.TimedataProvider;
import io.openems.edge.timedata.api.utils.CalculateActiveTime;
@@ -52,20 +61,23 @@
configurationPolicy = ConfigurationPolicy.REQUIRE //
)
public class ControllerApiModbusTcpReadWriteImpl extends AbstractModbusTcpApi
- implements ControllerApiModbusTcpReadWrite, ModbusTcpApi, Controller, OpenemsComponent, ComponentJsonApi, TimedataProvider {
+ implements ControllerApiModbusTcpReadWrite, ModbusTcpApi, Controller, OpenemsComponent, ComponentJsonApi,
+ TimedataProvider, ModbusSlave {
private final Logger log = LoggerFactory.getLogger(ControllerApiModbusTcpReadWriteImpl.class);
-
+
private final CalculateActiveTime calculateCumulatedActiveTime = new CalculateActiveTime(this,
ControllerApiModbusTcpReadWrite.ChannelId.CUMULATED_ACTIVE_TIME);
-
+
private final CalculateActiveTime calculateCumulatedInactiveTime = new CalculateActiveTime(this,
ControllerApiModbusTcpReadWrite.ChannelId.CUMULATED_INACTIVE_TIME);
-
+
private List writeChannels;
-
+
+ private List components = new ArrayList<>();
+
private boolean isActive = false;
-
+
@Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL)
private volatile Timedata timedata = null;
@@ -83,10 +95,12 @@ public class ControllerApiModbusTcpReadWriteImpl extends AbstractModbusTcpApi
)
protected void addComponent(OpenemsComponent component) {
super.addComponent(component);
+ this.components.add(component);
}
protected void removeComponent(OpenemsComponent component) {
super.removeComponent(component);
+ this.components.remove(component);
}
public ControllerApiModbusTcpReadWriteImpl() {
@@ -105,7 +119,6 @@ private void activate(ComponentContext context, Config config) throws ModbusExce
new ConfigRecord(config.id(), config.alias(), config.enabled(), this.metaComponent,
config.component_ids(), config.apiTimeout(), config.port(), config.maxConcurrentConnections()));
this.applyConfig(config);
- this.handleTimeDataChannels();
}
@Modified
@@ -114,7 +127,6 @@ private void modified(ComponentContext context, Config config) throws OpenemsNam
new ConfigRecord(config.id(), config.alias(), config.enabled(), this.metaComponent,
config.component_ids(), config.apiTimeout(), config.port(), config.maxConcurrentConnections()));
this.applyConfig(config);
- this.handleTimeDataChannels();
}
private void applyConfig(Config config) {
@@ -126,12 +138,12 @@ private void applyConfig(Config config) {
protected void deactivate() {
super.deactivate();
}
-
+
@Override
public void run() throws OpenemsNamedException {
this.isActive = false;
super.run();
-
+
this.calculateCumulatedActiveTime.update(this.isActive);
this.calculateCumulatedInactiveTime.update(!this.isActive);
}
@@ -166,7 +178,17 @@ private void configUpdate(String targetProperty, String requiredValue) {
this.logError(this.log, "ERROR: " + e.getMessage());
}
}
-
+
+ protected static String getChannelNameUpper(String componentId,
+ io.openems.edge.common.channel.ChannelId channelId) {
+ return channelIdCamelToUpper(componentId) + "_" + channelId.name();
+ }
+
+ protected static String getChannelNameCamel(String componentId,
+ io.openems.edge.common.channel.ChannelId channelId) {
+ return channelIdUpperToCamel(getChannelNameUpper(componentId, channelId));
+ }
+
@Override
protected Consumer, WriteObject>> handleWrites() {
return entry -> {
@@ -174,15 +196,19 @@ protected Consumer, WriteObject>> handleWrites() {
WriteChannel> channel = entry.getKey();
var writeObject = entry.getValue();
- String channelName = formatChannelName(channel);
- var currentChannel = new ChannelIdImpl(channelName,
- Doc.of(channel.getType()).persistencePriority(PersistencePriority.HIGH));
- if (!channels().stream().anyMatch(p -> p.channelId().name().equals(currentChannel.name()))) {
- addChannel(currentChannel).setNextValue(writeObject.value());
- } else {
- channel(currentChannel).setNextValue(writeObject.value());
+ var channelNameCamel = getChannelNameCamel(channel.getComponent().id(), channel.channelId());
+
+ @SuppressWarnings("deprecation")
+ var logChannel = this._channel(channelNameCamel);
+ if (logChannel == null) {
+ var channelNameUpper = getChannelNameUpper(channel.getComponent().id(), channel.channelId());
+ var currentChannel = new ChannelIdImpl(channelNameUpper,
+ Doc.of(channel.getType()).persistencePriority(PersistencePriority.HIGH));
+ addChannel(currentChannel);
+ logChannel = channel(currentChannel);
}
- this.configUpdate("writeChannels", channel(currentChannel).channelId().id());
+ logChannel.setNextValue(writeObject.value());
+ this.configUpdate("writeChannels", logChannel.channelId().id());
};
}
@@ -205,23 +231,28 @@ protected Runnable handleTimeouts() {
public Timedata getTimedata() {
return this.timedata;
}
-
- /**
- * Checks, if timedata channels are already set.
- * If not, they will be created and added to current channels.
- */
- protected void handleTimeDataChannels() {
- var activeTimeChannel = new ChannelIdImpl("CUMULATED_ACTIVE_TIME", //
- Doc.of(OpenemsType.DOUBLE).persistencePriority(PersistencePriority.HIGH));
- var inactiveTimeChannel = new ChannelIdImpl("CUMULATED_INACTIVE_TIME", //
- Doc.of(OpenemsType.DOUBLE).persistencePriority(PersistencePriority.HIGH));
-
- List timeChannels = Arrays.asList(activeTimeChannel, inactiveTimeChannel);
- timeChannels.forEach(channel -> {
- if (channels().stream().noneMatch(ch -> ch.channelId().id().equals(channel.id()))) {
- addChannel(channel);
- }
- });
+
+ protected Integer getChannelValue(String componentId, io.openems.edge.common.channel.ChannelId channelId) {
+ @SuppressWarnings("deprecation")
+ var channel = this._channel(getChannelNameCamel(componentId, channelId));
+ if (channel == null) {
+ return null;
+ }
+ return ((IntegerReadChannel) channel).value().get();
+ }
+
+ @Override
+ public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) {
+ return new ModbusSlaveTable(//
+ OpenemsComponent.getModbusSlaveNatureTable(AccessMode.READ_ONLY),
+ ModbusSlaveNatureTable.of(ControllerApiModbusTcpReadWriteImpl.class, AccessMode.READ_ONLY, 300)
+ .cycleValue(0, this.id() + "/ Ess0ActivePowerLimit", Unit.WATT, "", ModbusType.FLOAT32,
+ t -> this.getChannelValue("ess0",
+ ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS))
+ .cycleValue(2, this.id() + "/Ess0ReactivePowerLimit", Unit.WATT, "", ModbusType.FLOAT32,
+ t -> this.getChannelValue("ess0",
+ ManagedSymmetricEss.ChannelId.SET_REACTIVE_POWER_EQUALS))
+ .build());
}
}
diff --git a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImplTest.java b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImplTest.java
index a39ef6c3555..6248a9b4e41 100644
--- a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImplTest.java
+++ b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImplTest.java
@@ -1,28 +1,27 @@
package io.openems.edge.controller.api.modbus.readonly;
+import static io.openems.edge.controller.api.modbus.AbstractModbusTcpApi.DEFAULT_PORT;
+
import org.junit.Test;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyConfigurationAdmin;
-import io.openems.edge.controller.api.modbus.AbstractModbusTcpApi;
import io.openems.edge.controller.test.ControllerTest;
public class ControllerApiModbusTcpReadOnlyImplTest {
- private static final String CTRL_ID = "ctrl0";
-
@Test
public void test() throws Exception {
new ControllerTest(new ControllerApiModbusTcpReadOnlyImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
+ .setId("ctrl0") //
.setEnabled(false) // do not actually start server
.setComponentIds() //
.setMaxConcurrentConnections(5) //
- .setPort(AbstractModbusTcpApi.DEFAULT_PORT) //
+ .setPort(DEFAULT_PORT) //
.build()) //
.next(new TestCase()) //
- ;
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java
index 58ea9ca90c5..f87124a5295 100644
--- a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java
+++ b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java
@@ -1,43 +1,48 @@
package io.openems.edge.controller.api.modbus.readwrite;
+import static io.openems.edge.controller.api.modbus.AbstractModbusTcpApi.DEFAULT_PORT;
+import static io.openems.edge.controller.api.modbus.readwrite.ControllerApiModbusTcpReadWriteImpl.getChannelNameCamel;
+import static io.openems.edge.controller.api.modbus.readwrite.ControllerApiModbusTcpReadWriteImpl.getChannelNameUpper;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+
import org.junit.Test;
+
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyConfigurationAdmin;
import io.openems.edge.common.test.DummyCycle;
-import io.openems.edge.controller.api.modbus.AbstractModbusTcpApi;
import io.openems.edge.controller.test.ControllerTest;
public class ControllerApiModbusTcpReadWriteImplTest {
- private static final String CTRL_ID = "ctrl0";
-
@Test
public void test() throws Exception {
new ControllerTest(new ControllerApiModbusTcpReadWriteImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
+ .setId("ctrl0") //
.setEnabled(false) // do not actually start server
.setComponentIds() //
.setMaxConcurrentConnections(5) //
- .setPort(AbstractModbusTcpApi.DEFAULT_PORT) //
+ .setPort(DEFAULT_PORT) //
.setApiTimeout(60) //
.build()) //
.next(new TestCase()) //
+ .deactivate();
;
}
-
+
@Test
public void testTimedataChannels() throws Exception {
var controller = new ControllerApiModbusTcpReadWriteImpl(); //
boolean channelNotFound = controller.channels().stream().noneMatch(//
ch -> ch.channelId().id().equals("CumulatedActiveTime") //
- || ch.channelId().id().equals("CumulatedInactiveTime")); //
- assertFalse(channelNotFound);
+ || ch.channelId().id().equals("CumulatedInactiveTime")); //
+ assertFalse(channelNotFound);
}
-
+
@Test
public void testAddFalseComponents() throws Exception {
var controller = new ControllerApiModbusTcpReadWriteImpl(); //
@@ -45,4 +50,16 @@ public void testAddFalseComponents() throws Exception {
controller.getComponentNoModbusApiFaultChannel().nextProcessImage(); //
assertTrue(controller.getComponentNoModbusApiFault().get()); //
}
+
+ @Test
+ public void testGetChannelNameUpper() {
+ assertEquals("ESS0_SET_ACTIVE_POWER_EQUALS", getChannelNameUpper("ess0", SET_ACTIVE_POWER_EQUALS));
+ assertEquals("ESS0_SET_ACTIVE_POWER_EQUALS", getChannelNameUpper("Ess0", SET_ACTIVE_POWER_EQUALS));
+ }
+
+ @Test
+ public void testGetChannelNameCamel() {
+ assertEquals("Ess0SetActivePowerEquals", getChannelNameCamel("ess0", SET_ACTIVE_POWER_EQUALS));
+ assertEquals("Ess0SetActivePowerEquals", getChannelNameCamel("Ess0", SET_ACTIVE_POWER_EQUALS));
+ }
}
diff --git a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/MyConfig.java b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/MyConfig.java
index 5c06b078554..dfbd3ea52b8 100644
--- a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/MyConfig.java
+++ b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/MyConfig.java
@@ -1,12 +1,7 @@
package io.openems.edge.controller.api.modbus.readwrite;
-import java.nio.channels.Channels;
-
import io.openems.common.test.AbstractComponentConfig;
-import io.openems.common.types.EdgeConfig.Component.Channel;
import io.openems.common.utils.ConfigUtils;
-import io.openems.edge.common.channel.ChannelId;
-import io.openems.edge.common.channel.ChannelId.ChannelIdImpl;
@SuppressWarnings("all")
public class MyConfig extends AbstractComponentConfig implements Config {
diff --git a/io.openems.edge.controller.api.mqtt/bnd.bnd b/io.openems.edge.controller.api.mqtt/bnd.bnd
index ae32a25bd42..880e35262f0 100644
--- a/io.openems.edge.controller.api.mqtt/bnd.bnd
+++ b/io.openems.edge.controller.api.mqtt/bnd.bnd
@@ -5,8 +5,8 @@ Bundle-Version: 1.0.0.${tstamp}
-buildpath: \
${buildpath},\
- bcpkix;version='1.70',\
- bcprov;version='1.70',\
+ bcpkix;version='1.78.1',\
+ bcprov;version='1.78.1',\
io.openems.common,\
io.openems.edge.common,\
io.openems.edge.controller.api,\
diff --git a/io.openems.edge.controller.api.mqtt/test/io/openems/edge/controller/api/mqtt/ControllerApiMqttImplTest.java b/io.openems.edge.controller.api.mqtt/test/io/openems/edge/controller/api/mqtt/ControllerApiMqttImplTest.java
index 10981834b04..89486aa7d0b 100644
--- a/io.openems.edge.controller.api.mqtt/test/io/openems/edge/controller/api/mqtt/ControllerApiMqttImplTest.java
+++ b/io.openems.edge.controller.api.mqtt/test/io/openems/edge/controller/api/mqtt/ControllerApiMqttImplTest.java
@@ -1,5 +1,6 @@
package io.openems.edge.controller.api.mqtt;
+import static io.openems.common.channel.PersistencePriority.VERY_LOW;
import static io.openems.edge.controller.api.mqtt.ControllerApiMqttImpl.createTopicPrefix;
import static org.junit.Assert.assertEquals;
@@ -8,7 +9,6 @@
import org.junit.Test;
-import io.openems.common.channel.PersistencePriority;
import io.openems.common.test.TimeLeapClock;
import io.openems.edge.common.sum.DummySum;
import io.openems.edge.common.test.ComponentTest;
@@ -16,8 +16,6 @@
public class ControllerApiMqttImplTest {
- private static final String CTRL_ID = "ctrl0";
-
@Test
public void test() throws Exception {
final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800L) /* starts at 1. January 2020 00:00:00 */,
@@ -26,13 +24,13 @@ public void test() throws Exception {
.addReference("componentManager", new DummyComponentManager(clock)) //
.addComponent(new DummySum()) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
+ .setId("ctrl0") //
.setClientId("edge0") //
.setTopicPrefix("") //
.setUsername("guest") //
.setPassword("guest") //
.setUri("ws://localhost:1883") //
- .setPersistencePriority(PersistencePriority.VERY_LOW) //
+ .setPersistencePriority(VERY_LOW) //
.setDebugMode(true) //
.setCertPem("") //
.setPrivateKeyPem("") //
diff --git a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/DummyJsonRpcRestHandlerFactory.java b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/DummyJsonRpcRestHandlerFactory.java
index 2b99bdb06c3..927c148ca6c 100644
--- a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/DummyJsonRpcRestHandlerFactory.java
+++ b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/DummyJsonRpcRestHandlerFactory.java
@@ -1,21 +1,19 @@
package io.openems.edge.controller.api.rest;
-import java.lang.reflect.InvocationTargetException;
+import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentServiceObjects;
import com.google.common.base.Supplier;
-import io.openems.common.utils.ReflectionUtils;
+import io.openems.common.utils.ReflectionUtils.ReflectionException;
public class DummyJsonRpcRestHandlerFactory extends JsonRpcRestHandler.Factory {
- public DummyJsonRpcRestHandlerFactory(Supplier factoryMethod)
- throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+ public DummyJsonRpcRestHandlerFactory(Supplier factoryMethod) throws ReflectionException {
super();
- ReflectionUtils.setAttribute(JsonRpcRestHandler.Factory.class, this, "cso",
- new DummyJsonRpcRestHandlerCso(factoryMethod));
+ setAttributeViaReflection(this, "cso", new DummyJsonRpcRestHandlerCso(factoryMethod));
}
private static class DummyJsonRpcRestHandlerCso implements ComponentServiceObjects {
diff --git a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readonly/ControllerApiRestReadOnlyImplTest.java b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readonly/ControllerApiRestReadOnlyImplTest.java
index e7bc5a974ef..04c9d124dd6 100644
--- a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readonly/ControllerApiRestReadOnlyImplTest.java
+++ b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readonly/ControllerApiRestReadOnlyImplTest.java
@@ -12,8 +12,6 @@
public class ControllerApiRestReadOnlyImplTest {
- private static final String CTRL_ID = "ctrlApiRest0";
-
@Test
public void test() throws OpenemsException, Exception {
final var port = TestUtils.findRandomOpenPortOnAllLocalInterfaces();
@@ -23,11 +21,12 @@ public void test() throws OpenemsException, Exception {
.addReference("userService", new DummyUserService()) //
.addReference("restHandlerFactory", new DummyJsonRpcRestHandlerFactory(JsonRpcRestHandler::new)) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
+ .setId("ctrlApiRest0") //
.setEnabled(false) // do not actually start server
.setConnectionlimit(5) //
.setDebugMode(false) //
.setPort(port) //
- .build());
+ .build()) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/ControllerApiRestReadWriteImplTest.java b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/ControllerApiRestReadWriteImplTest.java
index 6baf712a911..0dd9165587a 100644
--- a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/ControllerApiRestReadWriteImplTest.java
+++ b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/ControllerApiRestReadWriteImplTest.java
@@ -1,9 +1,11 @@
package io.openems.edge.controller.api.rest.readwrite;
+import static io.openems.common.utils.JsonUtils.getAsJsonObject;
import static io.openems.edge.common.test.DummyUser.DUMMY_ADMIN;
import static io.openems.edge.common.test.DummyUser.DUMMY_GUEST;
import static io.openems.edge.common.test.DummyUser.DUMMY_INSTALLER;
import static io.openems.edge.common.test.DummyUser.DUMMY_OWNER;
+import static io.openems.edge.controller.api.rest.readwrite.ControllerApiRestReadWrite.ChannelId.API_WORKER_LOG;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -27,7 +29,6 @@
import io.openems.common.exceptions.OpenemsException;
import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess;
import io.openems.common.jsonrpc.request.GetEdgeConfigRequest;
-import io.openems.common.types.ChannelAddress;
import io.openems.common.types.OpenemsType;
import io.openems.common.utils.JsonUtils;
import io.openems.edge.common.channel.Doc;
@@ -51,9 +52,6 @@
public class ControllerApiRestReadWriteImplTest {
- private static final String CTRL_ID = "ctrlApiRest0";
- private static final String DUMMY_ID = "dummy0";
-
@Test
public void test() throws OpenemsException, Exception {
final var port = TestUtils.findRandomOpenPortOnAllLocalInterfaces();
@@ -81,10 +79,10 @@ public void test() throws OpenemsException, Exception {
.addReference("userService", new DummyUserService(//
DUMMY_GUEST, DUMMY_OWNER, DUMMY_INSTALLER, DUMMY_ADMIN)) //
.addReference("restHandlerFactory", factory) //
- .addComponent(new DummyComponent(DUMMY_ID) //
+ .addComponent(new DummyComponent("dummy0") //
.withReadChannel(1234)) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
+ .setId("ctrlApiRest0") //
.setApiTimeout(60) //
.setConnectionlimit(5) //
.setDebugMode(false) //
@@ -98,7 +96,7 @@ public void test() throws OpenemsException, Exception {
var channelGet = sendGetRequest(port, DUMMY_GUEST.password, "/rest/channel/dummy0/ReadChannel");
assertEquals(JsonUtils.buildJsonObject() //
.addProperty("address", "dummy0/ReadChannel") //
- .addProperty("type", "INTEGER") //
+ .addProperty("type", "INTEGER") // s
.addProperty("accessMode", "RO") //
.addProperty("text", "This is a Read-Channel") //
.addProperty("unit", "W") //
@@ -113,8 +111,8 @@ public void test() throws OpenemsException, Exception {
assertEquals(new JsonObject(), channelPost);
test //
.next(new TestCase() //
- .output(new ChannelAddress("dummy0", "WriteChannel"), 4321) //
- .output(new ChannelAddress(CTRL_ID, "ApiWorkerLog"), "dummy0/WriteChannel:4321"));
+ .output("dummy0", DummyComponent.ChannelId.WRITE_CHANNEL, 4321) //
+ .output(API_WORKER_LOG, "dummy0/WriteChannel:4321"));
// POST fails as GUEST
try {
@@ -132,7 +130,7 @@ public void test() throws OpenemsException, Exception {
// POST successful as OWNER
var request = new GetEdgeConfigRequest().toJsonObject();
JsonrpcResponseSuccess.from(//
- JsonUtils.getAsJsonObject(//
+ getAsJsonObject(//
sendPostRequest(port, DUMMY_OWNER.password, "/jsonrpc", request)));
// POST fails as GUEST
diff --git a/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WsData.java b/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WsData.java
index a92eca5bba8..d70d2299659 100644
--- a/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WsData.java
+++ b/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WsData.java
@@ -142,14 +142,15 @@ public Optional getUser() {
}
@Override
- public String toString() {
- String tokenString;
- if (this.sessionToken != null) {
- tokenString = this.sessionToken.toString();
- } else {
- tokenString = "UNKNOWN";
- }
- return "WebsocketApi.WsData [sessionToken=" + tokenString + ", user=" + this.user + "]";
+ public String toLogString() {
+ return new StringBuilder("WebsocketApi.WsData [sessionToken=") //
+ .append(this.sessionToken != null //
+ ? this.sessionToken.toString() //
+ : "UNKNOWN") //
+ .append(", user=") //
+ .append(this.user) //
+ .append("]") //
+ .toString();
}
/**
diff --git a/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/ControllerApiWebsocketImplTest.java b/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/ControllerApiWebsocketImplTest.java
index b37f1fb4929..f2321dcc2cf 100644
--- a/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/ControllerApiWebsocketImplTest.java
+++ b/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/ControllerApiWebsocketImplTest.java
@@ -1,5 +1,7 @@
package io.openems.edge.controller.api.websocket;
+import static io.openems.edge.controller.api.websocket.ControllerApiWebsocket.DEFAULT_PORT;
+
import org.junit.Test;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
@@ -8,20 +10,18 @@
public class ControllerApiWebsocketImplTest {
- private static final String CTRL_ID = "ctrl0";
-
@Test
public void test() throws Exception {
new ControllerTest(new ControllerApiWebsocketImpl()) //
.addReference("componentManager", new DummyComponentManager()) //
.addReference("onRequestFactory", new DummyOnRequestFactory()) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
+ .setId("ctrl0") //
.setApiTimeout(60) //
- .setPort(ControllerApiWebsocket.DEFAULT_PORT) //
+ .setPort(DEFAULT_PORT) //
.build()) //
.next(new TestCase()) //
- ;
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/DummyOnRequestFactory.java b/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/DummyOnRequestFactory.java
index a953b39803c..852eb16321e 100644
--- a/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/DummyOnRequestFactory.java
+++ b/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/DummyOnRequestFactory.java
@@ -1,17 +1,17 @@
package io.openems.edge.controller.api.websocket;
-import java.lang.reflect.InvocationTargetException;
+import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentServiceObjects;
-import io.openems.common.utils.ReflectionUtils;
+import io.openems.common.utils.ReflectionUtils.ReflectionException;
public class DummyOnRequestFactory extends OnRequest.Factory {
- public DummyOnRequestFactory() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+ public DummyOnRequestFactory() throws ReflectionException {
super();
- ReflectionUtils.setAttribute(OnRequest.Factory.class, this, "cso", new DummyOnRequestCso());
+ setAttributeViaReflection(this, "cso", new DummyOnRequestCso());
}
private static class DummyOnRequestCso implements ComponentServiceObjects {
diff --git a/io.openems.edge.controller.asymmetric.balancingcosphi/test/io/openems/edge/controller/asymmetric/balancingcosphi/ControllerAsymmetricBalancingCosPhiImplTest.java b/io.openems.edge.controller.asymmetric.balancingcosphi/test/io/openems/edge/controller/asymmetric/balancingcosphi/ControllerAsymmetricBalancingCosPhiImplTest.java
index 8f59d84837e..8f8fee699d0 100644
--- a/io.openems.edge.controller.asymmetric.balancingcosphi/test/io/openems/edge/controller/asymmetric/balancingcosphi/ControllerAsymmetricBalancingCosPhiImplTest.java
+++ b/io.openems.edge.controller.asymmetric.balancingcosphi/test/io/openems/edge/controller/asymmetric/balancingcosphi/ControllerAsymmetricBalancingCosPhiImplTest.java
@@ -9,24 +9,20 @@
public class ControllerAsymmetricBalancingCosPhiImplTest {
- private static final String CTRL_ID = "ctrl0";
- private static final String ESS_ID = "ess0";
- private static final String METER_ID = "meter0";
-
@Test
public void test() throws Exception {
new ControllerTest(new ControllerAsymmetricBalancingCosPhiImpl()) //
- .addComponent(new DummyManagedAsymmetricEss(ESS_ID)) //
- .addComponent(new DummyElectricityMeter(METER_ID)) //
+ .addComponent(new DummyManagedAsymmetricEss("ess0")) //
+ .addComponent(new DummyElectricityMeter("meter0")) //
.addReference("componentManager", new DummyComponentManager()) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
- .setMeterId(METER_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
+ .setMeterId("meter0") //
.setCosPhi(0.9) //
.setDirection(CosPhiDirection.CAPACITIVE) //
- .build()); //
- ;
+ .build()) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.asymmetric.fixreactivepower/test/io/openems/edge/controller/asymmetric/fixreactivepower/ControllerAsymmetricFixReactivePowerImplTest.java b/io.openems.edge.controller.asymmetric.fixreactivepower/test/io/openems/edge/controller/asymmetric/fixreactivepower/ControllerAsymmetricFixReactivePowerImplTest.java
index 53a2d3a18c1..f0c26cb202d 100644
--- a/io.openems.edge.controller.asymmetric.fixreactivepower/test/io/openems/edge/controller/asymmetric/fixreactivepower/ControllerAsymmetricFixReactivePowerImplTest.java
+++ b/io.openems.edge.controller.asymmetric.fixreactivepower/test/io/openems/edge/controller/asymmetric/fixreactivepower/ControllerAsymmetricFixReactivePowerImplTest.java
@@ -9,23 +9,20 @@
public class ControllerAsymmetricFixReactivePowerImplTest {
- private static final String CTRL_ID = "ctrl0";
- private static final String ESS_ID = "ess0";
-
@Test
public void test() throws Exception {
new ControllerTest(new ControllerAsymmetricFixReactivePowerImpl()) //
- .addComponent(new DummyManagedAsymmetricEss(ESS_ID)) //
+ .addComponent(new DummyManagedAsymmetricEss("ess0")) //
.addReference("componentManager", new DummyComponentManager()) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setPowerL1(0) //
.setPowerL2(0) //
.setPowerL3(0) //
.build()) //
- .next(new TestCase()); //
- ;
+ .next(new TestCase()) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.asymmetric.peakshaving/test/io/openems/edge/controller/asymmetric/peakshaving/ControllerAsymmetricPeakShavingImplTest.java b/io.openems.edge.controller.asymmetric.peakshaving/test/io/openems/edge/controller/asymmetric/peakshaving/ControllerAsymmetricPeakShavingImplTest.java
index e8090feb15a..7d59fb27424 100644
--- a/io.openems.edge.controller.asymmetric.peakshaving/test/io/openems/edge/controller/asymmetric/peakshaving/ControllerAsymmetricPeakShavingImplTest.java
+++ b/io.openems.edge.controller.asymmetric.peakshaving/test/io/openems/edge/controller/asymmetric/peakshaving/ControllerAsymmetricPeakShavingImplTest.java
@@ -1,8 +1,13 @@
package io.openems.edge.controller.asymmetric.peakshaving;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER;
+import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L1;
+import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L2;
+import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L3;
+
import org.junit.Test;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.controller.test.ControllerTest;
@@ -12,128 +17,117 @@
public class ControllerAsymmetricPeakShavingImplTest {
- private static final String CTRL_ID = "ctrl0";
-
- private static final String METER_ID = "meter0";
- private static final ChannelAddress GRID_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower");
- private static final ChannelAddress GRID_ACTIVE_POWER_L1 = new ChannelAddress(METER_ID, "ActivePowerL1");
- private static final ChannelAddress GRID_ACTIVE_POWER_L2 = new ChannelAddress(METER_ID, "ActivePowerL2");
- private static final ChannelAddress GRID_ACTIVE_POWER_L3 = new ChannelAddress(METER_ID, "ActivePowerL3");
-
- private static final String ESS_ID = "ess0";
- private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower");
- private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID,
- "SetActivePowerEquals");
-
@Test
public void symmetricMeterTest() throws Exception {
new ControllerTest(new ControllerAsymmetricPeakShavingImpl()) //
.addReference("componentManager", new DummyComponentManager()) //
- .addComponent(new DummyElectricityMeter(METER_ID)) //
- .addComponent(new DummyManagedSymmetricEss(ESS_ID) //
+ .addComponent(new DummyElectricityMeter("meter0")) //
+ .addComponent(new DummyManagedSymmetricEss("ess0") //
.setPower(new DummyPower(0.3, 0.3, 0.1))) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setMeterId(METER_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setMeterId("meter0") //
+ .setEssId("ess0") //
.setPeakShavingPower(33333) //
.setRechargePower(16666) //
.build())
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(GRID_ACTIVE_POWER, 120000) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 6000)) //
+ .input("ess0", ACTIVE_POWER, 0) //
+ .input("meter0", ACTIVE_POWER, 120000) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 6000)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(GRID_ACTIVE_POWER, 120000) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 12001)) //
+ .input("ess0", ACTIVE_POWER, 0) //
+ .input("meter0", ACTIVE_POWER, 120000) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 12001)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 3793) //
- .input(GRID_ACTIVE_POWER, 120000 - 3793) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 16484)) //
+ .input("ess0", ACTIVE_POWER, 3793) //
+ .input("meter0", ACTIVE_POWER, 120000 - 3793) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 16484)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 8981) //
- .input(GRID_ACTIVE_POWER, 120000 - 8981) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 19650)) //
+ .input("ess0", ACTIVE_POWER, 8981) //
+ .input("meter0", ACTIVE_POWER, 120000 - 8981) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 19650)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 13723) //
- .input(GRID_ACTIVE_POWER, 120000 - 13723) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 21578)) //
+ .input("ess0", ACTIVE_POWER, 13723) //
+ .input("meter0", ACTIVE_POWER, 120000 - 13723) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 21578)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 17469) //
- .input(GRID_ACTIVE_POWER, 120000 - 17469) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 22437)) //
+ .input("ess0", ACTIVE_POWER, 17469) //
+ .input("meter0", ACTIVE_POWER, 120000 - 17469) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 22437)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 20066) //
- .input(GRID_ACTIVE_POWER, 120000 - 20066) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 22533)) //
+ .input("ess0", ACTIVE_POWER, 20066) //
+ .input("meter0", ACTIVE_POWER, 120000 - 20066) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 22533)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 21564) //
- .input(GRID_ACTIVE_POWER, 120000 - 21564) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 22174)) //
+ .input("ess0", ACTIVE_POWER, 21564) //
+ .input("meter0", ACTIVE_POWER, 120000 - 21564) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 22174)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 22175) //
- .input(GRID_ACTIVE_POWER, 120000 - 22175) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 21610)) //
+ .input("ess0", ACTIVE_POWER, 22175) //
+ .input("meter0", ACTIVE_POWER, 120000 - 22175) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 21610)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 22173) //
- .input(GRID_ACTIVE_POWER, 120000 - 22173) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 21020)) //
+ .input("ess0", ACTIVE_POWER, 22173) //
+ .input("meter0", ACTIVE_POWER, 120000 - 22173) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 21020)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 21816) //
- .input(GRID_ACTIVE_POWER, 120000 - 21816) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 20511)) //
+ .input("ess0", ACTIVE_POWER, 21816) //
+ .input("meter0", ACTIVE_POWER, 120000 - 21816) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 20511)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 21311) //
- .input(GRID_ACTIVE_POWER, 120000 - 21311) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 20133)) //
+ .input("ess0", ACTIVE_POWER, 21311) //
+ .input("meter0", ACTIVE_POWER, 120000 - 21311) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 20133)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 20803) //
- .input(GRID_ACTIVE_POWER, 120000 - 20803) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 19893)) //
+ .input("ess0", ACTIVE_POWER, 20803) //
+ .input("meter0", ACTIVE_POWER, 120000 - 20803) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 19893)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 20377) //
- .input(GRID_ACTIVE_POWER, 120000 - 20377) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 19772)); //
+ .input("ess0", ACTIVE_POWER, 20377) //
+ .input("meter0", ACTIVE_POWER, 120000 - 20377) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 19772)) //
+ .deactivate();
}
@Test
public void asymmetricMeterTest() throws Exception {
new ControllerTest(new ControllerAsymmetricPeakShavingImpl()) //
.addReference("componentManager", new DummyComponentManager()) //
- .addComponent(new DummyElectricityMeter(METER_ID)) //
- .addComponent(new DummyManagedSymmetricEss(ESS_ID) //
+ .addComponent(new DummyElectricityMeter("meter0")) //
+ .addComponent(new DummyManagedSymmetricEss("ess0") //
.setPower(new DummyPower(0.3, 0.3, 0.1))) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setMeterId(METER_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setMeterId("meter0") //
+ .setEssId("ess0") //
.setPeakShavingPower(33333) //
.setRechargePower(16666) //
.build())
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(GRID_ACTIVE_POWER_L1, 20000) //
- .input(GRID_ACTIVE_POWER_L2, 40000) //
- .input(GRID_ACTIVE_POWER_L3, 10000) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 6000)) //
- .next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(GRID_ACTIVE_POWER_L1, 20000) //
- .input(GRID_ACTIVE_POWER_L2, 40000) //
- .input(GRID_ACTIVE_POWER_L3, 10000) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 12001)) //
- .next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 3793) //
- .input(GRID_ACTIVE_POWER_L1, 20000 - 3793 / 3) //
- .input(GRID_ACTIVE_POWER_L2, 40000 - 3793 / 3) //
- .input(GRID_ACTIVE_POWER_L3, 10000 - 3793 / 3) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 16484)) //
- .next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 8981) //
- .input(GRID_ACTIVE_POWER_L1, 20000 - 8981 / 3) //
- .input(GRID_ACTIVE_POWER_L2, 40000 - 8981 / 3) //
- .input(GRID_ACTIVE_POWER_L3, 10000 - 8981 / 3) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 19651)); //
+ .input("ess0", ACTIVE_POWER, 0) //
+ .input("meter0", ACTIVE_POWER_L1, 20000) //
+ .input("meter0", ACTIVE_POWER_L2, 40000) //
+ .input("meter0", ACTIVE_POWER_L3, 10000) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 6000)) //
+ .next(new TestCase() //
+ .input("ess0", ACTIVE_POWER, 0) //
+ .input("meter0", ACTIVE_POWER_L1, 20000) //
+ .input("meter0", ACTIVE_POWER_L2, 40000) //
+ .input("meter0", ACTIVE_POWER_L3, 10000) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 12001)) //
+ .next(new TestCase() //
+ .input("ess0", ACTIVE_POWER, 3793) //
+ .input("meter0", ACTIVE_POWER_L1, 20000 - 3793 / 3) //
+ .input("meter0", ACTIVE_POWER_L2, 40000 - 3793 / 3) //
+ .input("meter0", ACTIVE_POWER_L3, 10000 - 3793 / 3) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 16484)) //
+ .next(new TestCase() //
+ .input("ess0", ACTIVE_POWER, 8981) //
+ .input("meter0", ACTIVE_POWER_L1, 20000 - 8981 / 3) //
+ .input("meter0", ACTIVE_POWER_L2, 40000 - 8981 / 3) //
+ .input("meter0", ACTIVE_POWER_L3, 10000 - 8981 / 3) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 19651)) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.asymmetric.phaserectification/test/io/openems/edge/controller/asymmetric/phaserectification/ControllerAsymmetricPhaseRectificationImplTest.java b/io.openems.edge.controller.asymmetric.phaserectification/test/io/openems/edge/controller/asymmetric/phaserectification/ControllerAsymmetricPhaseRectificationImplTest.java
index f18c8669b3a..882d083baed 100644
--- a/io.openems.edge.controller.asymmetric.phaserectification/test/io/openems/edge/controller/asymmetric/phaserectification/ControllerAsymmetricPhaseRectificationImplTest.java
+++ b/io.openems.edge.controller.asymmetric.phaserectification/test/io/openems/edge/controller/asymmetric/phaserectification/ControllerAsymmetricPhaseRectificationImplTest.java
@@ -9,22 +9,18 @@
public class ControllerAsymmetricPhaseRectificationImplTest {
- private static final String CTRL_ID = "ctrl0";
- private static final String METER_ID = "meter0";
- private static final String ESS_ID = "ess0";
-
@Test
public void test() throws Exception {
new ControllerTest(new ControllerAsymmetricPhaseRectificationImpl()) //
- .addComponent(new DummyManagedAsymmetricEss(ESS_ID)) //
- .addComponent(new DummyElectricityMeter(METER_ID)) //
+ .addComponent(new DummyManagedAsymmetricEss("ess0")) //
+ .addComponent(new DummyElectricityMeter("meter0")) //
.addReference("componentManager", new DummyComponentManager()) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
- .setMeterId(METER_ID) //
- .build()); //
- ;
+ .setId("ctrl0") //
+ .setEssId("ess0") //
+ .setMeterId("meter0") //
+ .build()) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/ControllerChannelThresholdImplTest.java b/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/ControllerChannelThresholdImplTest.java
index b5418c94413..5c2501c8981 100644
--- a/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/ControllerChannelThresholdImplTest.java
+++ b/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/ControllerChannelThresholdImplTest.java
@@ -8,7 +8,6 @@
public class ControllerChannelThresholdImplTest {
- private static final String CTRL_ID = "ctrl0";
private static final ChannelAddress IO0_INPUT = new ChannelAddress("io0", "Input0");
private static final ChannelAddress IO0_OUTPUT = new ChannelAddress("io0", "Output0");
@@ -17,12 +16,12 @@ public void test() throws Exception {
new ControllerTest(new ControllerChannelThresholdImpl()) //
.addReference("componentManager", new DummyComponentManager()) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setInputChannelAddress(IO0_INPUT.toString()) //
- .setOutputChannelAddress(IO0_OUTPUT.toString()) //
+ .setId("ctrl0") //
+ .setInputChannelAddress(IO0_INPUT) //
+ .setOutputChannelAddress(IO0_OUTPUT) //
.setLowThreshold(40) //
.setHighThreshold(80) //
- .build()); //
+ .build()) //
+ .deactivate();
}
-
}
diff --git a/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/MyConfig.java b/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/MyConfig.java
index ab9677f984e..7bb1199c7bc 100644
--- a/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/MyConfig.java
+++ b/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/MyConfig.java
@@ -1,6 +1,7 @@
package io.openems.edge.controller.channelthreshold;
import io.openems.common.test.AbstractComponentConfig;
+import io.openems.common.types.ChannelAddress;
@SuppressWarnings("all")
public class MyConfig extends AbstractComponentConfig implements Config {
@@ -22,13 +23,13 @@ public Builder setId(String id) {
return this;
}
- public Builder setInputChannelAddress(String inputChannelAddress) {
- this.inputChannelAddress = inputChannelAddress;
+ public Builder setInputChannelAddress(ChannelAddress inputChannelAddress) {
+ this.inputChannelAddress = inputChannelAddress.toString();
return this;
}
- public Builder setOutputChannelAddress(String outputChannelAddress) {
- this.outputChannelAddress = outputChannelAddress;
+ public Builder setOutputChannelAddress(ChannelAddress outputChannelAddress) {
+ this.outputChannelAddress = outputChannelAddress.toString();
return this;
}
diff --git a/io.openems.edge.controller.chp.soc/test/io/openems/edge/controller/chp/soc/ControllerChpSocImplTest.java b/io.openems.edge.controller.chp.soc/test/io/openems/edge/controller/chp/soc/ControllerChpSocImplTest.java
index 8937f49f4ce..a469e21f2bd 100644
--- a/io.openems.edge.controller.chp.soc/test/io/openems/edge/controller/chp/soc/ControllerChpSocImplTest.java
+++ b/io.openems.edge.controller.chp.soc/test/io/openems/edge/controller/chp/soc/ControllerChpSocImplTest.java
@@ -1,8 +1,10 @@
package io.openems.edge.controller.chp.soc;
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC;
+import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0;
+
import org.junit.Test;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.controller.test.ControllerTest;
@@ -11,56 +13,49 @@
public class ControllerChpSocImplTest {
- private static final String CTRL_ID = "ctrl0";
-
- private static final String ESS_ID = "ess0";
- private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc");
-
- private static final String IO_ID = "io0";
- private static final ChannelAddress IO_OUTPUT0 = new ChannelAddress(IO_ID, "InputOutput0");
-
@Test
public void test() throws Exception {
new ControllerTest(new ControllerChpSocImpl()) //
.addReference("componentManager", new DummyComponentManager()) //
- .addComponent(new DummyManagedSymmetricEss(ESS_ID)) //
- .addComponent(new DummyInputOutput(IO_ID)) //
+ .addComponent(new DummyManagedSymmetricEss("ess0")) //
+ .addComponent(new DummyInputOutput("io0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setInputChannelAddress(ESS_SOC.toString()) //
- .setOutputChannelAddress(IO_OUTPUT0.toString()) //
+ .setId("ctrl0") //
+ .setInputChannelAddress("ess0/Soc") //
+ .setOutputChannelAddress("io0/InputOutput0") //
.setLowThreshold(15) //
.setHighThreshold(85) //
.setMode(Mode.AUTOMATIC) //
.setInvert(false) //
.build())
.next(new TestCase() //
- .input(ESS_SOC, 14) //
- .output(IO_OUTPUT0, true)) //
+ .input("ess0", SOC, 14) //
+ .output("io0", INPUT_OUTPUT0, true)) //
.next(new TestCase() //
- .input(ESS_SOC, 50) //
- .output(IO_OUTPUT0, null)) //
+ .input("ess0", SOC, 50) //
+ .output("io0", INPUT_OUTPUT0, null)) //
.next(new TestCase() //
- .input(ESS_SOC, 90) //
- .output(IO_OUTPUT0, false)) //
+ .input("ess0", SOC, 90) //
+ .output("io0", INPUT_OUTPUT0, false)) //
.next(new TestCase() //
- .input(ESS_SOC, 50) //
- .output(IO_OUTPUT0, null)) //
+ .input("ess0", SOC, 50) //
+ .output("io0", INPUT_OUTPUT0, null)) //
.next(new TestCase() //
- .input(ESS_SOC, 15) //
- .output(IO_OUTPUT0, true)) //
+ .input("ess0", SOC, 15) //
+ .output("io0", INPUT_OUTPUT0, true)) //
.next(new TestCase() //
- .input(ESS_SOC, 85) //
- .output(IO_OUTPUT0, false)) //
+ .input("ess0", SOC, 85) //
+ .output("io0", INPUT_OUTPUT0, false)) //
.next(new TestCase() //
- .input(ESS_SOC, 86) //
- .output(IO_OUTPUT0, false)) //
+ .input("ess0", SOC, 86) //
+ .output("io0", INPUT_OUTPUT0, false)) //
.next(new TestCase() //
- .input(ESS_SOC, 14) //
- .output(IO_OUTPUT0, true)) //
+ .input("ess0", SOC, 14) //
+ .output("io0", INPUT_OUTPUT0, true)) //
.next(new TestCase() //
- .input(ESS_SOC, 45) //
- .output(IO_OUTPUT0, null));
+ .input("ess0", SOC, 45) //
+ .output("io0", INPUT_OUTPUT0, null)) //
+ .deactivate();
}
}
\ No newline at end of file
diff --git a/io.openems.edge.controller.debug.detailedlog/test/io/openems/edge/controller/debug/detailedlog/ControllerDebugDetailedLogImplTest.java b/io.openems.edge.controller.debug.detailedlog/test/io/openems/edge/controller/debug/detailedlog/ControllerDebugDetailedLogImplTest.java
index ed138710c30..0ae17386d3d 100644
--- a/io.openems.edge.controller.debug.detailedlog/test/io/openems/edge/controller/debug/detailedlog/ControllerDebugDetailedLogImplTest.java
+++ b/io.openems.edge.controller.debug.detailedlog/test/io/openems/edge/controller/debug/detailedlog/ControllerDebugDetailedLogImplTest.java
@@ -7,16 +7,15 @@
public class ControllerDebugDetailedLogImplTest {
- private static final String CTRL_ID = "ctrl0";
-
@Test
public void test() throws Exception {
new ControllerTest(new ControllerDebugDetailedLogImpl()) //
.addReference("componentManager", new DummyComponentManager()) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
+ .setId("ctrl0") //
.setComponentIds() //
- .build()); //
+ .build()) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/ControllerDebugLogImplTest.java b/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/ControllerDebugLogImplTest.java
index 0f0d97848c6..beabd1ba311 100644
--- a/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/ControllerDebugLogImplTest.java
+++ b/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/ControllerDebugLogImplTest.java
@@ -1,5 +1,6 @@
package io.openems.edge.controller.debuglog;
+import static io.openems.edge.common.sum.Sum.ChannelId.ESS_SOC;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
@@ -7,7 +8,6 @@
import org.junit.Test;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.sum.DummySum;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
@@ -16,20 +16,6 @@
public class ControllerDebugLogImplTest {
- private static final String CTRL_ID = "ctrl0";
-
- private static final String DUMMY0_ID = "dummy0";
- private static final String DUMMY1_ID = "dummy1";
- private static final String DUMMY1_ALIAS = "This is Dummy1";
- private static final String DUMMY2_ID = "dummy2";
- private static final String DUMMY2_ALIAS = DUMMY2_ID;
- private static final String DUMMY10_ID = "dummy10";
-
- private static final String ANY_DUMMY = "dummy*";
-
- private static final ChannelAddress SUM_ESS_SOC = new ChannelAddress("_sum", "EssSoc");
- private static final ChannelAddress SUM_FOO_BAR = new ChannelAddress("_sum", "FooBar");
-
@Test
public void test() throws Exception {
List components = new ArrayList<>();
@@ -39,25 +25,26 @@ public String debugLog() {
return "foo:bar";
}
});
- components.add(new DummyController(DUMMY0_ID) {
+ components.add(new DummyController("dummy0") {
@Override
public String debugLog() {
return "abc:xyz";
}
});
- components.add(new DummyController(DUMMY1_ID, DUMMY1_ALIAS) {
+ components.add(new DummyController("dummy1", "This is Dummy1") {
+
@Override
public String debugLog() {
return "def:uvw";
}
});
- components.add(new DummyController(DUMMY2_ID, DUMMY2_ALIAS) {
+ components.add(new DummyController("dummy2", "dummy2") {
@Override
public String debugLog() {
return "ghi:rst";
}
});
- components.add(new DummyController(DUMMY10_ID) {
+ components.add(new DummyController("dummy10") {
@Override
public String debugLog() {
return "jkl:opq";
@@ -68,19 +55,14 @@ public String debugLog() {
new ControllerTest(sut) //
.addReference("components", components) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
+ .setId("ctrl0") //
.setShowAlias(true) //
.setCondensedOutput(true) //
- .setAdditionalChannels(new String[] { //
- SUM_ESS_SOC.toString(), //
- SUM_FOO_BAR.toString() //
- }) //
- .setIgnoreComponents(new String[] { //
- DUMMY0_ID //
- }) //
+ .setAdditionalChannels("_sum/EssSoc", "_sum/FooBar") //
+ .setIgnoreComponents("dummy0") //
.build()) //
.next(new TestCase() //
- .input(SUM_ESS_SOC, 50));
+ .input(ESS_SOC, 50));
assertEquals(
"_sum[Core.Sum|foo:bar|EssSoc:50 %|FooBar:CHANNEL_IS_NOT_DEFINED] dummy1[This is Dummy1|def:uvw] dummy2[ghi:rst] dummy10[jkl:opq]",
@@ -97,13 +79,14 @@ public String debugLog() {
return "foo:bar";
}
});
- components.add(new DummyController(DUMMY0_ID) {
+ components.add(new DummyController("dummy0") {
@Override
public String debugLog() {
return "abc:xyz";
}
});
- components.add(new DummyController(DUMMY1_ID) {
+ components.add(new DummyController("dummy1") {
+
@Override
public String debugLog() {
return "def:uvw";
@@ -116,17 +99,14 @@ public String debugLog() {
.addComponent(components.get(0)) //
.addComponent(components.get(1)) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
+ .setId("ctrl0") //
.setCondensedOutput(true) //
- .setAdditionalChannels(new String[] { //
- SUM_ESS_SOC.toString() //
- }) //
- .setIgnoreComponents(new String[] { //
- ANY_DUMMY //
- }) //
+ .setAdditionalChannels("_sum/EssSoc") //
+ .setIgnoreComponents("dummy*") //
.build()) //
.next(new TestCase() //
- .input(SUM_ESS_SOC, 50));
+ .input(ESS_SOC, 50)) //
+ .deactivate();
assertEquals("_sum[foo:bar|EssSoc:50 %]", sut.getLogMessage());
diff --git a/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/MyConfig.java b/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/MyConfig.java
index f23d1d89e1a..b8edc931126 100644
--- a/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/MyConfig.java
+++ b/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/MyConfig.java
@@ -25,12 +25,12 @@ public Builder setShowAlias(boolean showAlias) {
return this;
}
- public Builder setAdditionalChannels(String[] additionalChannels) {
+ public Builder setAdditionalChannels(String... additionalChannels) {
this.additionalChannels = additionalChannels;
return this;
}
- public Builder setIgnoreComponents(String[] ignoreComponents) {
+ public Builder setIgnoreComponents(String... ignoreComponents) {
this.ignoreComponents = ignoreComponents;
return this;
}
diff --git a/io.openems.edge.controller.ess.acisland/test/io/openems/edge/controller/ess/acisland/ControllerEssAcIslandImplTest.java b/io.openems.edge.controller.ess.acisland/test/io/openems/edge/controller/ess/acisland/ControllerEssAcIslandImplTest.java
index 1a25df66607..7208178a501 100644
--- a/io.openems.edge.controller.ess.acisland/test/io/openems/edge/controller/ess/acisland/ControllerEssAcIslandImplTest.java
+++ b/io.openems.edge.controller.ess.acisland/test/io/openems/edge/controller/ess/acisland/ControllerEssAcIslandImplTest.java
@@ -2,35 +2,26 @@
import org.junit.Test;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.controller.test.ControllerTest;
public class ControllerEssAcIslandImplTest {
- private static final String CTRL_ID = "ctrl0";
-
- private static final String ESS_ID = "ess0";
-
- private static final String IO_ID = "io0";
- private static final ChannelAddress IO_OUTPUT0 = new ChannelAddress(IO_ID, "Output0");
- private static final ChannelAddress IO_OUTPUT1 = new ChannelAddress(IO_ID, "Output1");
-
@Test
public void test() throws Exception {
new ControllerTest(new ControllerEssAcIslandImpl()) //
.addReference("componentManager", new DummyComponentManager()) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setInvertOffGridOutput(false) //
.setInvertOnGridOutput(false) //
.setMaxSoc(90) //
.setMinSoc(4) //
- .setOffGridOutputChannelAddress(IO_OUTPUT0.toString()) //
- .setOnGridOutputChannelAddress(IO_OUTPUT1.toString()) //
+ .setOffGridOutputChannelAddress("io0/Output0") //
+ .setOnGridOutputChannelAddress("io0/Output1") //
.build()) //
- ;
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.ess.activepowervoltagecharacteristic/test/io/openems/edge/controller/ess/activepowervoltagecharacteristic/CharacteristicImplTest.java b/io.openems.edge.controller.ess.activepowervoltagecharacteristic/test/io/openems/edge/controller/ess/activepowervoltagecharacteristic/CharacteristicImplTest.java
index 26f3eb8d98f..1c1cdea75dc 100644
--- a/io.openems.edge.controller.ess.activepowervoltagecharacteristic/test/io/openems/edge/controller/ess/activepowervoltagecharacteristic/CharacteristicImplTest.java
+++ b/io.openems.edge.controller.ess.activepowervoltagecharacteristic/test/io/openems/edge/controller/ess/activepowervoltagecharacteristic/CharacteristicImplTest.java
@@ -1,5 +1,9 @@
package io.openems.edge.controller.ess.activepowervoltagecharacteristic;
+import static io.openems.common.utils.JsonUtils.buildJsonArray;
+import static io.openems.common.utils.JsonUtils.buildJsonObject;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
@@ -8,7 +12,6 @@
import io.openems.common.test.TimeLeapClock;
import io.openems.common.types.ChannelAddress;
-import io.openems.common.utils.JsonUtils;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.common.test.DummyConfigurationAdmin;
@@ -18,11 +21,7 @@
public class CharacteristicImplTest {
- private static final String CTRL_ID = "ctrlActivePowerVoltageCharacteristic0";
- private static final String ESS_ID = "ess1";
- private static final String METER_ID = "meter0";
- private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "SetActivePowerEquals");
- private static final ChannelAddress METER_VOLTAGE = new ChannelAddress(METER_ID, "Voltage");
+ private static final ChannelAddress METER_VOLTAGE = new ChannelAddress("meter0", "Voltage");
@Test
public void test() throws Exception {
@@ -30,36 +29,36 @@ public void test() throws Exception {
new ControllerTest(new ControllerEssActivePowerVoltageCharacteristicImpl())//
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("meter", new DummyElectricityMeter(METER_ID)) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("meter", new DummyElectricityMeter("meter0")) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess1")) //
.activate(MyConfig.create()//
- .setId(CTRL_ID)//
- .setEssId(ESS_ID)//
- .setMeterId(METER_ID)//
+ .setId("ctrlActivePowerVoltageCharacteristic0")//
+ .setEssId("ess1")//
+ .setMeterId("meter0")//
.setNominalVoltage(240)//
.setWaitForHysteresis(5)//
- .setPowerVoltConfig(JsonUtils.buildJsonArray()//
- .add(JsonUtils.buildJsonObject()//
+ .setPowerVoltConfig(buildJsonArray()//
+ .add(buildJsonObject()//
.addProperty("voltageRatio", 0.95) //
.addProperty("power", 4000) //
.build()) //
- .add(JsonUtils.buildJsonObject()//
+ .add(buildJsonObject()//
.addProperty("voltageRatio", 0.98) //
.addProperty("power", 1000) //
.build()) //
- .add(JsonUtils.buildJsonObject()//
+ .add(buildJsonObject()//
.addProperty("voltageRatio", 0.98001) //
.addProperty("power", 0) //
.build()) //
- .add(JsonUtils.buildJsonObject()//
+ .add(buildJsonObject()//
.addProperty("voltageRatio", 1.02999) //
.addProperty("power", 0) //
.build()) //
- .add(JsonUtils.buildJsonObject()//
+ .add(buildJsonObject()//
.addProperty("voltageRatio", 1.03) //
.addProperty("power", -1000) //
.build()) //
- .add(JsonUtils.buildJsonObject()//
+ .add(buildJsonObject()//
.addProperty("voltageRatio", 1.05) //
.addProperty("power", -4000) //
.build() //
@@ -67,52 +66,52 @@ public void test() throws Exception {
).build()) //
.next(new TestCase("First Input") //
.input(METER_VOLTAGE, 250_000) // [mV]
- .output(ESS_ACTIVE_POWER, -2749)) //
+ .output("ess1", SET_ACTIVE_POWER_EQUALS, -2749)) //
.next(new TestCase("Second Input, \"Power: -1500 \"") //
.timeleap(clock, 5, ChronoUnit.SECONDS) //
.input(METER_VOLTAGE, 248_000) // [mV]
- .output(ESS_ACTIVE_POWER, -1499))//
+ .output("ess1", SET_ACTIVE_POWER_EQUALS, -1499))//
.next(new TestCase() //
.input(METER_VOLTAGE, 240_200) // [mV]
- .output(ESS_ACTIVE_POWER, null)) //
+ .output("ess1", SET_ACTIVE_POWER_EQUALS, null)) //
.next(new TestCase("Third Input, \"Power: 0 \"") //
.timeleap(clock, 5, ChronoUnit.SECONDS) //
.input(METER_VOLTAGE, 238_100) // [mV]
- .output(ESS_ACTIVE_POWER, 0)) //
+ .output("ess1", SET_ACTIVE_POWER_EQUALS, 0)) //
.next(new TestCase() //
.input(METER_VOLTAGE, 240_000) // [mV]
- .output(ESS_ACTIVE_POWER, null)) //
+ .output("ess1", SET_ACTIVE_POWER_EQUALS, null)) //
.next(new TestCase() //
.input(METER_VOLTAGE, 238_800)// [mV]
- .output(ESS_ACTIVE_POWER, null)) //
+ .output("ess1", SET_ACTIVE_POWER_EQUALS, null)) //
.next(new TestCase("Fourth Input, \"Power: 0 \"") //
.timeleap(clock, 5, ChronoUnit.SECONDS) //
.input(METER_VOLTAGE, 235_200) // [mV]
- .output(ESS_ACTIVE_POWER, 998)) //
+ .output("ess1", SET_ACTIVE_POWER_EQUALS, 998)) //
.next(new TestCase() //
.timeleap(clock, 2, ChronoUnit.SECONDS) //
.input(METER_VOLTAGE, 235_600) // [mV]
- .output(ESS_ACTIVE_POWER, null)) //
+ .output("ess1", SET_ACTIVE_POWER_EQUALS, null)) //
.next(new TestCase() //
.timeleap(clock, 2, ChronoUnit.SECONDS) //
.input(METER_VOLTAGE, 234_000) // [mV]
- .output(ESS_ACTIVE_POWER, null)) //
+ .output("ess1", SET_ACTIVE_POWER_EQUALS, null)) //
.next(new TestCase("Fifth Input, \"Power: 1625 \"") //
.timeleap(clock, 1, ChronoUnit.SECONDS) //
.input(METER_VOLTAGE, 233_700) // [mV]
- .output(ESS_ACTIVE_POWER, 1625)) //
+ .output("ess1", SET_ACTIVE_POWER_EQUALS, 1625)) //
.next(new TestCase("Fourth Input, \"Power: 0 \"") //
.timeleap(clock, 5, ChronoUnit.SECONDS) //
.input(METER_VOLTAGE, 225_000) // [mV]
- .output(ESS_ACTIVE_POWER, 4000)) //
+ .output("ess1", SET_ACTIVE_POWER_EQUALS, 4000)) //
.next(new TestCase("Smaller then Min Key, \"Power: 0 \"") //
.timeleap(clock, 5, ChronoUnit.SECONDS) //
.input(METER_VOLTAGE, 255_000) // [mV]
- .output(ESS_ACTIVE_POWER, -4000)) //
+ .output("ess1", SET_ACTIVE_POWER_EQUALS, -4000)) //
.next(new TestCase("Bigger than Max Key, \"Power: 0 \"") //
.timeleap(clock, 5, ChronoUnit.SECONDS) //
.input(METER_VOLTAGE, 270_000) // [mV]
- .output(ESS_ACTIVE_POWER, -4000)) //
- ;
+ .output("ess1", SET_ACTIVE_POWER_EQUALS, -4000)) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.ess.balancing/test/io/openems/edge/controller/ess/balancing/BalancingImplTest.java b/io.openems.edge.controller.ess.balancing/test/io/openems/edge/controller/ess/balancing/BalancingImplTest.java
index e6a320f6733..4cab4cf6d5a 100644
--- a/io.openems.edge.controller.ess.balancing/test/io/openems/edge/controller/ess/balancing/BalancingImplTest.java
+++ b/io.openems.edge.controller.ess.balancing/test/io/openems/edge/controller/ess/balancing/BalancingImplTest.java
@@ -1,96 +1,90 @@
package io.openems.edge.controller.ess.balancing;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+
import org.junit.Test;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyConfigurationAdmin;
import io.openems.edge.controller.test.ControllerTest;
+import io.openems.edge.ess.api.SymmetricEss;
import io.openems.edge.ess.test.DummyManagedSymmetricEss;
import io.openems.edge.ess.test.DummyPower;
+import io.openems.edge.meter.api.ElectricityMeter;
import io.openems.edge.meter.test.DummyElectricityMeter;
public class BalancingImplTest {
- private static final String CTRL_ID = "ctrl0";
-
- private static final String ESS_ID = "ess0";
- private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower");
- private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID,
- "SetActivePowerEquals");
-
- private static final String METER_ID = "meter0";
- private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower");
-
@Test
public void test() throws Exception {
new ControllerTest(new ControllerEssBalancingImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0") //
.setPower(new DummyPower(0.3, 0.3, 0.1))) //
- .addReference("meter", new DummyElectricityMeter(METER_ID)) //
+ .addReference("meter", new DummyElectricityMeter("meter0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
- .setMeterId(METER_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
+ .setMeterId("meter0") //
.setTargetGridSetpoint(0) //
.build())
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(METER_ACTIVE_POWER, 20000) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 6000)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 6000)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(METER_ACTIVE_POWER, 20000) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 12000)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 12000)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 3793) //
- .input(METER_ACTIVE_POWER, 20000 - 3793) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 16483)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 3793) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 3793) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 16483)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 8981) //
- .input(METER_ACTIVE_POWER, 20000 - 8981) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 19649)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 8981) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 8981) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 19649)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 13723) //
- .input(METER_ACTIVE_POWER, 20000 - 13723) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 21577)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 13723) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 13723) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 21577)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 17469) //
- .input(METER_ACTIVE_POWER, 20000 - 17469) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 22436)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 17469) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 17469) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 22436)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 20066) //
- .input(METER_ACTIVE_POWER, 20000 - 20066) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 22531)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20066) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 20066) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 22531)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 21564) //
- .input(METER_ACTIVE_POWER, 20000 - 21564) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 22171)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21564) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 21564) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 22171)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 22175) //
- .input(METER_ACTIVE_POWER, 20000 - 22175) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 21608)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 22175) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 22175) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 21608)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 22173) //
- .input(METER_ACTIVE_POWER, 20000 - 22173) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 21017)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 22173) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 22173) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 21017)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 21816) //
- .input(METER_ACTIVE_POWER, 20000 - 21816) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 20508)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21816) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 21816) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 20508)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 21311) //
- .input(METER_ACTIVE_POWER, 20000 - 21311) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 20129)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21311) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 21311) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 20129)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 20803) //
- .input(METER_ACTIVE_POWER, 20000 - 20803) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 19889)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20803) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 20803) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 19889)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 20377) //
- .input(METER_ACTIVE_POWER, 20000 - 20377) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 19767));
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20377) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 20377) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 19767)) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.ess.cycle/test/io/openems/edge/controller/ess/cycle/ControllerEssCycleImplTest.java b/io.openems.edge.controller.ess.cycle/test/io/openems/edge/controller/ess/cycle/ControllerEssCycleImplTest.java
index 3018b8a775c..93c3db68b5e 100644
--- a/io.openems.edge.controller.ess.cycle/test/io/openems/edge/controller/ess/cycle/ControllerEssCycleImplTest.java
+++ b/io.openems.edge.controller.ess.cycle/test/io/openems/edge/controller/ess/cycle/ControllerEssCycleImplTest.java
@@ -1,5 +1,15 @@
package io.openems.edge.controller.ess.cycle;
+import static io.openems.edge.controller.ess.cycle.ControllerEssCycle.ChannelId.COMPLETED_CYCLES;
+import static io.openems.edge.controller.ess.cycle.ControllerEssCycle.ChannelId.STATE_MACHINE;
+import static io.openems.edge.controller.ess.cycle.CycleOrder.START_WITH_DISCHARGE;
+import static io.openems.edge.controller.ess.cycle.HybridEssMode.TARGET_AC;
+import static io.openems.edge.controller.ess.cycle.Mode.MANUAL_ON;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_CHARGE_POWER;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_DISCHARGE_POWER;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC;
+
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
@@ -7,7 +17,6 @@
import org.junit.Test;
import io.openems.common.test.TimeLeapClock;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.common.test.DummyConfigurationAdmin;
@@ -18,74 +27,61 @@
public class ControllerEssCycleImplTest {
- private static final String CTRL_ID = "ctrl0";
- private static final String ESS_ID = "ess0";
-
- private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID,
- ControllerEssCycle.ChannelId.STATE_MACHINE.id());
-
- private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc");
- private static final ChannelAddress MAX_CHARGE_POWER = new ChannelAddress(ESS_ID, "AllowedChargePower");
- private static final ChannelAddress MAX_DISCHARGE_POWER = new ChannelAddress(ESS_ID, "AllowedDischargePower");
- private static final ChannelAddress SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, "SetActivePowerEquals");
-
- private static final ChannelAddress COMPLETED_CYCLES = new ChannelAddress(CTRL_ID, "CompletedCycles");
-
@Test
public void test() throws Exception {
final var clock = new TimeLeapClock(Instant.parse("2000-01-01T01:00:00.00Z"), ZoneOffset.UTC);
final var power = new DummyPower(10_000);
- final var ess = new DummyManagedSymmetricEss(ESS_ID) //
+ final var ess = new DummyManagedSymmetricEss("ess0") //
.setPower(power);
final var test = new ControllerTest(new ControllerEssCycleImpl()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("ess", ess) //
.activate(MyConfig.create()//
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
- .setCycleOrder(CycleOrder.START_WITH_DISCHARGE) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
+ .setCycleOrder(START_WITH_DISCHARGE) //
.setStandbyTime(10)//
.setStartTime("2000-01-01 01:00")//
.setMaxSoc(100)//
.setMinSoc(0)//
.setPower(10000)//
- .setMode(Mode.MANUAL_ON)//
- .setHybridEssMode(HybridEssMode.TARGET_AC)//
+ .setMode(MANUAL_ON)//
+ .setHybridEssMode(TARGET_AC)//
.setTotalCycleNumber(3)//
.setFinalSoc(50)//
.build());
power.addEss(ess);
test.next(new TestCase()//
.input(STATE_MACHINE, State.UNDEFINED)//
- .input(MAX_CHARGE_POWER, -10_000)//
- .input(MAX_DISCHARGE_POWER, 10_000)//
- .input(SET_ACTIVE_POWER_EQUALS, 10_000) //
- .input(ESS_SOC, 50)) //
+ .input("ess0", ALLOWED_CHARGE_POWER, -10_000)//
+ .input("ess0", ALLOWED_DISCHARGE_POWER, 10_000)//
+ .input("ess0", SET_ACTIVE_POWER_EQUALS, 10_000) //
+ .input("ess0", SOC, 50)) //
.next(new TestCase("First Discharge") //
.output(STATE_MACHINE, State.START_DISCHARGE))//
.next(new TestCase()//
- .input(MAX_CHARGE_POWER, -10_000)//
- .input(MAX_DISCHARGE_POWER, 1000))//
+ .input("ess0", ALLOWED_CHARGE_POWER, -10_000)//
+ .input("ess0", ALLOWED_DISCHARGE_POWER, 1000))//
.next(new TestCase()//
.timeleap(clock, 10, ChronoUnit.MINUTES))//
.next(new TestCase()//
.output(STATE_MACHINE, State.START_DISCHARGE))//
.next(new TestCase()//
- .input(MAX_CHARGE_POWER, -10_000)//
- .input(MAX_DISCHARGE_POWER, 0))//
+ .input("ess0", ALLOWED_CHARGE_POWER, -10_000)//
+ .input("ess0", ALLOWED_DISCHARGE_POWER, 0))//
.next(new TestCase()//
.timeleap(clock, 11, ChronoUnit.MINUTES))//
.next(new TestCase("First Charge")//
.output(STATE_MACHINE, State.CONTINUE_WITH_CHARGE))//
.next(new TestCase()//
- .input(MAX_CHARGE_POWER, -1000)//
- .input(MAX_DISCHARGE_POWER, 10_000))//
+ .input("ess0", ALLOWED_CHARGE_POWER, -1000)//
+ .input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))//
.next(new TestCase()//
.output(STATE_MACHINE, State.CONTINUE_WITH_CHARGE))//
.next(new TestCase()//
- .input(MAX_CHARGE_POWER, 0)//
- .input(MAX_DISCHARGE_POWER, 10_000))//
+ .input("ess0", ALLOWED_CHARGE_POWER, 0)//
+ .input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))//
.next(new TestCase()//
.timeleap(clock, 11, ChronoUnit.MINUTES))//
.next(new TestCase() //
@@ -94,17 +90,17 @@ public void test() throws Exception {
.output(COMPLETED_CYCLES, 1)//
.output(STATE_MACHINE, State.START_DISCHARGE))//
.next(new TestCase("Second Discharge")//
- .input(ESS_SOC, 0)//
- .input(MAX_CHARGE_POWER, -10_000)//
- .input(MAX_DISCHARGE_POWER, 0))//
+ .input("ess0", SOC, 0)//
+ .input("ess0", ALLOWED_CHARGE_POWER, -10_000)//
+ .input("ess0", ALLOWED_DISCHARGE_POWER, 0))//
.next(new TestCase()//
.timeleap(clock, 11, ChronoUnit.MINUTES))//
.next(new TestCase()//
.output(STATE_MACHINE, State.CONTINUE_WITH_CHARGE))//
.next(new TestCase("Second Charge")//
- .input(ESS_SOC, 100)//
- .input(MAX_CHARGE_POWER, 0)//
- .input(MAX_DISCHARGE_POWER, 10_000))//
+ .input("ess0", SOC, 100)//
+ .input("ess0", ALLOWED_CHARGE_POWER, 0)//
+ .input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))//
.next(new TestCase()//
.timeleap(clock, 11, ChronoUnit.MINUTES))//
.next(new TestCase("Second completed cycle") //
@@ -113,17 +109,17 @@ public void test() throws Exception {
.output(COMPLETED_CYCLES, 2)//
.output(STATE_MACHINE, State.START_DISCHARGE))//
.next(new TestCase("Third Discharge")//
- .input(ESS_SOC, 0)//
- .input(MAX_CHARGE_POWER, -10_000)//
- .input(MAX_DISCHARGE_POWER, 0))//
+ .input("ess0", SOC, 0)//
+ .input("ess0", ALLOWED_CHARGE_POWER, -10_000)//
+ .input("ess0", ALLOWED_DISCHARGE_POWER, 0))//
.next(new TestCase()//
.timeleap(clock, 11, ChronoUnit.MINUTES))//
.next(new TestCase()//
.output(STATE_MACHINE, State.CONTINUE_WITH_CHARGE))//
.next(new TestCase("Third Charge")//
- .input(ESS_SOC, 100)//
- .input(MAX_CHARGE_POWER, 0)//
- .input(MAX_DISCHARGE_POWER, 10_000))//
+ .input("ess0", SOC, 100)//
+ .input("ess0", ALLOWED_CHARGE_POWER, 0)//
+ .input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))//
.next(new TestCase()//
.timeleap(clock, 11, ChronoUnit.MINUTES))//
.next(new TestCase("Cycle Number 3 Test")//
@@ -132,11 +128,11 @@ public void test() throws Exception {
.next(new TestCase()//
.output(STATE_MACHINE, State.FINAL_SOC))//
.next(new TestCase()//
- .input(ESS_SOC, 50)//
- .input(MAX_CHARGE_POWER, -10_000)//
- .input(MAX_DISCHARGE_POWER, 10_000))//
+ .input("ess0", SOC, 50)//
+ .input("ess0", ALLOWED_CHARGE_POWER, -10_000)//
+ .input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))//
.next(new TestCase() //
- .output(STATE_MACHINE, State.FINISHED))//
- ; //
+ .output(STATE_MACHINE, State.FINISHED)) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.ess.delaycharge/test/io/openems/edge/controller/ess/delaycharge/ControllerEssDelayChargeImplTest.java b/io.openems.edge.controller.ess.delaycharge/test/io/openems/edge/controller/ess/delaycharge/ControllerEssDelayChargeImplTest.java
index 4c495a15a52..7f8dc9e62c8 100644
--- a/io.openems.edge.controller.ess.delaycharge/test/io/openems/edge/controller/ess/delaycharge/ControllerEssDelayChargeImplTest.java
+++ b/io.openems.edge.controller.ess.delaycharge/test/io/openems/edge/controller/ess/delaycharge/ControllerEssDelayChargeImplTest.java
@@ -1,13 +1,14 @@
package io.openems.edge.controller.ess.delaycharge;
+import static io.openems.edge.controller.ess.delaycharge.ControllerEssDelayCharge.ChannelId.CHARGE_POWER_LIMIT;
+import static java.time.temporal.ChronoUnit.HOURS;
+
import java.time.Instant;
import java.time.ZoneId;
-import java.time.temporal.ChronoUnit;
import org.junit.Test;
import io.openems.common.test.TimeLeapClock;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.controller.test.ControllerTest;
@@ -15,11 +16,6 @@
public class ControllerEssDelayChargeImplTest {
- private static final String CTRL_ID = "ctrl0";
- private static final ChannelAddress CTRL_CHARGE_POWER_LIMIT = new ChannelAddress(CTRL_ID, "ChargePowerLimit");
-
- private static final String ESS_ID = "ess0";
-
@Test
public void test() throws Exception {
// Initialize mocked Clock
@@ -27,32 +23,32 @@ public void test() throws Exception {
Instant.ofEpochMilli(1546300800000L /* Tuesday, 1. January 2019 00:00:00 */), ZoneId.of("UTC"));
new ControllerTest(new ControllerEssDelayChargeImpl()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addComponent(new DummyManagedSymmetricEss(ESS_ID) //
+ .addComponent(new DummyManagedSymmetricEss("ess0") //
.withSoc(20) //
.withCapacity(9000)) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setTargetHour(15) //
.build())
.next(new TestCase() //
- .timeleap(clock, 6, ChronoUnit.HOURS) // = 6 am
- .output(CTRL_CHARGE_POWER_LIMIT, 800))
+ .timeleap(clock, 6, HOURS) // = 6 am
+ .output(CHARGE_POWER_LIMIT, 800))
.next(new TestCase() //
- .timeleap(clock, 2, ChronoUnit.HOURS) // = 8 am
- .output(CTRL_CHARGE_POWER_LIMIT, 1028))
+ .timeleap(clock, 2, HOURS) // = 8 am
+ .output(CHARGE_POWER_LIMIT, 1028))
.next(new TestCase() //
- .timeleap(clock, 2, ChronoUnit.HOURS) // = 10 am
- .output(CTRL_CHARGE_POWER_LIMIT, 1440))
+ .timeleap(clock, 2, HOURS) // = 10 am
+ .output(CHARGE_POWER_LIMIT, 1440))
.next(new TestCase() //
- .timeleap(clock, 2, ChronoUnit.HOURS) // = 12 am
- .output(CTRL_CHARGE_POWER_LIMIT, 2400))
+ .timeleap(clock, 2, HOURS) // = 12 am
+ .output(CHARGE_POWER_LIMIT, 2400))
.next(new TestCase() //
- .timeleap(clock, 2, ChronoUnit.HOURS) // = 14 am
- .output(CTRL_CHARGE_POWER_LIMIT, 7200))
+ .timeleap(clock, 2, HOURS) // = 14 am
+ .output(CHARGE_POWER_LIMIT, 7200))
.next(new TestCase() //
- .timeleap(clock, 3, ChronoUnit.HOURS) // = 16 am
- .output(CTRL_CHARGE_POWER_LIMIT, 0));
+ .timeleap(clock, 3, HOURS) // = 16 am
+ .output(CHARGE_POWER_LIMIT, 0)) //
+ .deactivate();
}
-
}
diff --git a/io.openems.edge.controller.ess.delayedselltogrid/test/io/openems/edge/controller/ess/delayedselltogrid/ControllerEssDelayedSellToGridImplTest.java b/io.openems.edge.controller.ess.delayedselltogrid/test/io/openems/edge/controller/ess/delayedselltogrid/ControllerEssDelayedSellToGridImplTest.java
index 3d258054b4f..6843f1ca737 100644
--- a/io.openems.edge.controller.ess.delayedselltogrid/test/io/openems/edge/controller/ess/delayedselltogrid/ControllerEssDelayedSellToGridImplTest.java
+++ b/io.openems.edge.controller.ess.delayedselltogrid/test/io/openems/edge/controller/ess/delayedselltogrid/ControllerEssDelayedSellToGridImplTest.java
@@ -1,76 +1,70 @@
package io.openems.edge.controller.ess.delayedselltogrid;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+
import org.junit.Test;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyConfigurationAdmin;
import io.openems.edge.controller.test.ControllerTest;
+import io.openems.edge.ess.api.SymmetricEss;
import io.openems.edge.ess.test.DummyManagedSymmetricEss;
import io.openems.edge.meter.test.DummyElectricityMeter;
public class ControllerEssDelayedSellToGridImplTest {
- private static final String CTRL_ID = "ctrlDelayedSellToGrid0";
- private static final String ESS_ID = "ess0";
- private static final String METER_ID = "meter0";
- private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower");
- private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID,
- "SetActivePowerEquals");
- private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower");
-
@Test
public void test() throws Exception {
new ControllerTest(new ControllerEssDelayedSellToGridImpl())//
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("meter", new DummyElectricityMeter(METER_ID)) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("meter", new DummyElectricityMeter("meter0")) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create()//
- .setId(CTRL_ID)//
- .setEssId(ESS_ID)//
- .setMeterId(METER_ID)//
+ .setId("ctrlDelayedSellToGrid0")//
+ .setEssId("ess0")//
+ .setMeterId("meter0")//
.setSellToGridPowerLimit(12_500_000)//
.setContinuousSellToGridPower(500_000).build())//
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(METER_ACTIVE_POWER, 0) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 500_000)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 500_000)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(METER_ACTIVE_POWER, -30_000)//
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 470_000)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -30_000)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 470_000)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 500_000) //
- .input(METER_ACTIVE_POWER, -500_000)//
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 500_000)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 500_000) //
+ .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -500_000)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 500_000)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 50_000) //
- .input(METER_ACTIVE_POWER, -500_000)//
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 50_000)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 50_000) //
+ .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -500_000)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 50_000)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, -50_000) //
- .input(METER_ACTIVE_POWER, -500_000)//
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -50_000) //
+ .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -500_000)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 150_000) //
- .input(METER_ACTIVE_POWER, -500_000)//
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 150_000)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 150_000) //
+ .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -500_000)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 150_000)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(METER_ACTIVE_POWER, -1_500_000)//
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -1_500_000)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, -100_000) //
- .input(METER_ACTIVE_POWER, -15_000_000)//
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -2_600_000)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -100_000) //
+ .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -15_000_000)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -2_600_000)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, -1_000_000) //
- .input(METER_ACTIVE_POWER, -16_000_000)//
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -4_500_000)) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -1_000_000) //
+ .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -16_000_000)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -4_500_000)) //
.next(new TestCase() //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(METER_ACTIVE_POWER, -16_000_000)//
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -3_500_000)) //
- ;
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -16_000_000)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -3_500_000)) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/bnd.bnd b/io.openems.edge.controller.ess.emergencycapacityreserve/bnd.bnd
index 3f814d45c0c..0a8bfd62f05 100644
--- a/io.openems.edge.controller.ess.emergencycapacityreserve/bnd.bnd
+++ b/io.openems.edge.controller.ess.emergencycapacityreserve/bnd.bnd
@@ -8,6 +8,7 @@ Bundle-Version: 1.0.0.${tstamp}
io.openems.common,\
io.openems.edge.common,\
io.openems.edge.controller.api,\
+ io.openems.edge.energy.api,\
io.openems.edge.ess.api,\
io.openems.edge.ess.generic,\
diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImpl.java b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImpl.java
index 35ba0a200aa..68e1ad4fc09 100644
--- a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImpl.java
+++ b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImpl.java
@@ -1,6 +1,10 @@
package io.openems.edge.controller.ess.emergencycapacityreserve;
+import static io.openems.edge.energy.api.EnergyUtils.socToEnergy;
+import static java.lang.Math.max;
+
import java.util.OptionalInt;
+import java.util.function.Supplier;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
@@ -25,6 +29,8 @@
import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.Context;
import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.StateMachine;
import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.StateMachine.State;
+import io.openems.edge.energy.api.EnergySchedulable;
+import io.openems.edge.energy.api.EnergyScheduleHandler;
import io.openems.edge.ess.api.ManagedSymmetricEss;
@Designate(ocd = Config.class, factory = true)
@@ -34,7 +40,7 @@
configurationPolicy = ConfigurationPolicy.REQUIRE //
)
public class ControllerEssEmergencyCapacityReserveImpl extends AbstractOpenemsComponent
- implements ControllerEssEmergencyCapacityReserve, Controller, OpenemsComponent {
+ implements ControllerEssEmergencyCapacityReserve, EnergySchedulable, Controller, OpenemsComponent {
/** Minimum reserve SoC value in [%]. */
private static final int reservSocMinValue = 5;
@@ -42,6 +48,7 @@ public class ControllerEssEmergencyCapacityReserveImpl extends AbstractOpenemsCo
private static final int reservSocMaxValue = 100;
private final Logger log = LoggerFactory.getLogger(ControllerEssEmergencyCapacityReserveImpl.class);
+ private final EnergyScheduleHandler energyScheduleHandler;
private final StateMachine stateMachine = new StateMachine(State.NO_LIMIT);
private final RampFilter rampFilter = new RampFilter();
@@ -65,6 +72,10 @@ public ControllerEssEmergencyCapacityReserveImpl() {
Controller.ChannelId.values(), //
ControllerEssEmergencyCapacityReserve.ChannelId.values() //
);
+ this.energyScheduleHandler = buildEnergyScheduleHandler(//
+ () -> this.config.isReserveSocEnabled() //
+ ? this.config.reserveSoc() //
+ : null);
}
@Activate
@@ -77,6 +88,7 @@ private void activate(ComponentContext context, Config config) {
protected void modified(ComponentContext context, String id, String alias, boolean enabled) {
super.modified(context, id, alias, enabled);
this.updateConfig(this.config);
+ this.energyScheduleHandler.triggerReschedule();
}
@Override
@@ -191,4 +203,31 @@ private OptionalInt getLastValidSoc(IntegerReadChannel channel) {
.mapToInt(Value::get) //
.findFirst();
}
+
+ /**
+ * Builds the {@link EnergyScheduleHandler}.
+ *
+ *
+ * This is public so that it can be used by the EnergyScheduler integration
+ * test.
+ *
+ * @param minSoc supplier for the configured minSoc
+ * @return a {@link EnergyScheduleHandler}
+ */
+ public static EnergyScheduleHandler buildEnergyScheduleHandler(Supplier minSoc) {
+ return EnergyScheduleHandler.of(//
+ simContext -> minSoc.get() == null //
+ ? null //
+ : socToEnergy(simContext.ess().totalEnergy(), minSoc.get()), //
+ (simContext, period, energyFlow, minEnergy) -> {
+ if (minEnergy != null) {
+ energyFlow.setEssMaxDischarge(max(0, simContext.getEssInitial() - minEnergy));
+ }
+ });
+ }
+
+ @Override
+ public EnergyScheduleHandler getEnergyScheduleHandler() {
+ return this.energyScheduleHandler;
+ }
}
diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImplTest.java b/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImplTest.java
index 127acdc997d..4b8e05a2819 100644
--- a/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImplTest.java
+++ b/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImplTest.java
@@ -1,9 +1,19 @@
package io.openems.edge.controller.ess.emergencycapacityreserve;
+import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_AC_ACTIVE_POWER;
+import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_DC_ACTUAL_POWER;
+import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.DEBUG_RAMP_POWER;
+import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS;
+import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.DEBUG_TARGET_POWER;
+import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE;
+import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.STATE_MACHINE;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_LESS_OR_EQUALS;
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.MAX_APPARENT_POWER;
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC;
+
import org.junit.Test;
import io.openems.common.function.ThrowingRunnable;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.sum.DummySum;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyComponentManager;
@@ -14,100 +24,82 @@
public class ControllerEssEmergencyCapacityReserveImplTest {
- private static final String CTRL_ID = "ctrlEmergencyCapacityReserve0";
- private static final String ESS_ID = "ess0";
- private static final String SUM_ID = "_sum";
-
- private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine");
- private static final ChannelAddress DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(CTRL_ID,
- "DebugSetActivePowerLessOrEquals");
- private static final ChannelAddress DEBUG_TARGET_POWER = new ChannelAddress(CTRL_ID, "DebugTargetPower");
- private static final ChannelAddress DEBUG_RAMP_POWER = new ChannelAddress(CTRL_ID, "DebugRampPower");
-
- private static final ChannelAddress RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE = new ChannelAddress(CTRL_ID,
- "RangeOfReserveSocOutsideAllowedValue");
-
- private static final ChannelAddress ESS_MAX_APPARENT_POWER = new ChannelAddress(ESS_ID, "MaxApparentPower");
- private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc");
- private static final ChannelAddress SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(ESS_ID,
- "SetActivePowerLessOrEquals");
-
- private static final ChannelAddress PRODUCTION_DC_ACTUAL_POWER = new ChannelAddress(SUM_ID,
- "ProductionDcActualPower");
- private static final ChannelAddress PRODUCTION_AC_ACTIVE_POWER = new ChannelAddress(SUM_ID,
- "ProductionAcActivePower");
-
@Test
public void testReserveSocRange() throws Exception {
new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) //
.addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setReserveSoc(20) //
.setReserveSocEnabled(true) //
.build()) //
.next(new TestCase() //
- .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false));
+ .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)) //
+ .deactivate();
new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) //
.addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setReserveSoc(5) //
.setReserveSocEnabled(true) //
.build()) //
.next(new TestCase() //
- .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false));
+ .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)) //
+ .deactivate();
new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) //
.addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setReserveSoc(4) //
.setReserveSocEnabled(true) //
.build()) //
.next(new TestCase() //
- .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, true));
+ .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, true)) //
+ .deactivate();
new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) //
.addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setReserveSoc(100) //
.setReserveSocEnabled(true) //
.build()) //
.next(new TestCase() //
- .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false));
+ .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)) //
+ .deactivate();
new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) //
.addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setReserveSoc(101) //
.setReserveSocEnabled(true) //
.build()) //
.next(new TestCase() //
- .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, true));
+ .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, true)) //
+ .deactivate();
}
@Test
@@ -116,19 +108,19 @@ public void testReachTargetPower() throws Exception {
.addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setReserveSoc(20) //
.setReserveSocEnabled(true) //
.build()) //
.next(new TestCase() //
- .input(ESS_SOC, 22) //
- .input(ESS_MAX_APPARENT_POWER, 10000) //
+ .input("ess0", SOC, 22) //
+ .input("ess0", MAX_APPARENT_POWER, 10000) //
.output(STATE_MACHINE, State.NO_LIMIT)) //
.next(new TestCase() //
- .input(ESS_SOC, 21) //
+ .input("ess0", SOC, 21) //
.output(STATE_MACHINE, State.NO_LIMIT)); //
var maxApparentPower = 10000;
@@ -141,14 +133,16 @@ public void testReachTargetPower() throws Exception {
result -= rampPower;
}
- controllerTest.next(new TestCase().input(ESS_SOC, 21) //
+ controllerTest.next(new TestCase().input("ess0", SOC, 21) //
.input(PRODUCTION_DC_ACTUAL_POWER, 0) //
.output(STATE_MACHINE, State.ABOVE_RESERVE_SOC) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, result) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, result) //
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, result) //
.output(DEBUG_TARGET_POWER, targetPower.floatValue()) //
);
}
+
+ controllerTest.deactivate();
}
@Test
@@ -157,41 +151,42 @@ public void testAllStates() throws Exception {
.addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setReserveSoc(20) //
.setReserveSocEnabled(true) //
.build())
.next(new TestCase() //
- .input(ESS_SOC, 22) //
- .input(ESS_MAX_APPARENT_POWER, 10000) //
+ .input("ess0", SOC, 22) //
+ .input("ess0", MAX_APPARENT_POWER, 10000) //
.output(STATE_MACHINE, State.NO_LIMIT)) //
.next(new TestCase() //
- .input(ESS_SOC, 21) //
+ .input("ess0", SOC, 21) //
.output(STATE_MACHINE, State.NO_LIMIT)) //
.next(new TestCase() //
- .input(ESS_SOC, 20) //
+ .input("ess0", SOC, 20) //
.output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)) //
.next(new TestCase() //
- .input(ESS_SOC, 19) //
+ .input("ess0", SOC, 19) //
.output(STATE_MACHINE, State.AT_RESERVE_SOC)) //
.next(new TestCase() //
- .input(ESS_SOC, 16) //
+ .input("ess0", SOC, 16) //
.output(STATE_MACHINE, State.BELOW_RESERVE_SOC)) //
.next(new TestCase() //
- .input(ESS_SOC, 20) //
+ .input("ess0", SOC, 20) //
.output(STATE_MACHINE, State.FORCE_CHARGE)) //
.next(new TestCase() //
- .input(ESS_SOC, 21) //
+ .input("ess0", SOC, 21) //
.output(STATE_MACHINE, State.AT_RESERVE_SOC)) //
.next(new TestCase() //
- .input(ESS_SOC, 22) //
+ .input("ess0", SOC, 22) //
.output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)) //
.next(new TestCase() //
- .input(ESS_SOC, 22) //
- .output(STATE_MACHINE, State.NO_LIMIT));
+ .input("ess0", SOC, 22) //
+ .output(STATE_MACHINE, State.NO_LIMIT)) //
+ .deactivate();
}
@Test
@@ -200,23 +195,24 @@ public void testIncreaseRampByNoLimitState() throws Exception {
.addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setReserveSoc(20) //
.setReserveSocEnabled(true) //
.build()) //
.next(new TestCase() //
- .input(ESS_SOC, 80)) //
+ .input("ess0", SOC, 80)) //
.next(new TestCase() //
- .input(ESS_SOC, 80) //
- .input(ESS_MAX_APPARENT_POWER, 10000) //
+ .input("ess0", SOC, 80) //
+ .input("ess0", MAX_APPARENT_POWER, 10000) //
.output(STATE_MACHINE, State.NO_LIMIT) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null) //
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null) //
.output(DEBUG_TARGET_POWER, 10000f) //
- .output(DEBUG_RAMP_POWER, 100f));
+ .output(DEBUG_RAMP_POWER, 100f)) //
+ .deactivate();
}
@Test
@@ -225,65 +221,66 @@ public void testDecreaseRampByAboveReserveSocState() throws Exception {
.addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setReserveSoc(20) //
.setReserveSocEnabled(true) //
.build()) //
.next(new TestCase() //
- .input(ESS_SOC, 22) //
- .input(ESS_MAX_APPARENT_POWER, 10000) //
+ .input("ess0", SOC, 22) //
+ .input("ess0", MAX_APPARENT_POWER, 10000) //
.output(STATE_MACHINE, State.NO_LIMIT)) //
.next(new TestCase() //
- .input(ESS_SOC, 21) //
+ .input("ess0", SOC, 21) //
.output(STATE_MACHINE, State.NO_LIMIT)//
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null) //
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) //
// to reach 50% of maxApparentPower
.next(new TestCase() //
- .input(ESS_SOC, 21) //
+ .input("ess0", SOC, 21) //
.input(PRODUCTION_DC_ACTUAL_POWER, 0) //
.output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)//
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)) //
.next(new TestCase() //
- .input(ESS_SOC, 21) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)//
+ .input("ess0", SOC, 21) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) //
.next(new TestCase() //
- .input(ESS_SOC, 21) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)//
+ .input("ess0", SOC, 21) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)) //
// to reach is DC-PV
.next(new TestCase() //
- .input(ESS_SOC, 21) //
+ .input("ess0", SOC, 21) //
.input(PRODUCTION_DC_ACTUAL_POWER, 6000) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600))
.next(new TestCase() //
- .input(ESS_SOC, 21) //
+ .input("ess0", SOC, 21) //
.input(PRODUCTION_DC_ACTUAL_POWER, 10000) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700))
.next(new TestCase() //
- .input(ESS_SOC, 21) //
+ .input("ess0", SOC, 21) //
.input(PRODUCTION_DC_ACTUAL_POWER, 10000) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800))
.next(new TestCase() //
- .input(ESS_SOC, 21) //
+ .input("ess0", SOC, 21) //
.input(PRODUCTION_DC_ACTUAL_POWER, 6000) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700))
.next(new TestCase() //
- .input(ESS_SOC, 21) //
+ .input("ess0", SOC, 21) //
.input(PRODUCTION_DC_ACTUAL_POWER, 6000) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)//
- .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600));
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)//
+ .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)) //
+ .deactivate();
}
@Test
@@ -292,41 +289,42 @@ public void testDecreaseRampByAtReserveSocState() throws Exception {
.addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setReserveSoc(20) //
.setReserveSocEnabled(true) //
.build()) //
.next(new TestCase() //
- .input(ESS_SOC, 22) //
- .input(ESS_MAX_APPARENT_POWER, 10000) //
+ .input("ess0", SOC, 22) //
+ .input("ess0", MAX_APPARENT_POWER, 10000) //
.output(STATE_MACHINE, State.NO_LIMIT)) //
.next(new TestCase() //
- .input(ESS_SOC, 20) //
+ .input("ess0", SOC, 20) //
.output(STATE_MACHINE, State.NO_LIMIT)//
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) //
.next(new TestCase() //
- .input(ESS_SOC, 20) //
+ .input("ess0", SOC, 20) //
.output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)//
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)) //
.next(new TestCase() //
- .input(ESS_SOC, 20) //
+ .input("ess0", SOC, 20) //
.input(PRODUCTION_DC_ACTUAL_POWER, 0) //
.output(STATE_MACHINE, State.AT_RESERVE_SOC)//
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) //
.next(new TestCase() //
- .input(ESS_SOC, 20) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)//
+ .input("ess0", SOC, 20) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)) //
.next(new TestCase() //
- .input(ESS_SOC, 20) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)//
- .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600));
+ .input("ess0", SOC, 20) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)//
+ .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)) //
+ .deactivate();
}
@Test
@@ -335,45 +333,46 @@ public void testDecreaseRampByUnderReserveSocState() throws Exception {
.addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setReserveSoc(20) //
.setReserveSocEnabled(true) //
.build()) //
.next(new TestCase() //
- .input(ESS_SOC, 22) //
- .input(ESS_MAX_APPARENT_POWER, 10000) //
+ .input("ess0", SOC, 22) //
+ .input("ess0", MAX_APPARENT_POWER, 10000) //
.output(STATE_MACHINE, State.NO_LIMIT)) //
.next(new TestCase() //
- .input(ESS_SOC, 19) //
+ .input("ess0", SOC, 19) //
.output(STATE_MACHINE, State.NO_LIMIT)//
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) //
.next(new TestCase() //
- .input(ESS_SOC, 19) //
+ .input("ess0", SOC, 19) //
.output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)//
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)) //
.next(new TestCase() //
- .input(ESS_SOC, 19) //
+ .input("ess0", SOC, 19) //
.output(STATE_MACHINE, State.AT_RESERVE_SOC)//
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) //
.next(new TestCase() //
- .input(ESS_SOC, 19) //
+ .input("ess0", SOC, 19) //
.output(STATE_MACHINE, State.BELOW_RESERVE_SOC)//
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)) //
.next(new TestCase() //
- .input(ESS_SOC, 19) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)//
+ .input("ess0", SOC, 19) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)) //
.next(new TestCase() //
- .input(ESS_SOC, 19) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 8300)//
- .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8300));
+ .input("ess0", SOC, 19) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 8300)//
+ .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8300)) //
+ .deactivate();
}
@Test
@@ -382,62 +381,63 @@ public void testDecreaseRampByForceStartChargeState() throws Exception {
.addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setReserveSoc(20) //
.setReserveSocEnabled(true) //
.build()) //
.next(new TestCase() //
- .input(ESS_SOC, 22) //
- .input(ESS_MAX_APPARENT_POWER, 10000) //
+ .input("ess0", SOC, 22) //
+ .input("ess0", MAX_APPARENT_POWER, 10000) //
.output(STATE_MACHINE, State.NO_LIMIT)) //
.next(new TestCase() //
- .input(ESS_SOC, 16) //
+ .input("ess0", SOC, 16) //
.output(STATE_MACHINE, State.NO_LIMIT)//
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) //
.next(new TestCase() //
- .input(ESS_SOC, 16) //
+ .input("ess0", SOC, 16) //
.output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)//
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)) //
.next(new TestCase() //
- .input(ESS_SOC, 16) //
+ .input("ess0", SOC, 16) //
.output(STATE_MACHINE, State.AT_RESERVE_SOC)//
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) //
.next(new TestCase() //
- .input(ESS_SOC, 16) //
+ .input("ess0", SOC, 16) //
.output(STATE_MACHINE, State.BELOW_RESERVE_SOC)//
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)) //
.next(new TestCase() //
- .input(ESS_SOC, 16) //
+ .input("ess0", SOC, 16) //
.input(PRODUCTION_AC_ACTIVE_POWER, 100) //
.output(STATE_MACHINE, State.FORCE_CHARGE)//
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9200)//
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9200)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9200)) //
.next(new TestCase() //
- .input(ESS_SOC, 19) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9100)//
+ .input("ess0", SOC, 19) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9100)//
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9100)) //
.next(new TestCase() //
- .input(ESS_SOC, 21) //
+ .input("ess0", SOC, 21) //
.output(STATE_MACHINE, State.FORCE_CHARGE) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9000) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9000) //
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9000)) //
.next(new TestCase() //
- .input(ESS_SOC, 21) //
+ .input("ess0", SOC, 21) //
.output(STATE_MACHINE, State.AT_RESERVE_SOC) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 8900) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 8900) //
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8900)) //
.next(new TestCase() //
- .input(ESS_SOC, 21) //
+ .input("ess0", SOC, 21) //
.output(STATE_MACHINE, State.ABOVE_RESERVE_SOC) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)//
- .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800));
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)//
+ .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)) //
+ .deactivate();
}
@Test
@@ -451,11 +451,11 @@ public void testUndefinedSoc() throws Exception {
.addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0") //
.withMaxApparentPower(10000)) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setReserveSoc(20) //
.setReserveSocEnabled(true) //
.build()) //
@@ -464,16 +464,17 @@ public void testUndefinedSoc() throws Exception {
.output(STATE_MACHINE, State.NO_LIMIT)) //
.next(new TestCase() //
.onAfterProcessImage(sleep) //
- .input(ESS_SOC, 16)) //
+ .input("ess0", SOC, 16)) //
.next(new TestCase() //
.onAfterProcessImage(sleep) //
.output(STATE_MACHINE, State.ABOVE_RESERVE_SOC))//
.next(new TestCase() //
.onAfterProcessImage(sleep) //
- .input(ESS_SOC, null)) //
+ .input("ess0", SOC, null)) //
.next(new TestCase() //
.onAfterProcessImage(sleep) //
- .output(STATE_MACHINE, State.BELOW_RESERVE_SOC));
+ .output(STATE_MACHINE, State.BELOW_RESERVE_SOC)) //
+ .deactivate();
}
@Test
@@ -482,18 +483,18 @@ public void testIncreaseRampToMaxApparentPower() throws Exception {
.addReference("componentManager", new DummyComponentManager()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0") //
.withMaxApparentPower(10000)) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setReserveSoc(20) //
.setReserveSocEnabled(true) //
.build()) //
.next(new TestCase() //
.output(STATE_MACHINE, State.NO_LIMIT)) //
.next(new TestCase() //
- .input(ESS_SOC, 21)) //
+ .input("ess0", SOC, 21)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.ABOVE_RESERVE_SOC) //
.output(DEBUG_TARGET_POWER, 5000f) //
@@ -510,7 +511,7 @@ public void testIncreaseRampToMaxApparentPower() throws Exception {
.output(DEBUG_RAMP_POWER, 100f) //
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)) //
.next(new TestCase() //
- .input(ESS_SOC, 22)) //
+ .input("ess0", SOC, 22)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.NO_LIMIT) //
.output(DEBUG_TARGET_POWER, 10000f) //
@@ -531,13 +532,14 @@ public void testIncreaseRampToMaxApparentPower() throws Exception {
.output(DEBUG_TARGET_POWER, 10000f) //
.output(DEBUG_RAMP_POWER, 100f) //
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.NO_LIMIT) //
.output(DEBUG_TARGET_POWER, 10000f) //
.output(DEBUG_RAMP_POWER, 100f) //
.output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null) //
- .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null));
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/.classpath b/io.openems.edge.controller.ess.fastfrequencyreserve/.classpath
new file mode 100644
index 00000000000..bbfbdbe40e7
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/.classpath
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/.gitignore b/io.openems.edge.controller.ess.fastfrequencyreserve/.gitignore
new file mode 100644
index 00000000000..c2b941a96de
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/.gitignore
@@ -0,0 +1,2 @@
+/bin_test/
+/generated/
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/.project b/io.openems.edge.controller.ess.fastfrequencyreserve/.project
new file mode 100644
index 00000000000..0140daa493d
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/.project
@@ -0,0 +1,23 @@
+
+
+ io.openems.edge.controller.ess.fastfrequencyreserve
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ bndtools.core.bndbuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ bndtools.core.bndnature
+
+
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/.settings/org.eclipse.core.resources.prefs b/io.openems.edge.controller.ess.fastfrequencyreserve/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 00000000000..896a9a53a53
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/=UTF-8
\ No newline at end of file
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/bnd.bnd b/io.openems.edge.controller.ess.fastfrequencyreserve/bnd.bnd
new file mode 100644
index 00000000000..bee07090a70
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/bnd.bnd
@@ -0,0 +1,16 @@
+Bundle-Name: OpenEMS Edge Controller Fast Frequency Reserve
+Bundle-Vendor: FENECON GmbH
+Bundle-License: https://opensource.org/licenses/EPL-2.0
+Bundle-Version: 1.0.0.${tstamp}
+
+-buildpath: \
+ ${buildpath},\
+ Java-WebSocket,\
+ io.openems.common,\
+ io.openems.edge.common,\
+ io.openems.edge.controller.api,\
+ io.openems.edge.ess.api,\
+ io.openems.edge.meter.api,\
+
+-testpath: \
+ ${testpath}
\ No newline at end of file
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/doc/statemachine.md b/io.openems.edge.controller.ess.fastfrequencyreserve/doc/statemachine.md
new file mode 100644
index 00000000000..23c71c2221a
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/doc/statemachine.md
@@ -0,0 +1,18 @@
+# State-Machine
+
+```mermaid
+graph TD
+start --> Undefined
+Undefined --> |condition: Inside set time, task : Charge to maintain soc| PreActivateState
+PreActivateState --> |condition: Outside set time, task : do nothing| Undefined
+PreActivateState --> |condition: grid freq > freqlimit, task : setpower 0Watt| ActivationTime
+ActivationTime --> |condition: 1.7 sec, task : discharge setActivepower| SupportDuration
+SupportDuration --> |condition: 30 sec, task : discharge setActivepower| DeactivationTime
+DeactivationTime -->|condition: 1.7 sec, task : setpower 0Watt| BufferedTime
+BufferedTime --> |condition: 10 sec, task : setpower for 0Watt| BufferedSupportTime
+BufferedSupportTime --> |condition: 15 min, Charge to maintain soc| RecoveryTime
+RecoveryTime --> PreActivateState
+RecoveryTime -->|condition: Outside set time, task : do nothing| Undefined
+```
+
+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.controller.ess.fastfrequencyreserve/readme.adoc b/io.openems.edge.controller.ess.fastfrequencyreserve/readme.adoc
new file mode 100644
index 00000000000..028257dcea9
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/readme.adoc
@@ -0,0 +1,333 @@
+= ESS Fast Frequency Reserve
+
+== 1.1 Introduction
+
+In electricity networks, the Fast Frequency Reserve (FFR) controller is providing power available to the system operator within a short interval to meet demand in case of a frequency drop, i.e. in case a generator goes down or there is another disruption to the supply. More details on link:https://en.wikipedia.org/wiki/Operating_reserve[Wikipedia].
+
+This controller helps the Energy Storage System (ESS) to provide power, essentially battery discharge, when the measured "Grid frequency" is lower than that of a defined "Frequency limit".
+
+== 1.2 Controller Parameters
+
+- **mode**: mode of the controller, On or Off?
+- **id**: the id for the controller
+- **alias**: Alias for the controller
+- **enabled**: enabled or not?
+- **meterId**: the id of the meter
+- **essId**: the id of the Ess
+- **batteryInverterId**: the id of the battery inverter
+- **preActivationTime**: A time before the activation time for charging the system(min).
+- **schedule**: scheduling of the controller, via JSON see below for the example
+
+=== 1.2.1 The Example Schedule-JSON
+
+[source,json]
+----
+[
+ {
+ "startTimestamp": 1684792800,
+ "duration": 86400,
+ "dischargePowerSetPoint": 92000,
+ "frequencyLimit": 50000,
+ "activationRunTime": "LONG_ACTIVATION_RUN",
+ "supportDuration": "LONG_SUPPORT_DURATION"
+ },
+ {
+ "startTimestamp": 1684879200,
+ "duration": 86400,
+ "dischargePowerSetPoint": 6000,
+ "frequencyLimit": 50000,
+ "activationRunTime": "LONG_ACTIVATION_RUN",
+ "supportDuration": "LONG_SUPPORT_DURATION"
+ }
+]
+----
+
+=== 1.2.2 JSON Element details
+
+- `StartTimeStamp`: When the controller should be activated.
+- `Duration`: How long is the controller to be activated?
+- `frequency limit`: The controller continuously monitors and checks whether a Frequency limit or threshold is less than the
+measured grid frequency.
+- `DischargePower`: The Ess discharges from the batteries when generating capacity.
+- `activationRunTime`: The time in milliseconds required for the reserve to fully activate. Short(700 ms) or Medium(1000 ms) or Long(1300 ms) activation Time.
+- `supportDuration`: The time in milliseconds for which the reserve should continue providing support after the frequency has stabilized. Short(5 seconds) or Long(30 seconds) support duration.
+
+=== 1.2.3 Explanation of the Schedule
+The Schedule JSON activates FFR for a full day (86400 seconds or 24 hours) with the following parameters:
+
+1. Schedule for 23rd May 2023 00:00:00 to 24th May:
+ - *Threshold frequency:* 49700 mHz
+ - *Discharge power:* 92000 W
+ - *Long activation time:* 1.3 seconds
+ - *Support duration:* 30 seconds
+
+2. Following Schedule for 24th May 2023 00:00:00 to 25th May:
+ - *Threshold frequency:* 49700 mHz
+ - *Discharge power:* 52000 W
+ - *Long activation time:* 1.3 seconds
+ - *Support duration:* 30 seconds
+
+
+== 2.1 REST API for updating Fast Frequency Reserve controllers schedule locally
+
+note : The controller/ App should be activated to update the schedule, which can be done using online monitoring or apache felix.
+
+== 2.1.1 Overview
+
+This REST API allows you to update FFR schedule for the specified edge device. The API endpoint takes a JSON payload that specifies the schedule, including the start time, duration, discharge power set point, frequency limit, activation runtime, and support duration.
+
+== 2.1.2 Endpoint
+
+- *URL*: http://:8084/jsonrpc
+- *Method*: POST
+- *Content-Type*: application/json
+- *Authorization*: Basic Authentication, username: x, password: owner
+
+== 2.1.3 Body
+
+The request body must be a JSON object with the following structure:
+
+[source,json]
+----
+{
+ "method": "componentJsonApi",
+ "params": {
+ "componentId": "ctrlFastFreqReserve0",
+ "payload": {
+ "method": "setActivateFastFreqReserve",
+ "params": {
+ "id": "edge0",
+ "schedule": [
+ {
+ "startTimestamp": 1701871562,
+ "duration": 999,
+ "dischargePowerSetPoint": 6000,
+ "frequencyLimit": 502000,
+ "activationRunTime": "LONG_ACTIVATION_RUN",
+ "supportDuration": "LONG_SUPPORT_DURATION"
+ }
+ ]
+ }
+ }
+ }
+}
+----
+
+== 2.1.4 Request Parameters
+
+The request body for this REST API call is a JSON object with the following parameters:
+
+- *method*: The specific method to call within the component. In this case, it is `componentJsonApi`.
+- *params*: The parameters associated with the method call.
+- *componentId*: The unique identifier of the component that is receiving the request.
+- *payload*: The specific data being sent to the component. See below
+
+=== 2.1.5 Payload Parameters
+
+Within the payload parameter, there is another JSON object that specifies the details of the activation request:
+
+- *method*: The method to call within the component to handle the activation request. In this case, it is `setActivateFastFreqReserve`.
+- *params*: The parameters associated with the `setActivateFastFreqReserve` method.
+- *id*: The unique identifier of the edge device for which the activation is being requested, locally is always `edge0`.
+- *schedule*: An array of schedule items that define the activation pattern for the reserve.
+
+=== 2.1.6 Schedule Item Parameters
+
+Each schedule item within the schedule array specifies a specific activation period:
+
+- *startTimestamp*: The unix time stamp when the FFR should start activating.
+- *duration*: The duration in milliseconds for which the reserve should remain active.
+- *dischargePowerSetPoint*: The maximum power in kilowatts that the reserve should discharge during activation.
+- *frequencyLimit*: The frequency threshold below which the reserve should be activated.
+- *activationRunTime*: The time in milliseconds required for the reserve to fully activate.
+- *supportDuration*: The time in milliseconds for which the reserve should continue providing support after the frequency has stabilized.
+
+=== 2.1.7 Example Python code
+
+[source,python]
+----
+import requests
+import json
+from requests.auth import HTTPBasicAuth
+
+# API URL
+url = 'http://10.0.10.178:8084/jsonrpc'
+
+# Authentication
+auth = HTTPBasicAuth('x', 'owner')
+
+# Request headers
+headers = {
+ 'Content-Type': 'application/json',
+}
+
+# Request payload
+payload = {
+ 'jsonrpc': '2.0',
+ 'id': '00000000-0000-0000-0000-000000000000',
+ 'method': 'componentJsonApi',
+ 'params': {
+ 'componentId': 'ctrlFastFreqReserve0',
+ 'payload': {
+ 'method': 'setActivateFastFreqReserve',
+ 'params': {
+ 'id': 'edge0',
+ 'schedule': [
+ {
+ 'startTimestamp': 1701871562,
+ 'duration': 999,
+ 'dischargePowerSetPoint': 6000,
+ 'frequencyLimit': 502000,
+ 'activationRunTime': 'LONG_ACTIVATION_RUN',
+ 'supportDuration': 'LONG_SUPPORT_DURATION'
+ }
+ ]
+ }
+ }
+ }
+}
+
+# Make the request
+response = requests.post(url, auth=auth, headers=headers, json=payload)
+
+# Print the response
+print(response.json())
+----
+
+
+== 3.1 REST API for Activating Fast Frequency Reserve controllers schedule using Backend
+note : The controller/ App should be activated to update the schedule, which can be done using online monitoring or apache felix.
+
+== 3.1.1 Overview
+
+This REST API allows you to update FFR for a specific edge device. The API endpoint takes a JSON payload that updates activation schedule, including the start time, duration, discharge power set point, frequency limit, activation runtime, and support duration.
+
+== 3.1.2 Endpoint
+
+- *URL*: https://femecon.de/fems/rest/jsonrpc
+- *Method*: POST
+- *Content-Type*: application/json
+- *Authorization*: Basic Authentication, username:foo.com, password:****
+
+== 3.1.3 Body
+
+The request body must be a JSON object with the following structure:
+
+[source,json]
+----
+{
+ "method": "edgeRpc",
+ "params": {
+ "edgeId": "fems3734",
+ "payload": {
+ "method": "componentJsonApi",
+ "params": {
+ "componentId": "ctrlFastFreqReserve0",
+ "payload": {
+ "method": "setActivateFastFreqReserve",
+ "params": {
+ "id": "edge3734",
+ "schedule": [
+ {
+ "startTimestamp": "1701767477",
+ "duration": "11000",
+ "dischargePowerSetPoint": "6000",
+ "frequencyLimit": "52000",
+ "activationRunTime": "LONG_ACTIVATION_RUN",
+ "supportDuration": "LONG_SUPPORT_DURATION"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+}
+----
+
+== 3.1.4 Request Parameters
+
+- *method*: The JSONRPC method to call. In this case, it is `edgeRpc`.
+- *params*: The JSONRPC parameters.
+- *edgeId*: The ID of the edge device for which to activate the FFR.
+- *payload*: The JSONRPC payload.
+
+== 3.1.5 Payload Parameters
+
+Within the payload parameter, there is another JSON object that specifies the details of the activation request:
+
+- *method*: The JSONRPC method to call within the component. In this case, it is `componentJsonApi`.
+- *params*: The JSONRPC parameters for the `componentJsonApi` method.
+- *componentId*: The ID of the component within which to call the method. In this case, it is `ctrlFastFreqReserve0`.
+- *payload*: The JSONRPC payload for the method.
+
+== 3.1.6 Schedule Item Parameters
+
+Each schedule item within the schedule array specifies a specific activation period:
+
+- *startTimestamp*: The unix time stamp in milliseconds when the FFR should start activating.
+- *duration*: The duration in milliseconds for which the reserve should remain active.
+- *dischargePowerSetPoint*: The maximum power in kilowatts that the reserve should discharge during activation.
+- *frequencyLimit*: The frequency threshold below which the reserve should be activated.
+- *activationRunTime*: The time in milliseconds required for the reserve to fully activate.
+- *supportDuration*: The time in milliseconds for which the reserve should continue providing support after the frequency has stabilized.
+
+== 3.1.7 Example Python code:
+
+[source,python]
+----
+import requests
+import json
+from requests.auth import HTTPBasicAuth
+import base64
+import os
+
+url = "https://fenecon.de/fems/rest/jsonrpc"
+
+username = os.getenv("FENECON_USERNAME")
+password = os.getenv("FENECON_PASSWORD")
+
+headers = {
+ "Content-Type": "application/json",
+ "Authorization": "Basic " + base64.b64encode(f"{username}:{password}".encode("utf-8")).decode("utf-8")
+}
+
+body = {
+ "method": "edgeRpc",
+ "params": {
+ "edgeId": "fems3734",
+ "payload": {
+ "method": "componentJsonApi",
+ "params": {
+ "componentId": "ctrlFastFreqReserve0",
+ "payload": {
+ "method": "setActivateFastFreqReserve",
+ "params": {
+ "id": "edge3734",
+ "schedule": [
+ {
+ "startTimestamp": "1701767477",
+ "duration": "11000",
+ "dischargePowerSetPoint": "6000",
+ "frequencyLimit": "52000",
+ "activationRunTime": "LONG_ACTIVATION_RUN",
+ "supportDuration": "LONG_SUPPORT_DURATION"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+}
+
+response = requests.post(url, headers=headers, data=json.dumps(body))
+
+if response.status_code == 200:
+ print("Fast Frequency Reserve activated successfully")
+else:
+ print("Error activating Fast Frequency Reserve:", response.text)
+----
+
+
+https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.controller.ess.fastfrequencyreserve[Source Code icon:github[]]
\ No newline at end of file
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/Config.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/Config.java
new file mode 100644
index 00000000000..2ca1b8b52f3
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/Config.java
@@ -0,0 +1,54 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode;
+
+@ObjectClassDefinition(//
+ name = "Controller Ess Fast Frequency Reserve", //
+ description = "This Controller helps the energy storage system (ESS) generate capacity, essentially battery discharge. When the measured\n"
+ + "\"Grid frequency\" is lower than the predefined \"Frequency limit\".") //
+@interface Config {
+
+ @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component")
+ String id() default "ctrlFastFreqReserve0";
+
+ @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID")
+ String alias() default "";
+
+ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?")
+ boolean enabled() default true;
+
+ @AttributeDefinition(name = "Control Mode", description = "Set the type of control mode.")
+ ControlMode controlMode() default ControlMode.MANUAL_OFF;
+
+ @AttributeDefinition(name = "Activation Schdule", description = "Schedule for the activation.")
+ String activationScheduleJson() default "[\n" //
+ + " {\n" //
+ + " \"startTimestamp\": 1684879200,\n" //
+ + " \"duration\": 86400,\n" //
+ + " \"dischargePowerSetPoint\": 92000,\n" //
+ + " \"frequencyLimit\": 49500,\n" //
+ + " \"activationRunTime\": \"LONG_ACTIVATION_RUN\",\n" //
+ + " \"supportDuration\": \"LONG_SUPPORT_DURATION\"\n" //
+ + " }\n" //
+ + "]"; //
+
+ @AttributeDefinition(name = "Ess-ID", description = "ID of Ess device.")
+ String ess_id();
+
+ @AttributeDefinition(name = "Grid-Meter-Id", description = "ID of the Grid-Meter.")
+ String meter_id();
+
+ @AttributeDefinition(name = "Pre activation time", description = "A time before the activation time for charging the system(min).")
+ int preActivationTime() default 0;
+
+ @AttributeDefinition(name = "Ess target filter", description = "This is auto-generated by 'Ess-ID'.")
+ String ess_target() default "(enabled=true)";
+
+ @AttributeDefinition(name = "Meter target filter", description = "This is auto-generated by 'Grid-Meter-ID'.")
+ String meter_target() default "(enabled=true)";
+
+ String webconsole_configurationFactory_nameHint() default "Controller Ess Fast Frequency Reserve [{id}]";
+}
\ No newline at end of file
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserve.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserve.java
new file mode 100644
index 00000000000..efff694c2ee
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserve.java
@@ -0,0 +1,283 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve;
+
+import io.openems.common.channel.AccessMode;
+import io.openems.common.channel.Level;
+import io.openems.common.channel.PersistencePriority;
+import io.openems.common.types.OpenemsType;
+import io.openems.edge.common.channel.Channel;
+import io.openems.edge.common.channel.Doc;
+import io.openems.edge.common.channel.StateChannel;
+import io.openems.edge.common.channel.WriteChannel;
+import io.openems.edge.common.channel.value.Value;
+import io.openems.edge.common.component.OpenemsComponent;
+import io.openems.edge.controller.api.Controller;
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime;
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode;
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration;
+import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State;
+
+public interface ControllerFastFrequencyReserve extends Controller, OpenemsComponent {
+
+ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
+ CONTROL_MODE(Doc.of(ControlMode.values()) //
+ .initialValue(ControlMode.MANUAL_OFF) //
+ .text("Configured Control Mode")), //
+ STATE_MACHINE(Doc.of(State.values()) //
+ .persistencePriority(PersistencePriority.HIGH)//
+ .text("Current State of State-Machine")), //
+ SCHEDULE_PARSE_FAILED(Doc.of(Level.FAULT) //
+ .text("Unable to parse Schedule")), //
+ NO_ACTIVE_SETPOINT(Doc.of(OpenemsType.BOOLEAN) //
+ .text("No active Set-Point given")), //
+ DISCHARGE_POWER_SET_POINT(Doc.of(OpenemsType.INTEGER) //
+ .persistencePriority(PersistencePriority.HIGH)//
+ .accessMode(AccessMode.READ_WRITE)),
+ NO_FREQUENCY_LIMIT(Doc.of(OpenemsType.BOOLEAN) //
+ .text("No Frequency limit is given")), //
+ FREQUENCY_LIMIT(Doc.of(OpenemsType.INTEGER) //
+ .persistencePriority(PersistencePriority.HIGH)//
+ .accessMode(AccessMode.READ_WRITE)), //
+ NO_START_TIMESTAMP(Doc.of(OpenemsType.BOOLEAN) //
+ .text("No start timestamp")), //
+ START_TIMESTAMP(Doc.of(OpenemsType.LONG) //
+ .persistencePriority(PersistencePriority.HIGH)//
+ .accessMode(AccessMode.READ_WRITE)), //
+ NO_DURATION(Doc.of(OpenemsType.BOOLEAN) //
+ .text("No duration")), //
+ DURATION(Doc.of(OpenemsType.INTEGER) //
+ .persistencePriority(PersistencePriority.HIGH)//
+ .accessMode(AccessMode.READ_WRITE)), //
+ ACTIVATION_TIME(Doc.of(ActivationTime.values())//
+ .accessMode(AccessMode.READ_WRITE)), //
+ SUPPORT_DURATIN(Doc.of(SupportDuration.values())//
+ .accessMode(AccessMode.READ_WRITE)),
+ LAST_TRIGGERED_TIME(Doc.of(OpenemsType.STRING) //
+ .persistencePriority(PersistencePriority.HIGH)//
+ .accessMode(AccessMode.READ_WRITE) //
+ .text("Last Triggered time in Human readable form")//
+
+ );
+
+ private final Doc doc;
+
+ private ChannelId(Doc doc) {
+ this.doc = doc;
+ }
+
+ @Override
+ public Doc doc() {
+ return this.doc;
+ }
+ }
+
+ /**
+ * Gets the Channel for {@link ChannelId#SUPPORT_DURATIN}.
+ *
+ * @return the Channel
+ */
+ public default Channel getSupportDurationChannel() {
+ return this.channel(ChannelId.SUPPORT_DURATIN);
+ }
+
+ /**
+ * Gets the SupportDuration, see {@link ChannelId#SUPPORT_DURATIN}.
+ *
+ * @return the Channel {@link Value}
+ */
+ public default SupportDuration getSupportDuration() {
+ return this.getSupportDurationChannel().value().asEnum();
+ }
+
+ /**
+ * Gets the Channel for {@link ChannelId#ACTIVATION_TIME}.
+ *
+ * @return the Channel
+ */
+ public default Channel getActivationTimeChannel() {
+ return this.channel(ChannelId.ACTIVATION_TIME);
+ }
+
+ /**
+ * Gets the ActivationTime, see {@link ChannelId#ACTIVATION_TIME}.
+ *
+ * @return the Channel {@link Value}
+ */
+ public default ActivationTime getActivationTime() {
+ return this.getActivationTimeChannel().value().asEnum();
+ }
+
+ /**
+ * Gets the WriteChannel {@link ChannelId#LAST_TRIGGERED_TIME}.
+ *
+ * @return the WriteChannel
+ */
+ public default WriteChannel getLastTriggeredTimeChannel() {
+ return this.channel(ChannelId.LAST_TRIGGERED_TIME);
+ }
+
+ /**
+ * Gets the getLastTriggeredTime, see {@link ChannelId#LAST_TRIGGERED_TIME}.
+ *
+ * @return the Channel {@link Value}
+ */
+ public default Value getLastTriggeredTime() {
+ return this.getLastTriggeredTimeChannel().value();
+ }
+
+ /**
+ * Sets the LastTriggeredTimseStamp, see {@link ChannelId#LAST_TRIGGERED_TIME}.
+ *
+ * @param value the value to be set
+ */
+ public default void setLastTriggeredTime(String value) {
+ this.getLastTriggeredTimeChannel().setNextValue(value);
+ }
+
+ /**
+ * Gets the Channel {@link ChannelId#SCHEDULE_PARSE_FAILED}.
+ *
+ * @return the Channel
+ */
+ public default StateChannel getScheduleParseFailedChannel() {
+ return this.channel(ChannelId.SCHEDULE_PARSE_FAILED);
+ }
+
+ /**
+ * Internal method to set the 'nextValue' on
+ * {@link ChannelId#SCHEDULE_PARSE_FAILED} Channel.
+ *
+ * @param value the next value
+ */
+ public default void _setScheduleParseFailed(boolean value) {
+ this.getScheduleParseFailedChannel().setNextValue(value);
+ }
+
+ /**
+ * Gets the Channel {@link ChannelId#DISCHARGE_POWER_SET_POINT}.
+ *
+ * @return the Channel
+ */
+ public default WriteChannel getDischargePowerSetPointChannel() {
+ return this.channel(ChannelId.DISCHARGE_POWER_SET_POINT);
+ }
+
+ /**
+ * Gets the getDischargeActivePowerSetPoint, see
+ * {@link ChannelId#DISCHARGE_POWER_SET_POINT}.
+ *
+ * @return the Channel {@link Value}
+ */
+ public default Value getDischargePowerSetPoint() {
+ return this.getDischargePowerSetPointChannel().value();
+ }
+
+ /**
+ * Gets the WriteChannel {@link ChannelId#FREQUENCY_LIMIT}.
+ *
+ * @return the WriteChannel
+ */
+ public default WriteChannel getFrequencyLimitChannel() {
+ return this.channel(ChannelId.FREQUENCY_LIMIT);
+ }
+
+ /**
+ * Gets the getFrequencyLimit, see {@link ChannelId#FREQUENCY_LIMIT}.
+ *
+ * @return the Channel {@link Value}
+ */
+ public default Value getFrequencyLimit() {
+ return this.getFrequencyLimitChannel().value();
+ }
+
+ /**
+ * Gets the WriteChannel {@link ChannelId#DURATION}.
+ *
+ * @return the WriteChannel
+ */
+ public default WriteChannel getDurationChannel() {
+ return this.channel(ChannelId.DURATION);
+ }
+
+ /*
+ * Gets the getDuration, see {@link ChannelId#DURATION}.
+ *
+ * @return the Channel {@link Value}
+ */
+ public default Value getDuration() {
+ return this.getDurationChannel().value();
+ }
+
+ /**
+ * Gets the WriteChannel {@link ChannelId#START_TIMESTAMP}.
+ *
+ * @return the WriteChannel
+ */
+ public default WriteChannel getStartTimestampChannel() {
+ return this.channel(ChannelId.START_TIMESTAMP);
+ }
+
+ /**
+ * Gets the getStartTimestamp, see {@link ChannelId#START_TIMESTAMP}.
+ *
+ * @return the Channel {@link Value}
+ */
+ public default Value getStartTimestamp() {
+ return this.getStartTimestampChannel().value();
+ }
+
+ /**
+ * Gets the Channel for {@link ChannelId#STATE_MACHINE}.
+ *
+ * @return the Channel
+ */
+ public default Channel getStateMachineChannel() {
+ return this.channel(ChannelId.STATE_MACHINE);
+ }
+
+ /**
+ * Gets the {@link StateChannel} for {@link ChannelId#STATE_MACHINE}.
+ *
+ * @return the Channel {@link Value}
+ */
+ public default Value getStateMachine() {
+ return this.getStateMachineChannel().value();
+ }
+
+ /**
+ * Internal method to set the 'nextValue' on {@link ChannelId#STATE_MACHINE}
+ * Channel.
+ *
+ * @param value the next value
+ */
+ public default void _setStateMachine(State value) {
+ this.getStateMachineChannel().setNextValue(value);
+ }
+
+ /**
+ * Gets the Channel for {@link ChannelId#CONTROL_MODE}.
+ *
+ * @return the Channel
+ */
+ public default Channel getControlModeChannel() {
+ return this.channel(ChannelId.CONTROL_MODE);
+ }
+
+ /**
+ * Gets the {@link StateChannel} for {@link ChannelId#CONTROL_MODE}.
+ *
+ * @return the Channel {@link Value}
+ */
+ public default Value getControlMode() {
+ return this.getControlModeChannel().value();
+ }
+
+ /**
+ * Internal method to set the 'nextValue' on {@link ChannelId#CONTROL_MODE}
+ * Channel.
+ *
+ * @param value the next value
+ */
+ public default void _setControlMode(ControlMode value) {
+ this.getControlModeChannel().setNextValue(value);
+ }
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImpl.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImpl.java
new file mode 100644
index 00000000000..5e9bf35f124
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImpl.java
@@ -0,0 +1,312 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.osgi.service.metatype.annotations.Designate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonArray;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.jsonrpc.base.GenericJsonrpcResponseSuccess;
+import io.openems.common.session.Role;
+import io.openems.common.utils.JsonUtils;
+import io.openems.edge.common.channel.WriteChannel;
+import io.openems.edge.common.channel.value.Value;
+import io.openems.edge.common.component.AbstractOpenemsComponent;
+import io.openems.edge.common.component.ComponentManager;
+import io.openems.edge.common.component.OpenemsComponent;
+import io.openems.edge.common.jsonapi.ComponentJsonApi;
+import io.openems.edge.common.jsonapi.EdgeGuards;
+import io.openems.edge.common.jsonapi.JsonApiBuilder;
+import io.openems.edge.controller.api.Controller;
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime;
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration;
+import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest;
+import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest.ActivateFastFreqReserveSchedule;
+import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.Context;
+import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine;
+import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State;
+import io.openems.edge.ess.api.ManagedSymmetricEss;
+import io.openems.edge.meter.api.ElectricityMeter;
+
+@Designate(ocd = Config.class, factory = true)
+@Component(//
+ name = "Controller.Ess.FastFrequencyReserve", //
+ immediate = true, //
+ configurationPolicy = ConfigurationPolicy.REQUIRE //
+)
+public class ControllerFastFrequencyReserveImpl extends AbstractOpenemsComponent
+ implements ControllerFastFrequencyReserve, Controller, OpenemsComponent, ComponentJsonApi {
+
+ private final Logger log = LoggerFactory.getLogger(ControllerFastFrequencyReserveImpl.class);
+ private final StateMachine stateMachine = new StateMachine(State.UNDEFINED);
+
+ private Config config = null;
+ private List schedule = new CopyOnWriteArrayList<>();
+
+ private static final Function OBTAIN_DICHARGE_POWER = ActivateFastFreqReserveSchedule::dischargePowerSetPoint;
+ private static final Function OBTAIN_FREQ_LIMIT = ActivateFastFreqReserveSchedule::frequencyLimit;
+ private static final Function OBTAIN_STARTTIME_STAMP = ActivateFastFreqReserveSchedule::startTimestamp;
+ private static final Function OBTAIN_DURATION = ActivateFastFreqReserveSchedule::duration;
+ private static final Function OBTAIN_ACTIVATION_TIME = ActivateFastFreqReserveSchedule::activationRunTime;
+ private static final Function OBTAIN_SUPPORT_DURATION = ActivateFastFreqReserveSchedule::supportDuration;
+
+ @Reference
+ private ConfigurationAdmin cm;
+
+ @Reference
+ private ComponentManager componentManager;
+
+ @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY)
+ private ManagedSymmetricEss ess;
+
+ @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY)
+ private ElectricityMeter meter;
+
+ public ControllerFastFrequencyReserveImpl() {
+ super(//
+ OpenemsComponent.ChannelId.values(), //
+ Controller.ChannelId.values(), //
+ ControllerFastFrequencyReserve.ChannelId.values() //
+ );
+ }
+
+ @Activate
+ private void activate(ComponentContext context, Config config) throws OpenemsNamedException {
+ this.config = config;
+ super.activate(context, config.id(), config.alias(), config.enabled());
+ this.updateConfig();
+ }
+
+ @Override
+ public void buildJsonApiRoutes(JsonApiBuilder builder) {
+ builder.handleRequest(SetActivateFastFreqReserveRequest.METHOD, //
+ endpoint -> {
+ endpoint.setGuards(EdgeGuards.roleIsAtleast(Role.OWNER));
+ }, call -> {
+ this.handleSetActivateFastFreqReserveRequest(
+ SetActivateFastFreqReserveRequest.from(call.getRequest()));
+
+ return new GenericJsonrpcResponseSuccess(call.getRequest().getId(), JsonUtils.buildJsonObject() //
+ .addProperty("startTimestamp", "recieved") //
+ .build());
+ });
+ }
+
+ /**
+ * Updates the configuration for the component, setting control mode,
+ * references, and activation schedule.
+ *
+ * @throws OpenemsNamedException On Exception.
+ */
+ private void updateConfig() throws OpenemsNamedException {
+ this._setControlMode(this.config.controlMode());
+
+ if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "ess", //
+ this.config.ess_id())) {
+ return;
+ }
+ if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "meter", //
+ this.config.meter_id())) {
+ return;
+ }
+ try {
+ if (!this.config.activationScheduleJson().trim().isEmpty()) {
+ final var scheduleElement = JsonUtils.parse(this.config.activationScheduleJson());
+ final var scheduleArray = JsonUtils.getAsJsonArray(scheduleElement);
+ this.applySchedule(scheduleArray);
+ this._setScheduleParseFailed(false);
+ }
+ } catch (IllegalStateException | OpenemsNamedException e) {
+ this._setScheduleParseFailed(true);
+ this.logError(this.log, "Unable to parse Schedule: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Updates the configuration for activating fast frequency reserve based on the
+ * provided request.
+ *
+ * @param request The request containing the schedule information.
+ */
+ private void updateConfig(SetActivateFastFreqReserveRequest request) {
+ var scheduleString = SetActivateFastFreqReserveRequest.listToString(request.getSchedule());
+ OpenemsComponent.updateConfigurationProperty(this.cm, this.servicePid(), "activationScheduleJson",
+ scheduleString);
+ }
+
+ private void applySchedule(JsonArray jsonArray) throws OpenemsNamedException {
+ this.schedule = SetActivateFastFreqReserveRequest.ActivateFastFreqReserveSchedule.from(jsonArray);
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ super.deactivate();
+ }
+
+ @Override
+ public void run() throws OpenemsNamedException {
+ switch (this.config.controlMode()) {
+ case MANUAL_ON -> {
+ this.getConfigParams();
+ this.handleStatemachine();
+ }
+ case MANUAL_OFF -> {
+ // Do nothing
+ }
+ }
+ this._setControlMode(this.config.controlMode());
+ }
+
+ private void getConfigParams() {
+
+ this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.DISCHARGE_POWER_SET_POINT,
+ OBTAIN_DICHARGE_POWER);
+ final var channelValue = this.getDischargePowerSetPoint();
+
+ // Avoid calling
+ if (!channelValue.isDefined()) {
+ return;
+ }
+ this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.FREQUENCY_LIMIT, //
+ OBTAIN_FREQ_LIMIT);
+ this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.DURATION, //
+ OBTAIN_DURATION);
+ this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.START_TIMESTAMP, //
+ OBTAIN_STARTTIME_STAMP);
+ // TODO get it for the activation time and support time, But currently this
+ // tested for long activation time and long support time, other enums are for
+ // future
+ this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.ACTIVATION_TIME, //
+ OBTAIN_ACTIVATION_TIME);
+ this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.SUPPORT_DURATIN, //
+ OBTAIN_SUPPORT_DURATION);
+ }
+
+ /**
+ * Sets the value for the specified {@code FastFrequencyReserve.ChannelId} based
+ * on the provided {@code Function}, only if needed.
+ *
+ * @param channelId The channel to set the value for.
+ * @param obtainFunction A {@code Function} to retrieve the corresponding value
+ * based on the provided schedule entry.
+ */
+ private void setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId channelId,
+ Function obtainFunction) {
+ WriteChannel> channel = this.channel(channelId);
+ var setPointFromChannel = channel.value();
+ if (setPointFromChannel.isDefined()) {
+ return;
+ }
+
+ var currentTime = this.componentManager.getClock().withZone(ZoneId.systemDefault());
+ var now = Instant.now(currentTime).getEpochSecond();
+
+ for (var scheduleEntry : this.schedule) {
+ var endTime = scheduleEntry.startTimestamp() + scheduleEntry.duration();
+
+ // Configurable minutes, and convert into seconds
+ var preActivationTimeBeforeStartTime = this.config.preActivationTime() * 60;
+ if (now >= scheduleEntry.startTimestamp() - preActivationTimeBeforeStartTime && now <= endTime) {
+ channel.setNextValue(obtainFunction.apply(scheduleEntry));
+ return;
+ }
+ }
+ channel.setNextValue(null);
+ return;
+ }
+
+ private void handleStatemachine() {
+ if (this.checkGridMode()) {
+ return;
+ }
+
+ var state = this.stateMachine.getCurrentState();
+ this._setStateMachine(state);
+
+ if (!this.areChannelsDefined()) {
+ return;
+ }
+
+ var context = new Context(this, //
+ this.componentManager.getClock(), //
+ this.ess, //
+ this.meter, //
+ this.getStartTimestamp().get(), //
+ this.getDuration().get(), //
+ this.getDischargePowerSetPoint().get(), //
+ this.getFrequencyLimit().get(), //
+ // TODO if other version of FFR needed, need to test first with the Inverter
+ // Capabilities
+ this.getActivationTime(), //
+ this.getSupportDuration());
+
+ try {
+ this.stateMachine.run(context);
+ } catch (OpenemsNamedException e) {
+ this.logError(this.log, "StateMachine failed: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Checks the grid mode and returns a boolean value based on the grid mode
+ * state. If the grid mode is "ON_GRID" or "UNDEFINED," it returns false and
+ * logs a warning message when the grid mode is "UNDEFINED." If the grid mode is
+ * "OFF_GRID," it returns true.
+ *
+ * @return true if the grid mode is "OFF_GRID," false otherwise.
+ */
+ private boolean checkGridMode() {
+ return switch (this.ess.getGridMode()) {
+ case ON_GRID -> false;
+ case UNDEFINED -> {
+ this.logWarn(this.log, "Grid-Mode is [UNDEFINED]");
+ yield false;
+ }
+ case OFF_GRID -> true;
+ };
+ }
+
+ private boolean areChannelsDefined() {
+ return Stream.of(//
+ this.getDischargePowerSetPoint(), //
+ this.getFrequencyLimit(), //
+ this.getDuration(), //
+ this.getStartTimestamp())//
+ .allMatch(Value::isDefined);
+ }
+
+ private void handleSetActivateFastFreqReserveRequest(SetActivateFastFreqReserveRequest request)
+ throws OpenemsNamedException {
+ this.schedule = request.getSchedule();
+
+ // get current schedule
+ var currentSchedule = (String) this.getComponentContext()//
+ .getProperties()//
+ .get("activationScheduleJson");
+ var currentScheduleArray = JsonUtils.getAsJsonArray(JsonUtils.parse(currentSchedule).getAsJsonArray());
+ var currentScheduleList = ActivateFastFreqReserveSchedule.from(currentScheduleArray);
+
+ if (this.schedule.size() == currentScheduleArray.size() && currentScheduleList.equals(this.schedule)) {
+ return;
+ }
+ this.updateConfig(request);
+ }
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ActivationTime.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ActivationTime.java
new file mode 100644
index 00000000000..5d483a3ef44
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ActivationTime.java
@@ -0,0 +1,30 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve.enums;
+
+import io.openems.common.types.OptionsEnum;
+
+public enum ActivationTime implements OptionsEnum {
+ SHORT_ACTIVATION_RUN(700, "Short activation time run, 700 in milliseconds"), //
+ MEDIUM_ACTIVATION_RUN(1000, "Medium activation time run, 1000 in milliseconds"), //
+ LONG_ACTIVATION_RUN(1300, "Long activation time run, 1300 in milliseconds");
+
+ private final int value;
+ private final String name;
+
+ private ActivationTime(int value, String name) {
+ this.value = value;
+ this.name = name;
+ }
+
+ public int getValue() {
+ return this.value;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public OptionsEnum getUndefined() {
+ return LONG_ACTIVATION_RUN;
+ }
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ControlMode.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ControlMode.java
new file mode 100644
index 00000000000..e203ad4afdd
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ControlMode.java
@@ -0,0 +1,32 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve.enums;
+
+import io.openems.common.types.OptionsEnum;
+
+public enum ControlMode implements OptionsEnum {
+ MANUAL_ON(0, "Manual control for the ON signal, FFR is swtiched on"), //
+ MANUAL_OFF(1, "Manual control for the OFF signal, FFR is swtiched off") //
+ ; //
+
+ private final int value;
+ private final String name;
+
+ private ControlMode(int value, String name) {
+ this.value = value;
+ this.name = name;
+ }
+
+ @Override
+ public int getValue() {
+ return this.value;
+ }
+
+ @Override
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public OptionsEnum getUndefined() {
+ return MANUAL_OFF;
+ }
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/SupportDuration.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/SupportDuration.java
new file mode 100644
index 00000000000..040806aa262
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/SupportDuration.java
@@ -0,0 +1,32 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve.enums;
+
+import io.openems.common.types.OptionsEnum;
+
+public enum SupportDuration implements OptionsEnum {
+ SHORT_SUPPORT_DURATION(5, "long support duration 5 seconds"),
+ LONG_SUPPORT_DURATION(30, "long support duration 30 seconds");
+
+ private final int value;
+ private final String name;
+
+ private SupportDuration(int value, String name) {
+ this.value = value;
+ this.name = name;
+ }
+
+ @Override
+ public int getValue() {
+ return this.value;
+ }
+
+ @Override
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public OptionsEnum getUndefined() {
+ return LONG_SUPPORT_DURATION;
+ }
+
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/jsonrpc/SetActivateFastFreqReserveRequest.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/jsonrpc/SetActivateFastFreqReserveRequest.java
new file mode 100644
index 00000000000..4c422a365bc
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/jsonrpc/SetActivateFastFreqReserveRequest.java
@@ -0,0 +1,228 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.jsonrpc.base.JsonrpcRequest;
+import io.openems.common.utils.JsonUtils;
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime;
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration;
+
+/**
+ * Represents a JSON-RPC Request for 'setActivateFastFreqReserve'.
+ *
+ *
+ {
+ "jsonrpc": "2.0",
+ "id": "UUID",
+ "method": "setActivateFastFreqReserve",
+ "params": {
+ "schedule": [{
+ "startTimestamp": 1542464697,
+ "duration": 900,
+ "dischargeActivePowerSetPoint": 92000,
+ "frequencyLimit": 49500
+ "activationRunTime": "LONG_ACTIVATION_RUN",
+ "supportDuration": "LONG_SUPPORT_DURATION"
+ }]
+ }
+ }
+ *
+ */
+public class SetActivateFastFreqReserveRequest extends JsonrpcRequest {
+
+ /**
+ * Create {@link SetActivateFastFreqReserveRequest} from a template
+ * {@link JsonrpcRequest}.
+ *
+ * @param request the template {@link JsonrpcRequest}
+ * @return the {@link SetActivateFastFreqReserveRequest}
+ * @throws OpenemsNamedException on parse error
+ */
+ public static SetActivateFastFreqReserveRequest from(JsonrpcRequest request) throws OpenemsNamedException {
+ final var params = request.getParams();
+ final var edgeId = JsonUtils.getAsString(params, "id");
+ final var scheduleArray = JsonUtils.getAsJsonArray(params, "schedule");
+ final var schedule = ActivateFastFreqReserveSchedule.from(scheduleArray);
+ return new SetActivateFastFreqReserveRequest(request, edgeId, schedule);
+ }
+
+ public static final String METHOD = "setActivateFastFreqReserve";
+
+ private final String edgeId;
+ private final List schedule;
+
+ public SetActivateFastFreqReserveRequest(String edgeId) {
+ this(edgeId, new ArrayList<>());
+ }
+
+ private SetActivateFastFreqReserveRequest(String edgeId, List schedule) {
+ super(SetActivateFastFreqReserveRequest.METHOD);
+ this.edgeId = edgeId;
+ this.schedule = schedule;
+ }
+
+ private SetActivateFastFreqReserveRequest(JsonrpcRequest request, String edgeId,
+ List schedule) {
+ super(request, SetActivateFastFreqReserveRequest.METHOD);
+ this.edgeId = edgeId;
+ this.schedule = schedule;
+ }
+
+ /**
+ * Adds a new schedule entry for activating Fast Frequency Reserve.
+ *
+ * @param scheduleEntry The schedule entry to be added.
+ */
+ public void addScheduleEntry(ActivateFastFreqReserveSchedule scheduleEntry) {
+ this.schedule.add(scheduleEntry);
+ }
+
+ @Override
+ public JsonObject getParams() {
+ var schedule = new JsonArray();
+ for (var se : this.schedule) {
+ schedule.add(se.toJson());
+ }
+ return JsonUtils.buildJsonObject() //
+ .addProperty("id", this.getEdgeId()) //
+ .add("schedule", schedule) //
+ .build();
+ }
+
+ /**
+ * Gets the Edge-ID.
+ *
+ * @return Edge-ID
+ */
+ public String getEdgeId() {
+ return this.edgeId;
+ }
+
+ public List getSchedule() {
+ return this.schedule;
+ }
+
+ /**
+ * Converts a list of ActivateFastFreqReserveSchedule objects to a formatted
+ * string.
+ *
+ * @param scheduleList The list of ActivateFastFreqReserveSchedule objects to
+ * convert.
+ * @return A string representation of the schedule list.
+ * @see ActivateFastFreqReserveSchedule#toString()
+ */
+ public static String listToString(List scheduleList) {
+ return "["//
+ + scheduleList.stream()//
+ .map(ActivateFastFreqReserveSchedule::toString)//
+ .collect(Collectors.joining(", "))//
+ + "]";
+ }
+
+ public record ActivateFastFreqReserveSchedule(long startTimestamp, int duration, int dischargePowerSetPoint,
+ int frequencyLimit, ActivationTime activationRunTime, SupportDuration supportDuration) {
+
+ /**
+ * Builds a list of ActivateFastFreqReserveSchedule from a JsonArray.
+ *
+ * @param jsonArray JsonArray
+ * @return list of {@link ActivateFastFreqReserveSchedule}
+ * @throws OpenemsNamedException on error
+ */
+ public static List from(JsonArray jsonArray) throws OpenemsNamedException {
+ List schedule = new ArrayList<>();
+ for (var jsonElement : jsonArray) {
+ var newSchedule = new ActivateFastFreqReserveSchedule(
+ JsonUtils.getAsLong(jsonElement, "startTimestamp"), //
+ JsonUtils.getAsInt(jsonElement, "duration"),
+ JsonUtils.getAsInt(jsonElement, "dischargePowerSetPoint"),
+ JsonUtils.getAsInt(jsonElement, "frequencyLimit"),
+ JsonUtils.getAsEnum(ActivationTime.class, jsonElement, "activationRunTime"),
+ JsonUtils.getAsEnum(SupportDuration.class, jsonElement, "supportDuration"));
+
+ // Check for overlap with existing schedules before adding
+ if (!overlapsExistingSchedule(schedule, newSchedule)) {
+ schedule.add(newSchedule);
+ }
+ }
+
+ schedule.sort(Comparator.comparing(ActivateFastFreqReserveSchedule::startTimestamp));
+ return schedule;
+ }
+
+ /**
+ * Checks whether a new schedule overlaps with existing schedules or is an exact
+ * duplicate.
+ *
+ * @param schedule List of existing schedules to compare against
+ * @param newSchedule The new schedule to check for overlap or duplication
+ * @return {@code true} if the new schedule overlaps with existing schedules or
+ * is an exact duplicate, {@code false} otherwise
+ */
+ private static boolean overlapsExistingSchedule(List schedule,
+ ActivateFastFreqReserveSchedule newSchedule) {
+ for (ActivateFastFreqReserveSchedule existingSchedule : schedule) {
+ if (newSchedule.equals(existingSchedule)) {
+ // duplicate found
+ return true;
+ }
+ // Check for overlap
+ if (newSchedule.startTimestamp < (existingSchedule.startTimestamp + existingSchedule.duration)
+ && (newSchedule.startTimestamp + newSchedule.duration) > existingSchedule.startTimestamp) {
+ return true;
+ }
+ }
+ // No overlap or exact duplicate found
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ var that = (ActivateFastFreqReserveSchedule) o;
+ return this.startTimestamp == that.startTimestamp //
+ && this.duration == that.duration //
+ && this.dischargePowerSetPoint == that.dischargePowerSetPoint //
+ && this.frequencyLimit == that.frequencyLimit //
+ && this.activationRunTime.equals(that.activationRunTime) //
+ && this.supportDuration.equals(that.supportDuration);
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "{\"startTimestamp\":%d, \"duration\":%d, \"dischargePowerSetPoint\":%d, \"frequencyLimit\":%d, \"activationRunTime\":\"%s\", \"supportDuration\":\"%s\"}",
+ this.startTimestamp, this.duration, this.dischargePowerSetPoint, this.frequencyLimit,
+ this.activationRunTime, this.supportDuration);
+ }
+
+ /**
+ * Converts this ActivateFastFreqReserveSchedule object to a JsonObject.
+ *
+ * @return A JsonObject representing this schedule, where each field is mapped
+ * to a corresponding property with its value.
+ */
+ public JsonObject toJson() {
+ return JsonUtils.buildJsonObject() //
+ .addProperty("startTimestamp", this.startTimestamp()) //
+ .addProperty("duration", this.duration()) //
+ .addProperty("dischargeActivePowerSetPoint", this.dischargePowerSetPoint()) //
+ .addProperty("frequencyLimit", this.frequencyLimit()) //
+ .addProperty("activationRunTime", this.activationRunTime()) //
+ .addProperty("supportDuration", this.supportDuration()) //
+ .build();
+ }
+ }
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/ActivationTimeHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/ActivationTimeHandler.java
new file mode 100644
index 00000000000..57af0c89ba9
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/ActivationTimeHandler.java
@@ -0,0 +1,106 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine;
+
+import java.time.Duration;
+import java.time.Instant;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.exceptions.OpenemsException;
+import io.openems.common.utils.EnumUtils;
+import io.openems.edge.common.statemachine.StateHandler;
+import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State;
+
+public class ActivationTimeHandler extends StateHandler {
+
+ private static final int ZERO_WATT_POWER = 0; // [0 W]
+
+ protected Instant dipDetectedStartTime;
+ protected ActivationTimeState activationTimeState;
+
+ private static enum SubState {
+ INSIDE_TIME_FRAME, //
+ HANDLE_WAITING_FREQ_DIP, //
+ HANDLE_FREQ_DIP, //
+ FINISH_ACTIVATION
+ }
+
+ protected static record ActivationTimeState(SubState subState, Instant lastChange) {
+ }
+
+ @Override
+ protected void onEntry(Context context) throws OpenemsNamedException {
+ context.ess.setActivePowerEquals(ZERO_WATT_POWER);
+ this.activationTimeState = new ActivationTimeState(SubState.INSIDE_TIME_FRAME, Instant.now(context.clock));
+ }
+
+ @Override
+ protected State runAndGetNextState(Context context) throws OpenemsNamedException {
+ final var nextSubState = this.getNextSubState(context);
+ if (nextSubState != this.activationTimeState.subState) {
+ this.activationTimeState = new ActivationTimeState(nextSubState, Instant.now(context.clock));
+ }
+ if (nextSubState == SubState.FINISH_ACTIVATION) {
+ return State.SUPPORT_DURATION;
+ }
+
+ return State.ACTIVATION_TIME;
+ }
+
+ private SubState getNextSubState(Context context) throws OpenemsNamedException {
+ return switch (this.activationTimeState.subState) {
+ case INSIDE_TIME_FRAME ->
+ this.isInsideTimeFrame(context) ? SubState.FINISH_ACTIVATION : SubState.HANDLE_WAITING_FREQ_DIP;
+ case HANDLE_WAITING_FREQ_DIP -> {
+ if (this.isFrequencyDipped(context)) {
+ context.ess.setActivePowerEquals(context.dischargePower);
+ var time = Instant.now(context.clock);
+ this.clockActivationTime(context, time);
+ this.dipDetectedStartTime = time;
+ yield SubState.HANDLE_FREQ_DIP;
+ }
+ context.ess.setActivePowerEquals(ZERO_WATT_POWER);
+ yield SubState.HANDLE_WAITING_FREQ_DIP;
+ }
+ case HANDLE_FREQ_DIP -> {
+ context.ess.setActivePowerEquals(context.dischargePower);
+ var activationExpirationTime = Duration.between(this.dipDetectedStartTime, Instant.now(context.clock))//
+ .toMillis(); //
+ if (activationExpirationTime >= context.activationRunTime.getValue()) {
+ yield SubState.FINISH_ACTIVATION;
+ }
+ yield SubState.HANDLE_FREQ_DIP;
+ }
+ case FINISH_ACTIVATION -> SubState.FINISH_ACTIVATION;
+ };
+ }
+
+ /**
+ * Clocks the activation time and sets it in the context.
+ *
+ * @param context the context.
+ * @param time time in instant
+ */
+ private void clockActivationTime(Context context, Instant time) {
+ context.setCycleStart(time);
+ }
+
+ private boolean isFrequencyDipped(Context context) throws OpenemsException {
+ var meterFrequency = context.meter.getFrequency();
+ if (!meterFrequency.isDefined()) {
+ throw new OpenemsException("meter has no frequency channel defined.");
+ }
+ return (meterFrequency.get() < context.freqLimit);
+ }
+
+ private boolean isInsideTimeFrame(Context context) {
+ final var now = Instant.now(context.clock).getEpochSecond();
+ final var startTimestamp = context.startTimestamp;
+ final var duration = context.duration;
+ return now >= startTimestamp + duration;
+ }
+
+ @Override
+ protected String debugLog() {
+ return State.ACTIVATION_TIME.asCamelCase() + "-"
+ + EnumUtils.nameAsCamelCase(this.activationTimeState.subState());
+ }
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/BufferedTimeBeforeRecoveryHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/BufferedTimeBeforeRecoveryHandler.java
new file mode 100644
index 00000000000..23d9ae25b3d
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/BufferedTimeBeforeRecoveryHandler.java
@@ -0,0 +1,103 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine;
+
+import java.time.Duration;
+import java.time.Instant;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.utils.EnumUtils;
+import io.openems.edge.common.statemachine.StateHandler;
+import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State;
+import io.openems.edge.ess.api.ManagedSymmetricEss;
+import io.openems.edge.ess.power.api.Phase;
+import io.openems.edge.ess.power.api.Pwr;
+
+public class BufferedTimeBeforeRecoveryHandler extends StateHandler {
+
+ private static final int ZERO_WATT_POWER = 0;
+ private static final int BUFFER_DURATION_THRESHOLD_SECONDS = 15; // [s]
+ private static final int RECOVERY_DURATION_THRESHOLD_MINUTES = 4; // [minute]
+ private static final double EIGHTEENX_PERCENT_OF_MAX_POWER = 0.18; // [%]
+
+ protected Instant bufferedTimeBeforeRecoveryStartTime = Instant.MIN;
+
+ protected static record BufferedTimeBeforeRecoveryState(SubState subState, Instant lastChange) {
+ }
+
+ protected BufferedTimeBeforeRecoveryState bufferedTimeBeforeRecoveryState;
+
+ private static enum SubState {
+ HOLD_BUFFERED_TIME_BEFORE_RECOVERY, //
+ BUFFERED_TIME_RECOVERY, //
+ FINISH_BUFFERED_TIME_BEFORE_RECOVERY
+ }
+
+ @Override
+ protected void onEntry(Context context) throws OpenemsNamedException {
+ context.ess.setActivePowerEquals(ZERO_WATT_POWER);
+ final var now = Instant.now(context.clock);
+ this.bufferedTimeBeforeRecoveryStartTime = now;
+ this.bufferedTimeBeforeRecoveryState = new BufferedTimeBeforeRecoveryState(
+ SubState.HOLD_BUFFERED_TIME_BEFORE_RECOVERY, now);
+
+ }
+
+ @Override
+ protected State runAndGetNextState(Context context) throws OpenemsNamedException {
+ final var nextSubState = this.getNextSubState(context);
+
+ if (nextSubState != this.bufferedTimeBeforeRecoveryState.subState) {
+ this.bufferedTimeBeforeRecoveryState = new BufferedTimeBeforeRecoveryState(nextSubState,
+ Instant.now(context.clock));
+ }
+
+ if (nextSubState == SubState.FINISH_BUFFERED_TIME_BEFORE_RECOVERY) {
+ return State.RECOVERY_TIME;
+ }
+
+ return State.BUFFERED_TIME_BEFORE_RECOVERY;
+ }
+
+ private SubState getNextSubState(Context context) throws OpenemsNamedException {
+ return switch (this.bufferedTimeBeforeRecoveryState.subState) {
+ case HOLD_BUFFERED_TIME_BEFORE_RECOVERY -> {
+ context.ess.setActivePowerEquals(ZERO_WATT_POWER);
+ var bufferedDurationExpiration = this.calculateBufferedDurationExpiration(context);
+ if (bufferedDurationExpiration >= BUFFER_DURATION_THRESHOLD_SECONDS) {
+ yield SubState.BUFFERED_TIME_RECOVERY;
+ }
+ yield SubState.HOLD_BUFFERED_TIME_BEFORE_RECOVERY;
+ }
+ case BUFFERED_TIME_RECOVERY -> {
+ var minPowerEss = this.calculateMinPower(context.ess);
+ context.ess.setActivePowerEquals(minPowerEss);
+ var bufferedRecoveryExpiration = this.calculateBufferedRecoveryExpiration(context);
+ if (bufferedRecoveryExpiration >= RECOVERY_DURATION_THRESHOLD_MINUTES) {
+ yield SubState.FINISH_BUFFERED_TIME_BEFORE_RECOVERY;
+ }
+ yield SubState.BUFFERED_TIME_RECOVERY;
+ }
+ case FINISH_BUFFERED_TIME_BEFORE_RECOVERY -> SubState.FINISH_BUFFERED_TIME_BEFORE_RECOVERY;
+ };
+ }
+
+ private long calculateBufferedDurationExpiration(Context context) {
+ return Duration.between(this.bufferedTimeBeforeRecoveryStartTime, Instant.now(context.clock))//
+ .toSeconds();
+ }
+
+ private long calculateBufferedRecoveryExpiration(Context context) {
+ return Duration//
+ .between(context.getCycleStart(), Instant.now(context.clock))//
+ .toMinutes();
+ }
+
+ private int calculateMinPower(ManagedSymmetricEss ess) {
+ return (int) (ess.getPower().getMinPower(ess, Phase.ALL, Pwr.ACTIVE) * EIGHTEENX_PERCENT_OF_MAX_POWER);
+ }
+
+ @Override
+ protected String debugLog() {
+ return State.BUFFERED_TIME_BEFORE_RECOVERY.asCamelCase() + "-"
+ + EnumUtils.nameAsCamelCase(this.bufferedTimeBeforeRecoveryState.subState());
+ }
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/Context.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/Context.java
new file mode 100644
index 00000000000..cbae7c9ec6e
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/Context.java
@@ -0,0 +1,64 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+
+import io.openems.edge.common.statemachine.AbstractContext;
+import io.openems.edge.controller.ess.fastfrequencyreserve.ControllerFastFrequencyReserve;
+import io.openems.edge.controller.ess.fastfrequencyreserve.ControllerFastFrequencyReserveImpl;
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime;
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration;
+import io.openems.edge.ess.api.ManagedSymmetricEss;
+import io.openems.edge.meter.api.ElectricityMeter;
+
+public class Context extends AbstractContext {
+
+ protected final Clock clock;
+ protected final int dischargePower;
+ protected final long startTimestamp;
+ protected final int duration;
+ protected final int freqLimit;
+ protected final ActivationTime activationRunTime;
+ protected final SupportDuration supportDuration;
+ protected final ControllerFastFrequencyReserve parentController;
+ protected final ManagedSymmetricEss ess;
+ protected final ElectricityMeter meter;
+
+ protected static Instant _cycleStart;
+
+ public Context(ControllerFastFrequencyReserve fastFrequencyReserve, //
+ Clock clock, //
+ ManagedSymmetricEss ess, //
+ ElectricityMeter meter, //
+ long startTimestamp, //
+ int duration, //
+ int dischargePower, //
+ int freqLimit, //
+ ActivationTime activationRunTime, //
+ SupportDuration supportDuration) {
+ this.clock = clock;
+ this.parentController = fastFrequencyReserve;
+ this.startTimestamp = startTimestamp;
+ this.duration = duration;
+ this.dischargePower = dischargePower;
+ this.freqLimit = freqLimit;
+ this.ess = ess;
+ this.meter = meter;
+ this.activationRunTime = activationRunTime;
+ this.supportDuration = supportDuration;
+ }
+
+ public Instant getCycleStart() {
+ return _cycleStart;
+ }
+
+ public void setCycleStart(Instant cycleStart) {
+ LocalDateTime lastTriggered = LocalDateTime.ofInstant(cycleStart, ZoneId.systemDefault());
+ String formattedDateTime = lastTriggered.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+ this.parentController.setLastTriggeredTime(formattedDateTime);
+ _cycleStart = cycleStart;
+ }
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/DeactivationTimeHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/DeactivationTimeHandler.java
new file mode 100644
index 00000000000..aa01cbddbbb
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/DeactivationTimeHandler.java
@@ -0,0 +1,81 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine;
+
+import java.time.Duration;
+import java.time.Instant;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.utils.EnumUtils;
+import io.openems.edge.common.statemachine.StateHandler;
+import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State;
+
+public class DeactivationTimeHandler extends StateHandler {
+
+ private static final int ZERO_WATT_POWER = 0; //[0 W]
+ protected Instant deactivationStateStartTime;
+
+ private static enum SubState {
+ HOLD_DEACTIVATION, //
+ FINISH_DEACTIVATION_DURATION
+ }
+
+ protected static record DeactivationTimeState(SubState subState, Instant lastChange) {
+ }
+
+ protected DeactivationTimeState deactivationTimeState;
+
+ @Override
+ protected void onEntry(Context context) throws OpenemsNamedException {
+ context.ess.setActivePowerEquals(ZERO_WATT_POWER);
+ this.deactivationStateStartTime = Instant.now(context.clock);
+ this.deactivationTimeState = new DeactivationTimeState(SubState.HOLD_DEACTIVATION, Instant.now(context.clock));
+ }
+
+ @Override
+ protected State runAndGetNextState(Context context) throws OpenemsNamedException {
+ var nextSubState = this.getNextSubState(context);
+ if (nextSubState != this.deactivationTimeState.subState) {
+ this.deactivationTimeState = new DeactivationTimeState(nextSubState, Instant.now(context.clock));
+ }
+ if (nextSubState == SubState.FINISH_DEACTIVATION_DURATION) {
+ return State.BUFFERED_TIME_BEFORE_RECOVERY;
+ }
+ return State.DEACTIVATION_TIME;
+ }
+
+ private SubState getNextSubState(Context context) throws OpenemsNamedException {
+ return switch (this.deactivationTimeState.subState) {
+
+ case HOLD_DEACTIVATION -> {
+ context.ess.setActivePowerEquals(ZERO_WATT_POWER);
+ var deactivationDurationExpiration = this.calculateDeactivationDurationExpiration(context);
+ if (deactivationDurationExpiration >= context.activationRunTime.getValue()) {
+ yield SubState.FINISH_DEACTIVATION_DURATION;
+ }
+ yield SubState.HOLD_DEACTIVATION;
+ }
+ case FINISH_DEACTIVATION_DURATION -> SubState.FINISH_DEACTIVATION_DURATION;
+ };
+ }
+
+ /**
+ * Calculates the expiration duration for the deactivation state. The expiration
+ * duration is the time elapsed between the deactivation state start time and
+ * the current time, measured in milliseconds.
+ *
+ * @param context the Context
+ * @return The expiration duration in milliseconds.
+ */
+ private long calculateDeactivationDurationExpiration(Context context) {
+ return Duration.between(//
+ this.deactivationStateStartTime, //
+ Instant.now(context.clock))//
+ .toMillis();
+ }
+
+ @Override
+ protected String debugLog() {
+ return State.DEACTIVATION_TIME.asCamelCase() + "-"
+ + EnumUtils.nameAsCamelCase(this.deactivationTimeState.subState());
+ }
+
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/PreActivationHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/PreActivationHandler.java
new file mode 100644
index 00000000000..86b9d62083e
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/PreActivationHandler.java
@@ -0,0 +1,53 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine;
+
+import java.time.ZonedDateTime;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.edge.common.statemachine.StateHandler;
+import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State;
+import io.openems.edge.ess.api.ManagedSymmetricEss;
+import io.openems.edge.ess.power.api.Phase;
+import io.openems.edge.ess.power.api.Pwr;
+
+public class PreActivationHandler extends StateHandler {
+
+ private static final double EIGHTEENX_PERCENT_OF_MAX_POWER = 0.18; // [%]
+
+ @Override
+ protected State runAndGetNextState(Context context) throws OpenemsNamedException {
+ var ess = context.ess;
+ int minPowerEss = this.calculateMinPower(ess);
+
+ if (this.isActivationTime(context)) {
+ return State.ACTIVATION_TIME;
+ } else {
+ ess.setActivePowerEquals(minPowerEss);
+ return State.PRE_ACTIVATION_STATE;
+ }
+ }
+
+ /**
+ * Calculates 18% of the minimum power of the given ess.
+ *
+ * @param ess The managed symmetric ess.
+ * @return 18% of the minimum power of the ess.
+ */
+ private int calculateMinPower(ManagedSymmetricEss ess) {
+ return (int) (ess.getPower().getMinPower(ess, Phase.ALL, Pwr.ACTIVE) * EIGHTEENX_PERCENT_OF_MAX_POWER);
+ }
+
+ /**
+ * Checks if the current time, as adjusted by the component manager's clock, is
+ * within the activation time window.
+ *
+ *
+ * @param context the context
+ * @return {@code true} if the current time is within the activation time
+ * window, {@code false} otherwise.
+ */
+ private boolean isActivationTime(Context context) {
+ var currentEpochSecond = ZonedDateTime.now(context.clock).toEpochSecond();
+ return currentEpochSecond >= context.startTimestamp
+ && currentEpochSecond <= context.startTimestamp + context.duration;
+ }
+}
\ No newline at end of file
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/RecoveryTimeHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/RecoveryTimeHandler.java
new file mode 100644
index 00000000000..d073c55f9cf
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/RecoveryTimeHandler.java
@@ -0,0 +1,29 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZonedDateTime;
+
+import io.openems.edge.common.statemachine.StateHandler;
+import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State;
+
+public class RecoveryTimeHandler extends StateHandler {
+
+ public static final int RECOVERY_DURATION_SECONDS = 15 * 60;
+
+ @Override
+ protected State runAndGetNextState(Context context) {
+ if (this.isItWithinDuration(context)) {
+ return State.ACTIVATION_TIME;
+ }
+ return State.RECOVERY_TIME;
+ }
+
+ private boolean isItWithinDuration(Context context) {
+ var now = Instant.now(context.clock).getEpochSecond();
+ var expiration = Duration//
+ .between(context.getCycleStart(), ZonedDateTime.now(context.clock))//
+ .toSeconds();
+ return expiration > RECOVERY_DURATION_SECONDS || now >= context.startTimestamp + context.duration;
+ }
+}
\ No newline at end of file
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/StateMachine.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/StateMachine.java
new file mode 100644
index 00000000000..c044760a26f
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/StateMachine.java
@@ -0,0 +1,62 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine;
+
+import io.openems.common.types.OptionsEnum;
+import io.openems.edge.common.statemachine.AbstractStateMachine;
+import io.openems.edge.common.statemachine.StateHandler;
+
+public class StateMachine extends AbstractStateMachine {
+
+ public enum State implements io.openems.edge.common.statemachine.State, OptionsEnum {
+ UNDEFINED(-1), //
+ PRE_ACTIVATION_STATE(10), //
+ ACTIVATION_TIME(20), //
+ SUPPORT_DURATION(30), //
+ DEACTIVATION_TIME(40), //
+ BUFFERED_TIME_BEFORE_RECOVERY(50), //
+ RECOVERY_TIME(60);//
+
+ private final int value;
+
+ private State(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public int getValue() {
+ return this.value;
+ }
+
+ @Override
+ public OptionsEnum getUndefined() {
+ return UNDEFINED;
+ }
+
+ @Override
+ public String getName() {
+ return this.name();
+ }
+
+ @Override
+ public State[] getStates() {
+ return State.values();
+ }
+
+ }
+
+ public StateMachine(State initialState) {
+ super(initialState);
+ }
+
+ @Override
+ public StateHandler getStateHandler(State state) {
+ return switch (state) {
+ case ACTIVATION_TIME -> new ActivationTimeHandler();
+ case BUFFERED_TIME_BEFORE_RECOVERY -> new BufferedTimeBeforeRecoveryHandler();
+ case DEACTIVATION_TIME -> new DeactivationTimeHandler();
+ case PRE_ACTIVATION_STATE -> new PreActivationHandler();
+ case RECOVERY_TIME -> new RecoveryTimeHandler();
+ case SUPPORT_DURATION -> new SupportDurationTimeHandler();
+ case UNDEFINED -> new UndefinedHandler();
+ };
+ }
+}
\ No newline at end of file
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/SupportDurationTimeHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/SupportDurationTimeHandler.java
new file mode 100644
index 00000000000..a5d5131c8e0
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/SupportDurationTimeHandler.java
@@ -0,0 +1,64 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.edge.common.statemachine.StateHandler;
+import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State;
+
+public class SupportDurationTimeHandler extends StateHandler {
+
+ protected LocalDateTime supportDurationStartTime;
+
+ private static enum SubState {
+ HOLD_SUPPORT, //
+ FINISH_SUPPORT_DURATION
+ }
+
+ protected static record SupportDurationTimeState(SubState subState, Instant lastChange) {
+ }
+
+ protected SupportDurationTimeState supportDurationTimeState;
+
+ @Override
+ protected void onEntry(Context context) throws OpenemsNamedException {
+ context.ess.setActivePowerEquals(context.dischargePower);
+ this.supportDurationStartTime = LocalDateTime.now(context.clock);
+ this.supportDurationTimeState = new SupportDurationTimeState(SubState.HOLD_SUPPORT, Instant.now(context.clock));
+ }
+
+ @Override
+ protected State runAndGetNextState(Context context) throws OpenemsNamedException {
+ final var nextSubState = this.getNextSubState(context);
+ if (nextSubState != this.supportDurationTimeState.subState) {
+ this.supportDurationTimeState = new SupportDurationTimeState(nextSubState, Instant.now(context.clock));
+ }
+ if (nextSubState == SubState.FINISH_SUPPORT_DURATION) {
+ return State.DEACTIVATION_TIME;
+ }
+ return State.SUPPORT_DURATION;
+ }
+
+ private SubState getNextSubState(Context context) throws OpenemsNamedException {
+ return switch (this.supportDurationTimeState.subState) {
+ case HOLD_SUPPORT -> {
+ context.ess.setActivePowerEquals(context.dischargePower);
+ var supportDurationExpiration = this.calculateSupportDurationExpiration(context);
+ if (supportDurationExpiration >= context.supportDuration.getValue()) {
+ yield SubState.FINISH_SUPPORT_DURATION;
+ }
+ yield SubState.HOLD_SUPPORT;
+ }
+ case FINISH_SUPPORT_DURATION -> SubState.FINISH_SUPPORT_DURATION;
+ };
+ }
+
+ private long calculateSupportDurationExpiration(Context context) {
+ return Duration.between(//
+ this.supportDurationStartTime, //
+ LocalDateTime.now(context.clock))//
+ .getSeconds();
+ }
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/UndefinedHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/UndefinedHandler.java
new file mode 100644
index 00000000000..513a119914a
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/UndefinedHandler.java
@@ -0,0 +1,40 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine;
+
+import java.time.ZonedDateTime;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.edge.common.statemachine.StateHandler;
+import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State;
+
+public class UndefinedHandler extends StateHandler {
+
+ public static final int FIFTEEN_MINUTES_IN_SECONDS = 15 * 60; // 15 minutes in seconds
+
+ @Override
+ protected State runAndGetNextState(Context context) throws OpenemsNamedException {
+ if (this.isPreActivationTime(context)) {
+ return State.PRE_ACTIVATION_STATE;
+ } else {
+ return State.UNDEFINED;
+ }
+ }
+
+ /**
+ * Checks if the current time, as adjusted by the component manager's clock, is
+ * within the pre-activation time window. pre-activation time window is 15
+ * minutes before the start time.
+ *
+ * @param context the context
+ * @return {@code true} if the current time is within the activation time
+ * window, {@code false} otherwise.
+ */
+ private boolean isPreActivationTime(Context context) {
+ var currentDateTime = ZonedDateTime.now(context.clock);
+ var currentEpochSecond = currentDateTime.toEpochSecond();
+ if (currentEpochSecond >= context.startTimestamp - FIFTEEN_MINUTES_IN_SECONDS
+ && currentEpochSecond <= context.startTimestamp + context.duration) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/.gitignore b/io.openems.edge.controller.ess.fastfrequencyreserve/test/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest.java
new file mode 100644
index 00000000000..e704c552b50
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest.java
@@ -0,0 +1,284 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve;
+
+import static io.openems.edge.common.test.TestUtils.createDummyClock;
+import static io.openems.edge.controller.ess.fastfrequencyreserve.ControllerFastFrequencyReserve.ChannelId.STATE_MACHINE;
+import static io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime.LONG_ACTIVATION_RUN;
+import static io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration.LONG_SUPPORT_DURATION;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.FREQUENCY;
+import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.HOURS;
+import static java.time.temporal.ChronoUnit.MILLIS;
+import static java.time.temporal.ChronoUnit.MINUTES;
+import static java.time.temporal.ChronoUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.util.List;
+
+import org.junit.Test;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.test.TimeLeapClock;
+import io.openems.common.utils.JsonUtils;
+import io.openems.edge.common.sum.GridMode;
+import io.openems.edge.common.test.AbstractComponentTest.TestCase;
+import io.openems.edge.common.test.DummyComponentManager;
+import io.openems.edge.common.test.DummyConfigurationAdmin;
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode;
+import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest.ActivateFastFreqReserveSchedule;
+import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State;
+import io.openems.edge.controller.test.ControllerTest;
+import io.openems.edge.ess.test.DummyManagedSymmetricEss;
+import io.openems.edge.meter.test.DummyElectricityMeter;
+
+public class ControllerFastFrequencyReserveImplTest {
+
+ @Test
+ public void testFfrController() throws Exception {
+ final var clock = new TimeLeapClock(Instant.parse("2023-07-13T08:45:00.00Z"), ZoneOffset.UTC);
+ new ControllerTest(new ControllerFastFrequencyReserveImpl()) //
+ .addReference("componentManager", new DummyComponentManager(clock)) //
+ .addReference("cm", new DummyConfigurationAdmin()) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0") //
+ .withGridMode(GridMode.ON_GRID)//
+ .withMaxApparentPower(92000)//
+ .withAllowedChargePower(-92000)//
+ .withAllowedDischargePower(92000)) //
+ .addReference("meter", new DummyElectricityMeter("meter0")) //
+ .activate(MyConfig.create() //
+ .setEssId("ess0") //
+ .setId("ctrl0") //
+ .setMeterId("meter0") //
+ .setMode(ControlMode.MANUAL_ON) //
+ .setPreActivationTime(15)//
+ .setactivationScheduleJson(JsonUtils.buildJsonArray() //
+ .add(JsonUtils.buildJsonObject() //
+ .addProperty("startTimestamp", 1689242400) // Thu Jul 13 2023 10:00:00 GMT+0000
+ .addProperty("duration", 86400) //
+ .addProperty("dischargePowerSetPoint", 92000) //
+ .addProperty("frequencyLimit", 49500) //
+ .addProperty("activationRunTime", LONG_ACTIVATION_RUN) //
+ .addProperty("supportDuration", LONG_SUPPORT_DURATION) //
+ .build())
+ .add(JsonUtils.buildJsonObject() //
+ .addProperty("startTimestamp", 1689331500) // Fri Jul 14 2023 10:45:00 GMT+0000
+ .addProperty("duration", 86400) //
+ .addProperty("dischargePowerSetPoint", 92000) //
+ .addProperty("frequencyLimit", 49500) //
+ .addProperty("activationRunTime", LONG_ACTIVATION_RUN) //
+ .addProperty("supportDuration", LONG_SUPPORT_DURATION) //
+ .build())
+ .build()//
+ .toString())//
+ .build())
+ .next(new TestCase("1") //
+ .input("meter0", FREQUENCY, 50000)//
+ .output(STATE_MACHINE, State.UNDEFINED) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, null))//
+ .next(new TestCase("2") //
+ .input("meter0", FREQUENCY, 50000)//
+ .output(STATE_MACHINE, State.UNDEFINED) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, null))//
+ .next(new TestCase("3") //
+ .timeleap(clock, 1, HOURS) //
+ .input("meter0", FREQUENCY, 50000))//
+ .next(new TestCase("4"))//
+ .next(new TestCase("5") //
+ .output(STATE_MACHINE, State.UNDEFINED))
+ .next(new TestCase("6") //
+ .timeleap(clock, 10, MINUTES)//
+ .output(STATE_MACHINE, State.PRE_ACTIVATION_STATE))
+ .next(new TestCase("7") //
+ .timeleap(clock, 10, MINUTES))
+ .next(new TestCase("8") //
+ .output(STATE_MACHINE, State.ACTIVATION_TIME)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0))
+ .next(new TestCase("9") //
+ .input("meter0", FREQUENCY, 50000)//
+ .output(STATE_MACHINE, State.ACTIVATION_TIME)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0))
+ .next(new TestCase("10") //
+ .input("meter0", FREQUENCY, 49400)//
+ .output(STATE_MACHINE, State.ACTIVATION_TIME)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000))
+ .next(new TestCase("11") //
+ .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) //
+ .next(new TestCase("12") //
+ .output(STATE_MACHINE, State.SUPPORT_DURATION)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) //
+ .next(new TestCase("13") //
+ .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), SECONDS) //
+ .output(STATE_MACHINE, State.SUPPORT_DURATION))
+ .next(new TestCase("14") //
+ .output(STATE_MACHINE, State.DEACTIVATION_TIME) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0))
+ .next(new TestCase("15") //
+ .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) //
+ .next(new TestCase("16") //
+ .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0))
+ .next(new TestCase("17") //
+ .timeleap(clock, 16, SECONDS) //
+ .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY))
+ .next(new TestCase("18") //
+ .timeleap(clock, 12, MINUTES)) //
+ .next(new TestCase("19") //
+ .output(STATE_MACHINE, State.RECOVERY_TIME) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -16560))
+ .next(new TestCase("20") //
+ .output(STATE_MACHINE, State.RECOVERY_TIME))
+ .next(new TestCase("21") //
+ .output(STATE_MACHINE, State.RECOVERY_TIME))
+ .next(new TestCase("22") //
+ .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), MINUTES))
+ .next(new TestCase("23") //
+ .input("meter0", FREQUENCY, 50000)//
+ .output(STATE_MACHINE, State.ACTIVATION_TIME))
+ .next(new TestCase("24") //
+ .timeleap(clock, 1, DAYS)) //
+ .next(new TestCase("25") //
+ .output(STATE_MACHINE, State.ACTIVATION_TIME))
+ .next(new TestCase("26") //
+ .timeleap(clock, 4, HOURS)) //
+ .next(new TestCase("27") //
+ .output(STATE_MACHINE, State.ACTIVATION_TIME))
+ .next(new TestCase("28")//
+ .input("meter0", FREQUENCY, 49400))
+ .next(new TestCase("29") //
+ .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) //
+ .next(new TestCase("30") //
+ .output(STATE_MACHINE, State.SUPPORT_DURATION)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) //
+ .next(new TestCase("31") //
+ .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) //
+ .next(new TestCase("32") //
+ .output(STATE_MACHINE, State.SUPPORT_DURATION)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) //
+ .next(new TestCase("33") //
+ .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), SECONDS) //
+ .output(STATE_MACHINE, State.SUPPORT_DURATION))
+ .next(new TestCase("34") //
+ .output(STATE_MACHINE, State.DEACTIVATION_TIME).output("ess0", SET_ACTIVE_POWER_EQUALS, 0))
+ .next(new TestCase("35") //
+ .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) //
+ .next(new TestCase("36") //
+ .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0))
+ .next(new TestCase("37") //
+ .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY))
+ .next(new TestCase("38") //
+ .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY))
+ .next(new TestCase("39") //
+ .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), MINUTES))
+ .next(new TestCase("40") //
+ .input("meter0", FREQUENCY, 50000)//
+ .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY))
+ .next(new TestCase("41") //
+ .input("meter0", FREQUENCY, 50000)//
+ .output(STATE_MACHINE, State.RECOVERY_TIME))
+ .next(new TestCase("42") //
+ .timeleap(clock, 1, DAYS)) //
+ .deactivate();
+ }
+
+ @Test
+ public void testInvalidJsonSchedule() throws Exception {
+ final var clock = createDummyClock();
+ new ControllerTest(new ControllerFastFrequencyReserveImpl()) //
+ .addReference("componentManager", new DummyComponentManager(clock)) //
+ .addReference("cm", new DummyConfigurationAdmin()) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0") //
+ .withGridMode(GridMode.ON_GRID)//
+ .withMaxApparentPower(92000)//
+ .withAllowedChargePower(-92000)//
+ .withAllowedDischargePower(92000)) //
+ .addReference("meter", new DummyElectricityMeter("meter0")) //
+ .activate(MyConfig.create() //
+ .setEssId("ess0") //
+ .setId("ctrl0") //
+ .setMeterId("meter0") //
+ .setMode(ControlMode.MANUAL_ON) //
+ .setPreActivationTime(15)//
+ .setactivationScheduleJson("foo")//
+ .build()) //
+ .deactivate();
+ }
+
+ @Test
+ public void testInvalidJsonSchedule1() throws Exception {
+ final var clock = createDummyClock();
+ new ControllerTest(new ControllerFastFrequencyReserveImpl()) //
+ .addReference("componentManager", new DummyComponentManager(clock)) //
+ .addReference("cm", new DummyConfigurationAdmin()) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0") //
+ .withGridMode(GridMode.ON_GRID)//
+ .withMaxApparentPower(92000)//
+ .withAllowedChargePower(-92000)//
+ .withAllowedDischargePower(92000)) //
+ .addReference("meter", new DummyElectricityMeter("meter0")) //
+ .activate(MyConfig.create() //
+ .setEssId("ess0") //
+ .setId("ctrl0") //
+ .setMeterId("meter0") //
+ .setMode(ControlMode.MANUAL_ON) //
+ .setPreActivationTime(15)//
+ .setactivationScheduleJson("[foo]")//
+ .build()) //
+ .deactivate();
+ }
+
+ public static final String MY_JSON = """
+ [
+ {
+ "startTimestamp":"1701738000",
+ "duration":"10800",
+ "dischargePowerSetPoint":"92000",
+ "frequencyLimit":"50000",
+ "activationRunTime":"LONG_ACTIVATION_RUN",
+ "supportDuration":"LONG_SUPPORT_DURATION"
+ },
+ {
+ "startTimestamp":"1701738000",
+ "duration":"10800",
+ "dischargePowerSetPoint":"92000",
+ "frequencyLimit":"50000",
+ "activationRunTime":"LONG_ACTIVATION_RUN",
+ "supportDuration":"LONG_SUPPORT_DURATION"
+ },
+ {
+ "startTimestamp":"1701752400",
+ "duration":"10800",
+ "dischargePowerSetPoint":"92000",
+ "frequencyLimit":"50000",
+ "activationRunTime":"LONG_ACTIVATION_RUN",
+ "supportDuration":"LONG_SUPPORT_DURATION"
+ },
+ {
+ "startTimestamp":"1701777600",
+ "duration":"10800",
+ "dischargePowerSetPoint":"92000",
+ "frequencyLimit":"50000",
+ "activationRunTime":"LONG_ACTIVATION_RUN",
+ "supportDuration":"LONG_SUPPORT_DURATION"
+ }
+ ]
+ """;
+
+ @Test
+ public void testFromMethod() throws OpenemsNamedException {
+ var scheduleArray = JsonUtils.parseToJsonArray(MY_JSON);
+ try {
+ List scheduleList = ActivateFastFreqReserveSchedule.from(scheduleArray);
+
+ assertEquals(3, scheduleList.size());
+ assertTrue(scheduleList.get(0).startTimestamp() <= scheduleList.get(1).startTimestamp());
+ assertTrue(scheduleList.get(1).startTimestamp() <= scheduleList.get(2).startTimestamp());
+
+ } catch (OpenemsNamedException e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest2.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest2.java
new file mode 100644
index 00000000000..d1bbb7c9da1
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest2.java
@@ -0,0 +1,182 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve;
+
+import static io.openems.common.utils.JsonUtils.buildJsonArray;
+import static io.openems.common.utils.JsonUtils.buildJsonObject;
+import static io.openems.edge.controller.ess.fastfrequencyreserve.ControllerFastFrequencyReserve.ChannelId.STATE_MACHINE;
+import static io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime.LONG_ACTIVATION_RUN;
+import static io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration.LONG_SUPPORT_DURATION;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.FREQUENCY;
+import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.HOURS;
+import static java.time.temporal.ChronoUnit.MILLIS;
+import static java.time.temporal.ChronoUnit.MINUTES;
+import static java.time.temporal.ChronoUnit.SECONDS;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+
+import org.junit.Test;
+
+import io.openems.common.test.TimeLeapClock;
+import io.openems.edge.common.sum.GridMode;
+import io.openems.edge.common.test.AbstractComponentTest.TestCase;
+import io.openems.edge.common.test.DummyComponentManager;
+import io.openems.edge.common.test.DummyConfigurationAdmin;
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode;
+import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State;
+import io.openems.edge.controller.test.ControllerTest;
+import io.openems.edge.ess.test.DummyManagedSymmetricEss;
+import io.openems.edge.meter.test.DummyElectricityMeter;
+
+public class ControllerFastFrequencyReserveImplTest2 {
+
+ @Test
+ public void test1() throws Exception {
+ final var clock = new TimeLeapClock(Instant.parse("2023-07-13T08:45:00.00Z"), ZoneOffset.UTC);
+ new ControllerTest(new ControllerFastFrequencyReserveImpl()) //
+ .addReference("componentManager", new DummyComponentManager(clock)) //
+ .addReference("cm", new DummyConfigurationAdmin()) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0") //
+ .withGridMode(GridMode.ON_GRID)//
+ .withMaxApparentPower(92000)//
+ .withAllowedChargePower(-92000)//
+ .withAllowedDischargePower(92000)) //
+ .addReference("meter", new DummyElectricityMeter("meter0")) //
+ .activate(MyConfig.create() //
+ .setEssId("ess0") //
+ .setId("ctrl0") //
+ .setMeterId("meter0") //
+ .setMode(ControlMode.MANUAL_ON) //
+ .setPreActivationTime(15)//
+ .setactivationScheduleJson(buildJsonArray() //
+ .add(buildJsonObject() //
+ .addProperty("startTimestamp", 1689242400) // Thu Jul 13 2023 10:00:00 GMT+0000
+ .addProperty("duration", 86400) //
+ .addProperty("dischargePowerSetPoint", 92000) //
+ .addProperty("frequencyLimit", 49500) //
+ .addProperty("activationRunTime", LONG_ACTIVATION_RUN) //
+ .addProperty("supportDuration", LONG_SUPPORT_DURATION) //
+ .build())
+ .add(buildJsonObject() //
+ .addProperty("startTimestamp", 1689331500) // Fri Jul 14 2023 10:45:00 GMT+0000
+ .addProperty("duration", 86400) //
+ .addProperty("dischargePowerSetPoint", 92000) //
+ .addProperty("frequencyLimit", 49500) //
+ .addProperty("activationRunTime", LONG_ACTIVATION_RUN) //
+ .addProperty("supportDuration", LONG_SUPPORT_DURATION) //
+ .build())
+ .build()//
+ .toString())//
+ .build())
+ .next(new TestCase("1") //
+ .input("meter0", FREQUENCY, 50000)//
+ .output(ControllerFastFrequencyReserve.ChannelId.STATE_MACHINE, State.UNDEFINED) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, null))//
+ .next(new TestCase("2") //
+ .input("meter0", FREQUENCY, 50000)//
+ .output(STATE_MACHINE, State.UNDEFINED) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, null))//
+ .next(new TestCase("3") //
+ .timeleap(clock, 1, HOURS) //
+ .input("meter0", FREQUENCY, 50000))//
+ .next(new TestCase("4"))//
+ .next(new TestCase("5") //
+ .output(STATE_MACHINE, State.UNDEFINED))
+ .next(new TestCase("6") //
+ .timeleap(clock, 10, MINUTES)//
+ .output(STATE_MACHINE, State.PRE_ACTIVATION_STATE))
+ .next(new TestCase("7") //
+ .timeleap(clock, 10, MINUTES))
+ .next(new TestCase("8") //
+ .output(STATE_MACHINE, State.ACTIVATION_TIME)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0))
+ .next(new TestCase("9") //
+ .input("meter0", FREQUENCY, 50000)//
+ .output(STATE_MACHINE, State.ACTIVATION_TIME)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0))
+ .next(new TestCase("10") //
+ .input("meter0", FREQUENCY, 49400)//
+ .output(STATE_MACHINE, State.ACTIVATION_TIME)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000))
+ .next(new TestCase("11") //
+ .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) //
+ .next(new TestCase("12") //
+ .output(STATE_MACHINE, State.SUPPORT_DURATION)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) //
+ .next(new TestCase("13") //
+ .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), SECONDS) //
+ .output(STATE_MACHINE, State.SUPPORT_DURATION))
+ .next(new TestCase("14") //
+ .output(STATE_MACHINE, State.DEACTIVATION_TIME).output("ess0", SET_ACTIVE_POWER_EQUALS, 0))
+ .next(new TestCase("15") //
+ .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) //
+ .next(new TestCase("16") //
+ .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0))
+ .next(new TestCase("17") //
+ .timeleap(clock, 16, SECONDS) //
+ .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY))
+ .next(new TestCase("18") //
+ .timeleap(clock, 12, MINUTES)) //
+ .next(new TestCase("19") //
+ .output(STATE_MACHINE, State.RECOVERY_TIME) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -16560))
+ .next(new TestCase("20") //
+ .output(STATE_MACHINE, State.RECOVERY_TIME))
+ .next(new TestCase("21") //
+ .output(STATE_MACHINE, State.RECOVERY_TIME))
+ .next(new TestCase("22") //
+ .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), MINUTES))
+ .next(new TestCase("23") //
+ .input("meter0", FREQUENCY, 50000)//
+ .output(STATE_MACHINE, State.ACTIVATION_TIME))
+ .next(new TestCase("24") //
+ .timeleap(clock, 1, DAYS)) //
+ .next(new TestCase("25") //
+ .output(STATE_MACHINE, State.ACTIVATION_TIME))
+ .next(new TestCase("26") //
+ .timeleap(clock, 4, HOURS)) //
+ .next(new TestCase("27") //
+ .output(STATE_MACHINE, State.ACTIVATION_TIME))
+ .next(new TestCase("28")//
+ .input("meter0", FREQUENCY, 49400))
+ .next(new TestCase("29") //
+ .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) //
+ .next(new TestCase("30") //
+ .output(STATE_MACHINE, State.SUPPORT_DURATION)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) //
+ .next(new TestCase("31") //
+ .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) //
+ .next(new TestCase("32") //
+ .output(STATE_MACHINE, State.SUPPORT_DURATION)//
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) //
+ .next(new TestCase("33") //
+ .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), SECONDS) //
+ .output(STATE_MACHINE, State.SUPPORT_DURATION))
+ .next(new TestCase("34") //
+ .output(STATE_MACHINE, State.DEACTIVATION_TIME) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0))
+ .next(new TestCase("35") //
+ .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) //
+ .next(new TestCase("36") //
+ .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0))
+ .next(new TestCase("37") //
+ .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY))
+ .next(new TestCase("38") //
+ .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY))
+ .next(new TestCase("39") //
+ .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), MINUTES))
+ .next(new TestCase("40") //
+ .input("meter0", FREQUENCY, 50000)//
+ .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY))
+ .next(new TestCase("41") //
+ .input("meter0", FREQUENCY, 50000)//
+ .output(STATE_MACHINE, State.RECOVERY_TIME))
+ .next(new TestCase("42") //
+ .timeleap(clock, 1, DAYS)) //
+ .deactivate();
+ }
+
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/JsonRpcTest.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/JsonRpcTest.java
new file mode 100644
index 00000000000..c0db3e9a28d
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/JsonRpcTest.java
@@ -0,0 +1,68 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve;
+
+import java.net.URI;
+//import org.junit.Test;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime;
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration;
+import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest;
+import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest.ActivateFastFreqReserveSchedule;
+
+/**
+ * This Test demonstrates the usage of the OpenEMS Backend-to-Backend API
+ * interface. To start the tests make sure to start OpenEMS Backend and activate
+ * the B2bWebsocket component via Apache Felix. Afterwards uncomment the "@Test"
+ * annotations below and execute the Tests.
+ */
+public class JsonRpcTest {
+
+ private static final String URI = "ws://localhost:8076";
+ private static final String USERNAME = "user";
+ private static final String PASSWORD = "password";
+
+ private static TestClient prepareTestClient() throws URISyntaxException, InterruptedException {
+ Map httpHeaders = new HashMap<>();
+ var auth = new String(
+ Base64.getEncoder().encode((JsonRpcTest.USERNAME + ":" + JsonRpcTest.PASSWORD).getBytes()),
+ StandardCharsets.UTF_8);
+ httpHeaders.put("Authorization", "Basic " + auth);
+ var client = new TestClient(new URI(JsonRpcTest.URI), httpHeaders);
+ client.startBlocking();
+ return client;
+ }
+
+ /**
+ * Tests the activation of Fast Frequency Reserve schedule.
+ *
+ * @throws URISyntaxException String could not be parsed as a URI reference.
+ * @throws InterruptedException interrupted exception.
+ */
+ // @Test
+ public void testActivateFastFreqReserveSchedule() throws URISyntaxException, InterruptedException {
+ var client = JsonRpcTest.prepareTestClient();
+
+ var request = new SetActivateFastFreqReserveRequest("edge0");
+ var now = System.currentTimeMillis() / 1000;
+ ActivateFastFreqReserveSchedule newEntry = new ActivateFastFreqReserveSchedule(//
+ now, //
+ 1000, //
+ 92000, //
+ 50000, //
+ ActivationTime.LONG_ACTIVATION_RUN, //
+ SupportDuration.LONG_SUPPORT_DURATION);
+ request.addScheduleEntry(newEntry);
+
+ try {
+ var responseFuture = client.sendRequest(request);
+ System.out.println(responseFuture.get().toString());
+ } catch (Exception e) {
+ System.out.println(e.getMessage());
+ }
+ }
+
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyConfig.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyConfig.java
new file mode 100644
index 00000000000..948d3e0fb3a
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyConfig.java
@@ -0,0 +1,105 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve;
+
+import io.openems.common.test.AbstractComponentConfig;
+import io.openems.common.utils.ConfigUtils;
+import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode;
+
+@SuppressWarnings("all")
+public class MyConfig extends AbstractComponentConfig implements Config {
+
+ protected static class Builder {
+ private String id;
+ private String essId;
+ private String meterId;
+ private ControlMode mode;
+ private String activationScheduleJson;
+ private int preActivationTime;
+
+ public Builder setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder setEssId(String essId) {
+ this.essId = essId;
+ return this;
+ }
+
+ public Builder setMeterId(String meterId) {
+ this.meterId = meterId;
+ return this;
+ }
+
+ public Builder setMode(ControlMode mode) {
+ this.mode = mode;
+ return this;
+ }
+
+ public Builder setactivationScheduleJson(String schedule) {
+ this.activationScheduleJson = schedule;
+ return this;
+ }
+
+ public Builder setPreActivationTime(int preActivationTime) {
+ this.preActivationTime = preActivationTime;
+ return this;
+ }
+
+ public MyConfig build() {
+ return new MyConfig(this);
+ }
+
+ }
+
+ /**
+ * Create a Config builder.
+ *
+ * @return a {@link Builder}
+ */
+ public static Builder create() {
+ return new Builder();
+ }
+
+ private final Builder builder;
+
+ private MyConfig(Builder builder) {
+ super(Config.class, builder.id);
+ this.builder = builder;
+ }
+
+ @Override
+ public String ess_id() {
+ return this.builder.essId;
+ }
+
+ @Override
+ public String meter_id() {
+ return this.builder.meterId;
+ }
+
+ @Override
+ public String ess_target() {
+ return ConfigUtils.generateReferenceTargetFilter(this.id(), this.ess_id());
+ }
+
+ @Override
+ public String meter_target() {
+ return ConfigUtils.generateReferenceTargetFilter(this.id(), this.meter_id());
+ }
+
+ @Override
+ public ControlMode controlMode() {
+ return this.builder.mode;
+ }
+
+ @Override
+ public String activationScheduleJson() {
+ return this.builder.activationScheduleJson;
+ }
+
+ @Override
+ public int preActivationTime() {
+ return this.builder.preActivationTime;
+ }
+
+}
diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/TestClient.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/TestClient.java
new file mode 100644
index 00000000000..991887efda7
--- /dev/null
+++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/TestClient.java
@@ -0,0 +1,122 @@
+package io.openems.edge.controller.ess.fastfrequencyreserve;
+
+import java.net.URI;
+import java.util.Map;
+
+import org.java_websocket.WebSocket;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.openems.common.websocket.AbstractWebsocketClient;
+import io.openems.common.websocket.OnClose;
+import io.openems.common.websocket.OnError;
+import io.openems.common.websocket.OnNotification;
+import io.openems.common.websocket.OnOpen;
+import io.openems.common.websocket.OnRequest;
+import io.openems.common.websocket.WsData;
+
+public class TestClient extends AbstractWebsocketClient {
+
+ private final Logger log = LoggerFactory.getLogger(TestClient.class);
+
+ private OnOpen onOpen;
+ private OnRequest onRequest;
+ private OnNotification onNotification;
+ private OnError onError;
+ private OnClose onClose;
+
+ protected TestClient(URI serverUri, Map httpHeaders) {
+ super("JsonTest.Unittest", serverUri, httpHeaders);
+ this.onOpen = (ws, handshake) -> {
+ return null;
+ };
+ this.onRequest = (ws, request) -> {
+ this.log.info("OnRequest: " + request);
+ return null;
+ };
+ this.onNotification = (ws, notification) -> {
+ this.log.info("OnNotification: " + notification);
+ };
+ this.onError = (ws, ex) -> {
+ this.log.info("onError: " + ex.getMessage());
+ };
+ this.onClose = (ws, code, reason, remote) -> {
+ this.log.info("onClose: " + reason);
+ };
+ }
+
+ @Override
+ public OnOpen getOnOpen() {
+ return this.onOpen;
+ }
+
+ public void setOnOpen(OnOpen onOpen) {
+ this.onOpen = onOpen;
+ }
+
+ @Override
+ public OnRequest getOnRequest() {
+ return this.onRequest;
+ }
+
+ public void setOnRequest(OnRequest onRequest) {
+ this.onRequest = onRequest;
+ }
+
+ @Override
+ public OnError getOnError() {
+ return this.onError;
+ }
+
+ public void setOnError(OnError onError) {
+ this.onError = onError;
+ }
+
+ @Override
+ public OnClose getOnClose() {
+ return this.onClose;
+ }
+
+ public void setOnClose(OnClose onClose) {
+ this.onClose = onClose;
+ }
+
+ @Override
+ protected OnNotification getOnNotification() {
+ return this.onNotification;
+ }
+
+ public void setOnNotification(OnNotification onNotification) {
+ this.onNotification = onNotification;
+ }
+
+ @Override
+ protected WsData createWsData(WebSocket ws) {
+ return new WsData(ws) {
+ @Override
+ public String toString() {
+ return "TestClient.WsData []";
+ }
+ };
+ }
+
+ @Override
+ protected void logInfo(Logger log, String message) {
+ log.info(message);
+ }
+
+ @Override
+ protected void logWarn(Logger log, String message) {
+ log.warn(message);
+ }
+
+ @Override
+ protected void logError(Logger log, String message) {
+ log.error(message);
+ }
+
+ @Override
+ protected void execute(Runnable command) {
+ command.run();
+ }
+}
diff --git a/io.openems.edge.controller.ess.fixactivepower/bnd.bnd b/io.openems.edge.controller.ess.fixactivepower/bnd.bnd
index 9b0e7d57001..38c5412d005 100644
--- a/io.openems.edge.controller.ess.fixactivepower/bnd.bnd
+++ b/io.openems.edge.controller.ess.fixactivepower/bnd.bnd
@@ -8,8 +8,9 @@ Bundle-Version: 1.0.0.${tstamp}
io.openems.common,\
io.openems.edge.common,\
io.openems.edge.controller.api,\
+ io.openems.edge.energy.api,\
io.openems.edge.ess.api,\
io.openems.edge.timedata.api,\
-
+
-testpath: \
${testpath}
diff --git a/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImpl.java b/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImpl.java
index ad3f5905e0e..2fe3a64a3e7 100644
--- a/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImpl.java
+++ b/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImpl.java
@@ -1,5 +1,9 @@
package io.openems.edge.controller.ess.fixactivepower;
+import static io.openems.edge.energy.api.EnergyUtils.toEnergy;
+
+import java.util.function.Supplier;
+
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
@@ -17,10 +21,13 @@
import io.openems.edge.common.component.AbstractOpenemsComponent;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.controller.api.Controller;
+import io.openems.edge.energy.api.EnergySchedulable;
+import io.openems.edge.energy.api.EnergyScheduleHandler;
import io.openems.edge.ess.api.HybridEss;
import io.openems.edge.ess.api.ManagedSymmetricEss;
import io.openems.edge.ess.api.PowerConstraint;
import io.openems.edge.ess.power.api.Pwr;
+import io.openems.edge.ess.power.api.Relationship;
import io.openems.edge.timedata.api.Timedata;
import io.openems.edge.timedata.api.TimedataProvider;
import io.openems.edge.timedata.api.utils.CalculateActiveTime;
@@ -32,10 +39,11 @@
configurationPolicy = ConfigurationPolicy.REQUIRE //
)
public class ControllerEssFixActivePowerImpl extends AbstractOpenemsComponent
- implements ControllerEssFixActivePower, Controller, OpenemsComponent, TimedataProvider {
+ implements ControllerEssFixActivePower, EnergySchedulable, Controller, OpenemsComponent, TimedataProvider {
private final CalculateActiveTime calculateCumulatedActiveTime = new CalculateActiveTime(this,
ControllerEssFixActivePower.ChannelId.CUMULATED_ACTIVE_TIME);
+ private final EnergyScheduleHandler energyScheduleHandler;
@Reference
private ConfigurationAdmin cm;
@@ -54,6 +62,13 @@ public ControllerEssFixActivePowerImpl() {
Controller.ChannelId.values(), //
ControllerEssFixActivePower.ChannelId.values() //
);
+ this.energyScheduleHandler = buildEnergyScheduleHandler(() -> new EshContext(//
+ this.config.mode(), //
+ toEnergy(switch (this.config.phase()) {
+ case ALL -> this.config.power();
+ case L1, L2, L3 -> this.config.power() * 3;
+ }), //
+ this.config.relationship()));
}
@Activate
@@ -70,6 +85,7 @@ private void modified(ComponentContext context, Config config) {
if (this.applyConfig(context, config)) {
return;
}
+ this.energyScheduleHandler.triggerReschedule();
}
private boolean applyConfig(ComponentContext context, Config config) {
@@ -137,4 +153,40 @@ protected static Integer getAcPower(ManagedSymmetricEss ess, HybridEssMode hybri
public Timedata getTimedata() {
return this.timedata;
}
+
+ /**
+ * Builds the {@link EnergyScheduleHandler}.
+ *
+ *
+ * This is public so that it can be used by the EnergyScheduler integration
+ * test.
+ *
+ * @param context a supplier for the configured {@link EshContext}
+ * @return a {@link EnergyScheduleHandler}
+ */
+ public static EnergyScheduleHandler buildEnergyScheduleHandler(Supplier context) {
+ return EnergyScheduleHandler.of(//
+ simContext -> context.get(), //
+ (simContext, period, energyFlow, ctrlContext) -> {
+ switch (ctrlContext.mode) {
+ case MANUAL_ON:
+ switch (ctrlContext.relationship) {
+ case EQUALS -> energyFlow.setEss(ctrlContext.energy);
+ case GREATER_OR_EQUALS -> energyFlow.setEssMaxCharge(-ctrlContext.energy);
+ case LESS_OR_EQUALS -> energyFlow.setEssMaxDischarge(ctrlContext.energy);
+ }
+ break;
+ case MANUAL_OFF:
+ break;
+ }
+ });
+ }
+
+ public static record EshContext(Mode mode, int energy, Relationship relationship) {
+ }
+
+ @Override
+ public EnergyScheduleHandler getEnergyScheduleHandler() {
+ return this.energyScheduleHandler;
+ }
}
\ No newline at end of file
diff --git a/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/package-info.java b/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/package-info.java
new file mode 100644
index 00000000000..aab27855be8
--- /dev/null
+++ b/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/package-info.java
@@ -0,0 +1,3 @@
+@org.osgi.annotation.versioning.Version("1.0.0")
+@org.osgi.annotation.bundle.Export
+package io.openems.edge.controller.ess.fixactivepower;
diff --git a/io.openems.edge.controller.ess.fixactivepower/test/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImplTest.java b/io.openems.edge.controller.ess.fixactivepower/test/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImplTest.java
index d26fa505e44..ebcde754b1d 100644
--- a/io.openems.edge.controller.ess.fixactivepower/test/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImplTest.java
+++ b/io.openems.edge.controller.ess.fixactivepower/test/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImplTest.java
@@ -1,5 +1,6 @@
package io.openems.edge.controller.ess.fixactivepower;
+import static io.openems.edge.controller.ess.fixactivepower.ControllerEssFixActivePowerImpl.getAcPower;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
@@ -14,45 +15,44 @@
public class ControllerEssFixActivePowerImplTest {
- private static final String CTRL_ID = "ctrl0";
- private static final String ESS_ID = "ess0";
-
@Test
public void testOn() throws OpenemsException, Exception {
- final var ess = new DummyManagedAsymmetricEss(ESS_ID);
+ final var ess = new DummyManagedAsymmetricEss("ess0");
new ControllerTest(new ControllerEssFixActivePowerImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("ess", ess) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setMode(Mode.MANUAL_ON) //
.setHybridEssMode(HybridEssMode.TARGET_DC) //
.setPower(1234) //
.setPhase(Phase.ALL) //
.setRelationship(Relationship.EQUALS) //
- .build()); //
+ .build()) //
+ .deactivate();
}
@Test
public void testOff() throws OpenemsException, Exception {
new ControllerTest(new ControllerEssFixActivePowerImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("ess", new DummyManagedAsymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedAsymmetricEss("ess0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setMode(Mode.MANUAL_OFF) //
.setHybridEssMode(HybridEssMode.TARGET_DC) //
.setPower(1234) //
.setPhase(Phase.ALL) //
.setRelationship(Relationship.EQUALS) //
- .build()); //
+ .build()) //
+ .deactivate();
}
@Test
public void testGetAcPower() throws OpenemsException, Exception {
- var hybridEss = new DummyHybridEss(ESS_ID) //
+ var hybridEss = new DummyHybridEss("ess0") //
.withActivePower(7000) //
.withMaxApparentPower(10000) //
.withAllowedChargePower(-5000) //
@@ -60,9 +60,9 @@ public void testGetAcPower() throws OpenemsException, Exception {
.withDcDischargePower(3000); //
assertEquals(Integer.valueOf(5000), //
- ControllerEssFixActivePowerImpl.getAcPower(hybridEss, HybridEssMode.TARGET_AC, 5000));
+ getAcPower(hybridEss, HybridEssMode.TARGET_AC, 5000));
assertEquals(Integer.valueOf(9000), //
- ControllerEssFixActivePowerImpl.getAcPower(hybridEss, HybridEssMode.TARGET_DC, 5000));
+ getAcPower(hybridEss, HybridEssMode.TARGET_DC, 5000));
}
}
diff --git a/io.openems.edge.controller.ess.fixstateofcharge/test/io/openems/edge/controller/ess/fixstateofcharge/ControllerEssFixStateOfChargeImplTest.java b/io.openems.edge.controller.ess.fixstateofcharge/test/io/openems/edge/controller/ess/fixstateofcharge/ControllerEssFixStateOfChargeImplTest.java
index 48553f06491..70d0b636428 100644
--- a/io.openems.edge.controller.ess.fixstateofcharge/test/io/openems/edge/controller/ess/fixstateofcharge/ControllerEssFixStateOfChargeImplTest.java
+++ b/io.openems.edge.controller.ess.fixstateofcharge/test/io/openems/edge/controller/ess/fixstateofcharge/ControllerEssFixStateOfChargeImplTest.java
@@ -1,10 +1,23 @@
package io.openems.edge.controller.ess.fixstateofcharge;
+import static io.openems.edge.controller.ess.fixstateofcharge.api.AbstractFixStateOfCharge.DEFAULT_POWER_FACTOR;
+import static io.openems.edge.controller.ess.fixstateofcharge.api.EndCondition.CAPACITY_CHANGED;
+import static io.openems.edge.controller.ess.fixstateofcharge.api.FixStateOfCharge.ChannelId.DEBUG_SET_ACTIVE_POWER;
+import static io.openems.edge.controller.ess.fixstateofcharge.api.FixStateOfCharge.ChannelId.DEBUG_SET_ACTIVE_POWER_RAW;
+import static io.openems.edge.controller.ess.fixstateofcharge.api.FixStateOfCharge.ChannelId.STATE_MACHINE;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.CAPACITY;
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.MAX_APPARENT_POWER;
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC;
+import static java.lang.Math.min;
+import static java.lang.Math.round;
+import static java.time.temporal.ChronoUnit.HOURS;
+import static java.time.temporal.ChronoUnit.MINUTES;
+
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
-import java.time.temporal.ChronoUnit;
import org.junit.Test;
@@ -14,54 +27,30 @@
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.common.test.DummyConfigurationAdmin;
-import io.openems.edge.controller.ess.fixstateofcharge.api.AbstractFixStateOfCharge;
-import io.openems.edge.controller.ess.fixstateofcharge.api.EndCondition;
-import io.openems.edge.controller.ess.fixstateofcharge.statemachine.StateMachine;
+import io.openems.edge.controller.ess.fixstateofcharge.statemachine.StateMachine.State;
import io.openems.edge.controller.test.ControllerTest;
import io.openems.edge.ess.test.DummyManagedSymmetricEss;
import io.openems.edge.timedata.test.DummyTimedata;
public class ControllerEssFixStateOfChargeImplTest {
- // Ids
- private static final String CTRL_ID = "ctrlFixStateOfCharge0";
- private static final String ESS_ID = "ess0";
-
- // Components
- private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss(ESS_ID) //
+ private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss("ess0") //
.withMaxApparentPower(10_000);
-
- // Defaults
private static final String DEFAULT_TARGET_TIME = "2022-10-27T10:30:00+01:00";
-
- // Ess channels
- private static final ChannelAddress ESS_CAPACITY = new ChannelAddress(ESS_ID, "Capacity");
- private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc");
- private static final ChannelAddress ESS_MAX_APPARENT_POWER = new ChannelAddress(ESS_ID, "MaxApparentPower");
- private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID,
- "SetActivePowerEquals");
-
- // Controller channels
- private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine");
- private static final ChannelAddress DEBUG_SET_ACTIVE_POWER = new ChannelAddress(CTRL_ID, "DebugSetActivePower");
- private static final ChannelAddress DEBUG_SET_ACTIVE_POWER_RAW = new ChannelAddress(CTRL_ID,
- "DebugSetActivePowerRaw");
- private static final ChannelAddress CTRL_ESS_CAPACITY = new ChannelAddress(CTRL_ID, "EssCapacity");
+ private static final ChannelAddress CTRL_ESS_CAPACITY = new ChannelAddress("ctrl0", "EssCapacity");
@Test
public void testNotRunning() throws Exception {
final var clock = new TimeLeapClock(Instant.parse("2022-01-01T08:00:00.00Z"), ZoneOffset.UTC);
- final var componentManager = new DummyComponentManager(clock);
-
new ControllerTest(new ControllerEssFixStateOfChargeImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("componentManager", componentManager) //
+ .addReference("componentManager", new DummyComponentManager(clock)) //
.addReference("timedata", new DummyTimedata("timedata0")) //
.addReference("sum", new DummySum()) //
.addReference("ess", ESS) //
.activate(FixStateOfChargeConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setRunning(false) //
.setTargetSoc(30) //
.setSpecifyTargetTime(true) //
@@ -69,29 +58,30 @@ public void testNotRunning() throws Exception {
.setSelfTermination(false) //
.setTerminationBuffer(720) //
.setConditionalTermination(false) //
- .setEndCondition(EndCondition.CAPACITY_CHANGED) //
+ .setEndCondition(CAPACITY_CHANGED) //
.build())
.next(new TestCase()) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, null) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, null) //
.output(DEBUG_SET_ACTIVE_POWER, null) //
.output(DEBUG_SET_ACTIVE_POWER_RAW, null) //
- .output(STATE_MACHINE, StateMachine.State.IDLE) //
- );
+ .output(STATE_MACHINE, State.IDLE)) //
+ .deactivate();
}
@Test
public void testAllStates() throws Exception {
+ final var clock = new TimeLeapClock(Instant.parse("2023-01-01T08:00:00.00Z"), ZoneOffset.UTC);
new ControllerTest(new ControllerEssFixStateOfChargeImpl()) //
- .addReference("componentManager", new DummyComponentManager()) //
+ .addReference("componentManager", new DummyComponentManager(clock)) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
.addReference("timedata", new DummyTimedata("timedata0")) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(FixStateOfChargeConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setRunning(true) //
.setTargetSoc(30) //
.setSpecifyTargetTime(true) //
@@ -99,39 +89,39 @@ public void testAllStates() throws Exception {
.setSelfTermination(false) //
.setTerminationBuffer(720) //
.setConditionalTermination(true) //
- .setEndCondition(EndCondition.CAPACITY_CHANGED) //
+ .setEndCondition(CAPACITY_CHANGED) //
.build())
.next(new TestCase() //
- .input(ESS_SOC, 22) //
- .input(ESS_MAX_APPARENT_POWER, 10000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 22) //
+ .input("ess0", MAX_APPARENT_POWER, 10000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 22) //
- .input(ESS_MAX_APPARENT_POWER, 10000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 22) //
+ .input("ess0", MAX_APPARENT_POWER, 10000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 21) //
- .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) //
+ .input("ess0", SOC, 21) //
+ .output(STATE_MACHINE, State.NOT_STARTED)) //
.next(new TestCase() //
- .input(ESS_SOC, 20) //
- .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC)) //
+ .input("ess0", SOC, 20) //
+ .output(STATE_MACHINE, State.BELOW_TARGET_SOC)) //
.next(new TestCase() //
- .input(ESS_SOC, 25) //
- .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC)) //
+ .input("ess0", SOC, 25) //
+ .output(STATE_MACHINE, State.BELOW_TARGET_SOC)) //
.next(new TestCase() //
- .input(ESS_SOC, 30)) //
+ .input("ess0", SOC, 30)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) ///
- .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC)) //
+ .input("ess0", SOC, 30) ///
+ .output(STATE_MACHINE, State.AT_TARGET_SOC)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC)) //
- ;
+ .input("ess0", SOC, 30) //
+ .output(STATE_MACHINE, State.AT_TARGET_SOC)) //
+ .deactivate();
}
@Test
public void testCapacityCondition() throws Exception {
-
+ final var clock = new TimeLeapClock(Instant.parse("2023-01-01T08:00:00.00Z"), ZoneOffset.UTC);
var timedata = new DummyTimedata("timedata0");
var start = ZonedDateTime.of(2022, 05, 05, 0, 0, 0, 0, ZoneId.of("UTC"));
@@ -139,15 +129,15 @@ public void testCapacityCondition() throws Exception {
timedata.add(start.plusMinutes(60), CTRL_ESS_CAPACITY, 8_000);
timedata.add(start.plusMinutes(90), CTRL_ESS_CAPACITY, 8_000);
- var test = new ControllerTest(new ControllerEssFixStateOfChargeImpl()) //
- .addReference("componentManager", new DummyComponentManager()) //
+ new ControllerTest(new ControllerEssFixStateOfChargeImpl()) //
+ .addReference("componentManager", new DummyComponentManager(clock)) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("sum", new DummySum()) //
.addReference("timedata", timedata) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(FixStateOfChargeConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setRunning(true) //
.setTargetSoc(30) //
.setSpecifyTargetTime(true) //
@@ -155,70 +145,66 @@ public void testCapacityCondition() throws Exception {
.setSelfTermination(false) //
.setTerminationBuffer(720) //
.setConditionalTermination(true) //
- .setEndCondition(EndCondition.CAPACITY_CHANGED) //
+ .setEndCondition(CAPACITY_CHANGED) //
.build())
.next(new TestCase() //
- .input(ESS_SOC, 22) //
- .input(ESS_MAX_APPARENT_POWER, 10000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 22) //
+ .input("ess0", MAX_APPARENT_POWER, 10000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 22) //
- .input(ESS_MAX_APPARENT_POWER, 10000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 22) //
+ .input("ess0", MAX_APPARENT_POWER, 10000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 21) //
- .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) //
+ .input("ess0", SOC, 21) //
+ .output(STATE_MACHINE, State.NOT_STARTED)) //
.next(new TestCase() //
- .input(ESS_SOC, 20) //
- .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC)) //
+ .input("ess0", SOC, 20) //
+ .output(STATE_MACHINE, State.BELOW_TARGET_SOC)) //
.next(new TestCase() //
- .input(ESS_SOC, 25) //
+ .input("ess0", SOC, 25) //
.input(CTRL_ESS_CAPACITY, 8_000) //
- .input(ESS_CAPACITY, 8_000) //
+ .input("ess0", CAPACITY, 8_000) //
.output(CTRL_ESS_CAPACITY, 8_000) //
- .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC)) //
+ .output(STATE_MACHINE, State.BELOW_TARGET_SOC)) //
.next(new TestCase() //
- .input(ESS_CAPACITY, 8_000) //
- .input(ESS_SOC, 30) //
+ .input("ess0", CAPACITY, 8_000) //
+ .input("ess0", SOC, 30) //
.output(CTRL_ESS_CAPACITY, 8_000)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) ///
- .input(ESS_CAPACITY, 8_000) //
+ .input("ess0", SOC, 30) ///
+ .input("ess0", CAPACITY, 8_000) //
.output(CTRL_ESS_CAPACITY, 8_000) //
- .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC)) //
- ;
+ .output(STATE_MACHINE, State.AT_TARGET_SOC)) //
- // EMS restart
- test.next(new TestCase() //
- .input(ESS_CAPACITY, null) //
- .input(CTRL_ESS_CAPACITY, null) //
- .output(CTRL_ESS_CAPACITY, null)); //
+ .next(new TestCase("EMS restart") //
+ .input("ess0", CAPACITY, null) //
+ .input(CTRL_ESS_CAPACITY, null) //
+ .output(CTRL_ESS_CAPACITY, null)) //
- // New Ess.Capacity (Ctrl is taking the last one from timedata)
- test.next(new TestCase() //
- .input(ESS_CAPACITY, 10_000) //
- .input(CTRL_ESS_CAPACITY, null) //
- .output(CTRL_ESS_CAPACITY, 8_000)); //
+ .next(new TestCase("New Ess.Capacity (Ctrl is taking the last one from timedata)") //
+ .input("ess0", CAPACITY, 10_000) //
+ .input(CTRL_ESS_CAPACITY, null) //
+ .output(CTRL_ESS_CAPACITY, 8_000)) //
- test.next(new TestCase() //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
- ;
+ .next(new TestCase() //
+ .output(STATE_MACHINE, State.IDLE)) //
+
+ .deactivate();
}
@Test
public void testAboveLimit() throws Exception {
final var clock = new TimeLeapClock(Instant.parse("2022-01-01T08:00:00.00Z"), ZoneOffset.UTC);
- final var componentManager = new DummyComponentManager(clock);
-
new ControllerTest(new ControllerEssFixStateOfChargeImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("componentManager", componentManager) //
+ .addReference("componentManager", new DummyComponentManager(clock)) //
.addReference("sum", new DummySum()) //
.addReference("timedata", new DummyTimedata("timedata0")) //
.addReference("ess", ESS) //
.activate(FixStateOfChargeConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setRunning(true) //
.setTargetSoc(30) //
.setSpecifyTargetTime(false) //
@@ -226,44 +212,42 @@ public void testAboveLimit() throws Exception {
.setSelfTermination(false) //
.setTerminationBuffer(720) //
.setConditionalTermination(false) //
- .setEndCondition(EndCondition.CAPACITY_CHANGED) //
+ .setEndCondition(CAPACITY_CHANGED) //
.build())
.next(new TestCase() //
- .input(ESS_SOC, 50) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 50) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 50) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 50) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 50) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) //
+ .input("ess0", SOC, 50) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.NOT_STARTED)) //
.next(new TestCase() //
- .input(ESS_SOC, 50) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.ABOVE_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 500) //
+ .input("ess0", SOC, 50) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.ABOVE_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 500) //
.output(DEBUG_SET_ACTIVE_POWER_RAW, 500) //
- .output(DEBUG_SET_ACTIVE_POWER, 500) // Would increase till 10_000
- );
+ .output(DEBUG_SET_ACTIVE_POWER, 500)) // Would increase till 10_000
+ .deactivate();
}
@Test
public void testBelowLimit() throws Exception {
final var clock = new TimeLeapClock(Instant.parse("2022-01-01T08:00:00.00Z"), ZoneOffset.UTC);
- final var componentManager = new DummyComponentManager(clock);
-
new ControllerTest(new ControllerEssFixStateOfChargeImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("componentManager", componentManager) //
+ .addReference("componentManager", new DummyComponentManager(clock)) //
.addReference("sum", new DummySum()) //
.addReference("timedata", new DummyTimedata("timedata0")) //
.addReference("ess", ESS) //
.activate(FixStateOfChargeConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setRunning(true) //
.setTargetSoc(30) //
.setSpecifyTargetTime(false) //
@@ -271,44 +255,42 @@ public void testBelowLimit() throws Exception {
.setSelfTermination(false) //
.setTerminationBuffer(720) //
.setConditionalTermination(false) //
- .setEndCondition(EndCondition.CAPACITY_CHANGED) //
+ .setEndCondition(CAPACITY_CHANGED) //
.build())
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.NOT_STARTED)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -500) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.BELOW_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -500) //
.output(DEBUG_SET_ACTIVE_POWER_RAW, -500) //
- .output(DEBUG_SET_ACTIVE_POWER, -500) // Would increase till 10_000
- );
+ .output(DEBUG_SET_ACTIVE_POWER, -500)) // Would increase till 10_000
+ .deactivate();
}
@Test
public void testAtLimit() throws Exception {
final var clock = new TimeLeapClock(Instant.parse("2022-10-27T08:00:00.00Z"), ZoneOffset.UTC);
- final var componentManager = new DummyComponentManager(clock);
-
new ControllerTest(new ControllerEssFixStateOfChargeImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("componentManager", componentManager) //
+ .addReference("componentManager", new DummyComponentManager(clock)) //
.addReference("sum", new DummySum()) //
.addReference("timedata", new DummyTimedata("timedata0")) //
.addReference("ess", ESS) //
.activate(FixStateOfChargeConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setRunning(true) //
.setTargetSoc(30) //
.setSpecifyTargetTime(false) //
@@ -316,85 +298,85 @@ public void testAtLimit() throws Exception {
.setSelfTermination(false) //
.setTerminationBuffer(720) //
.setConditionalTermination(false) //
- .setEndCondition(EndCondition.CAPACITY_CHANGED) //
+ .setEndCondition(CAPACITY_CHANGED) //
.build())
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.NOT_STARTED)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -500) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.BELOW_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -500) //
.output(DEBUG_SET_ACTIVE_POWER_RAW, -500) //
.output(DEBUG_SET_ACTIVE_POWER, -500)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -1500)) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -1500)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -2000)) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -2000)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -2500)) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -2500)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -3000)) //
+ .input("ess0", SOC, 10) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -3000)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -3500)) //
+ .input("ess0", SOC, 10) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -3500)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -4000)) //
+ .input("ess0", SOC, 10) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -4000)) //
.next(new TestCase()) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -5000)) //
+ .input("ess0", SOC, 10) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -5000)) //
.next(new TestCase()) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -6000)) //
+ .input("ess0", SOC, 10) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -6000)) //
.next(new TestCase()) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -7000)) //
+ .input("ess0", SOC, 10) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -7000)) //
.next(new TestCase()) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -8000)) //
+ .input("ess0", SOC, 10) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -8000)) //
.next(new TestCase()) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -9000)) //
+ .input("ess0", SOC, 10) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -9000)) //
.next(new TestCase()) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -10000)) //
+ .input("ess0", SOC, 10) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -10000)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .input(ESS_MAX_APPARENT_POWER, 10_000)) //
+ .input("ess0", SOC, 30) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -9000)) //
+ .input("ess0", SOC, 30) //
+ .output(STATE_MACHINE, State.AT_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -9000)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -8000)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -8000)) //
.next(new TestCase()) //
.next(new TestCase()) //
.next(new TestCase()) //
@@ -402,26 +384,24 @@ public void testAtLimit() throws Exception {
.next(new TestCase()) //
.next(new TestCase()) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) //
- ;
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) //
+ .deactivate();
}
@Test
public void testAtLimitDeadBand() throws Exception {
final var clock = new TimeLeapClock(Instant.parse("2022-10-27T08:00:00.00Z"), ZoneOffset.UTC);
- final var componentManager = new DummyComponentManager(clock);
-
new ControllerTest(new ControllerEssFixStateOfChargeImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("componentManager", componentManager) //
+ .addReference("componentManager", new DummyComponentManager(clock)) //
.addReference("sum", new DummySum()) //
.addReference("timedata", new DummyTimedata("timedata0")) //
.addReference("ess", ESS) //
.activate(FixStateOfChargeConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setRunning(true) //
.setTargetSoc(30) //
.setSpecifyTargetTime(false) //
@@ -429,78 +409,76 @@ public void testAtLimitDeadBand() throws Exception {
.setSelfTermination(false) //
.setTerminationBuffer(720) //
.setConditionalTermination(false) //
- .setEndCondition(EndCondition.CAPACITY_CHANGED) //
+ .setEndCondition(CAPACITY_CHANGED) //
.build())
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .input(ESS_CAPACITY, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 30) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .input("ess0", CAPACITY, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 30) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) //
+ .input("ess0", SOC, 30) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.NOT_STARTED)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 0) //
+ .input("ess0", SOC, 30) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.AT_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0) //
.output(DEBUG_SET_ACTIVE_POWER_RAW, 0) //
.output(DEBUG_SET_ACTIVE_POWER, 0)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) //
+ .input("ess0", SOC, 30) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) //
.next(new TestCase() //
- .input(ESS_SOC, 31) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) //
+ .input("ess0", SOC, 31) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) //
.next(new TestCase() //
- .input(ESS_SOC, 29) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) //
+ .input("ess0", SOC, 29) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) //
.next(new TestCase() //
- .input(ESS_SOC, 28)) //
+ .input("ess0", SOC, 28)) //
.next(new TestCase() //
- .input(ESS_SOC, 28) //
- .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -500)) //
+ .input("ess0", SOC, 28) //
+ .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -500)) //
.next(new TestCase() //
- .input(ESS_SOC, 28) //
- .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) //
+ .input("ess0", SOC, 28) //
+ .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) //
.next(new TestCase() //
- .input(ESS_SOC, 28) //
- .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -1500)) //
+ .input("ess0", SOC, 28) //
+ .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -1500)) //
.next(new TestCase() //
- .input(ESS_SOC, 28) //
- .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -1 * Math.round(//
- Math.min(10_000/* maxApparentPower */ * AbstractFixStateOfCharge.DEFAULT_POWER_FACTOR,
+ .input("ess0", SOC, 28) //
+ .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -1 * round(//
+ min(10_000/* maxApparentPower */ * DEFAULT_POWER_FACTOR,
10_000 /* capacity */ * (1f / 6f))) // 1467
- ))//
- ;
+ )) //
+ .deactivate();
}
@Test
public void testBoundaries() throws Exception {
final var clock = new TimeLeapClock(Instant.parse("2022-10-27T08:00:00.00Z"), ZoneOffset.UTC);
- final var componentManager = new DummyComponentManager(clock);
-
/*
* Below target SoC
*/
new ControllerTest(new ControllerEssFixStateOfChargeImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("componentManager", componentManager) //
+ .addReference("componentManager", new DummyComponentManager(clock)) //
.addReference("sum", new DummySum()) //
.addReference("timedata", new DummyTimedata("timedata0")) //
.addReference("ess", ESS) //
.activate(FixStateOfChargeConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setRunning(true) //
.setTargetSoc(30) //
.setSpecifyTargetTime(false) //
@@ -508,67 +486,67 @@ public void testBoundaries() throws Exception {
.setSelfTermination(false) //
.setTerminationBuffer(720) //
.setConditionalTermination(false) //
- .setEndCondition(EndCondition.CAPACITY_CHANGED) //
+ .setEndCondition(CAPACITY_CHANGED) //
.build())
.next(new TestCase() //
- .input(ESS_SOC, 20) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 20) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 20) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 20) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 20) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) //
+ .input("ess0", SOC, 20) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.NOT_STARTED)) //
.next(new TestCase() //
- .input(ESS_SOC, 20) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .input(ESS_CAPACITY, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -500) //
+ .input("ess0", SOC, 20) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .input("ess0", CAPACITY, 10_000) //
+ .output(STATE_MACHINE, State.BELOW_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -500) //
.output(DEBUG_SET_ACTIVE_POWER_RAW, -500) //
.output(DEBUG_SET_ACTIVE_POWER, -500)) //
.next(new TestCase() //
- .input(ESS_SOC, 22) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) //
+ .input("ess0", SOC, 22) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) //
// Skip Ramp
.next(new TestCase(), 17) //
.next(new TestCase() //
- .input(ESS_SOC, 27) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -10000)) //
+ .input("ess0", SOC, 27) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -10000)) //
.next(new TestCase() //
- .input(ESS_SOC, 28)) //
+ .input("ess0", SOC, 28)) //
.next(new TestCase() //
- .input(ESS_SOC, 28) //
- .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -9500)) //
+ .input("ess0", SOC, 28) //
+ .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -9500)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -9000)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -9000)) //
// Skip ramp
.next(new TestCase(), 13) //
.next(new TestCase() //
- .input(ESS_SOC, 29) //
- .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -2000)) //
+ .input("ess0", SOC, 29) //
+ .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -2000)) //
.next(new TestCase() //
- .input(ESS_SOC, 29) //
- .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -1 * Math.round(//
- Math.min(10_000/* maxApparentPower */ * AbstractFixStateOfCharge.DEFAULT_POWER_FACTOR,
+ .input("ess0", SOC, 29) //
+ .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -1 * round(//
+ min(10_000/* maxApparentPower */ * DEFAULT_POWER_FACTOR,
10_000 /* capacity */ * (1f / 6f))) //
))// 1667
.next(new TestCase() //
- .input(ESS_SOC, 30)) //
+ .input("ess0", SOC, 30)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -667)) //
+ .input("ess0", SOC, 30) //
+ .output(STATE_MACHINE, State.AT_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -667)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) //
+ .input("ess0", SOC, 30) //
+ .output(STATE_MACHINE, State.AT_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) //
;
/*
@@ -576,13 +554,13 @@ public void testBoundaries() throws Exception {
*/
new ControllerTest(new ControllerEssFixStateOfChargeImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("componentManager", componentManager) //
+ .addReference("componentManager", new DummyComponentManager(clock)) //
.addReference("sum", new DummySum()) //
.addReference("timedata", new DummyTimedata("timedata0")) //
.addReference("ess", ESS) //
.activate(FixStateOfChargeConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setRunning(true) //
.setTargetSoc(30) //
.setSpecifyTargetTime(false) //
@@ -590,66 +568,66 @@ public void testBoundaries() throws Exception {
.setSelfTermination(false) //
.setTerminationBuffer(720) //
.setConditionalTermination(false) //
- .setEndCondition(EndCondition.CAPACITY_CHANGED) //
+ .setEndCondition(CAPACITY_CHANGED) //
.build())
.next(new TestCase() //
- .input(ESS_SOC, 40) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 40) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 40) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 40) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 40) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) //
+ .input("ess0", SOC, 40) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.NOT_STARTED)) //
.next(new TestCase() //
- .input(ESS_SOC, 40) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.ABOVE_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 500) //
+ .input("ess0", SOC, 40) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.ABOVE_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 500) //
.output(DEBUG_SET_ACTIVE_POWER_RAW, 500) //
.output(DEBUG_SET_ACTIVE_POWER, 500)) //
.next(new TestCase() //
- .input(ESS_SOC, 40) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 1000)) //
+ .input("ess0", SOC, 40) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 1000)) //
// Skip ramp
.next(new TestCase(), 18) //
.next(new TestCase() //
- .input(ESS_SOC, 33)) //
+ .input("ess0", SOC, 33)) //
.next(new TestCase() //
- .input(ESS_SOC, 32)) //
+ .input("ess0", SOC, 32)) //
.next(new TestCase() //
- .input(ESS_SOC, 32) //
- .output(STATE_MACHINE, StateMachine.State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 9500)) //
+ .input("ess0", SOC, 32) //
+ .output(STATE_MACHINE, State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 9500)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 9000)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 9000)) //
// Skip ramp
.next(new TestCase(), 13) //
.next(new TestCase() //
- .input(ESS_SOC, 31) //
- .output(STATE_MACHINE, StateMachine.State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 2000)) //
+ .input("ess0", SOC, 31) //
+ .output(STATE_MACHINE, State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 2000)) //
.next(new TestCase() //
- .input(ESS_SOC, 31) //
- .output(STATE_MACHINE, StateMachine.State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, Math.round(//
- Math.min(10_000/* maxApparentPower */ * AbstractFixStateOfCharge.DEFAULT_POWER_FACTOR,
+ .input("ess0", SOC, 31) //
+ .output(STATE_MACHINE, State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, round(//
+ min(10_000/* maxApparentPower */ * DEFAULT_POWER_FACTOR,
10_000 /* capacity */ * (1f / 6f))) //
))// 1667
.next(new TestCase() //
- .input(ESS_SOC, 30)) //
+ .input("ess0", SOC, 30)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 667)) //
+ .input("ess0", SOC, 30) //
+ .output(STATE_MACHINE, State.AT_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 667)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) //
- ;
+ .input("ess0", SOC, 30) //
+ .output(STATE_MACHINE, State.AT_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) //
+ .deactivate();
}
@Test
@@ -665,8 +643,8 @@ public void testLimitWithSpecifiedTimeBelowLimit() throws Exception {
.addReference("timedata", new DummyTimedata("timedata0")) //
.addReference("ess", ESS) //
.activate(FixStateOfChargeConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setRunning(true) //
.setTargetSoc(30) //
.setSpecifyTargetTime(true) //
@@ -674,35 +652,35 @@ public void testLimitWithSpecifiedTimeBelowLimit() throws Exception {
.setSelfTermination(false) //
.setTerminationBuffer(720) //
.setConditionalTermination(false) //
- .setEndCondition(EndCondition.CAPACITY_CHANGED) //
+ .setEndCondition(CAPACITY_CHANGED) //
.build())
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_CAPACITY, 30_000) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", CAPACITY, 30_000) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.NOT_STARTED)) //
// Start time = 2022-10-27T09:14:24+01:00, Current: 2022-10-27T09:00:00+01:00
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .input(ESS_CAPACITY, 30_000) //
- .output(STATE_MACHINE, StateMachine.State.NOT_STARTED) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, null) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .input("ess0", CAPACITY, 30_000) //
+ .output(STATE_MACHINE, State.NOT_STARTED) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, null) //
.output(DEBUG_SET_ACTIVE_POWER_RAW, null) //
.output(DEBUG_SET_ACTIVE_POWER, null)) //
.next(new TestCase() //
- .timeleap(clock, 15, ChronoUnit.MINUTES))//
+ .timeleap(clock, 15, MINUTES))//
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -500)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -500)) //
.next(new TestCase()) //
.next(new TestCase()) //
.next(new TestCase()) //
@@ -712,33 +690,33 @@ public void testLimitWithSpecifiedTimeBelowLimit() throws Exception {
.next(new TestCase()) //
.next(new TestCase()) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -5000)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -5000)) //
.next(new TestCase() //
- .input(ESS_SOC, 10) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .input(ESS_CAPACITY, 30_000) //
- .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -5040) //
+ .input("ess0", SOC, 10) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .input("ess0", CAPACITY, 30_000) //
+ .output(STATE_MACHINE, State.BELOW_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -5040) //
.output(DEBUG_SET_ACTIVE_POWER_RAW, -5040) //
.output(DEBUG_SET_ACTIVE_POWER, -5040)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .input(ESS_MAX_APPARENT_POWER, 10_000)) //
+ .input("ess0", SOC, 30) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -4040)) //
+ .input("ess0", SOC, 30) //
+ .output(STATE_MACHINE, State.AT_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -4040)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -3040)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -3040)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -2040)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -2040)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -1040)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -1040)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -40)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -40)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) //
- ;
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) //
+ .deactivate();
}
@Test
@@ -753,8 +731,8 @@ public void testLimitWithSpecifiedTimeAboveLimit() throws Exception {
.addReference("timedata", new DummyTimedata("timedata0")) //
.addReference("ess", ESS) //
.activate(FixStateOfChargeConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setRunning(true) //
.setTargetSoc(30) //
.setSpecifyTargetTime(true) //
@@ -763,85 +741,85 @@ public void testLimitWithSpecifiedTimeAboveLimit() throws Exception {
.setSelfTermination(false) //
.setTerminationBuffer(720) //
.setConditionalTermination(false) //
- .setEndCondition(EndCondition.CAPACITY_CHANGED) //
+ .setEndCondition(CAPACITY_CHANGED) //
.build())
.next(new TestCase() //
- .input(ESS_SOC, 80) //
- .input(ESS_CAPACITY, 30_000) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 80) //
+ .input("ess0", CAPACITY, 30_000) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 80) //
- .input(ESS_CAPACITY, 30_000) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.IDLE)) //
+ .input("ess0", SOC, 80) //
+ .input("ess0", CAPACITY, 30_000) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.IDLE)) //
.next(new TestCase() //
- .input(ESS_SOC, 80) //
- .input(ESS_CAPACITY, 30_000) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) //
+ .input("ess0", SOC, 80) //
+ .input("ess0", CAPACITY, 30_000) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.NOT_STARTED)) //
// Start time = 2022-10-27T06:26:24, Current: 2022-10-26T23:00
.next(new TestCase() //
- .input(ESS_SOC, 80) //
- .input(ESS_CAPACITY, 30_000) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.NOT_STARTED) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, null) //
+ .input("ess0", SOC, 80) //
+ .input("ess0", CAPACITY, 30_000) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.NOT_STARTED) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, null) //
.output(DEBUG_SET_ACTIVE_POWER_RAW, null) //
.output(DEBUG_SET_ACTIVE_POWER, null)) //
.next(new TestCase() //
- .timeleap(clock, 7, ChronoUnit.HOURS)) //
+ .timeleap(clock, 7, HOURS)) //
.next(new TestCase() //
- .timeleap(clock, 31, ChronoUnit.MINUTES)) //
+ .timeleap(clock, 31, MINUTES)) //
.next(new TestCase() //
- .input(ESS_SOC, 80) //
- .input(ESS_CAPACITY, 30_000) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .output(STATE_MACHINE, StateMachine.State.ABOVE_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 500) //
+ .input("ess0", SOC, 80) //
+ .input("ess0", CAPACITY, 30_000) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .output(STATE_MACHINE, State.ABOVE_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 500) //
.output(DEBUG_SET_ACTIVE_POWER_RAW, 500) //
.output(DEBUG_SET_ACTIVE_POWER, 500)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 1000)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 1000)) //
.next(new TestCase()) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 2000)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 2000)) //
.next(new TestCase()) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 3000)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 3000)) //
.next(new TestCase()) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 4000)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 4000)) //
.next(new TestCase()) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 5000)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 5000)) //
.next(new TestCase()) //
.next(new TestCase() //
- .input(ESS_SOC, 80) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .input(ESS_CAPACITY, 30_000) //
- .output(STATE_MACHINE, StateMachine.State.ABOVE_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 5128) //
+ .input("ess0", SOC, 80) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .input("ess0", CAPACITY, 30_000) //
+ .output(STATE_MACHINE, State.ABOVE_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 5128) //
.output(DEBUG_SET_ACTIVE_POWER_RAW, 5128) //
.output(DEBUG_SET_ACTIVE_POWER, 5128)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .input(ESS_MAX_APPARENT_POWER, 10_000)) //
+ .input("ess0", SOC, 30) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000)) //
.next(new TestCase() //
- .input(ESS_SOC, 30) //
- .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 4128)) //
+ .input("ess0", SOC, 30) //
+ .output(STATE_MACHINE, State.AT_TARGET_SOC) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 4128)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 3128)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 3128)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 2128)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 2128)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 1128)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 1128)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 128)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 128)) //
.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) //
- ;
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/bnd.bnd b/io.openems.edge.controller.ess.gridoptimizedcharge/bnd.bnd
index ed3817a1757..d00c7cb673d 100644
--- a/io.openems.edge.controller.ess.gridoptimizedcharge/bnd.bnd
+++ b/io.openems.edge.controller.ess.gridoptimizedcharge/bnd.bnd
@@ -8,10 +8,12 @@ Bundle-Version: 1.0.0.${tstamp}
io.openems.common,\
io.openems.edge.common,\
io.openems.edge.controller.api,\
+ io.openems.edge.energy.api,\
io.openems.edge.ess.api,\
io.openems.edge.meter.api,\
io.openems.edge.predictor.api,\
io.openems.edge.timedata.api,\
-testpath: \
- ${testpath}
+ ${testpath},\
+ org.apache.commons.math3,\
diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedCharge.java b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedCharge.java
index 2ee38fba17a..05dc14dc251 100644
--- a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedCharge.java
+++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedCharge.java
@@ -192,6 +192,12 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
CONFIGURED_ESS_IS_NOT_MANAGED(Doc.of(Level.FAULT) //
.text("The Energy Storage System is in read-only mode and does not allow to be controlled.")), //
+ /**
+ * Production values for prediction not available.
+ */
+ NO_VALID_PRODUCTION_PREDICTION(Doc.of(Level.WARNING) //
+ .translationKey(ControllerEssGridOptimizedCharge.class, "noValidProductionPrediction")), //
+
/**
* Cumulated seconds of the state delay charge.
*/
@@ -696,6 +702,25 @@ public default void _setConfiguredEssIsNotManaged(Boolean value) {
this.getConfiguredEssIsNotManagedChannel().setNextValue(value);
}
+ /**
+ * Gets the Channel for {@link ChannelId#NO_VALID_PRODUCTION_PREDICTION}.
+ *
+ * @return the Channel
+ */
+ public default StateChannel noValidProductionPredictionChannel() {
+ return this.channel(ChannelId.NO_VALID_PRODUCTION_PREDICTION);
+ }
+
+ /**
+ * Internal method to set the 'nextValue' on
+ * {@link ChannelId#NO_VALID_PRODUCTION_PREDICTION} Channel.
+ *
+ * @param value the next value
+ */
+ public default void _setNoValidProductionPredictionChannel(Boolean value) {
+ this.noValidProductionPredictionChannel().setNextValue(value);
+ }
+
/**
* Gets the Channel for {@link ChannelId#DELAY_CHARGE_TIME}.
*
diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java
index aa727e503b4..dc05f7914c4 100644
--- a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java
+++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java
@@ -1,11 +1,19 @@
package io.openems.edge.controller.ess.gridoptimizedcharge;
+import static java.util.stream.Collectors.groupingBy;
+
+import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
+import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.OptionalDouble;
+import java.util.OptionalInt;
+import java.util.function.Supplier;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
@@ -22,6 +30,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.collect.ImmutableSortedMap;
+
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.exceptions.OpenemsException;
import io.openems.edge.common.channel.IntegerReadChannel;
@@ -33,6 +43,8 @@
import io.openems.edge.common.filter.RampFilter;
import io.openems.edge.common.sum.Sum;
import io.openems.edge.controller.api.Controller;
+import io.openems.edge.energy.api.EnergySchedulable;
+import io.openems.edge.energy.api.EnergyScheduleHandler;
import io.openems.edge.ess.api.ManagedSymmetricEss;
import io.openems.edge.meter.api.ElectricityMeter;
import io.openems.edge.predictor.api.manager.PredictorManager;
@@ -46,8 +58,9 @@
immediate = true, //
configurationPolicy = ConfigurationPolicy.REQUIRE //
)
-public class ControllerEssGridOptimizedChargeImpl extends AbstractOpenemsComponent implements
- ControllerEssGridOptimizedCharge, Controller, OpenemsComponent, TimedataProvider, ComponentManagerProvider {
+public class ControllerEssGridOptimizedChargeImpl extends AbstractOpenemsComponent
+ implements ControllerEssGridOptimizedCharge, EnergySchedulable, Controller, OpenemsComponent, TimedataProvider,
+ ComponentManagerProvider {
/**
* Buffer in watt taken into account in the calculation of the first and last
@@ -58,6 +71,7 @@ public class ControllerEssGridOptimizedChargeImpl extends AbstractOpenemsCompone
protected final RampFilter rampFilter = new RampFilter();
private final Logger log = LoggerFactory.getLogger(ControllerEssGridOptimizedChargeImpl.class);
+ private final EnergyScheduleHandler energyScheduleHandler;
/*
* Time counter for the important states
@@ -107,6 +121,9 @@ public ControllerEssGridOptimizedChargeImpl() {
Controller.ChannelId.values(), //
ControllerEssGridOptimizedCharge.ChannelId.values() //
);
+ this.energyScheduleHandler = buildEnergyScheduleHandler(//
+ () -> this.config.mode(), //
+ () -> DelayCharge.parseTime(this.config.manualTargetTime()));
}
@Activate
@@ -145,12 +162,19 @@ private void updateConfig(Config config) {
@Override
public void run() throws OpenemsNamedException {
+
if (!this.ess.isManaged() && this.config.mode() != Mode.OFF) {
this._setConfiguredEssIsNotManaged(true);
return;
}
this._setConfiguredEssIsNotManaged(false);
+ if (!this.sum.getProductionActivePower().isDefined()) {
+ this._setNoValidProductionPredictionChannel(true);
+ return;
+ }
+ this._setNoValidProductionPredictionChannel(false);
+
// Updates the time channels.
this.calculateTime();
@@ -435,4 +459,91 @@ public Timedata getTimedata() {
return this.timedata;
}
+ /**
+ * Builds the {@link EnergyScheduleHandler}.
+ *
+ *
+ * This is public so that it can be used by the EnergyScheduler integration
+ * test.
+ *
+ * @param mode a supplier for the configured {@link Mode}
+ * @param manualTargetTime a supplier for the configured manualTargetTime
+ * @return a {@link EnergyScheduleHandler}
+ */
+ public static EnergyScheduleHandler buildEnergyScheduleHandler(Supplier mode,
+ Supplier manualTargetTime) {
+ return EnergyScheduleHandler.of(//
+ simContext -> {
+ // TODO try to reuse existing logic for parsing, calculating limits, etc.; for
+ // now this only works for current day and MANUAL mode
+ final var limits = ImmutableSortedMap.naturalOrder();
+ final var periodsPerDay = simContext.periods().stream() //
+ .collect(groupingBy(p -> p.time().truncatedTo(ChronoUnit.DAYS)));
+ if (!periodsPerDay.isEmpty()) {
+ final var firstDayMignight = Collections.min(periodsPerDay.keySet());
+
+ for (var entry : periodsPerDay.entrySet()) {
+ // Find target time for this day
+ var midnight = entry.getKey(); // beginning of this day
+ var periods = entry.getValue(); // periods of this day
+ ZonedDateTime targetTime = switch (mode.get()) {
+ case OFF -> midnight; // Can not happen
+ case MANUAL -> midnight //
+ .withHour(manualTargetTime.get().getHour()) //
+ .withMinute(manualTargetTime.get().getMinute());
+ case AUTOMATIC -> midnight; // TODO
+ };
+ // Find first period with Production > Consumption
+ var firstExcessEnergyOpt = periods.stream() //
+ .filter(p -> p.production() > p.consumption()) //
+ .findFirst();
+ if (firstExcessEnergyOpt.isEmpty()
+ || targetTime.isBefore(firstExcessEnergyOpt.get().time())) {
+ // Production exceeds Consumption never or too late on this day
+ // -> set no limit for this day
+ limits.put(midnight, OptionalInt.empty());
+ continue;
+ }
+ var firstExcessEnergy = firstExcessEnergyOpt.get().time();
+
+ // Set no limit for early hours of the day
+ if (firstExcessEnergy.isAfter(midnight)) {
+ limits.put(midnight, OptionalInt.empty());
+ }
+
+ // Calculate actual charge limit
+ var noOfQuarters = (int) Duration.between(firstExcessEnergy, targetTime).toMinutes() / 15;
+ final var totalEnergy = midnight == firstDayMignight //
+ ? // use actual data for first day
+ simContext.ess().totalEnergy() - simContext.ess().currentEnergy()
+ : // assume full charge from second day
+ simContext.ess().totalEnergy();
+ limits.put(firstExcessEnergy, OptionalInt.of(totalEnergy / noOfQuarters));
+
+ // No limit after targetTime
+ limits.put(targetTime, OptionalInt.empty());
+ }
+ }
+
+ return new EshContext(limits.build());
+ }, //
+ (simContext, period, energyFlow, ctrlContext) -> {
+ var limitEntry = ctrlContext.limits.floorEntry(period.time());
+ if (limitEntry == null) {
+ return;
+ }
+ var limit = limitEntry.getValue();
+ if (limit.isPresent()) {
+ energyFlow.setEssMaxCharge(limit.getAsInt());
+ }
+ });
+ }
+
+ private static record EshContext(ImmutableSortedMap limits) {
+ }
+
+ @Override
+ public EnergyScheduleHandler getEnergyScheduleHandler() {
+ return this.energyScheduleHandler;
+ }
}
diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/package-info.java b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/package-info.java
new file mode 100644
index 00000000000..9736f9da6be
--- /dev/null
+++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/package-info.java
@@ -0,0 +1,3 @@
+@org.osgi.annotation.versioning.Version("1.0.0")
+@org.osgi.annotation.bundle.Export
+package io.openems.edge.controller.ess.gridoptimizedcharge;
diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_de.properties b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_de.properties
new file mode 100644
index 00000000000..2660bc97b77
--- /dev/null
+++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_de.properties
@@ -0,0 +1,2 @@
+# ControllerEssGridOptimizedCharge
+noValidProductionPrediction = Keine Erzeugungsprognose möglich. Bitte erfassen Sie die Erzeugung Ihrer Anlage über eine App oder wählen Sie in der netzdienlichen Beladung den Modus 'AUS'.
diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_en.properties b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_en.properties
new file mode 100644
index 00000000000..e2195067037
--- /dev/null
+++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_en.properties
@@ -0,0 +1,2 @@
+# ControllerEssGridOptimizedCharge
+noValidProductionPrediction = No production forecast available. Please log the production via an app or set the grid-optimized charge mode to 'OFF'.
diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java
index 03f197139e3..862b0955d56 100644
--- a/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java
+++ b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java
@@ -1,5 +1,20 @@
package io.openems.edge.controller.ess.gridoptimizedcharge;
+import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_DC_ACTUAL_POWER;
+import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT;
+import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.DELAY_CHARGE_STATE;
+import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.PREDICTED_TARGET_MINUTE;
+import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.PREDICTED_TARGET_MINUTE_ADJUSTED;
+import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT;
+import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT;
+import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT;
+import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.SELL_TO_GRID_LIMIT_STATE;
+import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.START_EPOCH_SECONDS;
+import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.TARGET_MINUTE;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_LESS_OR_EQUALS;
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.CAPACITY;
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.MAX_APPARENT_POWER;
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC;
import static io.openems.edge.predictor.api.prediction.Prediction.EMPTY_PREDICTION;
import static java.time.temporal.ChronoUnit.DAYS;
import static org.junit.Assert.assertEquals;
@@ -34,8 +49,10 @@
import io.openems.edge.common.test.Plot.AxisFormat;
import io.openems.edge.common.test.Plot.Data;
import io.openems.edge.controller.test.ControllerTest;
+import io.openems.edge.ess.api.SymmetricEss;
import io.openems.edge.ess.test.DummyHybridEss;
import io.openems.edge.ess.test.DummyManagedSymmetricEss;
+import io.openems.edge.meter.api.ElectricityMeter;
import io.openems.edge.meter.test.DummyElectricityMeter;
import io.openems.edge.predictor.api.prediction.Prediction;
import io.openems.edge.predictor.api.test.DummyPredictor;
@@ -43,50 +60,13 @@
public class ControllerEssGridOptimizedChargeImplTest {
- // Ids
- private static final String CTRL_ID = "ctrlGridOptimizedCharge0";
- private static final String PREDICTOR_ID = "predictor0";
- private static final String ESS_ID = "ess0";
- private static final String METER_ID = "meter0";
-
// Components
- private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss(ESS_ID);
- private static final DummyElectricityMeter METER = new DummyElectricityMeter(METER_ID);
- private static final DummyHybridEss HYBRID_ESS = new DummyHybridEss(ESS_ID);
- private static final DummyManagedSymmetricEss ESS_WITH_NONE_APPARENT_POWER = new DummyManagedSymmetricEss(ESS_ID) //
+ private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss("ess0");
+ private static final DummyElectricityMeter METER = new DummyElectricityMeter("meter0");
+ private static final DummyHybridEss HYBRID_ESS = new DummyHybridEss("ess0");
+ private static final DummyManagedSymmetricEss ESS_WITH_NONE_APPARENT_POWER = new DummyManagedSymmetricEss("ess0") //
.withMaxApparentPower(0);
- // Ess channels
- private static final ChannelAddress ESS_CAPACITY = new ChannelAddress(ESS_ID, "Capacity");
- private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc");
- private static final ChannelAddress ESS_MAX_APPARENT_POWER = new ChannelAddress(ESS_ID, "MaxApparentPower");
- private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower");
- private static final ChannelAddress ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(ESS_ID,
- "SetActivePowerLessOrEquals");
-
- // Meter channels
- private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower");
-
- // Controller channels
- private static final ChannelAddress PREDICTED_TARGET_MINUTE = new ChannelAddress(CTRL_ID, "PredictedTargetMinute");
- private static final ChannelAddress PREDICTED_TARGET_MINUTE_ADJUSTED = new ChannelAddress(CTRL_ID,
- "PredictedTargetMinuteAdjusted");
- private static final ChannelAddress TARGET_MINUTE = new ChannelAddress(CTRL_ID, "TargetMinute");
- private static final ChannelAddress DELAY_CHARGE_STATE = new ChannelAddress(CTRL_ID, "DelayChargeState");
- private static final ChannelAddress SELL_TO_GRID_LIMIT_STATE = new ChannelAddress(CTRL_ID, "SellToGridLimitState");
- private static final ChannelAddress DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT = new ChannelAddress(CTRL_ID,
- "DelayChargeMaximumChargeLimit");
- private static final ChannelAddress RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT = new ChannelAddress(CTRL_ID,
- "RawDelayChargeMaximumChargeLimit");
- private static final ChannelAddress SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT = new ChannelAddress(CTRL_ID,
- "SellToGridLimitMinimumChargeLimit");
- private static final ChannelAddress RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT = new ChannelAddress(CTRL_ID,
- "RawSellToGridLimitChargeLimit");
- private static final ChannelAddress START_EPOCH_SECONDS = new ChannelAddress(CTRL_ID, "StartEpochSeconds");
-
- // Sum channels
- private static final ChannelAddress SUM_PRODUCTION_DC_ACTUAL_POWER = new ChannelAddress("_sum",
- "ProductionDcActualPower");
private static final ChannelAddress SUM_PRODUCTION_ACTIVE_POWER = new ChannelAddress("_sum",
"ProductionActivePower");
private static final ChannelAddress SUM_CONSUMPTION_ACTIVE_POWER = new ChannelAddress("_sum",
@@ -157,11 +137,11 @@ public void automatic_default_predictions_at_midnight_test() throws Exception {
final var sum = new DummySum();
final var predictorManager = new DummyPredictorManager(
// Production
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION),
SUM_PRODUCTION_ACTIVE_POWER),
// Consumption
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION),
SUM_CONSUMPTION_ACTIVE_POWER));
@@ -173,10 +153,10 @@ public void automatic_default_predictions_at_midnight_test() throws Exception {
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.AUTOMATIC) //
.setSellToGridLimitEnabled(true) //
@@ -184,18 +164,20 @@ public void automatic_default_predictions_at_midnight_test() throws Exception {
.setManualTargetTime("") //
.build()) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, 0) //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(ESS_CAPACITY, 10_000) //
- .input(ESS_SOC, 20) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", CAPACITY, 10_000) //
+ .input("ess0", SOC, 20) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
.input(START_EPOCH_SECONDS, 1630566000) //
.output(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) //
.output(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) //
.output(DELAY_CHARGE_STATE, DelayChargeState.AVOID_LOW_CHARGING) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) //
- .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)); // Avoid low charge power
+ .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)) // Avoid low charge power
+ .deactivate();
}
@Test
@@ -206,11 +188,11 @@ public void automatic_default_predictions_at_midday_test() throws Exception {
final var sum = new DummySum();
final var predictorManager = new DummyPredictorManager(
// Production
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, midnight, DEFAULT_PRODUCTION_PREDICTION),
SUM_PRODUCTION_ACTIVE_POWER),
// Consumption
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, midnight, DEFAULT_CONSUMPTION_PREDICTION),
SUM_CONSUMPTION_ACTIVE_POWER));
@@ -222,10 +204,10 @@ public void automatic_default_predictions_at_midday_test() throws Exception {
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.AUTOMATIC) //
.setSellToGridLimitEnabled(true) //
@@ -233,11 +215,12 @@ public void automatic_default_predictions_at_midday_test() throws Exception {
.setManualTargetTime("") //
.build()) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, 0) //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(ESS_CAPACITY, 10_000) //
- .input(ESS_SOC, 20) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", CAPACITY, 10_000) //
+ .input("ess0", SOC, 20) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
.input(START_EPOCH_SECONDS, 1630566000) //
.output(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) //
.output(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) //
@@ -246,7 +229,8 @@ public void automatic_default_predictions_at_midday_test() throws Exception {
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) //
// If Energy calculation would be applied on medium risk level - Predicted
// available Energy is not enough to reach 100%
- .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2700));
+ .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2700)) //
+ .deactivate();
}
@Test
@@ -262,11 +246,11 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep
final var sum = new DummySum();
final var predictorManager = new DummyPredictorManager(
// Production
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, midnight, DEFAULT_PRODUCTION_PREDICTION),
SUM_PRODUCTION_ACTIVE_POWER),
// Consumption
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, midnight, DEFAULT_CONSUMPTION_PREDICTION),
SUM_CONSUMPTION_ACTIVE_POWER));
@@ -278,10 +262,10 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.AUTOMATIC) //
.setSellToGridLimitEnabled(true) //
@@ -290,11 +274,12 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep
.build()) //
.next(new TestCase() //
.onAfterProcessImage(sleep) //
- .input(METER_ACTIVE_POWER, 0) //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(ESS_CAPACITY, 10_000) //
- .input(ESS_SOC, 20) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", CAPACITY, 10_000) //
+ .input("ess0", SOC, 20) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
.input(START_EPOCH_SECONDS, 1630566000) //
.output(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) //
.output(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) //
@@ -305,23 +290,28 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep
.output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2700)) //
.next(new TestCase() //
.onAfterProcessImage(sleep) //
- .input(ESS_SOC, 21) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("ess0", SOC, 21) //
.input(START_EPOCH_SECONDS, 1630566000) //
.output(DELAY_CHARGE_STATE, DelayChargeState.ACTIVE_LIMIT) //
.output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2683) //
.output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2666)) //
.next(new TestCase() //
.onAfterProcessImage(sleep) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
.output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2677) //
.output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2666)) //
.next(new TestCase() //
.onAfterProcessImage(sleep) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
.output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2675) //
.output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2666)) //
.next(new TestCase() //
.onAfterProcessImage(sleep) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
.output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2673) //
.output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2666)) //
+ .deactivate();
;
}
@@ -333,11 +323,11 @@ public void automatic_default_predictions_at_evening_test() throws Exception {
final var sum = new DummySum();
final var predictorManager = new DummyPredictorManager(
// Production
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION),
SUM_PRODUCTION_ACTIVE_POWER),
// Consumption
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION),
SUM_CONSUMPTION_ACTIVE_POWER));
@@ -349,10 +339,10 @@ public void automatic_default_predictions_at_evening_test() throws Exception {
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.AUTOMATIC) //
.setSellToGridLimitEnabled(true) //
@@ -360,11 +350,12 @@ public void automatic_default_predictions_at_evening_test() throws Exception {
.setManualTargetTime("") //
.build()) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, 0) //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(ESS_CAPACITY, 10_000) //
- .input(ESS_SOC, 20) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", CAPACITY, 10_000) //
+ .input("ess0", SOC, 20) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
.input(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) //
.input(START_EPOCH_SECONDS, 1630566000) //
.input(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) //
@@ -373,6 +364,7 @@ public void automatic_default_predictions_at_evening_test() throws Exception {
// Value increases steadily by 0.25% of max apparent power 10_000
.output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2025))
.next(new TestCase() //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
.input(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) //
.input(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) //
.input(START_EPOCH_SECONDS, 1630566000) //
@@ -383,6 +375,7 @@ public void automatic_default_predictions_at_evening_test() throws Exception {
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) //
.output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2050))
.next(new TestCase() //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
.input(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) //
.input(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) //
.input(START_EPOCH_SECONDS, 1630566000) //
@@ -391,7 +384,8 @@ public void automatic_default_predictions_at_evening_test() throws Exception {
.output(DELAY_CHARGE_STATE, DelayChargeState.ACTIVE_LIMIT) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) //
- .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2075));
+ .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2075)) //
+ .deactivate();
}
@Test
@@ -399,8 +393,8 @@ public void automatic_no_predictions_test() throws Exception {
final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC);
final var cm = new DummyComponentManager(clock);
final var predictorManager = new DummyPredictorManager(
- new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), //
- new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER));
+ new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), //
+ new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER));
new ControllerTest(new ControllerEssGridOptimizedChargeImpl()) //
.addReference("predictorManager", predictorManager) //
@@ -410,10 +404,10 @@ public void automatic_no_predictions_test() throws Exception {
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.AUTOMATIC) //
.setSellToGridLimitEnabled(true) //
@@ -421,15 +415,17 @@ public void automatic_no_predictions_test() throws Exception {
.setManualTargetTime("") //
.build()) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, 0) //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(ESS_CAPACITY, 10_000) //
- .input(ESS_SOC, 20) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", CAPACITY, 10_000) //
+ .input("ess0", SOC, 20) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
.input(START_EPOCH_SECONDS, 1630566000) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) //
- .output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED));
+ .output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED)) //
+ .deactivate();
}
@Test
@@ -437,8 +433,8 @@ public void automatic_sell_to_grid_limit_test() throws Exception {
final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC);
final var cm = new DummyComponentManager(clock);
final var predictorManager = new DummyPredictorManager(
- new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), //
- new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER));
+ new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), //
+ new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER));
new ControllerTest(new ControllerEssGridOptimizedChargeImpl()) //
.addReference("predictorManager", predictorManager) //
@@ -448,10 +444,10 @@ public void automatic_sell_to_grid_limit_test() throws Exception {
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.AUTOMATIC) //
.setSellToGridLimitEnabled(true) //
@@ -459,68 +455,76 @@ public void automatic_sell_to_grid_limit_test() throws Exception {
.setManualTargetTime("") //
.build()) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -7500) //
- .input(ESS_CAPACITY, 10_000) //
- .input(ESS_SOC, 20) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .input(ESS_ACTIVE_POWER, 0) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7500) //
+ .input("ess0", CAPACITY, 10_000) //
+ .input("ess0", SOC, 20) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
.input(START_EPOCH_SECONDS, 1630566000) //
.output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED) //
.output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -850) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 850) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -12000) //
- .input(ESS_ACTIVE_POWER, -850) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -12000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -850) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6200) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6200) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6200) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6200) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -7000) //
- .input(ESS_ACTIVE_POWER, -6200) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6200) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6550) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6550) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6550) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6550) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -5000) //
- .input(ESS_ACTIVE_POWER, -6550) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -5000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6550) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6050) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6050) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6050) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6050) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -8000) //
- .input(ESS_ACTIVE_POWER, -6050) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -8000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6050) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -7400) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -7400) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -7400) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 7400) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
// Difference between last limit and current lower than the ramp - ramp is not
// applied
- .input(METER_ACTIVE_POWER, -7000) //
- .input(ESS_ACTIVE_POWER, -7400) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -7400) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -7750) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -7750) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -7750) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 7750) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -6000) //
- .input(ESS_ACTIVE_POWER, -7750) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -6000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -7750) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -7250) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -7250) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -7250) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 7250) //
- .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT));
+ .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
+ .deactivate();
}
@Test
@@ -528,8 +532,8 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception {
final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC);
final var cm = new DummyComponentManager(clock);
final var predictorManager = new DummyPredictorManager(
- new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), //
- new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER));
+ new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), //
+ new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER));
new ControllerTest(new ControllerEssGridOptimizedChargeImpl()) //
.addReference("predictorManager", predictorManager) //
@@ -539,10 +543,10 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception {
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.AUTOMATIC) //
.setSellToGridLimitEnabled(true) //
@@ -550,70 +554,78 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception {
.setManualTargetTime("") //
.build()) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -7500) //
- .input(ESS_CAPACITY, 10_000) //
- .input(ESS_SOC, 100) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .input(ESS_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7500) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("ess0", CAPACITY, 10_000) //
+ .input("ess0", SOC, 100) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
.input(START_EPOCH_SECONDS, 1630566000) //
.output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED) //
.output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -500) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -500) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -500) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 500) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -12000) //
- .input(ESS_ACTIVE_POWER, -1000) //
- .input(ESS_SOC, 100) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -12000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -1000) //
+ .input("ess0", SOC, 100) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6000) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6000) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6000) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6000) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -7000) //
- .input(ESS_ACTIVE_POWER, -6000) //
- .input(ESS_SOC, 100) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) //
+ .input("ess0", SOC, 100) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6000) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6000) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6000) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6000) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -5000) //
- .input(ESS_ACTIVE_POWER, -6000) //
- .input(ESS_SOC, 100) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -5500) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -5000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) //
+ .input("ess0", SOC, 100) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -5500) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -5500) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 5500) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -8000) //
- .input(ESS_ACTIVE_POWER, -5500) //
- .input(ESS_SOC, 100) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6500) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -8000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -5500) //
+ .input("ess0", SOC, 100) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6500) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6500) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6500) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
// Difference between last limit and current lower than the ramp - ramp is not
// applied
- .input(METER_ACTIVE_POWER, -7000) //
- .input(ESS_ACTIVE_POWER, -6300) //
- .input(ESS_SOC, 100) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6300) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6300) //
+ .input("ess0", SOC, 100) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6300) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6300) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6300) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -6000) //
- .input(ESS_ACTIVE_POWER, -6000) //
- .input(ESS_SOC, 100) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -5800) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -6000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) //
+ .input("ess0", SOC, 100) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -5800) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -5800) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 5800) //
- .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT));
+ .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
+ .deactivate();
}
@Test
@@ -621,8 +633,8 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception {
final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC);
final var cm = new DummyComponentManager(clock);
final var predictorManager = new DummyPredictorManager(
- new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), //
- new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER));
+ new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), //
+ new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER));
new ControllerTest(new ControllerEssGridOptimizedChargeImpl()) //
.addReference("predictorManager", predictorManager) //
@@ -632,10 +644,10 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception {
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.AUTOMATIC) //
.setSellToGridLimitEnabled(true) //
@@ -643,68 +655,76 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception {
.setManualTargetTime("") //
.build()) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -7500) //
- .input(ESS_CAPACITY, 10_000) //
- .input(ESS_SOC, 20) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .input(ESS_ACTIVE_POWER, 0) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7500) //
+ .input("ess0", CAPACITY, 10_000) //
+ .input("ess0", SOC, 20) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
.input(START_EPOCH_SECONDS, 1630566000) //
.output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED) //
.output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -850) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 850) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -12000) //
- .input(ESS_ACTIVE_POWER, -1000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -12000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -1000) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6350) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6350) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6350) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6350) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -7000) //
- .input(ESS_ACTIVE_POWER, -6000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6350) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6350) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6350) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6350) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -5000) //
- .input(ESS_ACTIVE_POWER, -6000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -5000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -5850) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -5850) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -5850) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 5850) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -8000) //
- .input(ESS_ACTIVE_POWER, -5500) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -8000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -5500) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6850) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6850) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6850) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6850) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
// Difference between last limit and current lower than the ramp - ramp is not
// applied
- .input(METER_ACTIVE_POWER, -7000) //
- .input(ESS_ACTIVE_POWER, -6300) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6300) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6650) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6650) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6650) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6650) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -6000) //
- .input(ESS_ACTIVE_POWER, -6000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -6000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6150) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6150) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6150) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6150) //
- .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT));
+ .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) //
+ .deactivate();
}
@Test
@@ -715,11 +735,11 @@ public void manual_midnight_test() throws Exception {
final var sum = new DummySum();
final var predictorManager = new DummyPredictorManager(
// Production
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION),
SUM_PRODUCTION_ACTIVE_POWER),
// Consumption
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION),
SUM_CONSUMPTION_ACTIVE_POWER));
@@ -731,10 +751,10 @@ public void manual_midnight_test() throws Exception {
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.MANUAL) //
.setSellToGridLimitEnabled(true) //
@@ -742,19 +762,21 @@ public void manual_midnight_test() throws Exception {
.setSellToGridLimitRampPercentage(5) //
.build()) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, 0) //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(ESS_CAPACITY, 10_000) //
- .input(ESS_SOC, 20) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", CAPACITY, 10_000) //
+ .input("ess0", SOC, 20) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .input(ESS_MAX_APPARENT_POWER, 10_000)) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000)) //
.next(new TestCase() //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
.input(START_EPOCH_SECONDS, 1630566000) //
.output(TARGET_MINUTE, /* QuarterHour */ 1020) //
.output(DELAY_CHARGE_STATE, DelayChargeState.AVOID_LOW_CHARGING) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) //
- .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)); // 476 W below minimum
+ .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)) // 476 W below minimum
+ .deactivate();
}
@Test
@@ -765,11 +787,11 @@ public void manual_midday_test() throws Exception {
final var sum = new DummySum();
final var predictorManager = new DummyPredictorManager(
// Production
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION),
SUM_PRODUCTION_ACTIVE_POWER),
// Consumption
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION),
SUM_CONSUMPTION_ACTIVE_POWER));
@@ -781,10 +803,10 @@ public void manual_midday_test() throws Exception {
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.MANUAL) //
.setSellToGridLimitEnabled(true) //
@@ -792,17 +814,19 @@ public void manual_midday_test() throws Exception {
.setSellToGridLimitRampPercentage(5) //
.build()) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, 0) //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(ESS_CAPACITY, 10_000) //
- .input(ESS_SOC, 20) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", CAPACITY, 10_000) //
+ .input("ess0", SOC, 20) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
.output(TARGET_MINUTE, /* QuarterHour */ 1020) //
.output(DELAY_CHARGE_STATE, DelayChargeState.ACTIVE_LIMIT) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) //
- .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 1620));
+ .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 1620)) //
+ .deactivate();
}
@Test
@@ -813,11 +837,11 @@ public void hybridEss_manual_midday_test() throws Exception {
final var sum = new DummySum();
final var predictorManager = new DummyPredictorManager(
// Production
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION),
SUM_PRODUCTION_ACTIVE_POWER),
// Consumption
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION),
SUM_CONSUMPTION_ACTIVE_POWER));
@@ -829,10 +853,10 @@ public void hybridEss_manual_midday_test() throws Exception {
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.MANUAL) //
.setSellToGridLimitEnabled(true) //
@@ -840,18 +864,21 @@ public void hybridEss_manual_midday_test() throws Exception {
.setSellToGridLimitRampPercentage(5) //
.build()) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, 0) //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(ESS_CAPACITY, 10_000) //
- .input(ESS_SOC, 20) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", CAPACITY, 10_000) //
+ .input("ess0", SOC, 20) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
.input(START_EPOCH_SECONDS, 1630566000) //
- .input(SUM_PRODUCTION_DC_ACTUAL_POWER, 10_000).output(TARGET_MINUTE, /* QuarterHour */ 1020) //
+ .input(PRODUCTION_DC_ACTUAL_POWER, 10_000) //
+ .output(TARGET_MINUTE, /* QuarterHour */ 1020) //
.output(DELAY_CHARGE_STATE, DelayChargeState.NO_CHARGE_LIMIT) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 3350) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_FIXED) //
- .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null));
+ .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null)) //
+ .deactivate();
}
@@ -863,11 +890,11 @@ public void mode_off_test() throws Exception {
final var sum = new DummySum();
final var predictorManager = new DummyPredictorManager(
// Production
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION),
SUM_PRODUCTION_ACTIVE_POWER),
// Consumption
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION),
SUM_CONSUMPTION_ACTIVE_POWER));
@@ -879,10 +906,10 @@ public void mode_off_test() throws Exception {
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.OFF) //
.setSellToGridLimitEnabled(true) //
@@ -890,16 +917,18 @@ public void mode_off_test() throws Exception {
.setManualTargetTime("") //
.build()) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -7500) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
- .input(ESS_ACTIVE_POWER, 0) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7500) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
.input(START_EPOCH_SECONDS, 1630566000) //
.output(DELAY_CHARGE_STATE, DelayChargeState.DISABLED) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT) //
.output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -850) //
- .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 850)); //
+ .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 850)) //
+ .deactivate();
}
@Test
@@ -910,11 +939,11 @@ public void no_capacity_left_test() throws Exception {
final var sum = new DummySum();
final var predictorManager = new DummyPredictorManager(
// Production
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION),
SUM_PRODUCTION_ACTIVE_POWER),
// Consumption
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION),
SUM_CONSUMPTION_ACTIVE_POWER));
@@ -926,10 +955,10 @@ public void no_capacity_left_test() throws Exception {
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.AUTOMATIC) //
.setSellToGridLimitEnabled(true) //
@@ -937,18 +966,20 @@ public void no_capacity_left_test() throws Exception {
.setManualTargetTime("") //
.build()) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, 0) //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(ESS_CAPACITY, 10_000) //
- .input(ESS_SOC, 99) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) //
+ .input(SUM_PRODUCTION_ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", CAPACITY, 10_000) //
+ .input("ess0", SOC, 99) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
.input(START_EPOCH_SECONDS, 1630566000) //
// ess.getPower().getMinPower() (Maximum allowed charge power) is '0' because
// the referenced
// DummyManagedSymmetricEss has an apparent power of zero.
.output(DELAY_CHARGE_STATE, DelayChargeState.NO_REMAINING_CAPACITY) //
- .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null)); //
+ .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null)) //
+ .deactivate();
}
@Test
@@ -964,11 +995,11 @@ public void start_production_not_enough_test() throws Exception {
final var sum = new DummySum();
final var predictorManager = new DummyPredictorManager(
// Production
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION),
SUM_PRODUCTION_ACTIVE_POWER),
// Consumption
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION),
SUM_CONSUMPTION_ACTIVE_POWER));
@@ -980,10 +1011,10 @@ public void start_production_not_enough_test() throws Exception {
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.OFF) //
.setSellToGridLimitEnabled(true) //
@@ -1010,7 +1041,8 @@ public void start_production_not_enough_test() throws Exception {
.input(SUM_CONSUMPTION_ACTIVE_POWER, 6000) //
.output(DELAY_CHARGE_STATE, DelayChargeState.NOT_STARTED) //
.output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.NOT_STARTED) //
- .output(START_EPOCH_SECONDS, null)); //
+ .output(START_EPOCH_SECONDS, null)) //
+ .deactivate();
}
@Test
@@ -1026,11 +1058,11 @@ public void start_production_average_test() throws Exception {
final var sum = new DummySum();
final var predictorManager = new DummyPredictorManager(
// Production
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION),
SUM_PRODUCTION_ACTIVE_POWER),
// Consumption
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION),
SUM_CONSUMPTION_ACTIVE_POWER));
@@ -1042,10 +1074,10 @@ public void start_production_average_test() throws Exception {
.addReference("meter", METER) //
.addReference("sum", new DummySum()) //
.activate(MyConfig.create() //
- .setEssId(ESS_ID) //
- .setId(CTRL_ID) //
+ .setEssId("ess0") //
+ .setId("ctrlGridOptimizedCharge0") //
.setMaximumSellToGridPower(7_000) //
- .setMeterId(METER_ID) //
+ .setMeterId("meter0") //
.setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) //
.setMode(Mode.MANUAL) //
.setSellToGridLimitEnabled(true) //
@@ -1151,11 +1183,11 @@ public void start_production_average_test() throws Exception {
.input(SUM_PRODUCTION_ACTIVE_POWER, 2000) // Avg: 1166
.input(SUM_CONSUMPTION_ACTIVE_POWER, 1000) //
.input(START_EPOCH_SECONDS, null) //
- .input(METER_ACTIVE_POWER, 0) //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(ESS_CAPACITY, 10_000) //
- .input(ESS_SOC, 20) //
- .input(ESS_MAX_APPARENT_POWER, 10_000) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) //
+ .input("ess0", CAPACITY, 10_000) //
+ .input("ess0", SOC, 20) //
+ .input("ess0", MAX_APPARENT_POWER, 10_000) //
// Epoch seconds at 2020-01-01 00:00:00: 1577836800 (Clock is not updated)
.output(START_EPOCH_SECONDS, 1577836800L) //
@@ -1163,12 +1195,12 @@ public void start_production_average_test() throws Exception {
.output(DELAY_CHARGE_STATE, DelayChargeState.AVOID_LOW_CHARGING) //
.output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) //
.output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) //
- .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)); // 506 W is not efficient
+ .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)) // 506 W is not efficient
+ .deactivate();
}
@Test
public void getCalculatedPowerLimit_middayTest() throws Exception {
-
/*
* Initial values
*/
@@ -1412,80 +1444,6 @@ private void testLogic(String description, Integer[] productionPrediction, Integ
});
}
- private DelayChargeResultState testOneDay(String testDescription, Integer[] productionPrediction,
- Integer[] consumptionPrediction, int soc, Optional targetMinuteOpt, int capacity,
- int maxApparentPower, int allowedChargePower, DelayChargeRiskLevel riskLevel, Integer[] productionActual,
- Integer[] consumptionActual, float resultBuffer) {
- DelayChargeResultState resultState;
- DelayChargeResult newLogic = ControllerEssGridOptimizedChargeImplTest.testOneDay(testDescription,
- productionPrediction, consumptionPrediction, soc, targetMinuteOpt, capacity, maxApparentPower,
- allowedChargePower, riskLevel, productionActual, consumptionActual, false);
-
- DelayChargeResult oldLogic = ControllerEssGridOptimizedChargeImplTest.testOneDay(testDescription,
- productionPrediction, consumptionPrediction, soc, targetMinuteOpt, capacity, maxApparentPower,
- allowedChargePower, riskLevel, productionActual, consumptionActual, true);
-
- if (newLogic.getFinalSoc() + resultBuffer < oldLogic.getFinalSoc()) {
- resultState = DelayChargeResultState.WARNING;
- } else if (newLogic.getFinalSoc() - resultBuffer > oldLogic.getFinalSoc()) {
- resultState = DelayChargeResultState.PERFECT;
- } else {
- resultState = DelayChargeResultState.OK;
- }
-
- float unefficientEnergy = Math
- .round(newLogic.getChargedEnergyWithLowPower() / newLogic.getChargedEnergy() * 1000) / 10.0f;
- float unefficientEnergyOld = Math
- .round(oldLogic.getChargedEnergyWithLowPower() / oldLogic.getChargedEnergy() * 1000) / 10.0f;
- System.out.println(resultState.text + "\t" + testDescription + " \t(New: "
- + Math.round(newLogic.getFinalSoc() * 100) / 100.0 + " | Old: "
- + Math.round(oldLogic.getFinalSoc() * 100) / 100.0 + ") \t Energy: (New: "
- + newLogic.getChargedEnergy() + "[" + newLogic.getChargedEnergyWithLowPower() + " -> "
- + unefficientEnergy + "%] | Old: " + oldLogic.getChargedEnergy() + "["
- + oldLogic.getChargedEnergyWithLowPower() + " -> " + unefficientEnergyOld + "%])");
-
- // fail("New logic results in a lower SoC (New: " + newLogic.getFinalSoc() + " |
- // Old: "+ oldLogic.getFinalSoc() + ") - " + testDescription);
- return resultState;
- }
-
- private static class DelayChargeResult {
-
- private float finalSoc;
- private float chargedEnergy;
- private float chargedEnergyWithLowPower;
-
- public DelayChargeResult(float finalSoc, float chargedEnergy, float chargedEnergyWithLowPower) {
- this.finalSoc = finalSoc;
- this.chargedEnergy = chargedEnergy;
- this.chargedEnergyWithLowPower = chargedEnergyWithLowPower;
- }
-
- public float getFinalSoc() {
- return this.finalSoc;
- }
-
- public float getChargedEnergy() {
- return this.chargedEnergy;
- }
-
- public float getChargedEnergyWithLowPower() {
- return this.chargedEnergyWithLowPower;
- }
- }
-
- private static enum DelayChargeResultState {
- OK("OK - SoC as bevore"), //
- WARNING("WARNING - Lower SoC"), //
- PERFECT("PERFECT - Higher SoC");
-
- private String text;
-
- DelayChargeResultState(String text) {
- this.text = text;
- }
- }
-
@SuppressWarnings("deprecation")
private static DelayChargeResult testOneDay(String testDescription, Integer[] productionPrediction,
Integer[] consumptionPrediction, int soc, Optional targetMinuteOpt, int capacity,
@@ -1626,6 +1584,80 @@ private static DelayChargeResult testOneDay(String testDescription, Integer[] pr
return new DelayChargeResult(socFloat, totoalActivePower * 0.25f, totoalActivePowerLessEfficiency * 0.25f);
}
+ private DelayChargeResultState testOneDay(String testDescription, Integer[] productionPrediction,
+ Integer[] consumptionPrediction, int soc, Optional targetMinuteOpt, int capacity,
+ int maxApparentPower, int allowedChargePower, DelayChargeRiskLevel riskLevel, Integer[] productionActual,
+ Integer[] consumptionActual, float resultBuffer) {
+ DelayChargeResultState resultState;
+ DelayChargeResult newLogic = ControllerEssGridOptimizedChargeImplTest.testOneDay(testDescription,
+ productionPrediction, consumptionPrediction, soc, targetMinuteOpt, capacity, maxApparentPower,
+ allowedChargePower, riskLevel, productionActual, consumptionActual, false);
+
+ DelayChargeResult oldLogic = ControllerEssGridOptimizedChargeImplTest.testOneDay(testDescription,
+ productionPrediction, consumptionPrediction, soc, targetMinuteOpt, capacity, maxApparentPower,
+ allowedChargePower, riskLevel, productionActual, consumptionActual, true);
+
+ if (newLogic.getFinalSoc() + resultBuffer < oldLogic.getFinalSoc()) {
+ resultState = DelayChargeResultState.WARNING;
+ } else if (newLogic.getFinalSoc() - resultBuffer > oldLogic.getFinalSoc()) {
+ resultState = DelayChargeResultState.PERFECT;
+ } else {
+ resultState = DelayChargeResultState.OK;
+ }
+
+ float unefficientEnergy = Math
+ .round(newLogic.getChargedEnergyWithLowPower() / newLogic.getChargedEnergy() * 1000) / 10.0f;
+ float unefficientEnergyOld = Math
+ .round(oldLogic.getChargedEnergyWithLowPower() / oldLogic.getChargedEnergy() * 1000) / 10.0f;
+ System.out.println(resultState.text + "\t" + testDescription + " \t(New: "
+ + Math.round(newLogic.getFinalSoc() * 100) / 100.0 + " | Old: "
+ + Math.round(oldLogic.getFinalSoc() * 100) / 100.0 + ") \t Energy: (New: "
+ + newLogic.getChargedEnergy() + "[" + newLogic.getChargedEnergyWithLowPower() + " -> "
+ + unefficientEnergy + "%] | Old: " + oldLogic.getChargedEnergy() + "["
+ + oldLogic.getChargedEnergyWithLowPower() + " -> " + unefficientEnergyOld + "%])");
+
+ // fail("New logic results in a lower SoC (New: " + newLogic.getFinalSoc() + " |
+ // Old: "+ oldLogic.getFinalSoc() + ") - " + testDescription);
+ return resultState;
+ }
+
+ private static class DelayChargeResult {
+
+ private float finalSoc;
+ private float chargedEnergy;
+ private float chargedEnergyWithLowPower;
+
+ public DelayChargeResult(float finalSoc, float chargedEnergy, float chargedEnergyWithLowPower) {
+ this.finalSoc = finalSoc;
+ this.chargedEnergy = chargedEnergy;
+ this.chargedEnergyWithLowPower = chargedEnergyWithLowPower;
+ }
+
+ public float getFinalSoc() {
+ return this.finalSoc;
+ }
+
+ public float getChargedEnergy() {
+ return this.chargedEnergy;
+ }
+
+ public float getChargedEnergyWithLowPower() {
+ return this.chargedEnergyWithLowPower;
+ }
+ }
+
+ private static enum DelayChargeResultState {
+ OK("OK - SoC as bevore"), //
+ WARNING("WARNING - Lower SoC"), //
+ PERFECT("PERFECT - Higher SoC");
+
+ private String text;
+
+ DelayChargeResultState(String text) {
+ this.text = text;
+ }
+ }
+
@Test
public void calculateAvailEnergy_test() throws Exception {
final var clock = new TimeLeapClock(Instant.parse("2020-01-01T08:00:00.00Z"), ZoneOffset.UTC);
@@ -1634,11 +1666,11 @@ public void calculateAvailEnergy_test() throws Exception {
final var sum = new DummySum();
final var predictorManager = new DummyPredictorManager(
// Production
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, midnight, DEFAULT_PRODUCTION_PREDICTION),
SUM_PRODUCTION_ACTIVE_POWER),
// Consumption
- new DummyPredictor(PREDICTOR_ID, cm,
+ new DummyPredictor("predictor0", cm,
Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, midnight, DEFAULT_CONSUMPTION_PREDICTION),
SUM_CONSUMPTION_ACTIVE_POWER));
diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/EnergyScheduleHandlerTest.java b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/EnergyScheduleHandlerTest.java
new file mode 100644
index 00000000000..0f33e93c960
--- /dev/null
+++ b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/EnergyScheduleHandlerTest.java
@@ -0,0 +1,40 @@
+package io.openems.edge.controller.ess.gridoptimizedcharge;
+
+import static org.junit.Assert.assertEquals;
+
+import java.time.LocalTime;
+
+import org.apache.commons.math3.optim.nonlinear.scalar.GoalType;
+import org.junit.Test;
+
+import io.openems.edge.energy.api.EnergyScheduleHandler;
+import io.openems.edge.energy.api.EnergyScheduleHandler.AbstractEnergyScheduleHandler;
+import io.openems.edge.energy.api.simulation.Coefficient;
+import io.openems.edge.energy.api.simulation.EnergyFlow;
+import io.openems.edge.energy.api.simulation.GlobalSimulationsContext;
+import io.openems.edge.energy.api.simulation.OneSimulationContext;
+import io.openems.edge.energy.api.test.DummyGlobalSimulationsContext;
+
+public class EnergyScheduleHandlerTest {
+
+ @Test
+ public void testManual() {
+ var esh = ControllerEssGridOptimizedChargeImpl.buildEnergyScheduleHandler(//
+ () -> Mode.MANUAL, //
+ () -> LocalTime.of(10, 00));
+ var gsc = DummyGlobalSimulationsContext.fromHandlers(esh);
+ ((AbstractEnergyScheduleHandler>) esh /* this is safe */).initialize(gsc);
+
+ assertEquals(3894, getEssMaxCharge(gsc, esh, 0));
+ assertEquals(1214, getEssMaxCharge(gsc, esh, 26));
+ assertEquals(4000, getEssMaxCharge(gsc, esh, 40));
+ }
+
+ private static int getEssMaxCharge(GlobalSimulationsContext gsc, EnergyScheduleHandler esh, int periodIndex) {
+ var osc = OneSimulationContext.from(gsc);
+ var period = gsc.periods().get(periodIndex);
+ var ef = EnergyFlow.Model.from(osc, period);
+ ((EnergyScheduleHandler.WithOnlyOneState>) esh).simulatePeriod(OneSimulationContext.from(gsc), period, ef);
+ return ((int) ef.getExtremeCoefficientValue(Coefficient.ESS, GoalType.MINIMIZE)) * -1;
+ }
+}
diff --git a/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/test/io/openems/edge/controller/ess/hybrid/surplusfeedtogrid/ControllerEssHybridSurplusFeedToGridImplTest.java b/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/test/io/openems/edge/controller/ess/hybrid/surplusfeedtogrid/ControllerEssHybridSurplusFeedToGridImplTest.java
index de89c02eda6..94d0e84fbb1 100644
--- a/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/test/io/openems/edge/controller/ess/hybrid/surplusfeedtogrid/ControllerEssHybridSurplusFeedToGridImplTest.java
+++ b/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/test/io/openems/edge/controller/ess/hybrid/surplusfeedtogrid/ControllerEssHybridSurplusFeedToGridImplTest.java
@@ -1,8 +1,10 @@
package io.openems.edge.controller.ess.hybrid.surplusfeedtogrid;
+import static io.openems.edge.controller.ess.hybrid.surplusfeedtogrid.ControllerEssHybridSurplusFeedToGrid.ChannelId.SURPLUS_FEED_TO_GRID_IS_LIMITED;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_GREATER_OR_EQUALS;
+
import org.junit.Test;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyConfigurationAdmin;
import io.openems.edge.controller.test.ControllerTest;
@@ -10,41 +12,33 @@
public class ControllerEssHybridSurplusFeedToGridImplTest {
- private static final String CTRL_ID = "ctrl0";
-
- private static final ChannelAddress CTRL_SURPLUS_FEED_TO_GRID_IS_LIMITED = new ChannelAddress(CTRL_ID,
- "SurplusFeedToGridIsLimited");
-
- private static final String ESS_ID = "ess0";
-
- private static final ChannelAddress ESS_SET_ACTIVE_POWER_GREATER_OR_EQUALS = new ChannelAddress(ESS_ID,
- "SetActivePowerGreaterOrEquals");
-
@Test
public void test() throws Exception {
- final var ess = new DummyHybridEss(ESS_ID);
+ final var ess = new DummyHybridEss("ess0");
final var test = new ControllerTest(new ControllerEssHybridSurplusFeedToGridImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("ess", ess) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.build());
ess.withSurplusPower(null);
test.next(new TestCase() //
- .output(ESS_SET_ACTIVE_POWER_GREATER_OR_EQUALS, null));
+ .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, null));
ess.withSurplusPower(5000);
ess.withMaxApparentPower(10000);
test.next(new TestCase() //
- .output(CTRL_SURPLUS_FEED_TO_GRID_IS_LIMITED, false) //
- .output(ESS_SET_ACTIVE_POWER_GREATER_OR_EQUALS, 5000));
+ .output(SURPLUS_FEED_TO_GRID_IS_LIMITED, false) //
+ .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, 5000));
ess.withSurplusPower(5000);
ess.withMaxApparentPower(2000);
test.next(new TestCase() //
- .output(CTRL_SURPLUS_FEED_TO_GRID_IS_LIMITED, true) //
- .output(ESS_SET_ACTIVE_POWER_GREATER_OR_EQUALS, 2000));
+ .output(SURPLUS_FEED_TO_GRID_IS_LIMITED, true) //
+ .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, 2000)) //
+
+ .deactivate();
}
}
\ No newline at end of file
diff --git a/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aImpl.java b/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aImpl.java
index a9c1082637f..f9ccb3b36ea 100644
--- a/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aImpl.java
+++ b/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aImpl.java
@@ -11,6 +11,7 @@
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.osgi.service.metatype.annotations.Designate;
+
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.channel.BooleanReadChannel;
diff --git a/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aTest.java b/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aTest.java
index 22405060dc7..b7d9cae4a52 100644
--- a/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aTest.java
+++ b/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aTest.java
@@ -1,61 +1,57 @@
package io.openems.edge.controller.ess.limiter14a;
+import static io.openems.edge.common.sum.Sum.ChannelId.GRID_MODE;
+import static io.openems.edge.controller.ess.limiter14a.ControllerEssLimiter14a.ChannelId.RESTRICTION_MODE;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_GREATER_OR_EQUALS;
+import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0;
+
import org.junit.Test;
import io.openems.common.exceptions.OpenemsException;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.sum.DummySum;
import io.openems.edge.common.sum.GridMode;
+import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.common.test.DummyConfigurationAdmin;
-import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.controller.test.ControllerTest;
import io.openems.edge.ess.test.DummyManagedSymmetricEss;
-import io.openems.edge.timedata.test.DummyTimedata;
import io.openems.edge.io.test.DummyInputOutput;
+import io.openems.edge.timedata.test.DummyTimedata;
public class ControllerEssLimiter14aTest {
- private static final String ESS_ID = "ess0";
- private static final String CTRL_ID = "ctrlEssLimiter14a0";
-
- private static final ChannelAddress RESTRICTION_MODE = new ChannelAddress(CTRL_ID, "RestrictionMode");
- private static final ChannelAddress GPIO = new ChannelAddress("io0", "InputOutput0");
- private static final ChannelAddress LIMITATION = new ChannelAddress(ESS_ID, "SetActivePowerGreaterOrEquals");
- private static final ChannelAddress GRID_MODE = new ChannelAddress("_sum", "GridMode");
-
@Test
public void testController() throws OpenemsException, Exception {
new ControllerTest(new ControllerEssLimiter14aImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager()) //
.addReference("timedata", new DummyTimedata("timedata0")) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.addReference("sum", new DummySum()) //
.addComponent(new DummyInputOutput("io0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID)//
+ .setId("ctrl0") //
+ .setEssId("ess0")//
.setInputChannelAddress("io0/InputOutput0")//
.build())
.next(new TestCase() //
// Since logic is reversed
- .input(GPIO, false) //
- .input(GRID_MODE, GridMode.ON_GRID)
- .output(LIMITATION, -4200)
+ .input("io0", INPUT_OUTPUT0, false) //
+ .input(GRID_MODE, GridMode.ON_GRID) //
+ .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, -4200) //
.output(RESTRICTION_MODE, RestrictionMode.ON)) //
.next(new TestCase() //
- .input(GPIO, null) //
- .output(LIMITATION, null)) //
+ .input("io0", INPUT_OUTPUT0, null) //
+ .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, null)) //
.next(new TestCase() //
- .input(GPIO, 1) //
+ .input("io0", INPUT_OUTPUT0, 1) //
.input(GRID_MODE, GridMode.OFF_GRID) //
.output(RESTRICTION_MODE, RestrictionMode.OFF)) //
.next(new TestCase() //
- .input(GPIO, false) //
+ .input("io0", INPUT_OUTPUT0, false) //
.input(GRID_MODE, GridMode.OFF_GRID) //
- .output(LIMITATION, null)) //
- ;
+ .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, null)) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.ess.limittotaldischarge/bnd.bnd b/io.openems.edge.controller.ess.limittotaldischarge/bnd.bnd
index 0f4d0e0a95c..db75c019243 100644
--- a/io.openems.edge.controller.ess.limittotaldischarge/bnd.bnd
+++ b/io.openems.edge.controller.ess.limittotaldischarge/bnd.bnd
@@ -8,7 +8,8 @@ Bundle-Version: 1.0.0.${tstamp}
io.openems.common,\
io.openems.edge.common,\
io.openems.edge.controller.api,\
- io.openems.edge.ess.api
+ io.openems.edge.energy.api,\
+ io.openems.edge.ess.api,\
-testpath: \
${testpath}
diff --git a/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImpl.java b/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImpl.java
index a75ecf85f1b..e826546a526 100644
--- a/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImpl.java
+++ b/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImpl.java
@@ -1,8 +1,12 @@
package io.openems.edge.controller.ess.limittotaldischarge;
+import static io.openems.edge.energy.api.EnergyUtils.socToEnergy;
+import static java.lang.Math.max;
+
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
+import java.util.function.IntSupplier;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
@@ -19,6 +23,8 @@
import io.openems.edge.common.component.ComponentManager;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.controller.api.Controller;
+import io.openems.edge.energy.api.EnergySchedulable;
+import io.openems.edge.energy.api.EnergyScheduleHandler;
import io.openems.edge.ess.api.ManagedSymmetricEss;
import io.openems.edge.ess.power.api.Phase;
import io.openems.edge.ess.power.api.Pwr;
@@ -30,9 +36,10 @@
configurationPolicy = ConfigurationPolicy.REQUIRE //
)
public class ControllerEssLimitTotalDischargeImpl extends AbstractOpenemsComponent
- implements ControllerEssLimitTotalDischarge, Controller, OpenemsComponent {
+ implements ControllerEssLimitTotalDischarge, EnergySchedulable, Controller, OpenemsComponent {
private final Logger log = LoggerFactory.getLogger(ControllerEssLimitTotalDischargeImpl.class);
+ private final EnergyScheduleHandler energyScheduleHandler;
@Reference
private ComponentManager componentManager;
@@ -55,6 +62,8 @@ public ControllerEssLimitTotalDischargeImpl() {
Controller.ChannelId.values(), //
ControllerEssLimitTotalDischarge.ChannelId.values() //
);
+ this.energyScheduleHandler = buildEnergyScheduleHandler(//
+ () -> this.minSoc);
}
@Activate
@@ -211,4 +220,27 @@ private boolean changeState(State nextState) {
return false;
}
}
+
+ /**
+ * Builds the {@link EnergyScheduleHandler}.
+ *
+ *
+ * This is public so that it can be used by the EnergyScheduler integration
+ * test.
+ *
+ * @param minSoc a supplier for the configured minSoc
+ * @return a {@link EnergyScheduleHandler}
+ */
+ public static EnergyScheduleHandler buildEnergyScheduleHandler(IntSupplier minSoc) {
+ return EnergyScheduleHandler.of(//
+ simContext -> socToEnergy(simContext.ess().totalEnergy(), minSoc.getAsInt()), //
+ (simContext, period, energyFlow, minEnergy) -> {
+ energyFlow.setEssMaxDischarge(max(0, simContext.getEssInitial() - minEnergy));
+ });
+ }
+
+ @Override
+ public EnergyScheduleHandler getEnergyScheduleHandler() {
+ return this.energyScheduleHandler;
+ }
}
diff --git a/io.openems.edge.controller.ess.limittotaldischarge/test/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImplTest.java b/io.openems.edge.controller.ess.limittotaldischarge/test/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImplTest.java
index d86836ed672..f240abc3ed9 100644
--- a/io.openems.edge.controller.ess.limittotaldischarge/test/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImplTest.java
+++ b/io.openems.edge.controller.ess.limittotaldischarge/test/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImplTest.java
@@ -1,11 +1,14 @@
package io.openems.edge.controller.ess.limittotaldischarge;
+import static io.openems.edge.common.test.TestUtils.createDummyClock;
+import static io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge.ChannelId.AWAITING_HYSTERESIS;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_LESS_OR_EQUALS;
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC;
+
import java.time.temporal.ChronoUnit;
import org.junit.Test;
-import io.openems.common.test.TimeLeapClock;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.controller.test.ControllerTest;
@@ -13,47 +16,39 @@
public class ControllerEssLimitTotalDischargeImplTest {
- private static final String CTRL_ID = "ctrl0";
- private static final ChannelAddress CTRL_AWAITING_HYSTERESIS = new ChannelAddress(CTRL_ID, "AwaitingHysteresis");
-
- private static final String ESS_ID = "ess0";
- private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc");
- private static final ChannelAddress ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(ESS_ID,
- "SetActivePowerLessOrEquals");
-
@Test
public void test() throws Exception {
- // Initialize mocked Clock
- final var clock = new TimeLeapClock();
+ final var clock = createDummyClock();
new ControllerTest(new ControllerEssLimitTotalDischargeImpl()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addComponent(new DummyManagedSymmetricEss(ESS_ID) //
+ .addComponent(new DummyManagedSymmetricEss("ess0") //
.withSoc(20) //
.withCapacity(9000)) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setMinSoc(15) //
.setForceChargeSoc(10) //
.setForceChargePower(1000) //
.build())
.next(new TestCase() //
- .input(ESS_SOC, 20) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)//
- .output(CTRL_AWAITING_HYSTERESIS, false)) //
+ .input("ess0", SOC, 20) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)//
+ .output(AWAITING_HYSTERESIS, false)) //
.next(new TestCase() //
- .input(ESS_SOC, 15) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, 0) //
- .output(CTRL_AWAITING_HYSTERESIS, false)) //
+ .input("ess0", SOC, 15) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 0) //
+ .output(AWAITING_HYSTERESIS, false)) //
.next(new TestCase() //
- .input(ESS_SOC, 16) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, 0) //
- .output(CTRL_AWAITING_HYSTERESIS, true)) //
+ .input("ess0", SOC, 16) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 0) //
+ .output(AWAITING_HYSTERESIS, true)) //
.next(new TestCase() //
.timeleap(clock, 6, ChronoUnit.MINUTES) //
- .input(ESS_SOC, 16) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, null) //
- .output(CTRL_AWAITING_HYSTERESIS, false));
+ .input("ess0", SOC, 16) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null) //
+ .output(AWAITING_HYSTERESIS, false)) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.ess.linearpowerband/test/io/openems/edge/controller/ess/linearpowerband/ControllerEssLinearPowerBandImplTest.java b/io.openems.edge.controller.ess.linearpowerband/test/io/openems/edge/controller/ess/linearpowerband/ControllerEssLinearPowerBandImplTest.java
index 676304b3b6e..6e85c146ed7 100644
--- a/io.openems.edge.controller.ess.linearpowerband/test/io/openems/edge/controller/ess/linearpowerband/ControllerEssLinearPowerBandImplTest.java
+++ b/io.openems.edge.controller.ess.linearpowerband/test/io/openems/edge/controller/ess/linearpowerband/ControllerEssLinearPowerBandImplTest.java
@@ -1,8 +1,10 @@
package io.openems.edge.controller.ess.linearpowerband;
+import static io.openems.edge.controller.ess.linearpowerband.ControllerEssLinearPowerBand.ChannelId.STATE_MACHINE;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+
import org.junit.Test;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyConfigurationAdmin;
import io.openems.edge.controller.test.ControllerTest;
@@ -10,21 +12,14 @@
public class ControllerEssLinearPowerBandImplTest {
- private static final String CTRL_ID = "ctrl0";
- private static final String ESS_ID = "ess0";
-
- private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine");
- private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID,
- "SetActivePowerEquals");
-
@Test
public void test() throws Exception {
new ControllerTest(new ControllerEssLinearPowerBandImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setMinPower(-1000) //
.setMaxPower(1000) //
.setAdjustPower(300) //
@@ -32,49 +27,49 @@ public void test() throws Exception {
.build()) //
.next(new TestCase() //
.output(STATE_MACHINE, State.DOWNWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -300)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -300)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.DOWNWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -600)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -600)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.DOWNWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -900)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -900)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.DOWNWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.UPWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -700)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -700)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.UPWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -400)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -400)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.UPWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -100)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -100)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.UPWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 200)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 200)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.UPWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 500)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 500)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.UPWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 800)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 800)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.UPWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 1000)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 1000)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.DOWNWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 700)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 700)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.DOWNWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 400)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 400)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.DOWNWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 100)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 100)) //
.next(new TestCase() //
.output(STATE_MACHINE, State.DOWNWARDS) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -200)) //
- ;
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -200)) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.ess.mindischargeperiod/test/io/openems/edge/controller/ess/mindischargeperiod/ControllerEssMinimumDischargePowerImplTest.java b/io.openems.edge.controller.ess.mindischargeperiod/test/io/openems/edge/controller/ess/mindischargeperiod/ControllerEssMinimumDischargePowerImplTest.java
index e2a90e1983b..53afa7614c7 100644
--- a/io.openems.edge.controller.ess.mindischargeperiod/test/io/openems/edge/controller/ess/mindischargeperiod/ControllerEssMinimumDischargePowerImplTest.java
+++ b/io.openems.edge.controller.ess.mindischargeperiod/test/io/openems/edge/controller/ess/mindischargeperiod/ControllerEssMinimumDischargePowerImplTest.java
@@ -7,21 +7,18 @@
public class ControllerEssMinimumDischargePowerImplTest {
- private static final String CTRL_ID = "ctrl0";
- private static final String ESS_ID = "ess0";
-
@Test
public void test() throws Exception {
new ControllerTest(new ControllerEssMinimumDischargePowerImpl()) //
.addReference("componentManager", new DummyComponentManager()) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setActivateDischargePower(0) //
.setDischargeTime(0) //
.setMinDischargePower(0) //
- .build()); //
- ;
+ .build()) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/test/io/openems/edge/controller/ess/reactivepowervoltagecharacteristic/ControllerEssReactivePowerVoltageCharacteristicImplTest.java b/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/test/io/openems/edge/controller/ess/reactivepowervoltagecharacteristic/ControllerEssReactivePowerVoltageCharacteristicImplTest.java
index 1b4492ede55..442be9ddfa2 100644
--- a/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/test/io/openems/edge/controller/ess/reactivepowervoltagecharacteristic/ControllerEssReactivePowerVoltageCharacteristicImplTest.java
+++ b/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/test/io/openems/edge/controller/ess/reactivepowervoltagecharacteristic/ControllerEssReactivePowerVoltageCharacteristicImplTest.java
@@ -1,14 +1,15 @@
package io.openems.edge.controller.ess.reactivepowervoltagecharacteristic;
-import java.time.Instant;
-import java.time.ZoneOffset;
-import java.time.temporal.ChronoUnit;
+import static io.openems.common.utils.JsonUtils.buildJsonArray;
+import static io.openems.common.utils.JsonUtils.buildJsonObject;
+import static io.openems.edge.common.test.TestUtils.createDummyClock;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_REACTIVE_POWER_EQUALS;
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.MAX_APPARENT_POWER;
+import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.VOLTAGE;
+import static java.time.temporal.ChronoUnit.SECONDS;
import org.junit.Test;
-import io.openems.common.test.TimeLeapClock;
-import io.openems.common.types.ChannelAddress;
-import io.openems.common.utils.JsonUtils;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.common.test.DummyConfigurationAdmin;
@@ -18,86 +19,79 @@
public class ControllerEssReactivePowerVoltageCharacteristicImplTest {
- private static final String CTRL_ID = "ctrlReactivePowerVoltageCharacteristic0";
- private static final String ESS_ID = "ess0";
- private static final String METER_ID = "meter0";
- private static final ChannelAddress ESS_REACTIVE_POWER = new ChannelAddress(ESS_ID, "SetReactivePowerEquals");
- private static final ChannelAddress METER_VOLTAGE = new ChannelAddress(METER_ID, "Voltage");
- private static final ChannelAddress MAX_APPARENT_POWER = new ChannelAddress(ESS_ID, "MaxApparentPower");
-
@Test
public void test() throws Exception {
- final var clock = new TimeLeapClock(Instant.parse("2020-10-05T14:00:00.00Z"), ZoneOffset.UTC);
+ final var clock = createDummyClock();
new ControllerTest(new ControllerEssReactivePowerVoltageCharacteristicImpl())//
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
- .addReference("meter", new DummyElectricityMeter(METER_ID)) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
+ .addReference("meter", new DummyElectricityMeter("meter0")) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
.activate(MyConfig.create()//
- .setId(CTRL_ID)//
- .setEssId(ESS_ID)//
- .setMeterId(METER_ID)//
+ .setId("ctrl0")//
+ .setEssId("ess0")//
+ .setMeterId("meter0")//
.setNominalVoltage(240)//
.setWaitForHysteresis(5)//
- .setPowerVoltConfig(JsonUtils.buildJsonArray()//
- .add(JsonUtils.buildJsonObject()//
+ .setPowerVoltConfig(buildJsonArray()//
+ .add(buildJsonObject()//
.addProperty("voltageRatio", 0.9) //
.addProperty("percent", 60) //
.build()) //
- .add(JsonUtils.buildJsonObject()//
+ .add(buildJsonObject()//
.addProperty("voltageRatio", 0.93) //
.addProperty("percent", 0) //
.build()) //
- .add(JsonUtils.buildJsonObject()//
+ .add(buildJsonObject()//
.addProperty("voltageRatio", 1.07) //
.addProperty("percent", 0) //
.build()) //
- .add(JsonUtils.buildJsonObject()//
+ .add(buildJsonObject()//
.addProperty("voltageRatio", 1.1) //
.addProperty("percent", -60) //
.build() //
).build().toString() //
).build()) //
.next(new TestCase("First Input") //
- .input(METER_VOLTAGE, 240_000) // [mV]
- .input(MAX_APPARENT_POWER, 10_000)) // [VA]
+ .input("meter0", VOLTAGE, 240_000) // [mV]
+ .input("ess0", MAX_APPARENT_POWER, 10_000)) // [VA]
.next(new TestCase() //
- .output(ESS_REACTIVE_POWER, 0))//
+ .output("ess0", SET_REACTIVE_POWER_EQUALS, 0))//
.next(new TestCase("Second Input") //
- .timeleap(clock, 5, ChronoUnit.SECONDS)//
- .input(METER_VOLTAGE, 216_000) // [mV]
- .input(MAX_APPARENT_POWER, 10_000) // [VA]
- .output(ESS_REACTIVE_POWER, 6000))//
+ .timeleap(clock, 5, SECONDS)//
+ .input("meter0", VOLTAGE, 216_000) // [mV]
+ .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA]
+ .output("ess0", SET_REACTIVE_POWER_EQUALS, 6000))//
.next(new TestCase("Third Input")//
- .timeleap(clock, 5, ChronoUnit.SECONDS)//
- .input(METER_VOLTAGE, 220_000) // [mV]
- .input(MAX_APPARENT_POWER, 10_000) // [VA]
- .output(ESS_REACTIVE_POWER, 2600))//
+ .timeleap(clock, 5, SECONDS)//
+ .input("meter0", VOLTAGE, 220_000) // [mV]
+ .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA]
+ .output("ess0", SET_REACTIVE_POWER_EQUALS, 2600))//
.next(new TestCase()//
- .timeleap(clock, 5, ChronoUnit.SECONDS)//
- .input(METER_VOLTAGE, 223_000) // [mV]
- .input(MAX_APPARENT_POWER, 10_000) // [VA]
- .output(ESS_REACTIVE_POWER, 100))//
+ .timeleap(clock, 5, SECONDS)//
+ .input("meter0", VOLTAGE, 223_000) // [mV]
+ .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA]
+ .output("ess0", SET_REACTIVE_POWER_EQUALS, 100))//
.next(new TestCase()//
- .timeleap(clock, 5, ChronoUnit.SECONDS)//
- .input(METER_VOLTAGE, 223_200) // [mV]
- .input(MAX_APPARENT_POWER, 10_000) // [VA]
- .output(ESS_REACTIVE_POWER, 0))//
+ .timeleap(clock, 5, SECONDS)//
+ .input("meter0", VOLTAGE, 223_200) // [mV]
+ .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA]
+ .output("ess0", SET_REACTIVE_POWER_EQUALS, 0))//
.next(new TestCase()//
- .timeleap(clock, 5, ChronoUnit.SECONDS)//
- .input(METER_VOLTAGE, 256_800) // [mV]
- .input(MAX_APPARENT_POWER, 10_000) // [VA]
- .output(ESS_REACTIVE_POWER, 0))//
+ .timeleap(clock, 5, SECONDS)//
+ .input("meter0", VOLTAGE, 256_800) // [mV]
+ .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA]
+ .output("ess0", SET_REACTIVE_POWER_EQUALS, 0))//
.next(new TestCase()//
- .timeleap(clock, 5, ChronoUnit.SECONDS)//
- .input(METER_VOLTAGE, 260_000) // [mV]
- .input(MAX_APPARENT_POWER, 10_000) // [VA]
- .output(ESS_REACTIVE_POWER, -2600))//
+ .timeleap(clock, 5, SECONDS)//
+ .input("meter0", VOLTAGE, 260_000) // [mV]
+ .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA]
+ .output("ess0", SET_REACTIVE_POWER_EQUALS, -2600))//
.next(new TestCase()//
- .timeleap(clock, 5, ChronoUnit.SECONDS)//
- .input(METER_VOLTAGE, 264_000) // [mV]
- .input(MAX_APPARENT_POWER, 10_000) // [VA]
- .output(ESS_REACTIVE_POWER, -6000))//
- ;
+ .timeleap(clock, 5, SECONDS)//
+ .input("meter0", VOLTAGE, 264_000) // [mV]
+ .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA]
+ .output("ess0", SET_REACTIVE_POWER_EQUALS, -6000)) //
+ .deactivate();
}
}
diff --git a/io.openems.edge.controller.ess.selltogridlimit/test/io/openems/edge/controller/symmetric/selltogridlimit/ControllerEssSellToGridLimitImplTest.java b/io.openems.edge.controller.ess.selltogridlimit/test/io/openems/edge/controller/symmetric/selltogridlimit/ControllerEssSellToGridLimitImplTest.java
index 5c191d0c4f5..0870799ee2a 100644
--- a/io.openems.edge.controller.ess.selltogridlimit/test/io/openems/edge/controller/symmetric/selltogridlimit/ControllerEssSellToGridLimitImplTest.java
+++ b/io.openems.edge.controller.ess.selltogridlimit/test/io/openems/edge/controller/symmetric/selltogridlimit/ControllerEssSellToGridLimitImplTest.java
@@ -1,47 +1,39 @@
package io.openems.edge.controller.symmetric.selltogridlimit;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_LESS_OR_EQUALS;
+
import org.junit.Test;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyConfigurationAdmin;
import io.openems.edge.controller.test.ControllerTest;
+import io.openems.edge.ess.api.SymmetricEss;
import io.openems.edge.ess.test.DummyManagedSymmetricEss;
+import io.openems.edge.meter.api.ElectricityMeter;
import io.openems.edge.meter.test.DummyElectricityMeter;
public class ControllerEssSellToGridLimitImplTest {
- private static final String CTRL_ID = "ctrl0";
-
- private static final String ESS_ID = "ess0";
-
- private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower");
- private static final ChannelAddress ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(ESS_ID,
- "SetActivePowerLessOrEquals");
-
- private static final String METER_ID = "meter00";
-
- private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower");
-
@Test
public void test() throws Exception {
new ControllerTest(new ControllerEssSellToGridLimitImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
- .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) //
- .addReference("meter", new DummyElectricityMeter(METER_ID)) //
+ .addReference("ess", new DummyManagedSymmetricEss("ess0")) //
+ .addReference("meter", new DummyElectricityMeter("meter0")) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
- .setMeterId(METER_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
+ .setMeterId("meter0") //
.setMaximumSellToGridPower(5000) //
.build()) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -5000) //
- .input(ESS_ACTIVE_POWER, 3000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) //
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -5000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 3000) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) //
.next(new TestCase() //
- .input(METER_ACTIVE_POWER, -6000) //
- .input(ESS_ACTIVE_POWER, 3000) //
- .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, 2000));
+ .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -6000) //
+ .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 3000) //
+ .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 2000)) //
+ .deactivate();
}
}
\ No newline at end of file
diff --git a/io.openems.edge.controller.ess.standby/test/io/openems/edge/controller/ess/standby/ControllerEssStandbyImplTest.java b/io.openems.edge.controller.ess.standby/test/io/openems/edge/controller/ess/standby/ControllerEssStandbyImplTest.java
index 6f3053a0b16..ac45a6cbb7e 100644
--- a/io.openems.edge.controller.ess.standby/test/io/openems/edge/controller/ess/standby/ControllerEssStandbyImplTest.java
+++ b/io.openems.edge.controller.ess.standby/test/io/openems/edge/controller/ess/standby/ControllerEssStandbyImplTest.java
@@ -1,5 +1,15 @@
package io.openems.edge.controller.ess.standby;
+import static io.openems.edge.common.sum.Sum.ChannelId.CONSUMPTION_ACTIVE_POWER;
+import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER;
+import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_ACTIVE_POWER;
+import static io.openems.edge.controller.ess.standby.ControllerEssStandby.ChannelId.STATE_MACHINE;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_CHARGE_POWER;
+import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS;
+import static io.openems.edge.ess.api.SymmetricEss.ChannelId.ACTIVE_POWER;
+import static java.time.temporal.ChronoUnit.HOURS;
+import static java.time.temporal.ChronoUnit.MINUTES;
+
import java.time.DayOfWeek;
import java.time.ZoneId;
import java.time.ZonedDateTime;
@@ -9,10 +19,8 @@
import org.junit.Test;
import io.openems.common.test.TimeLeapClock;
-import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.sum.DummySum;
import io.openems.edge.common.sum.GridMode;
-import io.openems.edge.common.sum.Sum;
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.controller.ess.standby.statemachine.StateMachine.State;
@@ -23,23 +31,6 @@ public class ControllerEssStandbyImplTest {
private static final int MAX_APPARENT_POWER = 50_000; // [W]
- private static final String CTRL_ID = "ctrlEssStandby0";
- private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine");
-
- private static final String SUM_ID = Sum.SINGLETON_COMPONENT_ID;
- private static final ChannelAddress SUM_GRID_ACTIVE_POWER = new ChannelAddress(SUM_ID, "GridActivePower");
- private static final ChannelAddress SUM_PRODUCTION_ACTIVE_POWER = new ChannelAddress(SUM_ID,
- "ProductionActivePower");
- private static final ChannelAddress SUM_CONSUMPTION_ACTIVE_POWER = new ChannelAddress(SUM_ID,
- "ConsumptionActivePower");
-
- private static final String ESS_ID = "ess0";
- private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower");
- private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID,
- "SetActivePowerEquals");
- private static final ChannelAddress ESS_ALLOWED_CHARGE_POWER = new ChannelAddress(ESS_ID, "AllowedChargePower");
-
- // Initialize mocked Clock
private static TimeLeapClock clock;
@Before
@@ -49,7 +40,7 @@ public void initialize() {
private static ControllerTest tillDischarge() throws Exception {
// Initialize ESS
- final var ess = new DummyManagedSymmetricEss(ESS_ID) //
+ final var ess = new DummyManagedSymmetricEss("ess0") //
.withGridMode(GridMode.ON_GRID) //
.withMaxApparentPower(MAX_APPARENT_POWER) //
.withSoc(70);
@@ -59,8 +50,8 @@ private static ControllerTest tillDischarge() throws Exception {
.addReference("sum", new DummySum()) //
.addComponent(ess) //
.activate(MyConfig.create() //
- .setId(CTRL_ID) //
- .setEssId(ESS_ID) //
+ .setId("ctrl0") //
+ .setEssId("ess0") //
.setStartDate("01.02.2020") //
.setEndDate("01.03.2020") //
.setDayOfWeek(DayOfWeek.SUNDAY) //
@@ -68,7 +59,7 @@ private static ControllerTest tillDischarge() throws Exception {
.next(new TestCase() //
.output(STATE_MACHINE, State.UNDEFINED)) //
.next(new TestCase("1 second before midnight (friday) before 01.02.2020") //
- .timeleap(clock, 10, ChronoUnit.MINUTES) //
+ .timeleap(clock, 10, MINUTES) //
.output(STATE_MACHINE, State.UNDEFINED)) //
.next(new TestCase("midnight (saturday)") //
.timeleap(clock, 1, ChronoUnit.SECONDS) //
@@ -80,113 +71,117 @@ private static ControllerTest tillDischarge() throws Exception {
* DISCHARGE
*/
.next(new TestCase("sunday -> switch to DISCHARGE") //
- .input(SUM_GRID_ACTIVE_POWER, 10_000 /* buy from grid */) //
- .input(ESS_ACTIVE_POWER, 100 /* discharge */) //
+ .input(GRID_ACTIVE_POWER, 10_000 /* buy from grid */) //
+ .input("ess0", ACTIVE_POWER, 100 /* discharge */) //
.output(STATE_MACHINE, State.DISCHARGE) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 10_100)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 10_100)) //
.next(new TestCase("discharge > 70 % of maxApparentPower") //
- .timeleap(clock, 30, ChronoUnit.MINUTES) //
- .input(SUM_GRID_ACTIVE_POWER, 29_900 /* buy from grid */) //
- .input(ESS_ACTIVE_POWER, 10_100 /* discharge */) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 40_000)) //
+ .timeleap(clock, 30, MINUTES) //
+ .input(GRID_ACTIVE_POWER, 29_900 /* buy from grid */) //
+ .input("ess0", ACTIVE_POWER, 10_100 /* discharge */) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 40_000)) //
.next(new TestCase("discharge > 70 % of maxApparentPower - 9 minutes") //
- .timeleap(clock, 9, ChronoUnit.MINUTES) //
- .input(SUM_GRID_ACTIVE_POWER, 0 /* buy from grid */) //
- .input(ESS_ACTIVE_POWER, 40_000 /* discharge */) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 40_000)) //
+ .timeleap(clock, 9, MINUTES) //
+ .input(GRID_ACTIVE_POWER, 0 /* buy from grid */) //
+ .input("ess0", ACTIVE_POWER, 40_000 /* discharge */) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 40_000)) //
.next(new TestCase("discharge > 70 % of maxApparentPower - 10 minutes: reduce to 50 %") //
- .timeleap(clock, 1, ChronoUnit.MINUTES) //
- .input(SUM_GRID_ACTIVE_POWER, 0 /* buy from grid */) //
- .input(ESS_ACTIVE_POWER, 40_000 /* discharge */) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * 0.5)))
+ .timeleap(clock, 1, MINUTES) //
+ .input(GRID_ACTIVE_POWER, 0 /* buy from grid */) //
+ .input("ess0", ACTIVE_POWER, 40_000 /* discharge */) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * 0.5)))
.next(new TestCase("do not charge") //
- .timeleap(clock, 1, ChronoUnit.MINUTES) //
- .input(SUM_GRID_ACTIVE_POWER, -100 /* buy from grid */) //
- .input(ESS_ACTIVE_POWER, 0 /* discharge */) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 0));
+ .timeleap(clock, 1, MINUTES) //
+ .input(GRID_ACTIVE_POWER, -100 /* buy from grid */) //
+ .input("ess0", ACTIVE_POWER, 0 /* discharge */) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) //
+ .deactivate();
}
private static ControllerTest tillSlowCharge1_1() throws Exception {
return tillDischarge() //
.next(new TestCase("production > consumption") //
- .input(SUM_GRID_ACTIVE_POWER, 0 /* buy from grid */) //
- .input(SUM_PRODUCTION_ACTIVE_POWER, 1000) //
- .input(SUM_CONSUMPTION_ACTIVE_POWER, 999) //
- .input(ESS_ACTIVE_POWER, 10_000 /* discharge */) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, 10_000)) //
+ .input(GRID_ACTIVE_POWER, 0 /* buy from grid */) //
+ .input(PRODUCTION_ACTIVE_POWER, 1000) //
+ .input(CONSUMPTION_ACTIVE_POWER, 999) //
+ .input("ess0", ACTIVE_POWER, 10_000 /* discharge */) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, 10_000)) //
.next(new TestCase("production > consumption: more than 1 minute -> SLOW_CHARGE") //
- .timeleap(clock, 1, ChronoUnit.MINUTES) //
- .input(SUM_GRID_ACTIVE_POWER, 0 /* buy from grid */) //
- .input(SUM_PRODUCTION_ACTIVE_POWER, 1000) //
- .input(SUM_CONSUMPTION_ACTIVE_POWER, 999)) //
+ .timeleap(clock, 1, MINUTES) //
+ .input(GRID_ACTIVE_POWER, 0 /* buy from grid */) //
+ .input(PRODUCTION_ACTIVE_POWER, 1000) //
+ .input(CONSUMPTION_ACTIVE_POWER, 999)) //
/*
* SLOW_CHARGE
*/
.next(new TestCase("SLOW_CHARGE") //
- .output(STATE_MACHINE, State.SLOW_CHARGE_1)); //
+ .output(STATE_MACHINE, State.SLOW_CHARGE_1)) //
+ .deactivate();
}
private static ControllerTest tillSlowCharge1_2() throws Exception {
return tillDischarge() //
.next(new TestCase("latest at 12 -> SLOW_CHARGE") //
- .timeleap(clock, 12, ChronoUnit.HOURS)) //
+ .timeleap(clock, 12, HOURS)) //
/*
* SLOW_CHARGE
*/
.next(new TestCase("SLOW_CHARGE") //
- .output(STATE_MACHINE, State.SLOW_CHARGE_1)); //
+ .output(STATE_MACHINE, State.SLOW_CHARGE_1)) //
+ .deactivate();
}
private static ControllerTest tillSlowCharge2_1() throws Exception {
return tillSlowCharge1_2() //
.next(new TestCase("") //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(SUM_GRID_ACTIVE_POWER, -20_000 /* sell to grid */) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -20_000)) //
+ .input("ess0", ACTIVE_POWER, 0) //
+ .input(GRID_ACTIVE_POWER, -20_000 /* sell to grid */) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -20_000)) //
.next(new TestCase("Charge with minimum 20 %") //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(SUM_GRID_ACTIVE_POWER, (int) (MAX_APPARENT_POWER * -0.19) /* sell to grid */) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * -0.20))) //
+ .input("ess0", ACTIVE_POWER, 0) //
+ .input(GRID_ACTIVE_POWER, (int) (MAX_APPARENT_POWER * -0.19)) // sell to grid
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * -0.20))) //
.next(new TestCase("Charge with maximum 50 %") //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(SUM_GRID_ACTIVE_POWER, (int) (MAX_APPARENT_POWER * -0.51) /* sell to grid */) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * -0.50))) //
+ .input("ess0", ACTIVE_POWER, 0) //
+ .input(GRID_ACTIVE_POWER, (int) (MAX_APPARENT_POWER * -0.51)) // sell to grid
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * -0.50))) //
.next(new TestCase("after 30 minutes -> FAST_CHARGE") //
- .timeleap(clock, 30, ChronoUnit.MINUTES)) //
+ .timeleap(clock, 30, MINUTES)) //
/*
* FAST_CHARGE
*/
.next(new TestCase("FAST_CHARGE with max power") //
.output(STATE_MACHINE, State.FAST_CHARGE) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, MAX_APPARENT_POWER * -1)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, MAX_APPARENT_POWER * -1)) //
.next(new TestCase("after 10 minutes -> SLOW_CHARGE_2") //
- .timeleap(clock, 10, ChronoUnit.MINUTES)) //
+ .timeleap(clock, 10, MINUTES)) //
/*
* SLOW_CHARGE_2
*/
.next(new TestCase("SLOW_CHARGE_2") //
- .input(ESS_ACTIVE_POWER, 0) //
- .input(ESS_ALLOWED_CHARGE_POWER, -60_000) //
- .input(SUM_GRID_ACTIVE_POWER, -20_000 /* sell to grid */) //
+ .input("ess0", ACTIVE_POWER, 0) //
+ .input("ess0", ALLOWED_CHARGE_POWER, -60_000) //
+ .input(GRID_ACTIVE_POWER, -20_000 /* sell to grid */) //
.output(STATE_MACHINE, State.SLOW_CHARGE_2) //
- .output(ESS_SET_ACTIVE_POWER_EQUALS, -20_000)) //
+ .output("ess0", SET_ACTIVE_POWER_EQUALS, -20_000)) //
/*
* FINISHED
*/
.next(new TestCase("no more charging allowed -> FINISHED") //
- .input(ESS_ALLOWED_CHARGE_POWER, 0)) //
+ .input("ess0", ALLOWED_CHARGE_POWER, 0)) //
.next(new TestCase("FINISHED") //
.output(STATE_MACHINE, State.FINISHED)) //
/*
* UNDEFINED
*/
.next(new TestCase("on day change -> UNDEFINED") //
- .timeleap(clock, 11, ChronoUnit.HOURS)) //
+ .timeleap(clock, 11, HOURS)) //
.next(new TestCase("UNDEFINED") //
.output(STATE_MACHINE, State.UNDEFINED)) //
.next(new TestCase("After test period") //
.timeleap(clock, 30, ChronoUnit.DAYS) //
- .output(STATE_MACHINE, State.UNDEFINED)); //
+ .output(STATE_MACHINE, State.UNDEFINED)) //
+ .deactivate();
}
@Test
diff --git a/io.openems.edge.controller.ess.timeofusetariff/bnd.bnd b/io.openems.edge.controller.ess.timeofusetariff/bnd.bnd
index d45bd8553ce..767f72aac39 100644
--- a/io.openems.edge.controller.ess.timeofusetariff/bnd.bnd
+++ b/io.openems.edge.controller.ess.timeofusetariff/bnd.bnd
@@ -3,6 +3,8 @@ Bundle-Vendor: FENECON GmbH
Bundle-License: https://opensource.org/licenses/EPL-2.0
Bundle-Version: 1.0.0.${tstamp}
+# TODO remove emergencycapacityreserve and limittotaldischarge after v1
+
-buildpath: \
${buildpath},\
io.openems.common,\
@@ -14,6 +16,7 @@ Bundle-Version: 1.0.0.${tstamp}
io.openems.edge.ess.api,\
io.openems.edge.timedata.api,\
io.openems.edge.timeofusetariff.api,\
+ org.apache.commons.math3,\
-testpath: \
${testpath},\
diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Config.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Config.java
index 99793e89191..bceba3c4d55 100644
--- a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Config.java
+++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Config.java
@@ -3,6 +3,8 @@
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import io.openems.edge.energy.api.Version;
+
@ObjectClassDefinition(//
name = "Controller Ess Time-Of-Use Tariff", //
description = "Optimize behaviour of an ESS in combination with a Time-Of-Use (ToU) Tariff.")
@@ -40,9 +42,12 @@
@AttributeDefinition(name = "Limit Charge Power for §14a EnWG", description = "Always apply §14a EnWG limitation of 4.2 kW")
boolean limitChargePowerFor14aEnWG() default false;
+ @AttributeDefinition(name = "Version", description = "Select version of implementation")
+ Version version() default Version.V1_ESS_ONLY;
+
@AttributeDefinition(name = "Ess target filter", description = "This is auto-generated by 'Ess-ID'.")
String ess_target() default "(enabled=true)";
String webconsole_configurationFactory_nameHint() default "Controller Ess Time-Of-Use Tariff [{id}]";
-}
\ No newline at end of file
+}
diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffController.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffController.java
index 9876f79201e..b241b53977a 100644
--- a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffController.java
+++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffController.java
@@ -8,10 +8,11 @@
import io.openems.edge.common.channel.value.Value;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.controller.api.Controller;
+import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1;
+import io.openems.edge.energy.api.EnergySchedulable;
-public interface TimeOfUseTariffController extends Controller, OpenemsComponent {
-
- public static final int PERIODS_PER_HOUR = 4;
+@SuppressWarnings("deprecation")
+public interface TimeOfUseTariffController extends Controller, EnergySchedulable, OpenemsComponent {
public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
/**
@@ -52,6 +53,14 @@ public Doc doc() {
}
}
+ /**
+ * Get the {@link EnergyScheduleHandlerV1}.
+ *
+ * @return {@link EnergyScheduleHandlerV1}
+ */
+ @Deprecated
+ public EnergyScheduleHandlerV1 getEnergyScheduleHandlerV1();
+
/**
* Gets the Channel for {@link ChannelId#QUARTERLY_PRICES}.
*
diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImpl.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImpl.java
index 5ae75684899..48b0bc09338 100644
--- a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImpl.java
+++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImpl.java
@@ -1,12 +1,25 @@
package io.openems.edge.controller.ess.timeofusetariff;
-import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING;
import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID;
import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE;
+import static io.openems.edge.controller.ess.timeofusetariff.Utils.ESS_MAX_SOC;
import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateAutomaticMode;
+import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateChargeEnergyInChargeGrid;
+import static io.openems.edge.energy.api.simulation.Coefficient.ESS;
+import static io.openems.edge.energy.api.simulation.Coefficient.GRID_TO_CONS;
+import static io.openems.edge.energy.api.simulation.Coefficient.GRID_TO_ESS;
+import static io.openems.edge.energy.api.simulation.Coefficient.PROD_TO_ESS;
+import static io.openems.edge.energy.api.simulation.Coefficient.PROD_TO_GRID;
+import static java.lang.Math.min;
+import static java.lang.Math.round;
+import static org.apache.commons.math3.optim.linear.Relationship.EQ;
+import static org.apache.commons.math3.optim.nonlinear.scalar.GoalType.MAXIMIZE;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.BooleanSupplier;
+import java.util.function.IntSupplier;
+import java.util.function.Supplier;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
@@ -31,11 +44,16 @@
import io.openems.edge.controller.api.Controller;
import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve;
import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge;
-import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.Context;
import io.openems.edge.controller.ess.timeofusetariff.Utils.ApplyState;
+import io.openems.edge.controller.ess.timeofusetariff.jsonrpc.GetScheduleRequest;
+import io.openems.edge.controller.ess.timeofusetariff.jsonrpc.GetScheduleResponse;
+import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1;
+import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1.ContextV1;
+import io.openems.edge.controller.ess.timeofusetariff.v1.UtilsV1;
import io.openems.edge.energy.api.EnergySchedulable;
import io.openems.edge.energy.api.EnergyScheduleHandler;
import io.openems.edge.energy.api.EnergyScheduler;
+import io.openems.edge.energy.api.simulation.EnergyFlow;
import io.openems.edge.ess.api.ManagedSymmetricEss;
import io.openems.edge.ess.power.api.Phase;
import io.openems.edge.ess.power.api.Pwr;
@@ -50,15 +68,13 @@
immediate = true, //
configurationPolicy = ConfigurationPolicy.REQUIRE //
)
+@SuppressWarnings("deprecation")
public class TimeOfUseTariffControllerImpl extends AbstractOpenemsComponent implements TimeOfUseTariffController,
- EnergySchedulable, Controller, OpenemsComponent, TimedataProvider, ComponentJsonApi {
+ EnergySchedulable, Controller, OpenemsComponent, TimedataProvider, ComponentJsonApi {
- public static record Context(List ctrlEmergencyCapacityReserves,
- List ctrlLimitTotalDischarges, ManagedSymmetricEss ess,
- ControlMode controlMode, int maxChargePowerFromGrid, boolean limitChargePowerFor14aEnWG) {
- }
-
- private final EnergyScheduleHandler energyScheduleHandler;
+ @Deprecated
+ private final EnergyScheduleHandlerV1 energyScheduleHandlerV1;
+ private final EnergyScheduleHandler.WithDifferentStates energyScheduleHandler;
private final CalculateActiveTime calculateDelayedTime = new CalculateActiveTime(this,
TimeOfUseTariffController.ChannelId.DELAYED_TIME);
private final CalculateActiveTime calculateChargedTime = new CalculateActiveTime(this,
@@ -80,20 +96,23 @@ public static record Context(List ctrlEme
@Reference(policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL)
private volatile Timedata timedata;
+ @Deprecated
@Reference(policyOption = ReferencePolicyOption.GREEDY, //
cardinality = ReferenceCardinality.MULTIPLE, //
target = "(&(enabled=true)(isReserveSocEnabled=true))")
- private List ctrlEmergencyCapacityReserves = new CopyOnWriteArrayList<>();
+ private volatile List ctrlEmergencyCapacityReserves = new CopyOnWriteArrayList<>();
+ @Deprecated
@Reference(policyOption = ReferencePolicyOption.GREEDY, //
cardinality = ReferenceCardinality.MULTIPLE, //
target = "(enabled=true)")
- private List ctrlLimitTotalDischarges = new CopyOnWriteArrayList<>();
+ private volatile List ctrlLimitTotalDischarges = new CopyOnWriteArrayList<>();
@Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY)
private ManagedSymmetricEss ess;
@Reference
+ @Deprecated
private EnergyScheduler energyScheduler;
private Config config = null;
@@ -104,11 +123,18 @@ public TimeOfUseTariffControllerImpl() {
Controller.ChannelId.values(), //
TimeOfUseTariffController.ChannelId.values() //
);
- this.energyScheduleHandler = new EnergyScheduleHandler<>(//
+
+ this.energyScheduleHandlerV1 = new EnergyScheduleHandlerV1(//
() -> this.config.controlMode().states, //
- () -> new Context(this.ctrlEmergencyCapacityReserves, this.ctrlLimitTotalDischarges, this.ess,
+ () -> new ContextV1(this.ctrlEmergencyCapacityReserves, this.ctrlLimitTotalDischarges, this.ess,
this.config.controlMode(), this.config.maxChargePowerFromGrid(),
this.config.limitChargePowerFor14aEnWG()));
+
+ this.energyScheduleHandler = buildEnergyScheduleHandler(//
+ () -> this.ess, //
+ () -> this.config.controlMode(), //
+ () -> this.config.maxChargePowerFromGrid(), //
+ () -> this.config.limitChargePowerFor14aEnWG());
}
@Activate
@@ -121,6 +147,7 @@ private void activate(ComponentContext context, Config config) {
private void modified(ComponentContext context, Config config) {
super.modified(context, config.id(), config.alias(), config.enabled());
this.applyConfig(config);
+ this.energyScheduleHandler.triggerReschedule();
}
private synchronized void applyConfig(Config config) {
@@ -138,12 +165,26 @@ protected void deactivate() {
@Override
public void run() throws OpenemsNamedException {
- // Mode given from the configuration.
- var as = switch (this.config.mode()) {
- case AUTOMATIC -> calculateAutomaticMode(this.sum, this.ess,
- this.energyScheduleHandler.getCurrentEssChargeInChargeGrid(), this.config.maxChargePowerFromGrid(),
- this.config.limitChargePowerFor14aEnWG(), this.getCurrentPeriodState());
- case OFF -> new ApplyState(StateMachine.BALANCING, null);
+ // Version and Mode given from the configuration.
+ final var as = switch (this.config.version()) {
+
+ case V1_ESS_ONLY //
+ -> switch (this.config.mode()) {
+ case AUTOMATIC //
+ -> UtilsV1.calculateAutomaticMode(this.energyScheduleHandlerV1, this.sum, this.ess,
+ this.config.maxChargePowerFromGrid(), this.config.limitChargePowerFor14aEnWG());
+ case OFF //
+ -> new ApplyState(StateMachine.BALANCING, null);
+ };
+
+ case V2_ENERGY_SCHEDULABLE //
+ -> switch (this.config.mode()) {
+ case AUTOMATIC //
+ -> calculateAutomaticMode(this.sum, this.ess, this.config.maxChargePowerFromGrid(),
+ this.config.limitChargePowerFor14aEnWG(), this.energyScheduleHandler.getCurrentPeriod());
+ case OFF //
+ -> new ApplyState(StateMachine.BALANCING, null);
+ };
};
// Update Channels
@@ -159,14 +200,6 @@ public void run() throws OpenemsNamedException {
}
}
- private StateMachine getCurrentPeriodState() {
- var state = this.energyScheduleHandler.getCurrentState();
- if (state != null) {
- return state;
- }
- return BALANCING; // Default Fallback
- }
-
@Override
public Timedata getTimedata() {
return this.timedata;
@@ -174,21 +207,134 @@ public Timedata getTimedata() {
@Override
public void buildJsonApiRoutes(JsonApiBuilder builder) {
- this.energyScheduler.buildJsonApiRoutes(builder);
+ builder.handleRequest(GetScheduleRequest.METHOD, call -> //
+ switch (this.config.version()) {
+ case V1_ESS_ONLY //
+ -> this.energyScheduler.handleGetScheduleRequestV1(call, this.id());
+
+ case V2_ENERGY_SCHEDULABLE //
+ -> GetScheduleResponse.from(call.getRequest().getId(), //
+ this.id(), this.componentManager.getClock(), this.ess, this.timedata, this.energyScheduleHandler);
+ });
}
@Override
public String debugLog() {
var b = new StringBuilder() //
.append(this.getStateMachine()); //
- if (this.getCurrentPeriodState() == null) {
- b.append("|No Schedule available");
+
+ switch (this.config.version()) {
+ case V1_ESS_ONLY -> {
+ if (this.energyScheduleHandlerV1 == null || this.energyScheduleHandlerV1.getCurrentState() == null) {
+ b.append("|No Schedule available");
+ }
+ }
+ case V2_ENERGY_SCHEDULABLE -> {
+ if (this.energyScheduleHandler.getCurrentPeriod() == null) {
+ b.append("|No Schedule available");
+ }
+ }
}
return b.toString();
}
+ /**
+ * Builds the {@link EnergyScheduleHandler}.
+ *
+ *
+ * This is public so that it can be used by the EnergyScheduler integration
+ * test.
+ *
+ * @param ess a supplier for the
+ * {@link ManagedSymmetricEss}
+ * @param controlMode a supplier for the configured
+ * {@link ControlMode}
+ * @param maxChargePowerFromGrid a supplier for the configured
+ * maxChargePowerFromGrid
+ * @param limitChargePowerFor14aEnWG a supplier for the configured
+ * limitChargePowerFor14aEnWG
+ * @return a typed {@link EnergyScheduleHandler}
+ */
+ public static EnergyScheduleHandler.WithDifferentStates buildEnergyScheduleHandler(
+ Supplier ess, Supplier controlMode, IntSupplier maxChargePowerFromGrid,
+ BooleanSupplier limitChargePowerFor14aEnWG) {
+ return EnergyScheduleHandler.of(//
+ StateMachine.BALANCING, //
+ () -> controlMode.get().states, //
+ simContext -> {
+ // Maximium-SoC in CHARGE_GRID is 90 %
+ var maxSocEnergyInChargeGrid = round(simContext.ess().totalEnergy() * (ESS_MAX_SOC / 100));
+ var essChargeInChargeGrid = calculateChargeEnergyInChargeGrid(simContext);
+ return new EshContext(ess.get(), controlMode.get(), maxChargePowerFromGrid.getAsInt(),
+ limitChargePowerFor14aEnWG.getAsBoolean(), maxSocEnergyInChargeGrid, essChargeInChargeGrid);
+ }, //
+ (simContext, period, energyFlow, ctrlContext, state) -> {
+ switch (state) {
+ case BALANCING -> applyBalancing(energyFlow); // TODO Move to CtrlBalancing
+ case DELAY_DISCHARGE -> applyDelayDischarge(energyFlow);
+ case CHARGE_GRID -> {
+ energyFlow.setEssMaxCharge(ctrlContext.maxSocEnergyInChargeGrid - simContext.getEssInitial());
+ applyChargeGrid(energyFlow, ctrlContext.essChargeInChargeGrid);
+ }
+ }
+ }, //
+ Utils::postprocessSimulatorState);
+ }
+
+ /**
+ * Simulate {@link EnergyFlow} in {@link StateMachine#BALANCING}.
+ *
+ * @param model the {@link EnergyFlow.Model}
+ */
+ public static void applyBalancing(EnergyFlow.Model model) {
+ var target = model.consumption - model.production;
+ model.setFittingCoefficientValue(ESS, EQ, target);
+ }
+
+ /**
+ * Simulate {@link EnergyFlow} in DELAY_DISCHARGE.
+ *
+ * @param model the {@link EnergyFlow.Model}
+ */
+ public static void applyDelayDischarge(EnergyFlow.Model model) {
+ var target = min(0 /* Charge -> apply Balancing */, model.consumption - model.production);
+ model.setFittingCoefficientValue(ESS, EQ, target);
+ }
+
+ /**
+ * Simulate {@link EnergyFlow} in {@link StateMachine#CHARGE_GRID}.
+ *
+ * @param model the {@link EnergyFlow.Model}
+ * @param chargeEnergy the target charge-from-grid energy
+ */
+ public static void applyChargeGrid(EnergyFlow.Model model, int chargeEnergy) {
+ model.setExtremeCoefficientValue(PROD_TO_ESS, MAXIMIZE);
+ model.setExtremeCoefficientValue(GRID_TO_CONS, MAXIMIZE);
+ model.setFittingCoefficientValue(GRID_TO_ESS, EQ, chargeEnergy);
+ }
+
+ /**
+ * Simulate {@link EnergyFlow} in a future DISCHARGE_GRID state.
+ *
+ * @param model the {@link EnergyFlow.Model}
+ * @param dischargeEnergy the target discharge-to-grid energy
+ */
+ public static void applyDischargeGrid(EnergyFlow.Model model, int dischargeEnergy) {
+ model.setExtremeCoefficientValue(PROD_TO_GRID, MAXIMIZE);
+ model.setFittingCoefficientValue(GRID_TO_ESS, EQ, -dischargeEnergy);
+ }
+
+ public static record EshContext(ManagedSymmetricEss ess, ControlMode controlMode, int maxChargePowerFromGrid,
+ boolean limitChargePowerFor14aEnWG, int maxSocEnergyInChargeGrid, int essChargeInChargeGrid) {
+ }
+
@Override
- public EnergyScheduleHandler getEnergyScheduleHandler() {
+ public EnergyScheduleHandler.WithDifferentStates getEnergyScheduleHandler() {
return this.energyScheduleHandler;
}
+
+ @Override
+ public EnergyScheduleHandlerV1 getEnergyScheduleHandlerV1() {
+ return this.energyScheduleHandlerV1;
+ }
}
diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Utils.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Utils.java
index 6543f9b24c1..7b21d653571 100644
--- a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Utils.java
+++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Utils.java
@@ -1,23 +1,27 @@
package io.openems.edge.controller.ess.timeofusetariff;
-import static io.openems.edge.common.type.TypeUtils.multiply;
+import static com.google.common.math.Quantiles.percentiles;
import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING;
import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID;
import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE;
-import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController.PERIODS_PER_HOUR;
+import static io.openems.edge.energy.api.EnergyConstants.PERIODS_PER_HOUR;
+import static io.openems.edge.energy.api.EnergyUtils.findFirstPeakIndex;
+import static io.openems.edge.energy.api.EnergyUtils.findFirstValleyIndex;
+import static io.openems.edge.energy.api.EnergyUtils.toPower;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.round;
-import static java.util.stream.IntStream.concat;
+import static java.util.Arrays.stream;
-import java.util.List;
-import java.util.Objects;
+import com.google.common.primitives.ImmutableIntArray;
import io.openems.common.types.ChannelAddress;
import io.openems.edge.common.sum.Sum;
import io.openems.edge.controller.api.Controller;
-import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve;
-import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge;
+import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.EshContext;
+import io.openems.edge.energy.api.EnergyScheduleHandler.WithDifferentStates.Period;
+import io.openems.edge.energy.api.simulation.EnergyFlow;
+import io.openems.edge.energy.api.simulation.GlobalSimulationsContext;
import io.openems.edge.ess.api.HybridEss;
import io.openems.edge.ess.api.ManagedSymmetricEss;
@@ -52,29 +56,6 @@ private Utils() {
public static final ChannelAddress SUM_ESS_DISCHARGE_POWER = new ChannelAddress("_sum", "EssDischargePower");
public static final ChannelAddress SUM_ESS_SOC = new ChannelAddress("_sum", "EssSoc");
- /**
- * Returns the configured minimum SoC, or zero.
- *
- * @param ctrlLimitTotalDischarges the list of
- * {@link ControllerEssLimitTotalDischarge}
- * @param ctrlEmergencyCapacityReserves the list of
- * {@link ControllerEssEmergencyCapacityReserve}
- * @return the value in [%]
- */
- public static int getEssMinSocPercentage(List ctrlLimitTotalDischarges,
- List ctrlEmergencyCapacityReserves) {
- return concat(//
- ctrlLimitTotalDischarges.stream() //
- .map(ctrl -> ctrl.getMinSoc().get()) //
- .filter(Objects::nonNull) //
- .mapToInt(v -> max(0, v)), // only positives
- ctrlEmergencyCapacityReserves.stream() //
- .map(ctrl -> ctrl.getActualReserveSoc().get()) //
- .filter(Objects::nonNull) //
- .mapToInt(v -> max(0, v))) // only positives
- .max().orElse(0);
- }
-
public static record ApplyState(StateMachine actualState, Integer setPoint) {
}
@@ -83,20 +64,19 @@ public static record ApplyState(StateMachine actualState, Integer setPoint) {
*
* @param sum the {@link Sum}
* @param ess the {@link ManagedSymmetricEss}
- * @param essChargeInChargeGrid ESS Charge Energy in CHARGE_GRID State [Wh]
* @param maxChargePowerFromGrid the configured max charge from grid power
* @param limitChargePowerFor14aEnWG Limit Charge Power for §14a EnWG
- * @param targetState the scheduled target {@link StateMachine}
+ * @param period the scheduled {@link Period}
* @return {@link ApplyState}
*/
- public static ApplyState calculateAutomaticMode(Sum sum, ManagedSymmetricEss ess, Integer essChargeInChargeGrid,
- int maxChargePowerFromGrid, boolean limitChargePowerFor14aEnWG, StateMachine targetState) {
+ public static ApplyState calculateAutomaticMode(Sum sum, ManagedSymmetricEss ess, int maxChargePowerFromGrid,
+ boolean limitChargePowerFor14aEnWG, Period period) {
final StateMachine actualState;
final Integer setPoint;
var gridActivePower = sum.getGridActivePower().get(); // current buy-from/sell-to grid
var essActivePower = ess.getActivePower().get(); // current charge/discharge ESS
- if (gridActivePower == null || essActivePower == null) {
+ if (period == null || gridActivePower == null || essActivePower == null) {
// undefined state
return new ApplyState(BALANCING, null);
}
@@ -104,9 +84,9 @@ public static ApplyState calculateAutomaticMode(Sum sum, ManagedSymmetricEss ess
// Post-process and get actual state
final var pwrBalancing = gridActivePower + essActivePower;
final var pwrDelayDischarge = calculateDelayDischargePower(ess);
- final var pwrChargeGrid = calculateChargeGridPower(essChargeInChargeGrid, ess, essActivePower, gridActivePower,
- maxChargePowerFromGrid, limitChargePowerFor14aEnWG);
- actualState = postprocessRunState(targetState, pwrBalancing, pwrDelayDischarge, pwrChargeGrid);
+ final var pwrChargeGrid = calculateChargeGridPower(period.context().essChargeInChargeGrid(), ess,
+ essActivePower, gridActivePower, maxChargePowerFromGrid, limitChargePowerFor14aEnWG);
+ actualState = postprocessRunState(period.state(), pwrBalancing, pwrDelayDischarge, pwrChargeGrid);
// Get and apply ActivePower Less-or-Equals Set-Point
setPoint = switch (actualState) {
@@ -156,8 +136,39 @@ public static StateMachine postprocessRunState(StateMachine state, int pwrBalanc
return state;
}
- protected static int calculateEssChargeInChargeGridPowerFromParams(Integer essChargeInChargeGrid,
- ManagedSymmetricEss ess) {
+ /**
+ * Post-Process a state of a Period during Simulation, i.e. replace with
+ * 'better' state with the same behaviour.
+ *
+ *
+ * NOTE: heavy computation is ok here, because this method is called only at the
+ * end with the best Schedule.
+ *
+ * @param ef the {@link EnergyFlow} for the state
+ * @param state the initial state
+ * @return the new state
+ */
+ public static StateMachine postprocessSimulatorState(EnergyFlow ef, StateMachine state) {
+ if (state == CHARGE_GRID) {
+ // CHARGE_GRID,...
+ if (ef.getGridToEss() <= 0) {
+ // but battery is not charged from grid
+ state = DELAY_DISCHARGE;
+ }
+ }
+
+ if (state == DELAY_DISCHARGE) {
+ // DELAY_DISCHARGE,...
+ if (ef.getEss() < 0) {
+ // but battery gets charged
+ state = BALANCING;
+ }
+ }
+
+ return state;
+ }
+
+ protected static int calculateEssChargeInChargeGridPower(Integer essChargeInChargeGrid, ManagedSymmetricEss ess) {
if (essChargeInChargeGrid != null) {
return toPower(essChargeInChargeGrid);
}
@@ -187,7 +198,7 @@ protected static int calculateEssChargeInChargeGridPowerFromParams(Integer essCh
public static int calculateChargeGridPower(Integer essChargeInChargeGrid, ManagedSymmetricEss ess,
int essActivePower, int gridActivePower, int maxChargePowerFromGrid, boolean limitChargePowerFor14aEnWG) {
var realGridPower = gridActivePower + essActivePower; // 'real', without current ESS charge/discharge
- var targetChargePower = calculateEssChargeInChargeGridPowerFromParams(essChargeInChargeGrid, ess) //
+ var targetChargePower = calculateEssChargeInChargeGridPower(essChargeInChargeGrid, ess) //
+ min(0, realGridPower) * -1; // add excess production
var effectiveGridBuyPower = max(0, realGridPower) + targetChargePower;
var chargePower = max(0, targetChargePower - max(0, effectiveGridBuyPower - maxChargePowerFromGrid));
@@ -236,12 +247,63 @@ public static int calculateDelayDischargePower(ManagedSymmetricEss ess) {
}
/**
- * Converts energy [Wh/15 min] to power [W].
+ * Calculates the default ESS charge energy per period in
+ * {@link StateMachine#CHARGE_GRID}.
*
- * @param energy the energy value
- * @return the power value
+ *
+ * Applies {@link #ESS_CHARGE_C_RATE} with the minimum of usable ESS energy or
+ * predicted consumption energy that cannot be supplied from production.
+ *
+ * @param gsc the {@link GlobalSimulationsContext}
+ * @return the value in [Wh]
*/
- private static Integer toPower(Integer energy) {
- return multiply(energy, PERIODS_PER_HOUR);
+ public static int calculateChargeEnergyInChargeGrid(GlobalSimulationsContext gsc) {
+ var refs = ImmutableIntArray.builder();
+
+ // Uses the total available energy as reference (= fallback)
+ var fallback = max(0, round(ESS_MAX_SOC / 100F * gsc.ess().totalEnergy()));
+ add(refs, fallback);
+
+ // Uses the total excess consumption as reference
+ add(refs, gsc.periods().stream() //
+ .mapToInt(p -> p.consumption() - p.production()) // calculates excess Consumption Energy per Period
+ .sum());
+
+ add(refs, gsc.periods().stream() //
+ .takeWhile(p -> p.consumption() >= p.production()) // take only first Periods
+ .mapToInt(p -> p.consumption() - p.production()) // calculates excess Consumption Energy per Period
+ .sum());
+
+ // Uses the excess consumption during high price periods as reference
+ {
+ var prices = gsc.periods().stream() //
+ .mapToDouble(GlobalSimulationsContext.Period::price) //
+ .toArray();
+ var peakIndex = findFirstPeakIndex(findFirstValleyIndex(0, prices), prices);
+ var firstPrices = stream(prices) //
+ .limit(peakIndex) //
+ .toArray();
+ if (firstPrices.length > 0) {
+ var percentilePrice = percentiles().index(95).compute(firstPrices);
+ add(refs, gsc.periods().stream() //
+ .limit(peakIndex) //
+ .filter(p -> p.price() >= percentilePrice) // takes only prices > percentile
+ .mapToInt(p -> p.consumption() - p.production()) // excess Consumption Energy per Period
+ .sum());
+ }
+ }
+
+ return (int) round(//
+ refs.build().stream() //
+ .average() //
+ .orElse(fallback) //
+ * ESS_CHARGE_C_RATE / PERIODS_PER_HOUR);
}
+
+ private static void add(ImmutableIntArray.Builder builder, int value) {
+ if (value > 0) {
+ builder.add(value);
+ }
+ }
+
}
diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleRequest.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleRequest.java
similarity index 94%
rename from io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleRequest.java
rename to io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleRequest.java
index 1dbdb0dc7d8..ff357bfd827 100644
--- a/io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleRequest.java
+++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleRequest.java
@@ -1,4 +1,4 @@
-package io.openems.edge.energy.jsonrpc;
+package io.openems.edge.controller.ess.timeofusetariff.jsonrpc;
import com.google.gson.JsonObject;
diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponse.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponse.java
new file mode 100644
index 00000000000..d60e3eae6e6
--- /dev/null
+++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponse.java
@@ -0,0 +1,230 @@
+package io.openems.edge.controller.ess.timeofusetariff.jsonrpc;
+
+import static io.openems.common.utils.JsonUtils.buildJsonObject;
+import static io.openems.common.utils.JsonUtils.getAsOptionalDouble;
+import static io.openems.common.utils.JsonUtils.getAsOptionalInt;
+import static io.openems.common.utils.JsonUtils.toJsonArray;
+import static io.openems.edge.common.type.TypeUtils.fitWithin;
+import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING;
+import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_CONSUMPTION;
+import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_ESS_DISCHARGE_POWER;
+import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_ESS_SOC;
+import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_GRID;
+import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_PRODUCTION;
+import static io.openems.edge.energy.api.EnergyUtils.toPower;
+import static java.lang.Math.round;
+import static java.util.Optional.ofNullable;
+
+import java.time.Clock;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+
+import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
+import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess;
+import io.openems.common.timedata.Resolution;
+import io.openems.common.types.ChannelAddress;
+import io.openems.edge.controller.ess.timeofusetariff.StateMachine;
+import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController;
+import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl;
+import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.EshContext;
+import io.openems.edge.controller.ess.timeofusetariff.Utils;
+import io.openems.edge.energy.api.EnergyScheduleHandler;
+import io.openems.edge.energy.api.EnergyScheduleHandler.WithDifferentStates.Period;
+import io.openems.edge.ess.api.SymmetricEss;
+import io.openems.edge.timedata.api.Timedata;
+
+/**
+ * Represents a JSON-RPC Response for 'getSchedule'.
+ *
+ *
+ * {
+ * "jsonrpc": "2.0",
+ * "id": "UUID",
+ * "result": {
+ * 'schedule': [{
+ * 'timestamp':...,
+ * 'price':...,
+ * 'state':...,
+ * 'grid':...,
+ * 'production':...,
+ * 'consumption':...,
+ * 'ess':...,
+ * 'soc':...,
+ * }]
+ * }
+ * }
+ *
+ */
+public class GetScheduleResponse extends JsonrpcResponseSuccess {
+
+ private static final Logger LOG = LoggerFactory.getLogger(GetScheduleResponse.class);
+
+ private final JsonObject result;
+
+ public GetScheduleResponse(UUID id, JsonObject result) {
+ super(id);
+ this.result = result;
+ }
+
+ @Override
+ public JsonObject getResult() {
+ return this.result;
+ }
+
+ /**
+ * Builds a {@link GetScheduleResponse} with last three hours data and current
+ * Schedule.
+ *
+ * @param requestId the JSON-RPC request-id
+ * @param componentId the Component-ID of the parent
+ * {@link TimeOfUseTariffController}
+ * @param clock a {@link Clock}
+ * @param ess the {@link SymmetricEss}
+ * @param timedata the {@link Timedata}
+ * @param energyScheduleHandler the {@link EnergyScheduleHandler}
+ * @return the {@link GetScheduleResponse}
+ * @throws OpenemsNamedException on error
+ */
+ public static GetScheduleResponse from(UUID requestId, String componentId, Clock clock, SymmetricEss ess,
+ Timedata timedata,
+ EnergyScheduleHandler.WithDifferentStates energyScheduleHandler) {
+ final var schedule = energyScheduleHandler.getSchedule();
+ final JsonArray result;
+ if (schedule.isEmpty()) {
+ result = new JsonArray();
+ } else {
+ final var historic = fromHistoricData(componentId, schedule.firstKey(), timedata);
+ final var future = fromSchedule(ess, schedule);
+ result = Stream.concat(historic, future) //
+ .collect(toJsonArray());
+ }
+
+ return new GetScheduleResponse(requestId, //
+ buildJsonObject() //
+ .add("schedule", result) //
+ .build());
+ }
+
+ /**
+ * Queries the last three hours' data and converts it to a {@link Stream} of
+ * {@link JsonObject}s suitable for a {@link GetScheduleResponse}.
+ *
+ * @param componentId Component-ID of {@link TimeOfUseTariffControllerImpl}
+ * @param firstSchedule {@link ZonedDateTime} of the first entry in the Schedule
+ * (rounded down to 15 minutes)
+ * @param timedata the {@link Timedata}
+ * @return {@link Stream} of {@link JsonObject}s
+ */
+ // TODO protected is sufficient after v1
+ public static Stream fromHistoricData(String componentId, ZonedDateTime firstSchedule,
+ Timedata timedata) {
+ // Process last three hours of historic data
+ final var fromTime = firstSchedule.minusHours(3);
+ final var toTime = firstSchedule.minusSeconds(1);
+ final var channelQuarterlyPrices = new ChannelAddress(componentId, "QuarterlyPrices");
+ final var channelStateMachine = new ChannelAddress(componentId, "StateMachine");
+ SortedMap