diff --git a/CHANGELOG.md b/CHANGELOG.md index ce2509a0..e32c140e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,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) - Adapted dependabot workflow and added CODEOWNERS [#294](https://github.com/ie3-institute/simonaAPI/issues/294) +- 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 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..33e527a1 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/connection/BiDirectional.java @@ -0,0 +1,58 @@ +/* + * © 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 edu.ie3.simona.api.exceptions.WrongResponseTypeException; +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 WrongResponseTypeException( + "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..6ae76641 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java @@ -0,0 +1,60 @@ +/* + * © 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.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.data.model.em.EmSetPoint; +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 setPoints to be sent + * @param maybeNextTick option for the next tick in the simulation + * @param log logger + */ + public void sendSetPoints( + 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, setPoints, maybeNextTick)); + } + } + + /** Mode of the em connection */ + public enum EmMode { + BASE, + EM_COMMUNICATION + } +} 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..1da7f3e5 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java @@ -0,0 +1,59 @@ +/* + * © 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)); + } + + /** + * 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 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. Data: {}", primaryData); + + 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..c548e93e --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtResultListener.java @@ -0,0 +1,56 @@ +/* + * © 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 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 { + + /** Data message queue containing messages from SIMONA */ + public final LinkedBlockingQueue receiveTriggerQueue = + new LinkedBlockingQueue<>(); + + public ExtResultListener() { + super(); + } + + @Override + public final void queueExtResponseMsg(ResultDataResponseMessageToExt msg) + throws InterruptedException { + receiveTriggerQueue.put(msg); + } + + @Override + public final ResultDataResponseMessageToExt receiveAny() throws InterruptedException { + return receiveTriggerQueue.take(); + } + + @Override + @SuppressWarnings("unchecked") + public final 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 WrongResponseTypeException( + "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..f11e7a5a --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java @@ -0,0 +1,188 @@ +/* + * © 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.EmSetPoint; +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 requests. */ + private final Map flexRequests = new HashMap<>(); + + /** Map uuid to flex options. */ + private final Map> flexOptions = new HashMap<>(); + + /** Map uuid to em set points. */ + 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 receiver the uuid of the agent, that will receive the request + * @param sender option for the uuid of the sender + */ + public void addRequest(UUID receiver, Optional sender) { + flexRequests.put(receiver, new FlexOptionRequest(receiver, sender)); + } + + /** + * 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 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(EmSetPoint setPoint) { + setPoints.put(setPoint.receiver, 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..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 */ +/** 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/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/EmSetPoint.java b/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java new file mode 100644 index 00000000..1093dda1 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java @@ -0,0 +1,52 @@ +/* + * © 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.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. */ +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/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..492838d6 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java @@ -0,0 +1,18 @@ +/* + * © 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.Optional; +import java.util.UUID; + +/** + * 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 new file mode 100644 index 00000000..c10b6481 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java @@ -0,0 +1,27 @@ +/* + * © 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; + +/** + * 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, + 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/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/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..4a657b7d 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.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.data.model.em.EmSetPoint; +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,52 @@ 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!"); + throw new ExtDataConnectionException(ExtEmDataConnection.class); } else { - log.info("Em connection with {} entities created.", emMapping.size()); - } + log.info( + "Em data connection with mode '{}' and {} controlled entities created.", + mode, + controlled.size()); - return extEmDataConnection; + return new ExtEmDataConnection(controlled, mode); + } } /** @@ -95,24 +101,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 +133,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!"); } @@ -142,19 +148,19 @@ 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 sendEmDataToSimona( + protected void sendEmSetPointsToSimona( ExtEmDataConnection extEmDataConnection, long tick, - Map dataMap, + Map setPoints, 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, setPoints, maybeNextTick, log); + log.debug("Provided em set points to SIMONA!"); } /** @@ -164,15 +170,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..b11d7b03 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy @@ -0,0 +1,68 @@ +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 +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.BASE) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + + def emData = Map.of(inputUuid, new EmSetPoint(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.BASE) + 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..0cbb19c2 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.sendPrimaryData(0L, primaryData, Optional.of(900L), log) 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/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/data/container/ExtInputContainerTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy new file mode 100644 index 00000000..ef5ce865 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/container/ExtInputContainerTest.groovy @@ -0,0 +1,211 @@ +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 +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() + + def container = new ExtInputContainer(0L) + + when: + container.addRequest(requester, Optional.empty()) + + then: + 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(receiver, 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 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(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 requestReceiver = UUID.randomUUID() + container.addRequest(requestReceiver, Optional.empty()) + + UUID receiver = UUID.randomUUID() + 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() + 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 requestReceiver = UUID.randomUUID() + container.addRequest(requestReceiver, Optional.empty()) + + UUID receiver = UUID.randomUUID() + 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() + def setPoint = new PValue(Quantities.getQuantity(5d, KILOWATT)) + container.addSetPoint(emAsset, setPoint) + + when: + def extracted = container.extractFlexRequests() + + then: + extracted.size() == 1 + extracted == [(requestReceiver): new FlexOptionRequest(requestReceiver, Optional.empty())] + + 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 requestReceiver = UUID.randomUUID() + container.addRequest(requestReceiver, Optional.empty()) + + UUID receiver = UUID.randomUUID() + 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() + 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 requestReceiver = UUID.randomUUID() + container.addRequest(requestReceiver, Optional.empty()) + + UUID receiver = UUID.randomUUID() + 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() + def power = new PValue(Quantities.getQuantity(5d, KILOWATT)) + container.addSetPoint(emAsset, power) + + when: + def extracted = container.extractSetPoints() + + then: + extracted.size() == 1 + extracted == [(emAsset): new EmSetPoint(emAsset, power)] + + 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..30a768f3 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,11 @@ 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.exceptions.ExtDataConnectionException +import edu.ie3.simona.api.mapping.DataType import org.slf4j.Logger import org.slf4j.LoggerFactory import spock.lang.Shared @@ -18,51 +20,62 @@ 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 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() 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.BASE, log) then: actual.getControlledEms() == [uuid1, uuid2] } + def "An ExtCoSimulation throws an ExtDataConnectionException while trying to build an empty em data connection"() { + when: + ExtCoSimulation.buildEmConnection([], EmMode.BASE, 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() 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) @@ -71,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!" + } } 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 )