From 7bf982c7165199f0bbcb3405b63a38bb6d1d54e6 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 7 Apr 2025 14:30:34 +0200 Subject: [PATCH 1/7] Refactoring data connections and container. --- docs/readthedocs/connections/connections.md | 40 ++++ .../DataQueueExtSimulationExtSimulator.java | 22 -- .../ie3/simona/api/data/ExtDataContainer.java | 10 - .../api/data/ExtDataContainerQueue.java | 63 ++++++ .../api/data/ExtInputDataConnection.java | 29 --- .../api/data/ExtInputDataContainer.java | 69 ------ .../api/data/ExtOutputDataConnection.java | 31 --- .../api/data/connection/BiDirectional.java | 57 +++++ .../{ => connection}/ExtDataConnection.java | 2 +- .../data/connection/ExtEmDataConnection.java | 84 ++++++++ .../data/connection/ExtEvDataConnection.java | 79 +++++++ .../connection/ExtInputDataConnection.java | 52 +++++ .../connection/ExtOutputDataConnection.java | 46 ++++ .../connection/ExtPrimaryDataConnection.java | 56 +++++ .../connection/ExtResultDataConnection.java | 100 +++++++++ .../data/connection/ExtResultListener.java | 53 +++++ .../api/data/container/ExtDataContainer.java | 31 +++ .../api/data/container/ExtInputContainer.java | 178 ++++++++++++++++ .../data/container/ExtResultContainer.java | 162 ++++++++++++++ .../api/data/em/ExtEmDataConnection.java | 88 -------- .../em/ontology/EmDataMessageFromExt.java | 2 +- .../ontology/EmDataResponseMessageToExt.java | 12 ++ .../em/ontology/ProvideEmSetPointData.java | 2 +- .../api/data/ev/ExtEvDataConnection.java | 150 ------------- .../api/data/model/em/FlexOptionRequest.java | 13 ++ .../simona/api/data/model/em/FlexOptions.java | 17 ++ .../primarydata/ExtPrimaryDataConnection.java | 88 -------- .../api/data/results/ExtResultContainer.java | 110 ---------- .../data/results/ExtResultDataConnection.java | 156 -------------- .../ontology/RequestResultEntities.java | 6 +- .../ExtDataConnectionException.java | 23 ++ .../exceptions/NoExtSimulationException.java | 5 +- .../{simulation => }/mapping/DataType.java | 8 +- .../api/simulation/ExtCoSimulation.java | 130 +++++------ .../simona/api/simulation/ExtSimulation.java | 2 +- .../simulation/mapping/ExtEntityEntry.java | 7 +- .../simulation/mapping/ExtEntityFactory.java | 8 +- .../simulation/mapping/ExtEntityMapping.java | 6 +- .../connection/ExtEmDataConnectionTest.groovy | 67 ++++++ .../ExtEvDataConnectionTest.groovy | 2 +- .../ExtPrimaryDataConnectionTest.groovy | 47 +--- .../ExtResultDataConnectionTest.groovy | 61 ++---- .../container/ExtInputContainerTest.groovy | 201 ++++++++++++++++++ .../ExtResultContainerTest.groovy | 65 ++++-- .../data/em/ExtEmDataConnectionTest.groovy | 94 -------- .../api/simulation/ExtCoSimulationTest.groovy | 42 ++-- .../api/simulation/ExtSimulationSpec.groovy | 2 +- .../mapping/ExtEntityMappingSourceTest.groovy | 1 + .../mapping/ExtEntityMappingTest.groovy | 5 +- 49 files changed, 1526 insertions(+), 1058 deletions(-) delete mode 100644 src/main/java/edu/ie3/simona/api/data/DataQueueExtSimulationExtSimulator.java delete mode 100644 src/main/java/edu/ie3/simona/api/data/ExtDataContainer.java create mode 100644 src/main/java/edu/ie3/simona/api/data/ExtDataContainerQueue.java delete mode 100644 src/main/java/edu/ie3/simona/api/data/ExtInputDataConnection.java delete mode 100644 src/main/java/edu/ie3/simona/api/data/ExtInputDataContainer.java delete mode 100644 src/main/java/edu/ie3/simona/api/data/ExtOutputDataConnection.java create mode 100644 src/main/java/edu/ie3/simona/api/data/connection/BiDirectional.java rename src/main/java/edu/ie3/simona/api/data/{ => connection}/ExtDataConnection.java (87%) create mode 100644 src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java create mode 100644 src/main/java/edu/ie3/simona/api/data/connection/ExtEvDataConnection.java create mode 100644 src/main/java/edu/ie3/simona/api/data/connection/ExtInputDataConnection.java create mode 100644 src/main/java/edu/ie3/simona/api/data/connection/ExtOutputDataConnection.java create mode 100644 src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java create mode 100644 src/main/java/edu/ie3/simona/api/data/connection/ExtResultDataConnection.java create mode 100644 src/main/java/edu/ie3/simona/api/data/connection/ExtResultListener.java create mode 100644 src/main/java/edu/ie3/simona/api/data/container/ExtDataContainer.java create mode 100644 src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java create mode 100644 src/main/java/edu/ie3/simona/api/data/container/ExtResultContainer.java delete mode 100644 src/main/java/edu/ie3/simona/api/data/em/ExtEmDataConnection.java create mode 100644 src/main/java/edu/ie3/simona/api/data/em/ontology/EmDataResponseMessageToExt.java delete mode 100644 src/main/java/edu/ie3/simona/api/data/ev/ExtEvDataConnection.java create mode 100644 src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java create mode 100644 src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java delete mode 100644 src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnection.java delete mode 100644 src/main/java/edu/ie3/simona/api/data/results/ExtResultContainer.java delete mode 100644 src/main/java/edu/ie3/simona/api/data/results/ExtResultDataConnection.java create mode 100644 src/main/java/edu/ie3/simona/api/exceptions/ExtDataConnectionException.java rename src/main/java/edu/ie3/simona/api/{simulation => }/mapping/DataType.java (75%) create mode 100644 src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy rename src/test/groovy/edu/ie3/simona/api/data/{ev => connection}/ExtEvDataConnectionTest.groovy (99%) rename src/test/groovy/edu/ie3/simona/api/data/{primarydata => connection}/ExtPrimaryDataConnectionTest.groovy (52%) rename src/test/groovy/edu/ie3/simona/api/data/{results => connection}/ExtResultDataConnectionTest.groovy (56%) create mode 100644 src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy rename src/test/groovy/edu/ie3/simona/api/data/{results => container}/ExtResultContainerTest.groovy (59%) delete mode 100644 src/test/groovy/edu/ie3/simona/api/data/em/ExtEmDataConnectionTest.groovy diff --git a/docs/readthedocs/connections/connections.md b/docs/readthedocs/connections/connections.md index a48fb91a..c13f7655 100644 --- a/docs/readthedocs/connections/connections.md +++ b/docs/readthedocs/connections/connections.md @@ -1 +1,41 @@ # Data connections + +In order to exchange data between SIMONA and an external simulation, this API defines some data connections. + +The data connections provided by the API can be divided into three kinds of data connections: +1. Input data connections +2. Output data connections +3. Bidirectional data connections + +## Input data connections + +These data connections are used to provide SIMONA with external data. Each input data connection contains two references +for SIMONA actors. The first reference is for the actual service within SIMONA that will receive the external data. The second +reference is for the external simulation adapter. The adapter will receive a message to schedule the data service in SIMONA. + +The process of sending data to the service and asking for scheduling of the service is taken care of by the method `sendExtMsg`. +In order to send a message, simply call the method with the message as input. + +Currently, the following input data connections are provided: +- ExtPrimaryDataConnection + + +## Output data connections + +These data connections are used to provide SIMONA response messages to the external simulation. Each output data connection +has a queue for messages send by SIMONA. The result data connection itself cannot send messages to SIMONA. + +Currently, the following input data connections are provided: +- ExtResultListener + + +## Bidirectional data connections + +The bidirectional data connection combines the functionality of both input and output data connections. These data connections +can be used to send data to SIMONA and receive responses. Also, one additional feature is, that a bidirectional data connections +can request responses, e.g. SIMONA results. + +Currently, the following input data connections are provided: +- ExtEmDataConnection +- ExtEvDataConnection +- ExtResultDataConnection diff --git a/src/main/java/edu/ie3/simona/api/data/DataQueueExtSimulationExtSimulator.java b/src/main/java/edu/ie3/simona/api/data/DataQueueExtSimulationExtSimulator.java deleted file mode 100644 index 67d8cb0e..00000000 --- a/src/main/java/edu/ie3/simona/api/data/DataQueueExtSimulationExtSimulator.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * © 2024. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.api.data; - -import java.util.concurrent.LinkedBlockingQueue; - -/** Data queue to allow data flow between SimonaAPI and an external simulation */ -public class DataQueueExtSimulationExtSimulator { - private final LinkedBlockingQueue receiverTriggerQueue = new LinkedBlockingQueue<>(); - - public void queueData(V data) throws InterruptedException { - this.receiverTriggerQueue.put(data); - } - - public V takeData() throws InterruptedException { - return this.receiverTriggerQueue.take(); - } -} diff --git a/src/main/java/edu/ie3/simona/api/data/ExtDataContainer.java b/src/main/java/edu/ie3/simona/api/data/ExtDataContainer.java deleted file mode 100644 index 224b9de7..00000000 --- a/src/main/java/edu/ie3/simona/api/data/ExtDataContainer.java +++ /dev/null @@ -1,10 +0,0 @@ -/* - * © 2024. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.api.data; - -/** Interface for data that are exchanged between an external simulation and SimonaAPI */ -public interface ExtDataContainer {} diff --git a/src/main/java/edu/ie3/simona/api/data/ExtDataContainerQueue.java b/src/main/java/edu/ie3/simona/api/data/ExtDataContainerQueue.java new file mode 100644 index 00000000..04703fc2 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/ExtDataContainerQueue.java @@ -0,0 +1,63 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data; + +import edu.ie3.simona.api.data.container.ExtDataContainer; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.function.Function; + +/** Data queue to allow data flow between SimonaAPI and an external simulation */ +public final class ExtDataContainerQueue { + private final LinkedBlockingDeque receiverTriggerDeque = new LinkedBlockingDeque<>(); + + /** + * Method for adding an {@link ExtDataContainer} to the queue. + * + * @param data to be added + * @throws InterruptedException if the thread running this has been interrupted during the + * blocking operation + */ + public void queueData(V data) throws InterruptedException { + receiverTriggerDeque.putLast(data); + } + + /** + * Method to take an {@link ExtDataContainer} from the queue. This method waits (blocks) until + * data is added to the queue. + * + * @return a data container + * @throws InterruptedException if the thread running this has been interrupted during the + * blocking operation + */ + public V takeContainer() throws InterruptedException { + return receiverTriggerDeque.takeFirst(); + } + + /** + * Method to take only a part of a container from the queue. This method waits (blocks) until data + * is added to the queue. + * + * @param extractor function to extract a part of the container + * @return the extracted part + * @param type of returned value + * @throws InterruptedException if the thread running this has been interrupted during the + * blocking operation + */ + public R takeData(Function extractor) throws InterruptedException { + // removes the first container from the queue + V data = receiverTriggerDeque.takeFirst(); + R result = extractor.apply(data); + + // if the container is not empty, it should remain in the queue. + // else the container needs to be removed + if (!data.isEmpty()) { + receiverTriggerDeque.putFirst(data); + } + + return result; + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/ExtInputDataConnection.java b/src/main/java/edu/ie3/simona/api/data/ExtInputDataConnection.java deleted file mode 100644 index 0a1430b6..00000000 --- a/src/main/java/edu/ie3/simona/api/data/ExtInputDataConnection.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * © 2024. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.api.data; - -import edu.ie3.simona.api.data.ontology.DataMessageFromExt; -import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt; -import org.apache.pekko.actor.typed.ActorRef; - -/** - * Interface for a connection between SIMONA and an external simulation with data flow from external - * to SIMONA. - */ -public interface ExtInputDataConnection extends ExtDataConnection { - - /** - * Sets the actor refs for data and control flow. - * - * @param dataService actor ref to the adapter of the data service for schedule activation - * messages - * @param extSimAdapter actor ref to the extSimAdapter - */ - void setActorRefs( - ActorRef dataService, - ActorRef extSimAdapter); -} diff --git a/src/main/java/edu/ie3/simona/api/data/ExtInputDataContainer.java b/src/main/java/edu/ie3/simona/api/data/ExtInputDataContainer.java deleted file mode 100644 index bec23e59..00000000 --- a/src/main/java/edu/ie3/simona/api/data/ExtInputDataContainer.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * © 2024. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.api.data; - -import edu.ie3.datamodel.models.value.Value; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -/** Contains all inputs for SIMONA for a certain tick */ -public class ExtInputDataContainer implements ExtDataContainer { - - /** The tick, the input data is meant for */ - private final long tick; - - /** Map external id to an input value for SIMONA */ - private final Map dataMap; - - /** The next tick, when data will be provided, if available */ - private final Optional maybeNextTick; - - /** - * Container class for input data for SIMONA which can be read by SimonaAPI - * - * @param tick The tick, the input data is meant for - * @param dataMap data to be provided to SIMONA - * @param nextTick tick, when the next data will be provided - */ - public ExtInputDataContainer(long tick, Map dataMap, long nextTick) { - this.tick = tick; - this.dataMap = dataMap; - this.maybeNextTick = Optional.of(nextTick); - } - - public ExtInputDataContainer(long tick, Map dataMap) { - this.tick = tick; - this.dataMap = dataMap; - this.maybeNextTick = Optional.empty(); - } - - public ExtInputDataContainer(long tick) { - this(tick, new HashMap<>()); - } - - public ExtInputDataContainer(long tick, long nextTick) { - this(tick, new HashMap<>(), nextTick); - } - - public long getTick() { - return tick; - } - - public Map getSimonaInputMap() { - return dataMap; - } - - public Optional getMaybeNextTick() { - return maybeNextTick; - } - - /** Adds a value to the input map */ - public void addValue(String id, Value value) { - dataMap.put(id, value); - } -} diff --git a/src/main/java/edu/ie3/simona/api/data/ExtOutputDataConnection.java b/src/main/java/edu/ie3/simona/api/data/ExtOutputDataConnection.java deleted file mode 100644 index 4d9ea51a..00000000 --- a/src/main/java/edu/ie3/simona/api/data/ExtOutputDataConnection.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * © 2024. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.api.data; - -import edu.ie3.simona.api.data.ontology.DataMessageFromExt; -import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt; -import org.apache.pekko.actor.typed.ActorRef; - -/** - * Interface for a connection between SIMONA and an external simulation with data flow from SIMONA - * to external. - */ -public interface ExtOutputDataConnection extends ExtDataConnection { - - /** - * Sets the actor refs for data and control flow - * - * @param extResultDataService actor ref to the adapter of the data service for data messages - * @param dataServiceActivation actor ref to the adapter of the data service for schedule - * activation messages - * @param extSimAdapter actor ref to the extSimAdapter - */ - void setActorRefs( - ActorRef extResultDataService, - ActorRef dataServiceActivation, - ActorRef extSimAdapter); -} diff --git a/src/main/java/edu/ie3/simona/api/data/connection/BiDirectional.java b/src/main/java/edu/ie3/simona/api/data/connection/BiDirectional.java new file mode 100644 index 00000000..f0b6875e --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/connection/BiDirectional.java @@ -0,0 +1,57 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.connection; + +import edu.ie3.simona.api.data.ontology.DataMessageFromExt; +import edu.ie3.simona.api.data.ontology.DataResponseMessageToExt; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Enables bidirectional communication when extended by an external data connection. + * + * @param type of message to SIMONA + * @param type of response messages to ext + */ +public abstract non-sealed class BiDirectional< + M extends DataMessageFromExt, R extends DataResponseMessageToExt> + extends ExtInputDataConnection implements ExtOutputDataConnection { + + /** Data message queue containing messages from SIMONA */ + public final LinkedBlockingQueue receiveTriggerQueue = new LinkedBlockingQueue<>(); + + protected BiDirectional() { + super(); + } + + @Override + public final void queueExtResponseMsg(R msg) throws InterruptedException { + receiveTriggerQueue.put(msg); + } + + @Override + public final R receiveAny() throws InterruptedException { + return receiveTriggerQueue.take(); + } + + @Override + @SuppressWarnings("unchecked") + public final T receiveWithType(Class expectedMessageClass) + throws InterruptedException { + // blocks until actor puts something here + R msg = receiveTriggerQueue.take(); + + if (msg.getClass().equals(expectedMessageClass)) { + return (T) msg; + } else + throw new RuntimeException( + "Received unexpected message '" + + msg + + "', expected type '" + + expectedMessageClass + + "'"); + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/ExtDataConnection.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtDataConnection.java similarity index 87% rename from src/main/java/edu/ie3/simona/api/data/ExtDataConnection.java rename to src/main/java/edu/ie3/simona/api/data/connection/ExtDataConnection.java index 9de5c6a3..7a2f53dd 100644 --- a/src/main/java/edu/ie3/simona/api/data/ExtDataConnection.java +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtDataConnection.java @@ -4,7 +4,7 @@ * Research group Distribution grid planning and operation */ -package edu.ie3.simona.api.data; +package edu.ie3.simona.api.data.connection; /** Interface that defines a data connection between SIMONA and an external simulation. */ public interface ExtDataConnection {} diff --git a/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java new file mode 100644 index 00000000..aad04fb3 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java @@ -0,0 +1,84 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.connection; + +import edu.ie3.datamodel.models.value.PValue; +import edu.ie3.simona.api.data.em.ontology.EmDataMessageFromExt; +import edu.ie3.simona.api.data.em.ontology.EmDataResponseMessageToExt; +import edu.ie3.simona.api.data.em.ontology.ProvideEmSetPointData; +import edu.ie3.simona.api.mapping.DataType; +import java.util.*; +import org.slf4j.Logger; + +/** Enables data connection of em data between SIMONA and SimonaAPI */ +public final class ExtEmDataConnection + extends BiDirectional { + + public final EmMode mode; + + /** Assets that are controlled by external simulation */ + private final List controlled; + + public ExtEmDataConnection(List controlled, EmMode mode) { + super(); + + this.mode = mode; + this.controlled = controlled; + } + + /** Returns a list of the uuids of the em agents that expect external set points */ + public List getControlledEms() { + return new ArrayList<>(controlled); + } + + /** + * Sends the em set points to SIMONA. + * + * @param tick current tick + * @param data to be sent + * @param maybeNextTick option for the next tick in the simulation + * @param log logger + */ + public void sendSetPoints( + long tick, Map data, Optional maybeNextTick, Logger log) { + if (data.isEmpty()) { + log.warn("No em set points found! Sending no em data to SIMONA for tick {}.", tick); + } else { + log.debug("Provided SIMONA with em set points."); + sendExtMsg(new ProvideEmSetPointData(tick, data, maybeNextTick)); + } + } + + /** Mode of the em connection */ + public enum EmMode { + SET_POINT("setPoint"), + EM_COMMUNICATION("emCommunication"), + EM_OPTIMIZATION("emOptimization"); + + public final String mode; + + EmMode(String mode) { + this.mode = mode; + } + + /** + * Method to get the {@link EmMode} from the em {@link DataType}. + * + * @param dataType given data type + * @return an {@link EmMode}, or throws an exception if no mode is found for the provided data + * type + */ + public static EmMode fromDataType(DataType dataType) { + return switch (dataType) { + case EXT_EM_INPUT -> EmMode.SET_POINT; + case EXT_EM_COMMUNICATION -> EmMode.EM_COMMUNICATION; + case EXT_EM_OPTIMIZER -> EmMode.EM_OPTIMIZATION; + default -> throw new IllegalStateException("Unexpected data type: " + dataType); + }; + } + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/connection/ExtEvDataConnection.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtEvDataConnection.java new file mode 100644 index 00000000..36f4e652 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtEvDataConnection.java @@ -0,0 +1,79 @@ +/* + * © 2021. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.connection; + +import edu.ie3.simona.api.data.ev.model.EvModel; +import edu.ie3.simona.api.data.ev.ontology.*; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +public final class ExtEvDataConnection + extends BiDirectional { + + public ExtEvDataConnection() { + super(); + } + + /** + * Requests currently available evcs charging stations lots from SIMONA. This method blocks until + * having received a response from SIMONA. + * + * @return a mapping from evcs uuid to the amount of available charging station lots + * @throws InterruptedException if the thread running this has been interrupted during the + * blocking operation + */ + public Map requestAvailablePublicEvcs() throws InterruptedException { + sendExtMsg(new RequestEvcsFreeLots()); + + return receiveWithType(ProvideEvcsFreeLots.class).evcs(); + } + + /** + * Requests prices at all EVCS station at current tick. This method blocks until having received a + * response from SIMONA. + * + * @return mapping from evcs uuid to current price + * @throws InterruptedException if the thread running this has been interrupted during the + * blocking operation + */ + public Map requestCurrentPrices() throws InterruptedException { + sendExtMsg(new RequestCurrentPrices()); + + return receiveWithType(ProvideCurrentPrices.class).prices(); + } + + /** + * Request the charged EVs that are departing from their charging stations at the current tick. + * SIMONA returns the charged departing vehicles with updated battery SOC. This method blocks + * until having received a response from SIMONA. + * + * @param departures the departing EV UUIDs per charging station UUID + * @return all charged departing vehicles + * @throws InterruptedException if the thread running this has been interrupted during the + * blocking operation + */ + public List requestDepartingEvs(Map> departures) + throws InterruptedException { + sendExtMsg(new RequestDepartingEvs(departures)); + + return receiveWithType(ProvideDepartingEvs.class).departedEvs(); + } + + /** + * Provide all EVs that are arriving at some charging station to SIMONA. Method returns right away + * without expecting an answer from SIMONA. + * + * @param arrivals the arriving EV models per charging station UUID + * @param maybeNextTick the next tick at which new arrivals are expected, or empty if simulation + * is about to end + */ + public void provideArrivingEvs(Map> arrivals, Optional maybeNextTick) { + sendExtMsg(new ProvideArrivingEvs(arrivals, maybeNextTick)); + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/connection/ExtInputDataConnection.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtInputDataConnection.java new file mode 100644 index 00000000..8d956292 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtInputDataConnection.java @@ -0,0 +1,52 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.connection; + +import edu.ie3.simona.api.data.ontology.DataMessageFromExt; +import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage; +import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt; +import org.apache.pekko.actor.typed.ActorRef; + +/** + * Abstract base class for a connection between SIMONA and an external simulation with data flow + * from external to SIMONA. + */ +public abstract class ExtInputDataConnection + implements ExtDataConnection { + + /** Actor reference to service that handles data within SIMONA */ + private ActorRef dataService; + + /** Actor reference to adapter that handles scheduler control flow in SIMONA */ + private ActorRef extSimAdapter; + + /** + * Sets the actor refs for data and control flow + * + * @param extResultDataService actor ref to the adapter of the data service for data messages + * @param extSimAdapter actor ref to the extSimAdapter + */ + public final void setActorRefs( + ActorRef extResultDataService, + ActorRef extSimAdapter) { + this.dataService = extResultDataService; + this.extSimAdapter = extSimAdapter; + } + + /** + * Send information from the external simulation to SIMONA's external data service. Furthermore, + * ExtSimAdapter within SIMONA is instructed to activate the external data service with the + * current tick. + * + * @param msg the data/information that is sent to SIMONA's result data service + */ + public final void sendExtMsg(M msg) { + dataService.tell(msg); + // we need to schedule data receiver activation with scheduler + extSimAdapter.tell(new ScheduleDataServiceMessage(dataService)); + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/connection/ExtOutputDataConnection.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtOutputDataConnection.java new file mode 100644 index 00000000..ca632c2b --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtOutputDataConnection.java @@ -0,0 +1,46 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.connection; + +import edu.ie3.simona.api.data.ontology.DataResponseMessageToExt; + +/** + * Interface for a connection between SIMONA and an external simulation with data flow from SIMONA + * to external. + * + * @param type of response messages to ext + */ +public sealed interface ExtOutputDataConnection + permits BiDirectional, ExtResultListener { + + /** Queues message from SIMONA that should be handled by the external simulation. */ + void queueExtResponseMsg(T msg) throws InterruptedException; + + /** + * Waits until a message of given type is added to the queue. All messages that extends the given + * type can be received. This method blocks until having received a response from SIMONA. + * + *

To receive only specific types of messages, use {@link #receiveWithType(Class)} instead. + * + * @return a message of the given type + * @throws InterruptedException if the thread running this has been interrupted during the + * blocking operation + */ + T receiveAny() throws InterruptedException; + + /** + * Waits until a message of given type is added to the queue. If the message has a different type, + * a RuntimeException is thrown. This method blocks until having received a response from SIMONA. + * + * @param expectedMessageClass the expected class of the message to be received + * @return a message of the expected type once it has been received + * @param the type of the expected message + * @throws InterruptedException if the thread running this has been interrupted during the + * blocking operation + */ + R receiveWithType(Class expectedMessageClass) throws InterruptedException; +} diff --git a/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java new file mode 100644 index 00000000..c858d5fd --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java @@ -0,0 +1,56 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.connection; + +import edu.ie3.datamodel.models.value.Value; +import edu.ie3.simona.api.data.primarydata.ontology.PrimaryDataMessageFromExt; +import edu.ie3.simona.api.data.primarydata.ontology.ProvidePrimaryData; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import org.slf4j.Logger; + +/** Enables data connection of primary data between SIMONA and SimonaAPI */ +public final class ExtPrimaryDataConnection + extends ExtInputDataConnection { + + private final Map> valueClasses; + + public ExtPrimaryDataConnection(Map> valueClasses) { + this.valueClasses = valueClasses; + } + + /** Returns a list of the uuids of the system participants that expect external primary data */ + public List getPrimaryDataAssets() { + return valueClasses.keySet().stream().toList(); + } + + /** + * @param uuid of the model + * @return an option for the value class associated with the model. + */ + public Optional> getValueClass(UUID uuid) { + return Optional.ofNullable(valueClasses.get(uuid)); + } + + public void sendPrimaryData( + long tick, Map data, Optional maybeNextTick, Logger log) { + if (data.isEmpty()) { + log.warn("No primary data found! Sending no primary data to SIMONA for tick {}.", tick); + } else { + log.debug("Provided SIMONA with primary data."); + log.info("Data: {}", data); + provideData(tick, data, maybeNextTick); + } + } + + /** Provide primary data from an external simulation in one tick. */ + public void provideData(long tick, Map primaryData, Optional maybeNextTick) { + sendExtMsg(new ProvidePrimaryData(tick, primaryData, maybeNextTick)); + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/connection/ExtResultDataConnection.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtResultDataConnection.java new file mode 100644 index 00000000..dd7a45d1 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtResultDataConnection.java @@ -0,0 +1,100 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.connection; + +import edu.ie3.datamodel.models.result.ResultEntity; +import edu.ie3.simona.api.data.results.ontology.ProvideResultEntities; +import edu.ie3.simona.api.data.results.ontology.RequestResultEntities; +import edu.ie3.simona.api.data.results.ontology.ResultDataMessageFromExt; +import edu.ie3.simona.api.data.results.ontology.ResultDataResponseMessageToExt; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** Enables data connection of results between SIMONA and SimonaAPI */ +public final class ExtResultDataConnection + extends BiDirectional { + + /** Map uuid to external id of grid related entities */ + private final List gridResults; + + /** Map uuid to external id of system participants */ + private final List participantResults; + + /** Map uuid to external id of participant flex options */ + private final List flexResults; + + public ExtResultDataConnection( + List participantResults, List gridResults, List flexResults) { + this.participantResults = participantResults; + this.gridResults = gridResults; + this.flexResults = flexResults; + } + + public List getGridResultDataAssets() { + return gridResults; + } + + public List getParticipantResultDataAssets() { + return participantResults; + } + + public List getFlexOptionAssets() { + return flexResults; + } + + /** Method that an external simulation can request results from SIMONA as a list. */ + private List requestResultList(long tick) throws InterruptedException { + List allExtEntities = + Stream.concat( + Stream.concat(getFlexOptionAssets().stream(), getGridResultDataAssets().stream()), + getParticipantResultDataAssets().stream()) + .toList(); + sendExtMsg(new RequestResultEntities(tick, allExtEntities)); + return receiveWithType(ProvideResultEntities.class).results(); + } + + private List requestFlexOptionResultsList(long tick) throws InterruptedException { + sendExtMsg(new RequestResultEntities(tick, getFlexOptionAssets())); + return receiveWithType(ProvideResultEntities.class).results(); + } + + private List requestGridResultsList(long tick) throws InterruptedException { + sendExtMsg(new RequestResultEntities(tick, getGridResultDataAssets())); + return receiveWithType(ProvideResultEntities.class).results(); + } + + private List requestParticipantResultsList(long tick) throws InterruptedException { + sendExtMsg(new RequestResultEntities(tick, getParticipantResultDataAssets())); + return receiveWithType(ProvideResultEntities.class).results(); + } + + /** + * Method that an external simulation can request results from SIMONA as a map string to object. + */ + public Map requestResults(long tick) throws InterruptedException { + return createResultMap(requestResultList(tick)); + } + + public Map requestFlexOptionResults(long tick) throws InterruptedException { + return createResultMap(requestFlexOptionResultsList(tick)); + } + + public Map requestGridResults(long tick) throws InterruptedException { + return createResultMap(requestGridResultsList(tick)); + } + + public Map requestParticipantResults(long tick) throws InterruptedException { + return createResultMap(requestParticipantResultsList(tick)); + } + + private Map createResultMap(List results) { + return results.stream().collect(Collectors.toMap(ResultEntity::getInputModel, i -> i)); + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/connection/ExtResultListener.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtResultListener.java new file mode 100644 index 00000000..07e86340 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtResultListener.java @@ -0,0 +1,53 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.connection; + +import edu.ie3.simona.api.data.results.ontology.ResultDataResponseMessageToExt; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * External result listener. This listener is similar to the {@link ExtResultDataConnection}, but is + * not able to request results from SIMONA. + */ +public non-sealed class ExtResultListener + implements ExtOutputDataConnection { + + /** Data message queue containing messages from SIMONA */ + public final LinkedBlockingQueue receiveTriggerQueue = new LinkedBlockingQueue<>(); + + protected ExtResultListener() { + super(); + } + + @Override + public final void queueExtResponseMsg(R msg) throws InterruptedException { + receiveTriggerQueue.put(msg); + } + + @Override + public final R receiveAny() throws InterruptedException { + return receiveTriggerQueue.take(); + } + + @Override + @SuppressWarnings("unchecked") + public final T receiveWithType(Class expectedMessageClass) + throws InterruptedException { + // blocks until actor puts something here + R msg = receiveTriggerQueue.take(); + + if (msg.getClass().equals(expectedMessageClass)) { + return (T) msg; + } else + throw new RuntimeException( + "Received unexpected message '" + + msg + + "', expected type '" + + expectedMessageClass + + "'"); + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/container/ExtDataContainer.java b/src/main/java/edu/ie3/simona/api/data/container/ExtDataContainer.java new file mode 100644 index 00000000..96a18846 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/container/ExtDataContainer.java @@ -0,0 +1,31 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.container; + +import java.util.HashMap; +import java.util.Map; + +/** Interface for data that are exchanged between an external simulation and SimonaAPI */ +public sealed interface ExtDataContainer permits ExtInputContainer, ExtResultContainer { + + /** Returns true, if the container is empty. */ + boolean isEmpty(); + + /** + * Method to copy a given map and clear the original. + * + * @param map to be copied and cleared + * @return the copy + * @param type of key + * @param type of value + */ + default Map copyAndClear(Map map) { + Map result = new HashMap<>(map); + map.clear(); + return result; + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java b/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java new file mode 100644 index 00000000..365d199b --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java @@ -0,0 +1,178 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.container; + +import edu.ie3.datamodel.models.value.PValue; +import edu.ie3.datamodel.models.value.Value; +import edu.ie3.simona.api.data.model.em.FlexOptionRequest; +import edu.ie3.simona.api.data.model.em.FlexOptions; +import java.util.*; + +/** Contains all inputs for SIMONA for a certain tick */ +public final class ExtInputContainer implements ExtDataContainer { + + /** The tick, the input data is meant for. */ + private final long tick; + + /** The next tick, when data will be provided, if available. */ + private final Optional maybeNextTick; + + // primary map + /** Map uuid to primary input value for SIMONA. */ + private final Map primaryData = new HashMap<>(); + + // em maps + /** Map uuid to flex option request. */ + private final Map flexRequests = new HashMap<>(); + + /** Map uuid to flex options. */ + private final Map> flexOptions = new HashMap<>(); + + /** Map uuid to em set point. */ + private final Map setPoints = new HashMap<>(); + + /** + * Container class for input data for SIMONA which can be read by SimonaAPI + * + * @param tick The tick, the input data is meant for + * @param nextTick tick, when the next data will be provided + */ + public ExtInputContainer(long tick, long nextTick) { + this.tick = tick; + this.maybeNextTick = Optional.of(nextTick); + } + + public ExtInputContainer(long tick) { + this.tick = tick; + this.maybeNextTick = Optional.empty(); + } + + @Override + public boolean isEmpty() { + return primaryData.isEmpty() + && flexRequests.isEmpty() + && flexOptions.isEmpty() + && setPoints.isEmpty(); + } + + /** Returns the tick data is provided for. */ + public long getTick() { + return tick; + } + + /** Returns an option for the next tick, data will be provided. */ + public Optional getMaybeNextTick() { + return maybeNextTick; + } + + // add data + + /** + * Method for adding primary input values for a given asset. + * + * @param asset uuid, that will receive primary data + * @param value the received value + */ + public void addPrimaryValue(UUID asset, Value value) { + primaryData.put(asset, value); + } + + /** + * Method for adding flex option requests. + * + * @param requester the uuid of the requesting agent + * @param emEntities list of em agents, that will receive a request + */ + public void addRequest(UUID requester, List emEntities) { + flexRequests.put(requester, new FlexOptionRequest(requester, emEntities)); + } + + /** + * Method for adding flex options to a given receiver. + * + * @param receiver that will receive the flex options + * @param flexOption that will be added + */ + public void addFlexOptions(UUID receiver, List flexOption) { + if (!flexOptions.containsKey(receiver)) { + List flexOptionValues = new ArrayList<>(flexOption); + flexOptions.put(receiver, flexOptionValues); + } else { + flexOptions.get(receiver).addAll(flexOption); + } + } + + /** + * Method for adding an em set point for a given asset. + * + * @param asset that will receive the set point + * @param setPoint given set point + */ + public void addSetPoint(UUID asset, PValue setPoint) { + setPoints.put(asset, setPoint); + } + + /** Extracts the primary input data from this container. All other input data remains the same. */ + public Map extractPrimaryData() { + return copyAndClear(primaryData); + } + + /** + * Extracts the flex option request input data from this container. All other input data remains + * the same. + */ + public Map extractFlexRequests() { + return copyAndClear(flexRequests); + } + + /** + * Extracts the flex option input data from this container. All other input data remains the same. + */ + public Map> extractFlexOptions() { + return copyAndClear(flexOptions); + } + + /** + * Extracts the set point input data from this container. All other input data remains the same. + */ + public Map extractSetPoints() { + return copyAndClear(setPoints); + } + + /** + * Returns a string representation of the primary input data without changing the data. To extract + * (remove) the primary input data, use {@link #extractPrimaryData()} instead. + */ + public String primaryDataString() { + return primaryData.toString(); + } + + /** + * Returns a string representation of the flex option request input data without changing the + * data. To extract (remove) the flex option request input data, use {@link + * #extractFlexRequests()} instead. + */ + public String flexRequestsString() { + return flexRequests.toString(); + } + + /** + * Returns a string representation of the flex option input data without changing the data. To + * extract (remove) the flex option input data, use {@link #extractFlexOptions()} instead. + */ + public String flexOptionsString() { + return flexOptions.toString(); + } + + /** + * Returns a string representation of the set point input data without changing the data. To + * extract (remove) the set point input data, use {@link #extractSetPoints()} instead. + */ + public String setPointsString() { + return setPoints.toString(); + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/container/ExtResultContainer.java b/src/main/java/edu/ie3/simona/api/data/container/ExtResultContainer.java new file mode 100644 index 00000000..adb12863 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/container/ExtResultContainer.java @@ -0,0 +1,162 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.container; + +import static edu.ie3.util.quantities.PowerSystemUnits.PU; + +import edu.ie3.datamodel.models.result.NodeResult; +import edu.ie3.datamodel.models.result.ResultEntity; +import edu.ie3.datamodel.models.result.connector.LineResult; +import edu.ie3.datamodel.models.result.system.SystemParticipantResult; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import javax.measure.quantity.Dimensionless; +import tech.units.indriya.ComparableQuantity; +import tech.units.indriya.quantity.Quantities; + +/** Contains all results from SIMONA for a certain tick */ +public final class ExtResultContainer implements ExtDataContainer { + + /** Tick the results are meant for */ + private final long tick; + + /** Tick the external simulation can expect the next results */ + private final Optional maybeNextTick; + + /** + * Map uuid to result from SIMONA + * + *

ATTENTION: The time stamp of the result entities is not necessarily corresponding to the + * tick + */ + private final Map resultMap; + + /** + * Container class for result data from SIMONA + * + * @param tick current tick + * @param resultMap results from SIMONA with external id as key + * @param nextTick tick the external simulation can expect the next results + */ + public ExtResultContainer(long tick, Map resultMap, Optional nextTick) { + this.tick = tick; + this.resultMap = resultMap; + this.maybeNextTick = nextTick; + } + + public ExtResultContainer(long tick, Map resultMap) { + this(tick, resultMap, Optional.empty()); + } + + @Override + public boolean isEmpty() { + return resultMap.isEmpty(); + } + + /** Returns a map: uuid to result. */ + public Map getResults() { + return resultMap; + } + + /** + * Method to extract a specific type of results. + * + * @param clazz of the results + * @return a map: uuid to requested result, or an empty map, if no results for the requested type + * are present + * @param type of result + */ + @SuppressWarnings("unchecked") + public Map getResults(Class clazz) { + Map result = new HashMap<>(); + + for (Map.Entry entry : resultMap.entrySet()) { + ResultEntity resultEntity = entry.getValue(); + + if (entry.getValue().getClass().equals(clazz)) { + // add the result, if the found result is of the requested type + result.put(entry.getKey(), (R) resultEntity); + } + } + + return result; + } + + /** Returns the tick data is provided for. */ + public long getTick() { + return tick; + } + + /** Returns an option for the next tick, data will be provided. */ + public Optional getMaybeNextTick() { + return maybeNextTick; + } + + /** Returns the result for a certain asset. */ + public ResultEntity getResult(UUID assetId) { + return resultMap.get(assetId); + } + + /** + * Returns the voltage deviation in pu for certain asset, if this asset provided a {@link + * NodeResult} + */ + public double getVoltageDeviation(UUID assetId) { + if (resultMap.get(assetId) instanceof NodeResult nodeResult) { + ComparableQuantity vMagDev = + Quantities.getQuantity(-1.0, PU).add(nodeResult.getvMag()); + return vMagDev.getValue().doubleValue(); + } else { + throw new IllegalArgumentException( + "VOLTAGE DEVIATION is only available for NodeResult's! AssetId: " + assetId); + } + } + + /** + * Returns the voltage deviation for certain asset, if this asset provided a {@link NodeResult} + */ + public double getVoltage(UUID assetId) { + if (resultMap.get(assetId) instanceof NodeResult nodeResult) { + return nodeResult.getvMag().getValue().doubleValue(); + } else { + throw new IllegalArgumentException("VOLTAGE is only available for NodeResult's!"); + } + } + + /** + * Returns the active power in kW for certain asset, if this asset provided a {@link + * SystemParticipantResult} + */ + public double getActivePower(UUID assetId) { + if (resultMap.get(assetId) instanceof SystemParticipantResult systemParticipantResult) { + return systemParticipantResult.getP().getValue().doubleValue(); + } else { + throw new IllegalArgumentException( + "ACTIVE POWER is only available for SystemParticipantResult's!"); + } + } + + /** + * Returns the reactive power in kVAr for certain asset, if this asset provided a {@link + * SystemParticipantResult} + */ + public double getReactivePower(UUID assetId) { + if (resultMap.get(assetId) instanceof SystemParticipantResult systemParticipantResult) { + return systemParticipantResult.getQ().getValue().doubleValue(); + } else { + throw new IllegalArgumentException( + "REACTIVE POWER is only available for SystemParticipantResult's!"); + } + } + + /** Returns the line loading for certain asset, if this asset provided a {@link LineResult} */ + public double getLineLoading(UUID assetId) { + throw new IllegalArgumentException("LINE LOADING is not implemented yet!"); + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/em/ExtEmDataConnection.java b/src/main/java/edu/ie3/simona/api/data/em/ExtEmDataConnection.java deleted file mode 100644 index 6079a2eb..00000000 --- a/src/main/java/edu/ie3/simona/api/data/em/ExtEmDataConnection.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * © 2024. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.api.data.em; - -import edu.ie3.datamodel.models.value.PValue; -import edu.ie3.datamodel.models.value.Value; -import edu.ie3.simona.api.data.ExtInputDataConnection; -import edu.ie3.simona.api.data.em.ontology.EmDataMessageFromExt; -import edu.ie3.simona.api.data.em.ontology.ProvideEmSetPointData; -import edu.ie3.simona.api.data.ontology.DataMessageFromExt; -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage; -import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; -import org.apache.pekko.actor.typed.ActorRef; -import org.slf4j.Logger; - -/** Enables data connection of em data between SIMONA and SimonaAPI */ -public class ExtEmDataConnection implements ExtInputDataConnection { - - /** Actor reference to service that handles ev data within SIMONA */ - private ActorRef emDataService; - - /** Actor reference to adapter that handles scheduler control flow in SIMONA */ - private ActorRef extSimAdapter; - - /** Assets that provide primary data to SIMONA */ - private final Map extEmMapping; - - public ExtEmDataConnection(Map extEmMapping) { - this.extEmMapping = extEmMapping; - } - - @Override - public void setActorRefs( - ActorRef emDataService, - ActorRef extSimAdapter) { - this.emDataService = emDataService; - this.extSimAdapter = extSimAdapter; - } - - public void convertAndSend( - long tick, Map data, Optional maybeNextTick, Logger log) { - // filtering the data and converting the keys - Map convertedMap = - data.entrySet().stream() - .filter(e -> extEmMapping.containsKey(e.getKey())) - .collect( - Collectors.toMap(e -> extEmMapping.get(e.getKey()), e -> (PValue) e.getValue())); - - if (convertedMap.isEmpty()) { - log.warn("No em data found! Sending no em data to SIMONA for tick {}.", tick); - } else { - log.debug("Provided SIMONA with em data."); - provideEmData(tick, convertedMap, maybeNextTick); - } - } - - /** Returns a list of the uuids of the em agents that expect external set points */ - public List getControlledEms() { - return extEmMapping.values().stream().toList(); - } - - /** Provide primary data from an external simulation for one tick. */ - public void provideEmData(Long tick, Map emData, Optional maybeNextTick) { - sendExtMsg(new ProvideEmSetPointData(tick, emData, maybeNextTick)); - } - - /** - * Send information from the external simulation to SIMONA's external primary data service. - * Furthermore, ExtSimAdapter within SIMONA is instructed to activate the ev data service with the - * current tick. - * - * @param msg the data/information that is sent to SIMONA's external primary data service - */ - public void sendExtMsg(EmDataMessageFromExt msg) { - emDataService.tell(msg); - // we need to schedule data receiver activation with scheduler - extSimAdapter.tell(new ScheduleDataServiceMessage(emDataService)); - } -} diff --git a/src/main/java/edu/ie3/simona/api/data/em/ontology/EmDataMessageFromExt.java b/src/main/java/edu/ie3/simona/api/data/em/ontology/EmDataMessageFromExt.java index 5125e292..a5ea688c 100644 --- a/src/main/java/edu/ie3/simona/api/data/em/ontology/EmDataMessageFromExt.java +++ b/src/main/java/edu/ie3/simona/api/data/em/ontology/EmDataMessageFromExt.java @@ -8,5 +8,5 @@ import edu.ie3.simona.api.data.ontology.DataMessageFromExt; -/** Messages that are sent from an external data simulation which provides em data to SIMONA */ +/** Messages that are sent from an external data simulation which provides em data to SIMONA. */ public interface EmDataMessageFromExt extends DataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/data/em/ontology/EmDataResponseMessageToExt.java b/src/main/java/edu/ie3/simona/api/data/em/ontology/EmDataResponseMessageToExt.java new file mode 100644 index 00000000..8f373132 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/em/ontology/EmDataResponseMessageToExt.java @@ -0,0 +1,12 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.em.ontology; + +import edu.ie3.simona.api.data.ontology.DataResponseMessageToExt; + +/** Messages that are sent from SIMONA to the external simulation that needs em data. */ +public interface EmDataResponseMessageToExt extends DataResponseMessageToExt {} diff --git a/src/main/java/edu/ie3/simona/api/data/em/ontology/ProvideEmSetPointData.java b/src/main/java/edu/ie3/simona/api/data/em/ontology/ProvideEmSetPointData.java index dc505dca..d3dc699a 100644 --- a/src/main/java/edu/ie3/simona/api/data/em/ontology/ProvideEmSetPointData.java +++ b/src/main/java/edu/ie3/simona/api/data/em/ontology/ProvideEmSetPointData.java @@ -11,7 +11,7 @@ import java.util.Optional; import java.util.UUID; -/** Message that provides em data (set points) from an external simulation */ +/** Message that provides em data (set points) from an external simulation. */ public record ProvideEmSetPointData( long tick, Map emData, Optional maybeNextTick) implements EmDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/data/ev/ExtEvDataConnection.java b/src/main/java/edu/ie3/simona/api/data/ev/ExtEvDataConnection.java deleted file mode 100644 index 564a87ad..00000000 --- a/src/main/java/edu/ie3/simona/api/data/ev/ExtEvDataConnection.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * © 2021. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.api.data.ev; - -import edu.ie3.simona.api.data.ExtInputDataConnection; -import edu.ie3.simona.api.data.ev.model.EvModel; -import edu.ie3.simona.api.data.ev.ontology.*; -import edu.ie3.simona.api.data.ontology.DataMessageFromExt; -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage; -import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.LinkedBlockingQueue; -import org.apache.pekko.actor.typed.ActorRef; - -public class ExtEvDataConnection implements ExtInputDataConnection { - /** Data message queue containing messages from SIMONA */ - public final LinkedBlockingQueue receiveTriggerQueue = - new LinkedBlockingQueue<>(); - - /** Actor reference to service that handles ev data within SIMONA */ - private ActorRef dataService; - - /** Actor reference to adapter that handles scheduler control flow in SIMONA */ - private ActorRef extSimAdapter; - - @Override - public void setActorRefs( - ActorRef dataService, - ActorRef extSimAdapter) { - this.dataService = dataService; - this.extSimAdapter = extSimAdapter; - } - - /** - * Requests currently available evcs charging stations lots from SIMONA. This method blocks until - * having received a response from SIMONA. - * - * @return a mapping from evcs uuid to the amount of available charging station lots - * @throws InterruptedException if the thread running this has been interrupted during the - * blocking operation - */ - public Map requestAvailablePublicEvcs() throws InterruptedException { - sendExtMsg(new RequestEvcsFreeLots()); - - return receiveWithType(ProvideEvcsFreeLots.class).evcs(); - } - - /** - * Requests prices at all EVCS station at current tick. This method blocks until having received a - * response from SIMONA. - * - * @return mapping from evcs uuid to current price - * @throws InterruptedException if the thread running this has been interrupted during the - * blocking operation - */ - public Map requestCurrentPrices() throws InterruptedException { - sendExtMsg(new RequestCurrentPrices()); - - return receiveWithType(ProvideCurrentPrices.class).prices(); - } - - /** - * Request the charged EVs that are departing from their charging stations at the current tick. - * SIMONA returns the charged departing vehicles with updated battery SOC. This method blocks - * until having received a response from SIMONA. - * - * @param departures the departing EV UUIDs per charging station UUID - * @return all charged departing vehicles - * @throws InterruptedException if the thread running this has been interrupted during the - * blocking operation - */ - public List requestDepartingEvs(Map> departures) - throws InterruptedException { - sendExtMsg(new RequestDepartingEvs(departures)); - - return receiveWithType(ProvideDepartingEvs.class).departedEvs(); - } - - /** - * Provide all EVs that are arriving at some charging station to SIMONA. Method returns right away - * without expecting an answer from SIMONA. - * - * @param arrivals the arriving EV models per charging station UUID - * @param maybeNextTick the next tick at which new arrivals are expected, or empty if simulation - * is about to end - */ - public void provideArrivingEvs(Map> arrivals, Optional maybeNextTick) { - sendExtMsg(new ProvideArrivingEvs(arrivals, maybeNextTick)); - } - - /** - * Send information from the external ev simulation to SIMONA's ev data service. Furthermore, - * ExtSimAdapter within SIMONA is instructed to activate the ev data service with the current - * tick. - * - * @param msg the data/information that is sent to SIMONA's ev data service - */ - public void sendExtMsg(EvDataMessageFromExt msg) { - dataService.tell(msg); - // we need to schedule data receiver activation with scheduler - extSimAdapter.tell(new ScheduleDataServiceMessage(dataService)); - } - - /** - * Queues message from SIMONA that should be handled by the external ev simulation. - * - * @param extEvResponse the message to be handled - * @throws InterruptedException if the thread running this has been interrupted during waiting for - * the message to be queued - */ - public void queueExtResponseMsg(EvDataResponseMessageToExt extEvResponse) - throws InterruptedException { - receiveTriggerQueue.put(extEvResponse); - } - - /** - * Waits until a message of given type is added to the queue. If the message has a different type, - * a RuntimeException is thrown. This method blocks until having received a response from SIMONA. - * - * @param expectedMessageClass the expected class of the message to be received - * @return a message of the expected type once it has been received - * @param the type of the expected message - * @throws InterruptedException if the thread running this has been interrupted during the - * blocking operation - */ - @SuppressWarnings("unchecked") - private T receiveWithType(Class expectedMessageClass) - throws InterruptedException { - - // blocks until actor puts something here - EvDataResponseMessageToExt evMessage = receiveTriggerQueue.take(); - - if (evMessage.getClass().equals(expectedMessageClass)) { - return (T) evMessage; - } else - throw new RuntimeException( - "Received unexpected message '" - + evMessage - + "', expected type '" - + expectedMessageClass - + "'"); - } -} diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java new file mode 100644 index 00000000..184dc1df --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java @@ -0,0 +1,13 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.model.em; + +import edu.ie3.datamodel.models.value.Value; +import java.util.List; +import java.util.UUID; + +public record FlexOptionRequest(UUID requester, List emEntities) implements Value {} diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java new file mode 100644 index 00000000..abc10063 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java @@ -0,0 +1,17 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.model.em; + +import java.util.UUID; +import javax.measure.quantity.Power; +import tech.units.indriya.ComparableQuantity; + +public record FlexOptions( + UUID sender, + ComparableQuantity pMin, + ComparableQuantity pRef, + ComparableQuantity pMax) {} diff --git a/src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnection.java b/src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnection.java deleted file mode 100644 index ce03e274..00000000 --- a/src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnection.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * © 2024. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.api.data.primarydata; - -import edu.ie3.datamodel.models.value.Value; -import edu.ie3.simona.api.data.ExtInputDataConnection; -import edu.ie3.simona.api.data.ontology.DataMessageFromExt; -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage; -import edu.ie3.simona.api.data.primarydata.ontology.PrimaryDataMessageFromExt; -import edu.ie3.simona.api.data.primarydata.ontology.ProvidePrimaryData; -import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; -import org.apache.pekko.actor.typed.ActorRef; -import org.slf4j.Logger; - -/** Enables data connection of primary data between SIMONA and SimonaAPI */ -public class ExtPrimaryDataConnection implements ExtInputDataConnection { - - /** Actor reference to service that handles primary data within SIMONA */ - private ActorRef dataService; - - /** Actor reference to adapter that handles scheduler control flow in SIMONA */ - private ActorRef extSimAdapter; - - /** Assets that provide primary data to SIMONA */ - private final Map extPrimaryDataMapping; - - public ExtPrimaryDataConnection(Map extPrimaryDataMapping) { - this.extPrimaryDataMapping = extPrimaryDataMapping; - } - - @Override - public void setActorRefs( - ActorRef dataService, - ActorRef extSimAdapter) { - this.dataService = dataService; - this.extSimAdapter = extSimAdapter; - } - - public void convertAndSend( - long tick, Map data, Optional maybeNextTick, Logger log) { - // filtering the data and converting the keys - Map convertedMap = - data.entrySet().stream() - .filter(e -> extPrimaryDataMapping.containsKey(e.getKey())) - .collect( - Collectors.toMap(e -> extPrimaryDataMapping.get(e.getKey()), Map.Entry::getValue)); - - if (convertedMap.isEmpty()) { - log.warn("No primary data found! Sending no primary data to SIMONA for tick {}.", tick); - } else { - log.debug("Provided SIMONA with primary data."); - providePrimaryData(tick, convertedMap, maybeNextTick); - } - } - - /** Returns a list of the uuids of the system participants that expect external primary data */ - public List getPrimaryDataAssets() { - return extPrimaryDataMapping.values().stream().toList(); - } - - /** Provide primary data from an external simulation in one tick. */ - public void providePrimaryData( - Long tick, Map primaryData, Optional maybeNextTick) { - sendExtMsg(new ProvidePrimaryData(tick, primaryData, maybeNextTick)); - } - - /** - * Send information from the external simulation to SIMONA's external primary data service. - * Furthermore, ExtSimAdapter within SIMONA is instructed to activate the ev data service with the - * current tick. - * - * @param msg the data/information that is sent to SIMONA's external primary data service - */ - public void sendExtMsg(PrimaryDataMessageFromExt msg) { - dataService.tell(msg); - // we need to schedule data receiver activation with scheduler - extSimAdapter.tell(new ScheduleDataServiceMessage(dataService)); - } -} diff --git a/src/main/java/edu/ie3/simona/api/data/results/ExtResultContainer.java b/src/main/java/edu/ie3/simona/api/data/results/ExtResultContainer.java deleted file mode 100644 index 30ad84d1..00000000 --- a/src/main/java/edu/ie3/simona/api/data/results/ExtResultContainer.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * © 2024. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.api.data.results; - -import static edu.ie3.util.quantities.PowerSystemUnits.PU; - -import edu.ie3.datamodel.models.result.NodeResult; -import edu.ie3.datamodel.models.result.ResultEntity; -import edu.ie3.datamodel.models.result.connector.LineResult; -import edu.ie3.datamodel.models.result.system.SystemParticipantResult; -import edu.ie3.simona.api.data.ExtDataContainer; -import java.util.Map; -import java.util.Optional; -import javax.measure.quantity.Dimensionless; -import tech.units.indriya.ComparableQuantity; -import tech.units.indriya.quantity.Quantities; - -/** Contains all results from SIMONA for a certain tick */ -public class ExtResultContainer implements ExtDataContainer { - - /** Tick the results are meant for */ - private final long tick; - - /** Tick the external simulation can expect the next results */ - private final Optional maybeNextTick; - - /** - * Map external id to result from SIMONA ATTENTION: The time stamp of the result entities is not - * necessarily corresponding to the tick - */ - private final Map simonaResultsMap; - - /** - * Container class for result data from SIMONA - * - * @param tick current tick - * @param simonaResultsMap results from SIMONA with external id as key - * @param nextTick tick the external simulation can expect the next results - */ - public ExtResultContainer( - long tick, Map simonaResultsMap, Optional nextTick) { - this.tick = tick; - this.simonaResultsMap = simonaResultsMap; - this.maybeNextTick = nextTick; - } - - public ExtResultContainer(long tick, Map simonaResultsMap) { - this(tick, simonaResultsMap, Optional.empty()); - } - - public Map getResults() { - return simonaResultsMap; - } - - public Long getTick() { - return tick; - } - - public Optional getNextTick() { - return maybeNextTick; - } - - /** - * Returns the voltage deviation for certain asset, if this asset provided a {@link NodeResult} - */ - public double getVoltageDeviation(String assetId) { - if (simonaResultsMap.get(assetId) instanceof NodeResult nodeResult) { - ComparableQuantity vMagDev = - Quantities.getQuantity(-1.0, PU).add(nodeResult.getvMag()); - return vMagDev.getValue().doubleValue(); - } else { - throw new IllegalArgumentException("VOLTAGE DEVIATION is only available for NodeResult's!"); - } - } - - /** - * Returns the active power in kW for certain asset, if this asset provided a {@link - * SystemParticipantResult} - */ - public double getActivePower(String assetId) { - if (simonaResultsMap.get(assetId) instanceof SystemParticipantResult systemParticipantResult) { - return systemParticipantResult.getP().getValue().doubleValue(); - } else { - throw new IllegalArgumentException( - "ACTIVE POWER is only available for SystemParticipantResult's!"); - } - } - - /** - * Returns the reactive power in kVAr for certain asset, if this asset provided a {@link - * SystemParticipantResult} - */ - public double getReactivePower(String assetId) { - if (simonaResultsMap.get(assetId) instanceof SystemParticipantResult systemParticipantResult) { - return systemParticipantResult.getQ().getValue().doubleValue(); - } else { - throw new IllegalArgumentException( - "REACTIVE POWER is only available for SystemParticipantResult's!"); - } - } - - /** Returns the line loading for certain asset, if this asset provided a {@link LineResult} */ - public double getLineLoading(String assetId) { - throw new IllegalArgumentException("LINE LOADING is not implemented yet!"); - } -} diff --git a/src/main/java/edu/ie3/simona/api/data/results/ExtResultDataConnection.java b/src/main/java/edu/ie3/simona/api/data/results/ExtResultDataConnection.java deleted file mode 100644 index 3a93a7f2..00000000 --- a/src/main/java/edu/ie3/simona/api/data/results/ExtResultDataConnection.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * © 2024. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.api.data.results; - -import edu.ie3.datamodel.models.result.NodeResult; -import edu.ie3.datamodel.models.result.ResultEntity; -import edu.ie3.datamodel.models.result.system.SystemParticipantResult; -import edu.ie3.simona.api.data.ExtOutputDataConnection; -import edu.ie3.simona.api.data.ontology.DataMessageFromExt; -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage; -import edu.ie3.simona.api.data.results.ontology.ProvideResultEntities; -import edu.ie3.simona.api.data.results.ontology.RequestResultEntities; -import edu.ie3.simona.api.data.results.ontology.ResultDataMessageFromExt; -import edu.ie3.simona.api.data.results.ontology.ResultDataResponseMessageToExt; -import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.LinkedBlockingQueue; -import org.apache.pekko.actor.typed.ActorRef; - -/** Enables data connection of results between SIMONA and SimonaAPI */ -public class ExtResultDataConnection implements ExtOutputDataConnection { - - /** Data message queue containing messages from SIMONA */ - public final LinkedBlockingQueue receiveTriggerQueue = - new LinkedBlockingQueue<>(); - - /** Actor reference to service that handles result data within SIMONA */ - private ActorRef extResultDataService; - - /** Actor reference to the dataServiceAdapter */ - private ActorRef dataServiceActivation; - - /** Actor reference to adapter that handles scheduler control flow in SIMONA */ - private ActorRef extSimAdapter; - - /** Map uuid to external id of grid related entities */ - private final Map gridResultAssetMapping; - - /** Map uuid to external id of system participants */ - private final Map participantResultAssetMapping; - - public ExtResultDataConnection( - Map participantResultAssetMapping, Map gridResultAssetMapping) { - this.participantResultAssetMapping = participantResultAssetMapping; - this.gridResultAssetMapping = gridResultAssetMapping; - } - - /** - * Sets the actor refs for data and control flow - * - * @param extResultDataService actor ref to the adapter of the data service for data messages - * @param dataServiceActivation actor ref to the adapter of the data service for schedule - * activation messages - * @param extSimAdapter actor ref to the extSimAdapter - */ - public void setActorRefs( - ActorRef extResultDataService, - ActorRef dataServiceActivation, - ActorRef extSimAdapter) { - this.extResultDataService = extResultDataService; - this.dataServiceActivation = dataServiceActivation; - this.extSimAdapter = extSimAdapter; - } - - public List getGridResultDataAssets() { - return gridResultAssetMapping.keySet().stream().toList(); - } - - public List getParticipantResultDataAssets() { - return participantResultAssetMapping.keySet().stream().toList(); - } - - /** Method that an external simulation can request results from SIMONA as a list. */ - private List requestResultList(long tick) throws InterruptedException { - sendExtMsg(new RequestResultEntities(tick)); - return receiveWithType(ProvideResultEntities.class).results(); - } - - /** - * Method that an external simulation can request results from SIMONA as a map string to object. - */ - public Map requestResults(long tick) throws InterruptedException { - return createResultMap(requestResultList(tick)); - } - - protected Map createResultMap(List results) { - Map resultMap = new HashMap<>(); - results.forEach( - result -> { - if (result instanceof NodeResult nodeResult) { - resultMap.put(gridResultAssetMapping.get(nodeResult.getInputModel()), nodeResult); - } else if (result instanceof SystemParticipantResult systemParticipantResult) { - resultMap.put( - participantResultAssetMapping.get(systemParticipantResult.getInputModel()), - systemParticipantResult); - } else { - throw new IllegalArgumentException( - "ExtResultData can only handle NodeResult's and SystemParticipantResult's!"); - } - }); - return resultMap; - } - - /** - * Send information from the external simulation to SIMONA's external data service. Furthermore, - * ExtSimAdapter within SIMONA is instructed to activate the external data service with the - * current tick. - * - * @param msg the data/information that is sent to SIMONA's result data service - */ - public void sendExtMsg(ResultDataMessageFromExt msg) { - extResultDataService.tell(msg); - // we need to schedule data receiver activation with scheduler - extSimAdapter.tell(new ScheduleDataServiceMessage(dataServiceActivation)); - } - - /** Queues message from SIMONA that should be handled by the external simulation. */ - public void queueExtResponseMsg(ResultDataResponseMessageToExt msg) throws InterruptedException { - receiveTriggerQueue.put(msg); - } - - /** - * Waits until a message of given type is added to the queue. If the message has a different type, - * a RuntimeException is thrown. This method blocks until having received a response from SIMONA. - * - * @param expectedMessageClass the expected class of the message to be received - * @return a message of the expected type once it has been received - * @param the type of the expected message - * @throws InterruptedException if the thread running this has been interrupted during the - * blocking operation - */ - @SuppressWarnings("unchecked") - private T receiveWithType( - Class expectedMessageClass) throws InterruptedException { - - // blocks until actor puts something here - ResultDataResponseMessageToExt msg = receiveTriggerQueue.take(); - - if (msg.getClass().equals(expectedMessageClass)) { - return (T) msg; - } else - throw new RuntimeException( - "Received unexpected message '" - + msg - + "', expected type '" - + expectedMessageClass - + "'"); - } -} diff --git a/src/main/java/edu/ie3/simona/api/data/results/ontology/RequestResultEntities.java b/src/main/java/edu/ie3/simona/api/data/results/ontology/RequestResultEntities.java index 8ef656b0..bc4dc37f 100644 --- a/src/main/java/edu/ie3/simona/api/data/results/ontology/RequestResultEntities.java +++ b/src/main/java/edu/ie3/simona/api/data/results/ontology/RequestResultEntities.java @@ -6,5 +6,9 @@ package edu.ie3.simona.api.data.results.ontology; +import java.util.List; +import java.util.UUID; + /** Request calculated results from SIMONA in the current tick */ -public record RequestResultEntities(Long tick) implements ResultDataMessageFromExt {} +public record RequestResultEntities(long tick, List requestedResults) + implements ResultDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/exceptions/ExtDataConnectionException.java b/src/main/java/edu/ie3/simona/api/exceptions/ExtDataConnectionException.java new file mode 100644 index 00000000..e409729c --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/exceptions/ExtDataConnectionException.java @@ -0,0 +1,23 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.exceptions; + +import edu.ie3.simona.api.data.connection.ExtDataConnection; + +public class ExtDataConnectionException extends RuntimeException { + + public ExtDataConnectionException(Class connectionClass) { + this( + "The external data connection '" + + connectionClass.getSimpleName() + + "' could not be build!"); + } + + public ExtDataConnectionException(final String message) { + super(message); + } +} diff --git a/src/main/java/edu/ie3/simona/api/exceptions/NoExtSimulationException.java b/src/main/java/edu/ie3/simona/api/exceptions/NoExtSimulationException.java index f4a242b3..9c403556 100644 --- a/src/main/java/edu/ie3/simona/api/exceptions/NoExtSimulationException.java +++ b/src/main/java/edu/ie3/simona/api/exceptions/NoExtSimulationException.java @@ -11,7 +11,10 @@ public class NoExtSimulationException extends RuntimeException { public NoExtSimulationException(Class linkClass) { - this("No external simulation was set up in ExtLinkInterface: ." + linkClass.getSimpleName()); + this( + "No external simulation was set up in ExtLinkInterface: " + + linkClass.getSimpleName() + + "."); } public NoExtSimulationException(final String message) { diff --git a/src/main/java/edu/ie3/simona/api/simulation/mapping/DataType.java b/src/main/java/edu/ie3/simona/api/mapping/DataType.java similarity index 75% rename from src/main/java/edu/ie3/simona/api/simulation/mapping/DataType.java rename to src/main/java/edu/ie3/simona/api/mapping/DataType.java index 67bfaa12..42794233 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/mapping/DataType.java +++ b/src/main/java/edu/ie3/simona/api/mapping/DataType.java @@ -4,15 +4,18 @@ * Research group Distribution grid planning and operation */ -package edu.ie3.simona.api.simulation.mapping; +package edu.ie3.simona.api.mapping; import edu.ie3.datamodel.exceptions.ParsingException; public enum DataType { EXT_PRIMARY_INPUT("primary_input"), EXT_EM_INPUT("em_input"), + EXT_EM_COMMUNICATION("em_communication"), + EXT_EM_OPTIMIZER("em_optimizer"), EXT_GRID_RESULT("grid_result"), - EXT_PARTICIPANT_RESULT("participant_result"); + EXT_PARTICIPANT_RESULT("participant_result"), + EXT_FLEX_OPTIONS_RESULT("flex_options_result"); public final String type; @@ -26,6 +29,7 @@ public static DataType parse(String type) throws ParsingException { case "em_input" -> EXT_EM_INPUT; case "grid_result" -> EXT_GRID_RESULT; case "participant_result" -> EXT_PARTICIPANT_RESULT; + case "flex_options_result" -> EXT_FLEX_OPTIONS_RESULT; default -> throw new ParsingException("Data type " + type + " is not supported!"); }; } diff --git a/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java b/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java index 76923312..b1810e36 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java +++ b/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java @@ -6,16 +6,20 @@ package edu.ie3.simona.api.simulation; +import static java.util.Collections.emptyList; + import edu.ie3.datamodel.models.result.ResultEntity; +import edu.ie3.datamodel.models.value.PValue; import edu.ie3.datamodel.models.value.Value; -import edu.ie3.simona.api.data.DataQueueExtSimulationExtSimulator; -import edu.ie3.simona.api.data.ExtInputDataContainer; -import edu.ie3.simona.api.data.em.ExtEmDataConnection; -import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection; -import edu.ie3.simona.api.data.results.ExtResultContainer; -import edu.ie3.simona.api.data.results.ExtResultDataConnection; -import edu.ie3.simona.api.simulation.mapping.DataType; -import edu.ie3.simona.api.simulation.mapping.ExtEntityMapping; +import edu.ie3.simona.api.data.ExtDataContainerQueue; +import edu.ie3.simona.api.data.connection.ExtEmDataConnection; +import edu.ie3.simona.api.data.connection.ExtPrimaryDataConnection; +import edu.ie3.simona.api.data.connection.ExtResultDataConnection; +import edu.ie3.simona.api.data.container.ExtInputContainer; +import edu.ie3.simona.api.data.container.ExtResultContainer; +import edu.ie3.simona.api.exceptions.ExtDataConnectionException; +import edu.ie3.simona.api.mapping.DataType; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -23,18 +27,18 @@ /** * Abstract class for an external co-simulation with the structure: external api - ext-co-simulation - * - extsimulation - simonaAPI - simona It contains all function to transfer primary data and em - * data to SIMONA and results to the external co-simulation. + * - ext-simulation - simonaAPI - simona + * + *

It contains all function to transfer primary data and em data to SIMONA and results to the + * external co-simulation. */ public abstract class ExtCoSimulation extends ExtSimulation { /** Queue for the data connection from the external co-simulation to SimonaAPI */ - protected final DataQueueExtSimulationExtSimulator - dataQueueExtCoSimulatorToSimonaApi; + protected final ExtDataContainerQueue queueToSimona; /** Queue for the data connection from SimonaAPI to the external co-simulation */ - protected final DataQueueExtSimulationExtSimulator - dataQueueSimonaApiToExtCoSimulator; + protected final ExtDataContainerQueue queueToExt; /** Name of the external co-simulation */ protected final String extSimulatorName; @@ -42,50 +46,50 @@ public abstract class ExtCoSimulation extends ExtSimulation { protected ExtCoSimulation(String simulationName, String extSimulatorName) { super(simulationName); this.extSimulatorName = extSimulatorName; - this.dataQueueExtCoSimulatorToSimonaApi = new DataQueueExtSimulationExtSimulator<>(); - this.dataQueueSimonaApiToExtCoSimulator = new DataQueueExtSimulationExtSimulator<>(); + this.queueToSimona = new ExtDataContainerQueue<>(); + this.queueToExt = new ExtDataContainerQueue<>(); } + // connection helper methods + /** * Builds an {@link ExtPrimaryDataConnection}. * - * @param mapping between the external simulation and SIMONA. + * @param assetToValueClasses between primary asset and its value class. * @param log logger * @return an ext primary data connection */ - protected static ExtPrimaryDataConnection buildPrimaryConnection( - ExtEntityMapping mapping, Logger log) { - Map primaryMapping = mapping.getExtId2UuidMapping(DataType.EXT_PRIMARY_INPUT); - ExtPrimaryDataConnection extPrimaryDataConnection = - new ExtPrimaryDataConnection(primaryMapping); - - if (primaryMapping.isEmpty()) { - log.warn("Primary with 0 entities created."); + public static ExtPrimaryDataConnection buildPrimaryConnection( + Map> assetToValueClasses, Logger log) { + + if (assetToValueClasses.isEmpty()) { + log.warn("No primary data connection was created."); + throw new ExtDataConnectionException(ExtPrimaryDataConnection.class); } else { - log.info("Primary connection with {} entities created.", primaryMapping.size()); - } + log.info("Primary data connection with {} entities created.", assetToValueClasses.size()); - return extPrimaryDataConnection; + return new ExtPrimaryDataConnection(assetToValueClasses); + } } /** * Builds an {@link ExtEmDataConnection}. * - * @param mapping between the external simulation and SIMONA. + * @param controlled uuids for controlled em agents. * @param log logger * @return an ext em data connection */ - protected static ExtEmDataConnection buildEmConnection(ExtEntityMapping mapping, Logger log) { - Map emMapping = mapping.getExtId2UuidMapping(DataType.EXT_EM_INPUT); - ExtEmDataConnection extEmDataConnection = new ExtEmDataConnection(emMapping); - - if (emMapping.isEmpty()) { - log.warn("Em connection with 0 entities created."); + public static ExtEmDataConnection buildEmConnection( + List controlled, ExtEmDataConnection.EmMode mode, Logger log) { + if (controlled.isEmpty()) { + log.warn("Em data connection with 0 controlled entities created. This might lead to errors!"); } else { - log.info("Em connection with {} entities created.", emMapping.size()); + log.info("Em data connection with {} controlled entities created.", controlled.size()); } - return extEmDataConnection; + log.info("Em mode: {}", mode); + + return new ExtEmDataConnection(controlled, mode); } /** @@ -95,24 +99,24 @@ protected static ExtEmDataConnection buildEmConnection(ExtEntityMapping mapping, * @param log logger * @return an ext result data connection */ - protected static ExtResultDataConnection buildResultConnection( - ExtEntityMapping mapping, Logger log) { - Map resultParticipantMapping = - mapping.getExtUuid2IdMapping(DataType.EXT_PARTICIPANT_RESULT); - Map resultGridMapping = mapping.getExtUuid2IdMapping(DataType.EXT_GRID_RESULT); - ExtResultDataConnection extResultDataConnection = - new ExtResultDataConnection(resultParticipantMapping, resultGridMapping); - - if (resultParticipantMapping.isEmpty() && resultGridMapping.isEmpty()) { - log.warn("Result connection with 0 participants and 0 grid assets created."); + public static ExtResultDataConnection buildResultConnection( + Map> mapping, Logger log) { + List participantResults = + mapping.getOrDefault(DataType.EXT_PARTICIPANT_RESULT, emptyList()); + List gridResults = mapping.getOrDefault(DataType.EXT_GRID_RESULT, emptyList()); + List flexResults = mapping.getOrDefault(DataType.EXT_FLEX_OPTIONS_RESULT, emptyList()); + + if (participantResults.isEmpty() && gridResults.isEmpty() && flexResults.isEmpty()) { + log.warn("No result connection was created."); + throw new ExtDataConnectionException(ExtResultDataConnection.class); } else { log.info( - "Result connection with {} participants and {} grid assets created.", - resultParticipantMapping.size(), - resultGridMapping.size()); + "Result connection with {} participants, {} grid assets and {} flex option mappings created.", + participantResults.size(), + gridResults.size(), + flexResults.size()); + return new ExtResultDataConnection(participantResults, gridResults, flexResults); } - - return extResultDataConnection; } /** @@ -127,12 +131,12 @@ protected static ExtResultDataConnection buildResultConnection( protected void sendPrimaryDataToSimona( ExtPrimaryDataConnection extPrimaryDataConnection, long tick, - Map dataMap, + Map dataMap, Optional maybeNextTick, Logger log) { log.debug("Wait for Primary Data from {}", extSimulatorName); log.debug("Received Primary Data from {}", extSimulatorName); - extPrimaryDataConnection.convertAndSend(tick, dataMap, maybeNextTick, log); + extPrimaryDataConnection.sendPrimaryData(tick, dataMap, maybeNextTick, log); log.debug("Provided Primary Data to SIMONA!"); } @@ -146,15 +150,15 @@ protected void sendPrimaryDataToSimona( * @param maybeNextTick option for the next tick data is sent * @param log logger */ - protected void sendEmDataToSimona( + protected void sendEmSetPointsToSimona( ExtEmDataConnection extEmDataConnection, long tick, - Map dataMap, + Map dataMap, Optional maybeNextTick, Logger log) { - log.debug("Received EmData from {}", extSimulatorName); - extEmDataConnection.convertAndSend(tick, dataMap, maybeNextTick, log); - log.debug("Provided EmData to SIMONA!"); + log.debug("Received em set points from {}", extSimulatorName); + extEmDataConnection.sendSetPoints(tick, dataMap, maybeNextTick, log); + log.debug("Provided em set points to SIMONA!"); } /** @@ -164,15 +168,15 @@ protected void sendEmDataToSimona( * @param tick for which data is received * @param maybeNextTick option for the next tick data is received * @param log logger + * @throws InterruptedException if the fetching of data is interrupted */ - protected void sendDataToExt( + protected void sendResultToExt( ExtResultDataConnection connection, long tick, Optional maybeNextTick, Logger log) throws InterruptedException { log.debug("Request results from SIMONA!"); - Map resultsToBeSend = connection.requestResults(tick); + Map resultsToBeSend = connection.requestResults(tick); log.debug("Received results from SIMONA!"); - dataQueueSimonaApiToExtCoSimulator.queueData( - new ExtResultContainer(tick, resultsToBeSend, maybeNextTick)); + queueToExt.queueData(new ExtResultContainer(tick, resultsToBeSend, maybeNextTick)); log.debug("Sent results to {}", extSimulatorName); } } diff --git a/src/main/java/edu/ie3/simona/api/simulation/ExtSimulation.java b/src/main/java/edu/ie3/simona/api/simulation/ExtSimulation.java index 0e52a819..71c1b98b 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/ExtSimulation.java +++ b/src/main/java/edu/ie3/simona/api/simulation/ExtSimulation.java @@ -6,7 +6,7 @@ package edu.ie3.simona.api.simulation; -import edu.ie3.simona.api.data.ExtDataConnection; +import edu.ie3.simona.api.data.connection.ExtDataConnection; import edu.ie3.simona.api.simulation.ontology.*; import java.util.Optional; import java.util.Set; diff --git a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityEntry.java b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityEntry.java index 1444c3e1..52f5bff2 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityEntry.java +++ b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityEntry.java @@ -8,6 +8,8 @@ import edu.ie3.datamodel.io.naming.timeseries.ColumnScheme; import edu.ie3.datamodel.models.input.InputEntity; +import edu.ie3.simona.api.mapping.DataType; +import java.util.Optional; import java.util.UUID; /** @@ -19,10 +21,7 @@ * @param dataType data types the external asset expects */ public record ExtEntityEntry( - UUID uuid, - String id, - ColumnScheme columnScheme, // FIXME: placeholder -> ColumnScheme should handle more data types - DataType dataType) + UUID uuid, String id, Optional columnScheme, DataType dataType) implements InputEntity { public String toString() { diff --git a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityFactory.java b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityFactory.java index 9a820258..4b79cca4 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityFactory.java +++ b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityFactory.java @@ -11,6 +11,7 @@ import edu.ie3.datamodel.io.factory.EntityData; import edu.ie3.datamodel.io.factory.EntityFactory; import edu.ie3.datamodel.io.naming.timeseries.ColumnScheme; +import edu.ie3.simona.api.mapping.DataType; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -46,11 +47,6 @@ protected ExtEntityEntry buildModel(EntityData data) { throw new FactoryException(e); } - return new ExtEntityEntry( - simonaUuid, - extId, - columnScheme - .orElseThrow(), // FIXME: Interim version -> ColumnScheme should handle more data types - inputType); + return new ExtEntityEntry(simonaUuid, extId, columnScheme, inputType); } } diff --git a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMapping.java b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMapping.java index 8fd033e6..6976be08 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMapping.java +++ b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMapping.java @@ -6,7 +6,11 @@ package edu.ie3.simona.api.simulation.mapping; -import java.util.*; +import edu.ie3.simona.api.mapping.DataType; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; import java.util.stream.Collectors; /** Contains the mapping between SIMONA uuid, the external id and the data type the assets hold */ diff --git a/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy new file mode 100644 index 00000000..a8bb2d4f --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy @@ -0,0 +1,67 @@ +package edu.ie3.simona.api.data.connection + +import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode +import edu.ie3.simona.api.data.ontology.DataMessageFromExt +import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.api.data.em.ontology.ProvideEmSetPointData +import edu.ie3.simona.api.test.common.DataServiceTestData +import org.apache.pekko.actor.testkit.typed.javadsl.ActorTestKit +import spock.lang.Shared +import spock.lang.Specification + +class ExtEmDataConnectionTest extends Specification implements DataServiceTestData { + + @Shared + ActorTestKit testKit + + @Shared + List controlled = [inputUuid] + + def setupSpec() { + testKit = ActorTestKit.create() + } + + def cleanupSpec() { + testKit.shutdownTestKit() + testKit = null + } + + def "ExtEmDataConnection should provide em data correctly"() { + given: + def dataService = testKit.createTestProbe(DataMessageFromExt) + def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.SET_POINT) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + + def emData = Map.of(inputUuid, pValue) + + when: + extEmDataConnection.sendSetPoints(0L, emData, Optional.of(900L), log) + + then: + dataService.expectMessage(new ProvideEmSetPointData(0, emData, Optional.of(900L))) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) + } + + def "ExtEmDataConnection should send no message, if input data is empty"() { + given: + def dataService = testKit.createTestProbe(DataMessageFromExt) + def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.SET_POINT) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + def inputDataMap = [:] as Map + + when: + extEmDataConnection.sendSetPoints(0L, inputDataMap, Optional.of(900L), log) + + then: + dataService.expectNoMessage() + } + +} diff --git a/src/test/groovy/edu/ie3/simona/api/data/ev/ExtEvDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEvDataConnectionTest.groovy similarity index 99% rename from src/test/groovy/edu/ie3/simona/api/data/ev/ExtEvDataConnectionTest.groovy rename to src/test/groovy/edu/ie3/simona/api/data/connection/ExtEvDataConnectionTest.groovy index ea925b95..581efacc 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/ev/ExtEvDataConnectionTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEvDataConnectionTest.groovy @@ -1,4 +1,4 @@ -package edu.ie3.simona.api.data.ev +package edu.ie3.simona.api.data.connection import edu.ie3.simona.api.data.ev.model.EvModel import edu.ie3.simona.api.data.ev.ontology.* diff --git a/src/test/groovy/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnectionTest.groovy similarity index 52% rename from src/test/groovy/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnectionTest.groovy rename to src/test/groovy/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnectionTest.groovy index 8cc119a1..707b24df 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnectionTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnectionTest.groovy @@ -1,5 +1,6 @@ -package edu.ie3.simona.api.data.primarydata +package edu.ie3.simona.api.data.connection +import edu.ie3.datamodel.models.value.PValue import edu.ie3.datamodel.models.value.Value import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage @@ -15,10 +16,7 @@ class ExtPrimaryDataConnectionTest extends Specification implements DataServiceT ActorTestKit testKit @Shared - Map extPrimaryDataMapping = Map.of( - "Pv", - inputUuid - ) + Map> assetToValueClasses = [ (inputUuid): PValue] as Map def setupSpec() { testKit = ActorTestKit.create() @@ -33,58 +31,35 @@ class ExtPrimaryDataConnectionTest extends Specification implements DataServiceT given: def dataService = testKit.createTestProbe(DataMessageFromExt) def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) - def extPrimaryDataConnection = new ExtPrimaryDataConnection(extPrimaryDataMapping) + def extPrimaryDataConnection = new ExtPrimaryDataConnection(assetToValueClasses) extPrimaryDataConnection.setActorRefs( dataService.ref(), extSimAdapter.ref() ) - def primaryData = [:] as HashMap - def uuid = UUID.randomUUID() - primaryData.put(uuid.toString(), pValue) - - def convertedPrimaryData = Map.of(uuid, pValue as Value) - - when: - extPrimaryDataConnection.providePrimaryData(0L, convertedPrimaryData, Optional.of(900L)) - - then: - dataService.expectMessage(new ProvidePrimaryData(0L, convertedPrimaryData, Optional.of(900L))) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) - } - - def "ExtPrimaryDataConnection should convert input data correctly"() { - given: - def dataService = testKit.createTestProbe(DataMessageFromExt) - def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) - def extPrimaryDataConnection = new ExtPrimaryDataConnection(extPrimaryDataMapping) - extPrimaryDataConnection.setActorRefs( - dataService.ref(), - extSimAdapter.ref() - ) - def inputDataMap = Map.of("Pv", pValue) + def primaryData = Map.of(inputUuid, pValue as Value) when: - extPrimaryDataConnection.convertAndSend(0L, inputDataMap, Optional.of(900L), log) + extPrimaryDataConnection.provideData(0L, primaryData, Optional.of(900L)) then: - dataService.expectMessage(new ProvidePrimaryData(0L, Map.of(inputUuid, pValue), Optional.of(900L))) + dataService.expectMessage(new ProvidePrimaryData(0L, primaryData, Optional.of(900L))) extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) } - def "ExtPrimaryDataConnection should send no message, if input data for a not requested asset was provided"() { + def "ExtPrimaryDataConnection should send no message, if input data is empty"() { given: def dataService = testKit.createTestProbe(DataMessageFromExt) def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) - def extPrimaryDataConnection = new ExtPrimaryDataConnection(extPrimaryDataMapping) + def extPrimaryDataConnection = new ExtPrimaryDataConnection(assetToValueClasses) extPrimaryDataConnection.setActorRefs( dataService.ref(), extSimAdapter.ref() ) - def inputDataMap = Map.of("Load", pValue) + def inputDataMap = [:] when: - extPrimaryDataConnection.convertAndSend(0L, inputDataMap, Optional.empty(), log) + extPrimaryDataConnection.sendPrimaryData(0L, inputDataMap, Optional.empty(), log) then: dataService.expectNoMessage() diff --git a/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtResultDataConnectionTest.groovy similarity index 56% rename from src/test/groovy/edu/ie3/simona/api/data/results/ExtResultDataConnectionTest.groovy rename to src/test/groovy/edu/ie3/simona/api/data/connection/ExtResultDataConnectionTest.groovy index 3bf9a64f..97cb81ba 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultDataConnectionTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtResultDataConnectionTest.groovy @@ -1,26 +1,14 @@ -package edu.ie3.simona.api.data.results +package edu.ie3.simona.api.data.connection -import edu.ie3.datamodel.models.StandardUnits -import edu.ie3.datamodel.models.result.connector.LineResult import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage import edu.ie3.simona.api.data.results.ontology.ProvideResultEntities import edu.ie3.simona.api.data.results.ontology.RequestResultEntities import edu.ie3.simona.api.data.results.ontology.ResultDataResponseMessageToExt -import edu.ie3.simona.api.simulation.ExtSimulation import edu.ie3.simona.api.test.common.DataServiceTestData -import org.apache.pekko.actor.ActorSystem import org.apache.pekko.actor.testkit.typed.javadsl.ActorTestKit -import org.apache.pekko.testkit.TestProbe -import org.apache.pekko.testkit.javadsl.TestKit import spock.lang.Shared import spock.lang.Specification -import tech.units.indriya.quantity.Quantities - -import javax.measure.Quantity -import javax.measure.quantity.Angle -import javax.measure.quantity.ElectricCurrent -import java.time.ZonedDateTime class ExtResultDataConnectionTest extends Specification implements DataServiceTestData { @@ -28,10 +16,13 @@ class ExtResultDataConnectionTest extends Specification implements DataServiceTe ActorTestKit testKit @Shared - Map participantResultAssetMapping = Map.of(inputUuid, "Load") + List participantResultAssets = [inputUuid] + + @Shared + List gridResultAssets = [] @Shared - Map gridResultAssetMapping = [:] + List flexResultAssets = [] class WrongResultDataResponseMessageToExt implements ResultDataResponseMessageToExt {} @@ -47,12 +38,10 @@ class ExtResultDataConnectionTest extends Specification implements DataServiceTe def "ExtResultsData should request and receive results correctly as ModelResultEntity"() { given: def dataService = testKit.createTestProbe(DataMessageFromExt) - def dataServiceActivation = testKit.createTestProbe(DataMessageFromExt) def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) - def extResultDataConnection = new ExtResultDataConnection(participantResultAssetMapping, gridResultAssetMapping) + def extResultDataConnection = new ExtResultDataConnection(participantResultAssets, gridResultAssets, flexResultAssets) extResultDataConnection.setActorRefs( dataService.ref(), - dataServiceActivation.ref(), extSimAdapter.ref() ) @@ -64,20 +53,18 @@ class ExtResultDataConnectionTest extends Specification implements DataServiceTe def receivedResults = extResultDataConnection.requestResults(0L) then: - dataService.expectMessage(new RequestResultEntities(0L)) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataServiceActivation.ref())) - receivedResults.get("Load") == loadResult + dataService.expectMessage(new RequestResultEntities(0L, [inputUuid])) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) + receivedResults.get(inputUuid) == loadResult } def "ExtResultsData should fail if wrong response is sent"() { given: def dataService = testKit.createTestProbe(DataMessageFromExt) - def dataServiceActivation = testKit.createTestProbe(DataMessageFromExt) def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) - def extResultDataConnection = new ExtResultDataConnection(participantResultAssetMapping, gridResultAssetMapping) + def extResultDataConnection = new ExtResultDataConnection(participantResultAssets, gridResultAssets, flexResultAssets) extResultDataConnection.setActorRefs( dataService.ref(), - dataServiceActivation.ref(), extSimAdapter.ref() ) @@ -89,38 +76,20 @@ class ExtResultDataConnectionTest extends Specification implements DataServiceTe extResultDataConnection.requestResults(0L) then: - dataService.expectMessage(new RequestResultEntities(0L)) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataServiceActivation.ref())) + dataService.expectMessage(new RequestResultEntities(0L, [inputUuid])) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) thrown RuntimeException } def "ExtResultData should convert a list of result entities correctly to a map of resultAssetMappingId to result entity"() { given: - def extResultDataConnection = new ExtResultDataConnection(participantResultAssetMapping, gridResultAssetMapping) + def extResultDataConnection = new ExtResultDataConnection(participantResultAssets, gridResultAssets, flexResultAssets) when: def mapOfResults = extResultDataConnection.createResultMap([loadResult]) then: mapOfResults.size() == 1 - mapOfResults.get("Load") == loadResult - } - - def "ExtResultData should throw an exception, if a result with a wrong data type was provided"() { - given: - def extResultDataConnection = new ExtResultDataConnection(participantResultAssetMapping, gridResultAssetMapping) - Quantity iAMag = Quantities.getQuantity(100, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE) - Quantity iAAng = Quantities.getQuantity(45, StandardUnits.ELECTRIC_CURRENT_ANGLE) - Quantity iBMag = Quantities.getQuantity(150, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE) - Quantity iBAng = Quantities.getQuantity(30, StandardUnits.ELECTRIC_CURRENT_ANGLE) - def wrongResult = new LineResult( - ZonedDateTime.parse("2020-01-30T17:26:44Z"), inputUuid, iAMag, iAAng, iBMag, iBAng - ) - - when: - extResultDataConnection.createResultMap([wrongResult]) - - then: - thrown IllegalArgumentException + mapOfResults.get(inputUuid) == loadResult } } \ No newline at end of file diff --git a/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy new file mode 100644 index 00000000..0ba975cf --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy @@ -0,0 +1,201 @@ +package edu.ie3.simona.api.data.container + +import edu.ie3.datamodel.models.value.PValue +import edu.ie3.simona.api.data.model.em.FlexOptionRequest +import edu.ie3.simona.api.data.model.em.FlexOptions +import spock.lang.Specification +import tech.units.indriya.quantity.Quantities + +import static edu.ie3.util.quantities.PowerSystemUnits.KILOWATT + +class ExtInputContainerTest extends Specification { + + def "An ExtInputContainer should add primary data correctly"() { + given: + UUID uuid = UUID.randomUUID() + def value = new PValue(Quantities.getQuantity(10d, KILOWATT)) + + def container = new ExtInputContainer(0L) + + when: + container.addPrimaryValue(uuid, value) + + then: + container.primaryData == [(uuid): value] + } + + def "An ExtInputContainer should add flex option request data correctly"() { + given: + UUID requester = UUID.randomUUID() + UUID uuid = UUID.randomUUID() + + def container = new ExtInputContainer(0L) + + when: + container.addRequest(requester, [uuid]) + + then: + container.flexRequests == [(requester): new FlexOptionRequest(requester, [uuid])] + } + + def "An ExtInputContainer should add flex option data correctly"() { + given: + UUID receiver = UUID.randomUUID() + UUID sender = UUID.randomUUID() + def flexOptions = new FlexOptions(sender, Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) + + def container = new ExtInputContainer(0L) + + when: + container.addFlexOptions(receiver, [flexOptions]) + + then: + container.flexOptions == [(receiver): [flexOptions]] + } + + def "An ExtInputContainer should add set point data correctly"() { + given: + UUID uuid = UUID.randomUUID() + def setPoint = new PValue(Quantities.getQuantity(5d, KILOWATT)) + + def container = new ExtInputContainer(0L) + + when: + container.addSetPoint(uuid, setPoint) + + then: + container.setPoints == [(uuid): setPoint] + } + + def "An ExtInputContainer should extract primary data correctly"() { + given: + def container = new ExtInputContainer(0L) + + UUID primaryUuid = UUID.randomUUID() + PValue primaryValue = new PValue(Quantities.getQuantity(10d, KILOWATT)) + container.addPrimaryValue(primaryUuid, primaryValue) + + UUID requester = UUID.randomUUID() + UUID requested = UUID.randomUUID() + container.addRequest(requester, [requested]) + + UUID receiver = UUID.randomUUID() + def flexOptions = new FlexOptions(UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) + container.addFlexOptions(receiver, [flexOptions]) + + UUID emAsset = UUID.randomUUID() + def setPoint = new PValue(Quantities.getQuantity(5d, KILOWATT)) + container.addSetPoint(emAsset, setPoint) + + when: + def extracted = container.extractPrimaryData() + + then: + extracted.size() == 1 + extracted == [(primaryUuid): primaryValue] + + container.primaryData.size() == 0 + container.flexRequests.size() == 1 + container.flexOptions.size() == 1 + container.setPoints.size() == 1 + } + + def "An ExtInputContainer should extract flex option request data correctly"() { + given: + def container = new ExtInputContainer(0L) + + UUID primaryUuid = UUID.randomUUID() + PValue primaryValue = new PValue(Quantities.getQuantity(10d, KILOWATT)) + container.addPrimaryValue(primaryUuid, primaryValue) + + UUID requester = UUID.randomUUID() + UUID requested = UUID.randomUUID() + container.addRequest(requester, [requested]) + + UUID receiver = UUID.randomUUID() + def flexOptions = new FlexOptions(UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) + container.addFlexOptions(receiver, [flexOptions]) + + UUID emAsset = UUID.randomUUID() + def setPoint = new PValue(Quantities.getQuantity(5d, KILOWATT)) + container.addSetPoint(emAsset, setPoint) + + when: + def extracted = container.extractFlexRequests() + + then: + extracted.size() == 1 + extracted == [(requester): new FlexOptionRequest(requester, [requested])] + + container.primaryData.size() == 1 + container.flexRequests.size() == 0 + container.flexOptions.size() == 1 + container.setPoints.size() == 1 + } + + def "An ExtInputContainer should extract flex option data correctly"() { + given: + def container = new ExtInputContainer(0L) + + UUID primaryUuid = UUID.randomUUID() + PValue primaryValue = new PValue(Quantities.getQuantity(10d, KILOWATT)) + container.addPrimaryValue(primaryUuid, primaryValue) + + UUID requester = UUID.randomUUID() + UUID requested = UUID.randomUUID() + container.addRequest(requester, [requested]) + + UUID receiver = UUID.randomUUID() + def flexOptions = new FlexOptions(UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) + container.addFlexOptions(receiver, [flexOptions]) + + UUID emAsset = UUID.randomUUID() + def setPoint = new PValue(Quantities.getQuantity(5d, KILOWATT)) + container.addSetPoint(emAsset, setPoint) + + when: + def extracted = container.extractFlexOptions() + + then: + extracted.size() == 1 + extracted == [(receiver): [flexOptions]] + + container.primaryData.size() == 1 + container.flexRequests.size() == 1 + container.flexOptions.size() == 0 + container.setPoints.size() == 1 + } + + def "An ExtInputContainer should extract set points data correctly"() { + given: + def container = new ExtInputContainer(0L) + + UUID primaryUuid = UUID.randomUUID() + PValue primaryValue = new PValue(Quantities.getQuantity(10d, KILOWATT)) + container.addPrimaryValue(primaryUuid, primaryValue) + + UUID requester = UUID.randomUUID() + UUID requested = UUID.randomUUID() + container.addRequest(requester, [requested]) + + UUID receiver = UUID.randomUUID() + def flexOptions = new FlexOptions(UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) + container.addFlexOptions(receiver, [flexOptions]) + + UUID emAsset = UUID.randomUUID() + def setPoint = new PValue(Quantities.getQuantity(5d, KILOWATT)) + container.addSetPoint(emAsset, setPoint) + + when: + def extracted = container.extractSetPoints() + + then: + extracted.size() == 1 + extracted == [(emAsset): setPoint] + + container.primaryData.size() == 1 + container.flexRequests.size() == 1 + container.flexOptions.size() == 1 + container.setPoints.size() == 0 + } +} diff --git a/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultContainerTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/container/ExtResultContainerTest.groovy similarity index 59% rename from src/test/groovy/edu/ie3/simona/api/data/results/ExtResultContainerTest.groovy rename to src/test/groovy/edu/ie3/simona/api/data/container/ExtResultContainerTest.groovy index b86f6aef..5838da4d 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultContainerTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/container/ExtResultContainerTest.groovy @@ -1,7 +1,9 @@ -package edu.ie3.simona.api.data.results +package edu.ie3.simona.api.data.container import edu.ie3.datamodel.models.StandardUnits import edu.ie3.datamodel.models.result.NodeResult +import edu.ie3.datamodel.models.result.system.FlexOptionsResult +import edu.ie3.datamodel.models.result.system.LoadResult import edu.ie3.simona.api.test.common.DataServiceTestData import edu.ie3.util.quantities.PowerSystemUnits import spock.lang.Shared @@ -23,15 +25,46 @@ class ExtResultContainerTest extends Specification implements DataServiceTestDat Quantities.getQuantity(45, StandardUnits.VOLTAGE_ANGLE) ) + def "ExtResultContainer should return all results correctly"() { + given: + def expected = [ + (nodeUuid): nodeResult, + (inputUuid): loadResult + ] + + def container = new ExtResultContainer(0L, expected) + + expect: + container.getResults() == expected + } + + def "ExtResultContainer should return specific results correctly"() { + given: + def expected = [ + (nodeUuid): nodeResult, + (inputUuid): loadResult + ] + + def container = new ExtResultContainer(0L, expected) + + when: + def nodeResults = container.getResults(NodeResult) + def loadResults = container.getResults(LoadResult) + def flexOptionsResults = container.getResults(FlexOptionsResult) + + then: + nodeResults == [(nodeUuid): nodeResult] + loadResults == [(inputUuid): loadResult] + flexOptionsResults == [:] + } + def "ExtResultContainer should return voltage deviation correctly"() { given: - def resultMap = Map.of( - "Node", nodeResult - ) + def resultMap = Map.of(nodeUuid, nodeResult) def extResultContainer = new ExtResultContainer(0L, resultMap) when: - def calculatedVoltageDeviation = extResultContainer.getVoltageDeviation("Node") + def calculatedVoltageDeviation = extResultContainer.getVoltageDeviation(nodeUuid) then: calculatedVoltageDeviation == -0.05d @@ -39,13 +72,11 @@ class ExtResultContainerTest extends Specification implements DataServiceTestDat def "ExtResultContainer should throw an exception, if voltage deviation was requested for a not NodeResult"() { given: - def resultMap = Map.of( - "Load", loadResult - ) + def resultMap = Map.of(inputUuid, loadResult) def extResultContainer = new ExtResultContainer(0L, resultMap) when: - extResultContainer.getVoltageDeviation("Load") + extResultContainer.getVoltageDeviation(inputUuid) then: thrown IllegalArgumentException @@ -53,13 +84,11 @@ class ExtResultContainerTest extends Specification implements DataServiceTestDat def "ExtResultContainer should return active power correctly"() { given: - def resultMap = Map.of( - "Load", loadResult - ) + def resultMap = Map.of(inputUuid, loadResult) def extResultContainer = new ExtResultContainer(0L, resultMap) when: - def returnedActivePower = extResultContainer.getActivePower("Load") + def returnedActivePower = extResultContainer.getActivePower(inputUuid) then: returnedActivePower == 10d @@ -67,13 +96,11 @@ class ExtResultContainerTest extends Specification implements DataServiceTestDat def "ExtResultContainer should return reactive power correctly"() { given: - def resultMap = Map.of( - "Load", loadResult - ) + def resultMap = Map.of(inputUuid, loadResult) def extResultContainer = new ExtResultContainer(0L, resultMap) when: - def returnedReactivePower = extResultContainer.getReactivePower("Load") + def returnedReactivePower = extResultContainer.getReactivePower(inputUuid) then: returnedReactivePower == 5d @@ -82,12 +109,12 @@ class ExtResultContainerTest extends Specification implements DataServiceTestDat def "ExtResultContainer should throw an exception, if active power was requested for a not SystemParticipantResult"() { given: def resultMap = Map.of( - "Node", nodeResult + nodeUuid, nodeResult ) def extResultContainer = new ExtResultContainer(0L, resultMap) when: - extResultContainer.getActivePower("Node") + extResultContainer.getActivePower(nodeUuid) then: thrown IllegalArgumentException diff --git a/src/test/groovy/edu/ie3/simona/api/data/em/ExtEmDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/em/ExtEmDataConnectionTest.groovy deleted file mode 100644 index 7b0f4ecb..00000000 --- a/src/test/groovy/edu/ie3/simona/api/data/em/ExtEmDataConnectionTest.groovy +++ /dev/null @@ -1,94 +0,0 @@ -package edu.ie3.simona.api.data.em - -import edu.ie3.datamodel.models.value.PValue -import edu.ie3.datamodel.models.value.Value -import edu.ie3.simona.api.data.em.ontology.ProvideEmSetPointData -import edu.ie3.simona.api.data.ontology.DataMessageFromExt -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.api.test.common.DataServiceTestData -import org.apache.pekko.actor.testkit.typed.javadsl.ActorTestKit -import spock.lang.Shared -import spock.lang.Specification - -class ExtEmDataConnectionTest extends Specification implements DataServiceTestData { - - @Shared - ActorTestKit testKit - - @Shared - Map extEmDataMapping = Map.of( - "Em", - inputUuid - ) - - def setupSpec() { - testKit = ActorTestKit.create() - } - - def cleanupSpec() { - testKit.shutdownTestKit() - testKit = null - } - - def "ExtEmDataConnection should provide em data correctly"() { - given: - def dataService = testKit.createTestProbe(DataMessageFromExt) - def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) - def extEmDataConnection = new ExtEmDataConnection(extEmDataMapping) - extEmDataConnection.setActorRefs( - dataService.ref(), - extSimAdapter.ref() - ) - - def emData = [:] as HashMap - def uuid = UUID.randomUUID() - emData.put(uuid.toString(), pValue) - - def convertedEmData = Map.of(uuid, pValue as PValue) - - when: - extEmDataConnection.provideEmData(0L, convertedEmData, Optional.of(900L)) - - then: - dataService.expectMessage(new ProvideEmSetPointData(0, convertedEmData, Optional.of(900L))) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) - } - - def "ExtEmDataConnection should convert input data correctly"() { - given: - def dataService = testKit.createTestProbe(DataMessageFromExt) - def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) - def extEmDataConnection = new ExtEmDataConnection(extEmDataMapping) - extEmDataConnection.setActorRefs( - dataService.ref(), - extSimAdapter.ref() - ) - def inputDataMap = Map.of("Em", pValue) - - when: - extEmDataConnection.convertAndSend(0L, inputDataMap, Optional.of(900L), log) - - then: - dataService.expectMessage(new ProvideEmSetPointData(0L, Map.of(inputUuid, pValue), Optional.of(900L))) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) - } - - def "ExtEmDataConnection should send no message, if input data for a not requested asset was provided"() { - given: - def dataService = testKit.createTestProbe(DataMessageFromExt) - def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) - def extEmDataConnection = new ExtEmDataConnection(extEmDataMapping) - extEmDataConnection.setActorRefs( - dataService.ref(), - extSimAdapter.ref() - ) - def inputDataMap = Map.of("Load", pValue) - - when: - extEmDataConnection.convertAndSend(0L, inputDataMap, Optional.of(900L), log) - - then: - dataService.expectNoMessage() - } - -} diff --git a/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy b/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy index 2903384c..0896d846 100644 --- a/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy @@ -1,9 +1,10 @@ package edu.ie3.simona.api.simulation -import edu.ie3.datamodel.io.naming.timeseries.ColumnScheme -import edu.ie3.simona.api.simulation.mapping.DataType -import edu.ie3.simona.api.simulation.mapping.ExtEntityEntry -import edu.ie3.simona.api.simulation.mapping.ExtEntityMapping +import edu.ie3.datamodel.models.value.PValue +import edu.ie3.datamodel.models.value.SValue +import edu.ie3.datamodel.models.value.Value +import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode +import edu.ie3.simona.api.mapping.DataType import org.slf4j.Logger import org.slf4j.LoggerFactory import spock.lang.Shared @@ -18,35 +19,28 @@ class ExtCoSimulationTest extends Specification { given: UUID uuid1 = UUID.randomUUID() UUID uuid2 = UUID.randomUUID() - UUID uuid3 = UUID.randomUUID() - ExtEntityMapping mapping = new ExtEntityMapping([ - new ExtEntityEntry(uuid1, "primary1", ColumnScheme.ACTIVE_POWER, DataType.EXT_PRIMARY_INPUT), - new ExtEntityEntry(uuid2, "em1", ColumnScheme.ACTIVE_POWER, DataType.EXT_EM_INPUT), - new ExtEntityEntry(uuid3, "primary2", ColumnScheme.ACTIVE_POWER, DataType.EXT_PRIMARY_INPUT), - ]) + Map> assetsToClasses = [ + (uuid1): PValue, + (uuid2): SValue + ] as Map when: - def actual = ExtCoSimulation.buildPrimaryConnection(mapping, log) + def actual = ExtCoSimulation.buildPrimaryConnection(assetsToClasses, log) then: - actual.getPrimaryDataAssets() == [uuid3, uuid1] + actual.getPrimaryDataAssets() == [uuid1, uuid2] } def "An ExtCoSimulation can build an em data connection correctly"() { given: UUID uuid1 = UUID.randomUUID() UUID uuid2 = UUID.randomUUID() - UUID uuid3 = UUID.randomUUID() - ExtEntityMapping mapping = new ExtEntityMapping([ - new ExtEntityEntry(uuid1, "em1", ColumnScheme.ACTIVE_POWER, DataType.EXT_EM_INPUT), - new ExtEntityEntry(uuid2, "em2", ColumnScheme.ACTIVE_POWER, DataType.EXT_EM_INPUT), - new ExtEntityEntry(uuid3, "primary1", ColumnScheme.ACTIVE_POWER, DataType.EXT_PRIMARY_INPUT), - ]) + def controlled = [uuid1, uuid2] when: - def actual = ExtCoSimulation.buildEmConnection(mapping, log) + def actual = ExtCoSimulation.buildEmConnection(controlled, EmMode.SET_POINT, log) then: actual.getControlledEms() == [uuid1, uuid2] @@ -58,11 +52,11 @@ class ExtCoSimulationTest extends Specification { UUID uuid2 = UUID.randomUUID() UUID uuid3 = UUID.randomUUID() - ExtEntityMapping mapping = new ExtEntityMapping([ - new ExtEntityEntry(uuid1, "grid_result", ColumnScheme.ACTIVE_POWER, DataType.EXT_GRID_RESULT), - new ExtEntityEntry(uuid2, "participant_result", ColumnScheme.ACTIVE_POWER, DataType.EXT_PARTICIPANT_RESULT), - new ExtEntityEntry(uuid3, "primary1", ColumnScheme.ACTIVE_POWER, DataType.EXT_PRIMARY_INPUT), - ]) + def mapping = [ + (DataType.EXT_GRID_RESULT) : [uuid1], + (DataType.EXT_PARTICIPANT_RESULT): [uuid2], + (DataType.EXT_FLEX_OPTIONS_RESULT): [uuid3] + ] when: def actual = ExtCoSimulation.buildResultConnection(mapping, log) diff --git a/src/test/groovy/edu/ie3/simona/api/simulation/ExtSimulationSpec.groovy b/src/test/groovy/edu/ie3/simona/api/simulation/ExtSimulationSpec.groovy index 7b24d955..56c2d129 100644 --- a/src/test/groovy/edu/ie3/simona/api/simulation/ExtSimulationSpec.groovy +++ b/src/test/groovy/edu/ie3/simona/api/simulation/ExtSimulationSpec.groovy @@ -1,6 +1,6 @@ package edu.ie3.simona.api.simulation -import edu.ie3.simona.api.data.ExtDataConnection +import edu.ie3.simona.api.data.connection.ExtDataConnection import edu.ie3.simona.api.simulation.ontology.* import org.apache.pekko.actor.testkit.typed.javadsl.ActorTestKit import spock.lang.Shared diff --git a/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingSourceTest.groovy b/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingSourceTest.groovy index 2589e219..ebebf43f 100644 --- a/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingSourceTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingSourceTest.groovy @@ -1,6 +1,7 @@ package edu.ie3.simona.api.simulation.mapping import edu.ie3.datamodel.models.input.NodeInput +import edu.ie3.simona.api.mapping.DataType import spock.lang.Specification import java.nio.file.Path diff --git a/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingTest.groovy b/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingTest.groovy index 9f49f614..dea3928e 100644 --- a/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingTest.groovy @@ -1,6 +1,7 @@ package edu.ie3.simona.api.simulation.mapping import edu.ie3.datamodel.io.naming.timeseries.ColumnScheme +import edu.ie3.simona.api.mapping.DataType import spock.lang.Shared import spock.lang.Specification @@ -12,7 +13,7 @@ class ExtEntityMappingTest extends Specification { ExtEntityEntry extResultEntry = new ExtEntityEntry( loadUuid, "Load", - ColumnScheme.parse("p").get(), + ColumnScheme.parse("p"), DataType.EXT_PARTICIPANT_RESULT ) @@ -20,7 +21,7 @@ class ExtEntityMappingTest extends Specification { ExtEntityEntry extInputEntry = new ExtEntityEntry( loadUuid, "Load", - ColumnScheme.parse("p").get(), + ColumnScheme.parse("p"), DataType.EXT_PRIMARY_INPUT ) From 0f633bdbf9edd2b267025af607fd6548193b0dba Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 7 Apr 2025 15:09:13 +0200 Subject: [PATCH 2/7] Addressing `sonarqube` issues. --- .../api/data/connection/BiDirectional.java | 3 +- .../connection/ExtPrimaryDataConnection.java | 23 +++++---- .../data/connection/ExtResultListener.java | 23 +++++---- .../WrongResponseTypeException.java | 13 +++++ .../api/simulation/ExtCoSimulation.java | 12 +++-- .../ExtPrimaryDataConnectionTest.groovy | 2 +- .../connection/ExtResultListenerTest.groovy | 48 +++++++++++++++++++ .../api/simulation/ExtCoSimulationTest.groovy | 28 +++++++++++ 8 files changed, 125 insertions(+), 27 deletions(-) create mode 100644 src/main/java/edu/ie3/simona/api/exceptions/WrongResponseTypeException.java create mode 100644 src/test/groovy/edu/ie3/simona/api/data/connection/ExtResultListenerTest.groovy diff --git a/src/main/java/edu/ie3/simona/api/data/connection/BiDirectional.java b/src/main/java/edu/ie3/simona/api/data/connection/BiDirectional.java index f0b6875e..33e527a1 100644 --- a/src/main/java/edu/ie3/simona/api/data/connection/BiDirectional.java +++ b/src/main/java/edu/ie3/simona/api/data/connection/BiDirectional.java @@ -8,6 +8,7 @@ import edu.ie3.simona.api.data.ontology.DataMessageFromExt; import edu.ie3.simona.api.data.ontology.DataResponseMessageToExt; +import edu.ie3.simona.api.exceptions.WrongResponseTypeException; import java.util.concurrent.LinkedBlockingQueue; /** @@ -47,7 +48,7 @@ public final T receiveWithType(Class expectedMessageClass) if (msg.getClass().equals(expectedMessageClass)) { return (T) msg; } else - throw new RuntimeException( + throw new WrongResponseTypeException( "Received unexpected message '" + msg + "', expected type '" diff --git a/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java index c858d5fd..1da7f3e5 100644 --- a/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java @@ -38,19 +38,22 @@ public Optional> getValueClass(UUID uuid) { return Optional.ofNullable(valueClasses.get(uuid)); } + /** + * Sends primary data from an external simulation to SIMONA + * + * @param tick current tick + * @param primaryData to be sent + * @param maybeNextTick option for the next tick in the simulation + * @param log logger + */ public void sendPrimaryData( - long tick, Map data, Optional maybeNextTick, Logger log) { - if (data.isEmpty()) { + long tick, Map primaryData, Optional maybeNextTick, Logger log) { + if (primaryData.isEmpty()) { log.warn("No primary data found! Sending no primary data to SIMONA for tick {}.", tick); } else { - log.debug("Provided SIMONA with primary data."); - log.info("Data: {}", data); - provideData(tick, data, maybeNextTick); - } - } + log.debug("Provided SIMONA with primary data. Data: {}", primaryData); - /** Provide primary data from an external simulation in one tick. */ - public void provideData(long tick, Map primaryData, Optional maybeNextTick) { - sendExtMsg(new ProvidePrimaryData(tick, primaryData, maybeNextTick)); + sendExtMsg(new ProvidePrimaryData(tick, primaryData, maybeNextTick)); + } } } diff --git a/src/main/java/edu/ie3/simona/api/data/connection/ExtResultListener.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtResultListener.java index 07e86340..c548e93e 100644 --- a/src/main/java/edu/ie3/simona/api/data/connection/ExtResultListener.java +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtResultListener.java @@ -7,43 +7,46 @@ package edu.ie3.simona.api.data.connection; import edu.ie3.simona.api.data.results.ontology.ResultDataResponseMessageToExt; +import edu.ie3.simona.api.exceptions.WrongResponseTypeException; import java.util.concurrent.LinkedBlockingQueue; /** * External result listener. This listener is similar to the {@link ExtResultDataConnection}, but is * not able to request results from SIMONA. */ -public non-sealed class ExtResultListener - implements ExtOutputDataConnection { +public non-sealed class ExtResultListener + implements ExtOutputDataConnection { /** Data message queue containing messages from SIMONA */ - public final LinkedBlockingQueue receiveTriggerQueue = new LinkedBlockingQueue<>(); + public final LinkedBlockingQueue receiveTriggerQueue = + new LinkedBlockingQueue<>(); - protected ExtResultListener() { + public ExtResultListener() { super(); } @Override - public final void queueExtResponseMsg(R msg) throws InterruptedException { + public final void queueExtResponseMsg(ResultDataResponseMessageToExt msg) + throws InterruptedException { receiveTriggerQueue.put(msg); } @Override - public final R receiveAny() throws InterruptedException { + public final ResultDataResponseMessageToExt receiveAny() throws InterruptedException { return receiveTriggerQueue.take(); } @Override @SuppressWarnings("unchecked") - public final T receiveWithType(Class expectedMessageClass) - throws InterruptedException { + public final T receiveWithType( + Class expectedMessageClass) throws InterruptedException { // blocks until actor puts something here - R msg = receiveTriggerQueue.take(); + ResultDataResponseMessageToExt msg = receiveTriggerQueue.take(); if (msg.getClass().equals(expectedMessageClass)) { return (T) msg; } else - throw new RuntimeException( + throw new WrongResponseTypeException( "Received unexpected message '" + msg + "', expected type '" diff --git a/src/main/java/edu/ie3/simona/api/exceptions/WrongResponseTypeException.java b/src/main/java/edu/ie3/simona/api/exceptions/WrongResponseTypeException.java new file mode 100644 index 00000000..72c3e1cb --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/exceptions/WrongResponseTypeException.java @@ -0,0 +1,13 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.exceptions; + +public class WrongResponseTypeException extends RuntimeException { + public WrongResponseTypeException(String message) { + super(message); + } +} diff --git a/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java b/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java index b1810e36..cea20044 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java +++ b/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java @@ -83,13 +83,15 @@ public static ExtEmDataConnection buildEmConnection( List controlled, ExtEmDataConnection.EmMode mode, Logger log) { if (controlled.isEmpty()) { log.warn("Em data connection with 0 controlled entities created. This might lead to errors!"); + throw new ExtDataConnectionException(ExtEmDataConnection.class); } else { - log.info("Em data connection with {} controlled entities created.", controlled.size()); - } - - log.info("Em mode: {}", mode); + log.info( + "Em data connection with mode '{}' and {} controlled entities created.", + mode, + controlled.size()); - return new ExtEmDataConnection(controlled, mode); + return new ExtEmDataConnection(controlled, mode); + } } /** diff --git a/src/test/groovy/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnectionTest.groovy index 707b24df..0cbb19c2 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnectionTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnectionTest.groovy @@ -40,7 +40,7 @@ class ExtPrimaryDataConnectionTest extends Specification implements DataServiceT def primaryData = Map.of(inputUuid, pValue as Value) when: - extPrimaryDataConnection.provideData(0L, primaryData, Optional.of(900L)) + extPrimaryDataConnection.sendPrimaryData(0L, primaryData, Optional.of(900L), log) then: dataService.expectMessage(new ProvidePrimaryData(0L, primaryData, Optional.of(900L))) diff --git a/src/test/groovy/edu/ie3/simona/api/data/connection/ExtResultListenerTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtResultListenerTest.groovy new file mode 100644 index 00000000..08bdbc28 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtResultListenerTest.groovy @@ -0,0 +1,48 @@ +package edu.ie3.simona.api.data.connection + +import edu.ie3.datamodel.models.result.NodeResult +import edu.ie3.simona.api.data.results.ontology.ProvideResultEntities +import edu.ie3.simona.api.exceptions.WrongResponseTypeException +import edu.ie3.simona.api.test.common.DataServiceTestData +import spock.lang.Specification + +class ExtResultListenerTest extends Specification implements DataServiceTestData { + + def "An ExtResultListener should receive any result correctly"() { + given: + ExtResultListener listener = new ExtResultListener() + + when: + listener.queueExtResponseMsg(new ProvideResultEntities([loadResult])) + + then: + ProvideResultEntities message = listener.receiveAny() + + message.results() == [loadResult] + } + + def "An ExtResultListener should receive a specific result correctly"() { + given: + ExtResultListener listener = new ExtResultListener() + + when: + listener.queueExtResponseMsg(new ProvideResultEntities([loadResult])) + + then: + def message = listener.receiveWithType(ProvideResultEntities) + + message.results() == [loadResult] + } + + def "An ExtResultListener should thrown an exception if the wrong type is received"() { + given: + ExtResultListener listener = new ExtResultListener() + + when: + listener.queueExtResponseMsg(new ProvideResultEntities([loadResult])) + listener.receiveWithType(NodeResult) + + then: + thrown(WrongResponseTypeException) + } +} diff --git a/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy b/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy index 0896d846..a41da52f 100644 --- a/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy @@ -4,6 +4,7 @@ import edu.ie3.datamodel.models.value.PValue import edu.ie3.datamodel.models.value.SValue import edu.ie3.datamodel.models.value.Value import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode +import edu.ie3.simona.api.exceptions.ExtDataConnectionException import edu.ie3.simona.api.mapping.DataType import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -32,6 +33,15 @@ class ExtCoSimulationTest extends Specification { actual.getPrimaryDataAssets() == [uuid1, uuid2] } + def "An ExtCoSimulation throws an ExtDataConnectionException while trying to build an empty primary data connection"() { + when: + ExtCoSimulation.buildPrimaryConnection([:], log) + + then: + ExtDataConnectionException ex = thrown(ExtDataConnectionException) + ex.message == "The external data connection 'ExtPrimaryDataConnection' could not be build!" + } + def "An ExtCoSimulation can build an em data connection correctly"() { given: UUID uuid1 = UUID.randomUUID() @@ -46,6 +56,15 @@ class ExtCoSimulationTest extends Specification { actual.getControlledEms() == [uuid1, uuid2] } + def "An ExtCoSimulation throws an ExtDataConnectionException while trying to build an empty em data connection"() { + when: + ExtCoSimulation.buildEmConnection([], EmMode.SET_POINT, log) + + then: + ExtDataConnectionException ex = thrown(ExtDataConnectionException) + ex.message == "The external data connection 'ExtEmDataConnection' could not be build!" + } + def "An ExtCoSimulation can build a result data connection correctly"() { given: UUID uuid1 = UUID.randomUUID() @@ -65,4 +84,13 @@ class ExtCoSimulationTest extends Specification { actual.getGridResultDataAssets() == [uuid1] actual.getParticipantResultDataAssets() == [uuid2] } + + def "An ExtCoSimulation throws an ExtDataConnectionException while trying to build an empty result data connection"() { + when: + ExtCoSimulation.buildResultConnection([:], log) + + then: + ExtDataConnectionException ex = thrown(ExtDataConnectionException) + ex.message == "The external data connection 'ExtResultDataConnection' could not be build!" + } } From 30cf61ec4aa37b22f751e986630001cb662926ac Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 8 Apr 2025 09:03:48 +0200 Subject: [PATCH 3/7] Adding `CHANGELOG` entries. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22f86c33..eb54b651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Converting pekko classic to typed [#232](https://github.com/ie3-institute/simonaAPI/issues/232) +- Refactoring external data connection [#267](https://github.com/ie3-institute/simonaAPI/issues/267) +- Refactoring data containers [#268](https://github.com/ie3-institute/simonaAPI/issues/268) ## [0.7.0] - 2025-03-11 From 498c15214cc92fd7505bd166c16bbd94c9dd6b1a Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 10 Apr 2025 09:48:27 +0200 Subject: [PATCH 4/7] Some small improvements. --- .../api/data/container/ExtInputContainer.java | 12 +++--- .../api/data/model/em/FlexOptionRequest.java | 11 ++++-- .../simona/api/data/model/em/FlexOptions.java | 10 +++++ .../container/ExtInputContainerTest.groovy | 37 ++++++++----------- 4 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java b/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java index 365d199b..ec8a8e58 100644 --- a/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java +++ b/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java @@ -26,13 +26,13 @@ public final class ExtInputContainer implements ExtDataContainer { private final Map primaryData = new HashMap<>(); // em maps - /** Map uuid to flex option request. */ + /** Map uuid to flex option requests. */ private final Map flexRequests = new HashMap<>(); /** Map uuid to flex options. */ private final Map> flexOptions = new HashMap<>(); - /** Map uuid to em set point. */ + /** Map uuid to em set points. */ private final Map setPoints = new HashMap<>(); /** @@ -84,11 +84,11 @@ public void addPrimaryValue(UUID asset, Value value) { /** * Method for adding flex option requests. * - * @param requester the uuid of the requesting agent - * @param emEntities list of em agents, that will receive a request + * @param receiver the uuid of the agent, that will receive the request + * @param sender option for the uuid of the sender */ - public void addRequest(UUID requester, List emEntities) { - flexRequests.put(requester, new FlexOptionRequest(requester, emEntities)); + public void addRequest(UUID receiver, Optional sender) { + flexRequests.put(receiver, new FlexOptionRequest(receiver, sender)); } /** diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java index 184dc1df..492838d6 100644 --- a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java +++ b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java @@ -6,8 +6,13 @@ package edu.ie3.simona.api.data.model.em; -import edu.ie3.datamodel.models.value.Value; -import java.util.List; +import java.util.Optional; import java.util.UUID; -public record FlexOptionRequest(UUID requester, List emEntities) implements Value {} +/** + * Flex option request that will be sent to SIMONA. + * + * @param receiver uuid of the agent, that will receive the request + * @param sender option for the uuid of the agent, that sent the request + */ +public record FlexOptionRequest(UUID receiver, Optional sender) {} diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java index abc10063..c10b6481 100644 --- a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java +++ b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java @@ -10,7 +10,17 @@ import javax.measure.quantity.Power; import tech.units.indriya.ComparableQuantity; +/** + * Flex option that will be sent to SIMONA. + * + * @param receiver uuid of the flex options + * @param sender uuid of the flex options + * @param pMin minimal active power + * @param pRef current active power + * @param pMax maximal active power + */ public record FlexOptions( + UUID receiver, UUID sender, ComparableQuantity pMin, ComparableQuantity pRef, diff --git a/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy index 0ba975cf..0aa17102 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy @@ -27,22 +27,21 @@ class ExtInputContainerTest extends Specification { def "An ExtInputContainer should add flex option request data correctly"() { given: UUID requester = UUID.randomUUID() - UUID uuid = UUID.randomUUID() def container = new ExtInputContainer(0L) when: - container.addRequest(requester, [uuid]) + container.addRequest(requester, Optional.empty()) then: - container.flexRequests == [(requester): new FlexOptionRequest(requester, [uuid])] + container.flexRequests == [(requester): new FlexOptionRequest(requester, Optional.empty())] } def "An ExtInputContainer should add flex option data correctly"() { given: UUID receiver = UUID.randomUUID() UUID sender = UUID.randomUUID() - def flexOptions = new FlexOptions(sender, Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) + def flexOptions = new FlexOptions(receiver, sender, Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) def container = new ExtInputContainer(0L) @@ -75,12 +74,11 @@ class ExtInputContainerTest extends Specification { PValue primaryValue = new PValue(Quantities.getQuantity(10d, KILOWATT)) container.addPrimaryValue(primaryUuid, primaryValue) - UUID requester = UUID.randomUUID() - UUID requested = UUID.randomUUID() - container.addRequest(requester, [requested]) + UUID requestReceiver = UUID.randomUUID() + container.addRequest(requestReceiver, Optional.empty()) UUID receiver = UUID.randomUUID() - def flexOptions = new FlexOptions(UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) + def flexOptions = new FlexOptions(receiver, UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) container.addFlexOptions(receiver, [flexOptions]) UUID emAsset = UUID.randomUUID() @@ -108,12 +106,11 @@ class ExtInputContainerTest extends Specification { PValue primaryValue = new PValue(Quantities.getQuantity(10d, KILOWATT)) container.addPrimaryValue(primaryUuid, primaryValue) - UUID requester = UUID.randomUUID() - UUID requested = UUID.randomUUID() - container.addRequest(requester, [requested]) + UUID requestReceiver = UUID.randomUUID() + container.addRequest(requestReceiver, Optional.empty()) UUID receiver = UUID.randomUUID() - def flexOptions = new FlexOptions(UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) + def flexOptions = new FlexOptions(receiver, UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) container.addFlexOptions(receiver, [flexOptions]) UUID emAsset = UUID.randomUUID() @@ -125,7 +122,7 @@ class ExtInputContainerTest extends Specification { then: extracted.size() == 1 - extracted == [(requester): new FlexOptionRequest(requester, [requested])] + extracted == [(requestReceiver): new FlexOptionRequest(requestReceiver, Optional.empty())] container.primaryData.size() == 1 container.flexRequests.size() == 0 @@ -141,12 +138,11 @@ class ExtInputContainerTest extends Specification { PValue primaryValue = new PValue(Quantities.getQuantity(10d, KILOWATT)) container.addPrimaryValue(primaryUuid, primaryValue) - UUID requester = UUID.randomUUID() - UUID requested = UUID.randomUUID() - container.addRequest(requester, [requested]) + UUID requestReceiver = UUID.randomUUID() + container.addRequest(requestReceiver, Optional.empty()) UUID receiver = UUID.randomUUID() - def flexOptions = new FlexOptions(UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) + def flexOptions = new FlexOptions(receiver, UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) container.addFlexOptions(receiver, [flexOptions]) UUID emAsset = UUID.randomUUID() @@ -174,12 +170,11 @@ class ExtInputContainerTest extends Specification { PValue primaryValue = new PValue(Quantities.getQuantity(10d, KILOWATT)) container.addPrimaryValue(primaryUuid, primaryValue) - UUID requester = UUID.randomUUID() - UUID requested = UUID.randomUUID() - container.addRequest(requester, [requested]) + UUID requestReceiver = UUID.randomUUID() + container.addRequest(requestReceiver, Optional.empty()) UUID receiver = UUID.randomUUID() - def flexOptions = new FlexOptions(UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) + def flexOptions = new FlexOptions(receiver, UUID.randomUUID(), Quantities.getQuantity(0d, KILOWATT), Quantities.getQuantity(2d, KILOWATT), Quantities.getQuantity(5d, KILOWATT)) container.addFlexOptions(receiver, [flexOptions]) UUID emAsset = UUID.randomUUID() From f2b13e8104fa5a5860439fc0a062a30a56bd8318 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 9 May 2025 15:11:52 +0200 Subject: [PATCH 5/7] Adapt `CHANGELOG` to latest release. --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ece52e5..25a0573d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Removed Jenkinsfile [#290](https://github.com/ie3-institute/simonaAPI/issues/290) +- Refactoring external data connection [#267](https://github.com/ie3-institute/simonaAPI/issues/267) +- Refactoring data containers [#268](https://github.com/ie3-institute/simonaAPI/issues/268) ## [0.9.0] - 2025-05-09 @@ -26,8 +28,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Converting pekko classic to typed [#232](https://github.com/ie3-institute/simonaAPI/issues/232) -- Refactoring external data connection [#267](https://github.com/ie3-institute/simonaAPI/issues/267) -- Refactoring data containers [#268](https://github.com/ie3-institute/simonaAPI/issues/268) ## [0.7.0] - 2025-03-11 From 94662cf6a8183207785f70b41ff823c49efd6d19 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 12 May 2025 14:58:33 +0200 Subject: [PATCH 6/7] Small changes to `ExtEmDataConnection`. --- .../data/connection/ExtEmDataConnection.java | 38 ++++--------------- .../em/ontology/ProvideEmSetPointData.java | 4 +- .../simona/api/data/model/em/EmSetPoint.java | 19 ++++++++++ .../api/simulation/ExtCoSimulation.java | 8 ++-- .../connection/ExtEmDataConnectionTest.groovy | 7 ++-- .../api/simulation/ExtCoSimulationTest.groovy | 4 +- 6 files changed, 38 insertions(+), 42 deletions(-) create mode 100644 src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java diff --git a/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java index aad04fb3..6ae76641 100644 --- a/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java @@ -6,11 +6,10 @@ package edu.ie3.simona.api.data.connection; -import edu.ie3.datamodel.models.value.PValue; import edu.ie3.simona.api.data.em.ontology.EmDataMessageFromExt; import edu.ie3.simona.api.data.em.ontology.EmDataResponseMessageToExt; import edu.ie3.simona.api.data.em.ontology.ProvideEmSetPointData; -import edu.ie3.simona.api.mapping.DataType; +import edu.ie3.simona.api.data.model.em.EmSetPoint; import java.util.*; import org.slf4j.Logger; @@ -39,46 +38,23 @@ public List getControlledEms() { * Sends the em set points to SIMONA. * * @param tick current tick - * @param data to be sent + * @param setPoints to be sent * @param maybeNextTick option for the next tick in the simulation * @param log logger */ public void sendSetPoints( - long tick, Map data, Optional maybeNextTick, Logger log) { - if (data.isEmpty()) { + long tick, Map setPoints, Optional maybeNextTick, Logger log) { + if (setPoints.isEmpty()) { log.warn("No em set points found! Sending no em data to SIMONA for tick {}.", tick); } else { log.debug("Provided SIMONA with em set points."); - sendExtMsg(new ProvideEmSetPointData(tick, data, maybeNextTick)); + sendExtMsg(new ProvideEmSetPointData(tick, setPoints, maybeNextTick)); } } /** Mode of the em connection */ public enum EmMode { - SET_POINT("setPoint"), - EM_COMMUNICATION("emCommunication"), - EM_OPTIMIZATION("emOptimization"); - - public final String mode; - - EmMode(String mode) { - this.mode = mode; - } - - /** - * Method to get the {@link EmMode} from the em {@link DataType}. - * - * @param dataType given data type - * @return an {@link EmMode}, or throws an exception if no mode is found for the provided data - * type - */ - public static EmMode fromDataType(DataType dataType) { - return switch (dataType) { - case EXT_EM_INPUT -> EmMode.SET_POINT; - case EXT_EM_COMMUNICATION -> EmMode.EM_COMMUNICATION; - case EXT_EM_OPTIMIZER -> EmMode.EM_OPTIMIZATION; - default -> throw new IllegalStateException("Unexpected data type: " + dataType); - }; - } + BASE, + EM_COMMUNICATION } } diff --git a/src/main/java/edu/ie3/simona/api/data/em/ontology/ProvideEmSetPointData.java b/src/main/java/edu/ie3/simona/api/data/em/ontology/ProvideEmSetPointData.java index d3dc699a..3f2bb40b 100644 --- a/src/main/java/edu/ie3/simona/api/data/em/ontology/ProvideEmSetPointData.java +++ b/src/main/java/edu/ie3/simona/api/data/em/ontology/ProvideEmSetPointData.java @@ -6,12 +6,12 @@ package edu.ie3.simona.api.data.em.ontology; -import edu.ie3.datamodel.models.value.PValue; +import edu.ie3.simona.api.data.model.em.EmSetPoint; import java.util.Map; import java.util.Optional; import java.util.UUID; /** Message that provides em data (set points) from an external simulation. */ public record ProvideEmSetPointData( - long tick, Map emData, Optional maybeNextTick) + long tick, Map emSetPoints, Optional maybeNextTick) implements EmDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java b/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java new file mode 100644 index 00000000..a163ff20 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java @@ -0,0 +1,19 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.model.em; + +import edu.ie3.datamodel.models.value.PValue; +import java.util.Optional; +import java.util.UUID; + +/** + * Energy management set point that will be sent to SIMONA. + * + * @param receiver uuid of the agent, that will receive the set point + * @param pValue option for the power value + */ +public record EmSetPoint(UUID receiver, Optional pValue) {} diff --git a/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java b/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java index cea20044..4a657b7d 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java +++ b/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java @@ -9,7 +9,6 @@ import static java.util.Collections.emptyList; import edu.ie3.datamodel.models.result.ResultEntity; -import edu.ie3.datamodel.models.value.PValue; import edu.ie3.datamodel.models.value.Value; import edu.ie3.simona.api.data.ExtDataContainerQueue; import edu.ie3.simona.api.data.connection.ExtEmDataConnection; @@ -17,6 +16,7 @@ import edu.ie3.simona.api.data.connection.ExtResultDataConnection; import edu.ie3.simona.api.data.container.ExtInputContainer; import edu.ie3.simona.api.data.container.ExtResultContainer; +import edu.ie3.simona.api.data.model.em.EmSetPoint; import edu.ie3.simona.api.exceptions.ExtDataConnectionException; import edu.ie3.simona.api.mapping.DataType; import java.util.List; @@ -148,18 +148,18 @@ protected void sendPrimaryDataToSimona( * * @param extEmDataConnection the connection to SIMONA * @param tick for which data is sent - * @param dataMap map: id to value + * @param setPoints map: id to set point * @param maybeNextTick option for the next tick data is sent * @param log logger */ protected void sendEmSetPointsToSimona( ExtEmDataConnection extEmDataConnection, long tick, - Map dataMap, + Map setPoints, Optional maybeNextTick, Logger log) { log.debug("Received em set points from {}", extSimulatorName); - extEmDataConnection.sendSetPoints(tick, dataMap, maybeNextTick, log); + extEmDataConnection.sendSetPoints(tick, setPoints, maybeNextTick, log); log.debug("Provided em set points to SIMONA!"); } diff --git a/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy index a8bb2d4f..e0adbb0e 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy @@ -1,6 +1,7 @@ package edu.ie3.simona.api.data.connection import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode +import edu.ie3.simona.api.data.model.em.EmSetPoint import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage import edu.ie3.simona.api.data.em.ontology.ProvideEmSetPointData @@ -30,13 +31,13 @@ class ExtEmDataConnectionTest extends Specification implements DataServiceTestDa given: def dataService = testKit.createTestProbe(DataMessageFromExt) def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) - def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.SET_POINT) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.BASE) extEmDataConnection.setActorRefs( dataService.ref(), extSimAdapter.ref() ) - def emData = Map.of(inputUuid, pValue) + def emData = Map.of(inputUuid, new EmSetPoint(inputUuid, Optional.of(pValue))) when: extEmDataConnection.sendSetPoints(0L, emData, Optional.of(900L), log) @@ -50,7 +51,7 @@ class ExtEmDataConnectionTest extends Specification implements DataServiceTestDa given: def dataService = testKit.createTestProbe(DataMessageFromExt) def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) - def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.SET_POINT) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.BASE) extEmDataConnection.setActorRefs( dataService.ref(), extSimAdapter.ref() diff --git a/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy b/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy index a41da52f..30a768f3 100644 --- a/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy @@ -50,7 +50,7 @@ class ExtCoSimulationTest extends Specification { def controlled = [uuid1, uuid2] when: - def actual = ExtCoSimulation.buildEmConnection(controlled, EmMode.SET_POINT, log) + def actual = ExtCoSimulation.buildEmConnection(controlled, EmMode.BASE, log) then: actual.getControlledEms() == [uuid1, uuid2] @@ -58,7 +58,7 @@ class ExtCoSimulationTest extends Specification { def "An ExtCoSimulation throws an ExtDataConnectionException while trying to build an empty em data connection"() { when: - ExtCoSimulation.buildEmConnection([], EmMode.SET_POINT, log) + ExtCoSimulation.buildEmConnection([], EmMode.BASE, log) then: ExtDataConnectionException ex = thrown(ExtDataConnectionException) From b55e1130e62bcb132146d0b85bab0b2e7bf63482 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 22 May 2025 14:52:29 +0200 Subject: [PATCH 7/7] Refactoring `EmSetPoint`. --- .../api/data/container/ExtInputContainer.java | 18 +++++-- .../simona/api/data/model/em/EmSetPoint.java | 47 ++++++++++++++++--- .../connection/ExtEmDataConnectionTest.groovy | 2 +- .../container/ExtInputContainerTest.groovy | 25 ++++++++-- 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java b/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java index ec8a8e58..f11e7a5a 100644 --- a/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java +++ b/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java @@ -8,6 +8,7 @@ import edu.ie3.datamodel.models.value.PValue; import edu.ie3.datamodel.models.value.Value; +import edu.ie3.simona.api.data.model.em.EmSetPoint; import edu.ie3.simona.api.data.model.em.FlexOptionRequest; import edu.ie3.simona.api.data.model.em.FlexOptions; import java.util.*; @@ -33,7 +34,7 @@ public final class ExtInputContainer implements ExtDataContainer { private final Map> flexOptions = new HashMap<>(); /** Map uuid to em set points. */ - private final Map setPoints = new HashMap<>(); + private final Map setPoints = new HashMap<>(); /** * Container class for input data for SIMONA which can be read by SimonaAPI @@ -110,10 +111,19 @@ public void addFlexOptions(UUID receiver, List flexOption) { * Method for adding an em set point for a given asset. * * @param asset that will receive the set point + * @param power of the set point + */ + public void addSetPoint(UUID asset, PValue power) { + setPoints.put(asset, new EmSetPoint(asset, power)); + } + + /** + * Method for adding an em set point for a given asset. + * * @param setPoint given set point */ - public void addSetPoint(UUID asset, PValue setPoint) { - setPoints.put(asset, setPoint); + public void addSetPoint(EmSetPoint setPoint) { + setPoints.put(setPoint.receiver, setPoint); } /** Extracts the primary input data from this container. All other input data remains the same. */ @@ -139,7 +149,7 @@ public Map> extractFlexOptions() { /** * Extracts the set point input data from this container. All other input data remains the same. */ - public Map extractSetPoints() { + public Map extractSetPoints() { return copyAndClear(setPoints); } diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java b/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java index a163ff20..1093dda1 100644 --- a/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java +++ b/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java @@ -7,13 +7,46 @@ package edu.ie3.simona.api.data.model.em; import edu.ie3.datamodel.models.value.PValue; +import java.util.Objects; import java.util.Optional; import java.util.UUID; +import javax.measure.quantity.Power; +import tech.units.indriya.ComparableQuantity; -/** - * Energy management set point that will be sent to SIMONA. - * - * @param receiver uuid of the agent, that will receive the set point - * @param pValue option for the power value - */ -public record EmSetPoint(UUID receiver, Optional pValue) {} +/** Energy management set point that will be sent to SIMONA. */ +public final class EmSetPoint { + public final UUID receiver; + public final Optional power; + + public EmSetPoint(UUID receiver) { + this.receiver = receiver; + this.power = Optional.empty(); + } + + public EmSetPoint(UUID receiver, ComparableQuantity p) { + this.receiver = receiver; + this.power = Optional.of(new PValue(p)); + } + + public EmSetPoint(UUID receiver, PValue power) { + this.receiver = receiver; + this.power = Optional.of(power); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + EmSetPoint that = (EmSetPoint) o; + return Objects.equals(receiver, that.receiver) && Objects.equals(power, that.power); + } + + @Override + public int hashCode() { + return Objects.hash(receiver, power); + } + + @Override + public String toString() { + return "EmSetPoint{" + "receiver=" + receiver + ", power=" + power + '}'; + } +} diff --git a/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy index e0adbb0e..b11d7b03 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy @@ -37,7 +37,7 @@ class ExtEmDataConnectionTest extends Specification implements DataServiceTestDa extSimAdapter.ref() ) - def emData = Map.of(inputUuid, new EmSetPoint(inputUuid, Optional.of(pValue))) + def emData = Map.of(inputUuid, new EmSetPoint(inputUuid, pValue)) when: extEmDataConnection.sendSetPoints(0L, emData, Optional.of(900L), log) diff --git a/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy index 0aa17102..ef5ce865 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy @@ -1,6 +1,7 @@ package edu.ie3.simona.api.data.container import edu.ie3.datamodel.models.value.PValue +import edu.ie3.simona.api.data.model.em.EmSetPoint import edu.ie3.simona.api.data.model.em.FlexOptionRequest import edu.ie3.simona.api.data.model.em.FlexOptions import spock.lang.Specification @@ -55,12 +56,26 @@ class ExtInputContainerTest extends Specification { def "An ExtInputContainer should add set point data correctly"() { given: UUID uuid = UUID.randomUUID() - def setPoint = new PValue(Quantities.getQuantity(5d, KILOWATT)) + def power = new PValue(Quantities.getQuantity(5d, KILOWATT)) + + def container = new ExtInputContainer(0L) + + when: + container.addSetPoint(uuid, power) + + then: + container.setPoints == [(uuid): new EmSetPoint(uuid, power)] + } + + def "An ExtInputContainer should add set point data correctly"() { + given: + UUID uuid = UUID.randomUUID() + def setPoint = new EmSetPoint(uuid, new PValue(Quantities.getQuantity(5d, KILOWATT))) def container = new ExtInputContainer(0L) when: - container.addSetPoint(uuid, setPoint) + container.addSetPoint(setPoint) then: container.setPoints == [(uuid): setPoint] @@ -178,15 +193,15 @@ class ExtInputContainerTest extends Specification { container.addFlexOptions(receiver, [flexOptions]) UUID emAsset = UUID.randomUUID() - def setPoint = new PValue(Quantities.getQuantity(5d, KILOWATT)) - container.addSetPoint(emAsset, setPoint) + def power = new PValue(Quantities.getQuantity(5d, KILOWATT)) + container.addSetPoint(emAsset, power) when: def extracted = container.extractSetPoints() then: extracted.size() == 1 - extracted == [(emAsset): setPoint] + extracted == [(emAsset): new EmSetPoint(emAsset, power)] container.primaryData.size() == 1 container.flexRequests.size() == 1