diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e499ca..b8984b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased/Snapshot] +## [0.6.0] - 2024-12-02 + +### Added +- Enable initialization of external data simulation [#167](https://github.com/ie3-institute/simonaAPI/issues/167) +- `ExtResultContainer` returns result map [#217](https://github.com/ie3-institute/simonaAPI/issues/217) + +### Changed +- Renaming power fields in `EvModel` [#208](https://github.com/ie3-institute/simonaAPI/issues/208) +- Enhancing external data connections [#219](https://github.com/ie3-institute/simonaAPI/issues/219) + ## [0.5.0] - 2024-08-09 ### Added @@ -37,7 +47,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Renamed messages to ease understanding [#62](https://github.com/ie3-institute/simonaAPI/issues/62) - Separating departures and arrivals in message protocol, properly handling exceptions [#77](https://github.com/ie3-institute/simonaAPI/issues/77) -[Unreleased/Snapshot]: https://github.com/ie3-institute/simonaapi/compare/0.5.0...HEAD +[Unreleased/Snapshot]: https://github.com/ie3-institute/simonaapi/compare/0.6.0...HEAD +[0.6.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/0.5.0...0.6.0 [0.5.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/0.4.0...0.5.0 [0.4.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/0.3.0...0.4.0 [0.3.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/0.2.0...0.3.0 diff --git a/build.gradle b/build.gradle index e27b37f..6f31561 100644 --- a/build.gradle +++ b/build.gradle @@ -3,8 +3,8 @@ plugins { id 'java' // java support id 'com.diffplug.spotless' version '6.25.0'//code format id 'pmd' // code check, working on source code - id 'com.github.spotbugs' version '6.0.20' // code check, working on byte code - id "org.sonarqube" version "5.1.0.4882" // sonarqube + id 'com.github.spotbugs' version '6.0.26' // code check, working on byte code + id "org.sonarqube" version "6.0.1.5171" // sonarqube id 'signing' id 'maven-publish' // publish to a maven repo (local or mvn central, has to be defined) id 'jacoco' // java code coverage plugin @@ -17,8 +17,8 @@ ext { // required for pekko scalaVersion = "2.13" - scalaBinaryVersion = "2.13.14" - pekkoVersion = "1.0.3" + scalaBinaryVersion = "2.13.15" + pekkoVersion = "1.1.2" } group = 'com.github.ie3-institute' @@ -46,18 +46,20 @@ repositories { dependencies{ - implementation 'tech.units:indriya:2.2' // quantities + implementation 'tech.units:indriya:2.2.1' // quantities // scala (needed for pekko) implementation "org.scala-lang:scala-library:${scalaBinaryVersion}" - //PSDM + //PSU implementation('com.github.ie3-institute:PowerSystemUtils:2.2.1') { exclude group: 'org.apache.logging.log4j' exclude group: 'org.slf4j' /* Exclude our own nested dependencies */ exclude group: 'com.github.ie3-institute' } + + //PSDM implementation('com.github.ie3-institute:PowerSystemDataModel:5.1.0') { exclude group: 'org.apache.logging.log4j' exclude group: 'org.slf4j' @@ -65,14 +67,18 @@ dependencies{ exclude group: 'com.github.ie3-institute' } + // logging + implementation platform('org.apache.logging.log4j:log4j-bom:2.24.2') + implementation 'org.apache.logging.log4j:log4j-api' // log4j + implementation 'org.apache.logging.log4j:log4j-core' // log4j + implementation 'org.apache.logging.log4j:log4j-slf4j-impl' // log4j -> slf4j + // pekko implementation "org.apache.pekko:pekko-actor_${scalaVersion}:${pekkoVersion}" testImplementation "org.apache.pekko:pekko-testkit_${scalaVersion}:${pekkoVersion}" // pekko testkit // TESTING testImplementation 'org.spockframework:spock-core:2.3-groovy-4.0' - - implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.17.2' } task printVersion { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135..a4b76b9 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 09523c0..e2847c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4..f5feea6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..9d21a21 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/src/main/java/edu/ie3/simona/api/ExtLinkInterface.java b/src/main/java/edu/ie3/simona/api/ExtLinkInterface.java index e69a27c..0761008 100644 --- a/src/main/java/edu/ie3/simona/api/ExtLinkInterface.java +++ b/src/main/java/edu/ie3/simona/api/ExtLinkInterface.java @@ -6,20 +6,25 @@ package edu.ie3.simona.api; -import edu.ie3.simona.api.data.ExtDataSimulation; +import edu.ie3.simona.api.exceptions.NoExtSimulationException; +import edu.ie3.simona.api.simulation.ExtSimAdapterData; import edu.ie3.simona.api.simulation.ExtSimulation; -import java.util.List; /** * Every external simulation has to provide a class {@code edu.ie3.simona.api.ExtLink} which * implements this interface. - * - *

{@link #getExtSimulation()} and {@link #getExtDataSimulations()} can return references to the - * same object, if that object implements both {@link ExtSimulation} and one or more variants of - * {@link ExtDataSimulation}. */ public interface ExtLinkInterface { - ExtSimulation getExtSimulation(); + /** Returns the external simulation. */ + default ExtSimulation getExtSimulation() { + throw new NoExtSimulationException(this.getClass()); + } - List getExtDataSimulations(); + /** + * Method to set up an external simulation. Everything that needs to be set up before the external + * simulation can be retrieved should be done here. + * + * @param data used for setting up the external simulation + */ + void setup(ExtSimAdapterData data); } diff --git a/src/main/java/edu/ie3/simona/api/data/DataQueueExtSimulationExtSimulator.java b/src/main/java/edu/ie3/simona/api/data/DataQueueExtSimulationExtSimulator.java new file mode 100644 index 0000000..67d8cb0 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/DataQueueExtSimulationExtSimulator.java @@ -0,0 +1,22 @@ +/* + * © 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/ExtData.java b/src/main/java/edu/ie3/simona/api/data/ExtDataConnection.java similarity index 61% rename from src/main/java/edu/ie3/simona/api/data/ExtData.java rename to src/main/java/edu/ie3/simona/api/data/ExtDataConnection.java index fd35fe7..9de5c6a 100644 --- a/src/main/java/edu/ie3/simona/api/data/ExtData.java +++ b/src/main/java/edu/ie3/simona/api/data/ExtDataConnection.java @@ -6,4 +6,5 @@ package edu.ie3.simona.api.data; -public interface ExtData {} +/** 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/exceptions/ConvertionException.java b/src/main/java/edu/ie3/simona/api/data/ExtDataContainer.java similarity index 50% rename from src/main/java/edu/ie3/simona/api/exceptions/ConvertionException.java rename to src/main/java/edu/ie3/simona/api/data/ExtDataContainer.java index 2065203..224b9de 100644 --- a/src/main/java/edu/ie3/simona/api/exceptions/ConvertionException.java +++ b/src/main/java/edu/ie3/simona/api/data/ExtDataContainer.java @@ -4,11 +4,7 @@ * Research group Distribution grid planning and operation */ -package edu.ie3.simona.api.exceptions; +package edu.ie3.simona.api.data; -public class ConvertionException extends Exception { - - public ConvertionException(final String message) { - super(message); - } -} +/** 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/ExtDataSimulation.java b/src/main/java/edu/ie3/simona/api/data/ExtDataSimulation.java deleted file mode 100644 index ffb06ec..0000000 --- a/src/main/java/edu/ie3/simona/api/data/ExtDataSimulation.java +++ /dev/null @@ -1,16 +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; - -/** - * Represents a data flow inside the external simulation. An external simulation can operate - * multiple data flows of different types. For each data type, there needs to be an interface - * extending this interface which provides methods that receive a data adapter. - * - *

See {@link edu.ie3.simona.api.data.ev.ExtEvSimulation} for an example. - */ -public interface ExtDataSimulation {} diff --git a/src/main/java/edu/ie3/simona/api/data/ExtInputDataConnection.java b/src/main/java/edu/ie3/simona/api/data/ExtInputDataConnection.java new file mode 100644 index 0000000..d8a29a7 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/ExtInputDataConnection.java @@ -0,0 +1,21 @@ +/* + * © 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 org.apache.pekko.actor.ActorRef; + +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 new file mode 100644 index 0000000..bec23e5 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/ExtInputDataContainer.java @@ -0,0 +1,69 @@ +/* + * © 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 new file mode 100644 index 0000000..6ac15e7 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/ExtOutputDataConnection.java @@ -0,0 +1,27 @@ +/* + * © 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 org.apache.pekko.actor.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/em/ExtEmDataConnection.java b/src/main/java/edu/ie3/simona/api/data/em/ExtEmDataConnection.java new file mode 100644 index 0000000..5a0bbaa --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/em/ExtEmDataConnection.java @@ -0,0 +1,81 @@ +/* + * © 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.ScheduleDataServiceMessage; +import java.util.*; +import java.util.stream.Collectors; +import org.apache.pekko.actor.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, ActorRef.noSender()); + // we need to schedule data receiver activation with scheduler + extSimAdapter.tell(new ScheduleDataServiceMessage(emDataService), ActorRef.noSender()); + } +} 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 new file mode 100644 index 0000000..5125e29 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/em/ontology/EmDataMessageFromExt.java @@ -0,0 +1,12 @@ +/* + * © 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.ontology; + +import edu.ie3.simona.api.data.ontology.DataMessageFromExt; + +/** 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/ProvideEmSetPointData.java b/src/main/java/edu/ie3/simona/api/data/em/ontology/ProvideEmSetPointData.java new file mode 100644 index 0000000..dc505dc --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/em/ontology/ProvideEmSetPointData.java @@ -0,0 +1,17 @@ +/* + * © 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.ontology; + +import edu.ie3.datamodel.models.value.PValue; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** Message that provides em data (set points) from an external simulation */ +public record ProvideEmSetPointData( + long tick, Map emData, Optional maybeNextTick) + implements EmDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/data/ev/ExtEvData.java b/src/main/java/edu/ie3/simona/api/data/ev/ExtEvDataConnection.java similarity index 92% rename from src/main/java/edu/ie3/simona/api/data/ev/ExtEvData.java rename to src/main/java/edu/ie3/simona/api/data/ev/ExtEvDataConnection.java index 303ff1f..53a71e8 100644 --- a/src/main/java/edu/ie3/simona/api/data/ev/ExtEvData.java +++ b/src/main/java/edu/ie3/simona/api/data/ev/ExtEvDataConnection.java @@ -6,28 +6,30 @@ package edu.ie3.simona.api.data.ev; -import edu.ie3.simona.api.data.ExtData; +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.ScheduleDataServiceMessage; -import java.util.*; +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.ActorRef; -public class ExtEvData implements ExtData { +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 final ActorRef dataService; + private ActorRef dataService; /** Actor reference to adapter that handles scheduler control flow in SIMONA */ - private final ActorRef extSimAdapter; + private ActorRef extSimAdapter; - // important trigger queue must be the same as hold in actor - // to make it safer one might consider asking the actor for ara reference on its trigger queue?! - public ExtEvData(ActorRef dataService, ActorRef extSimAdapter) { + @Override + public void setActorRefs(ActorRef dataService, ActorRef extSimAdapter) { this.dataService = dataService; this.extSimAdapter = extSimAdapter; } diff --git a/src/main/java/edu/ie3/simona/api/data/ev/ExtEvSimulation.java b/src/main/java/edu/ie3/simona/api/data/ev/ExtEvSimulation.java deleted file mode 100644 index b14fb1f..0000000 --- a/src/main/java/edu/ie3/simona/api/data/ev/ExtEvSimulation.java +++ /dev/null @@ -1,23 +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.ExtDataSimulation; - -/** - * An external simulation that provides an ev mobility simulation should implement this interface - * and handle the ExtEvData that is handed over. - */ -public interface ExtEvSimulation extends ExtDataSimulation { - - /** - * Hand over an ExtEvData which enables communication regarding ev movements. - * - * @param evData the ev data - */ - void setExtEvData(ExtEvData evData); -} diff --git a/src/main/java/edu/ie3/simona/api/data/ev/model/EvModel.java b/src/main/java/edu/ie3/simona/api/data/ev/model/EvModel.java index 06f8b7c..2ea3082 100644 --- a/src/main/java/edu/ie3/simona/api/data/ev/model/EvModel.java +++ b/src/main/java/edu/ie3/simona/api/data/ev/model/EvModel.java @@ -23,14 +23,14 @@ public interface EvModel { String getId(); /** - * @return the maximum AC charging power of this ev + * @return the maximum AC charging power of this ev (as active power) */ - ComparableQuantity getSRatedAC(); + ComparableQuantity getPRatedAC(); /** - * @return the maximum DC charging power of this ev + * @return the maximum DC charging power of this ev (as active power) */ - ComparableQuantity getSRatedDC(); + ComparableQuantity getPRatedDC(); /** * @return the storage capacity of this ev's battery diff --git a/src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryData.java b/src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryData.java deleted file mode 100644 index 7188e29..0000000 --- a/src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryData.java +++ /dev/null @@ -1,63 +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.ExtData; -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.exceptions.ConvertionException; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import org.apache.pekko.actor.ActorRef; - -public class ExtPrimaryData implements ExtData { - - /** Actor reference to service that handles ev data within SIMONA */ - private final ActorRef dataService; - - /** Actor reference to adapter that handles scheduler control flow in SIMONA */ - private final ActorRef extSimAdapter; - - /** Factory to convert an external object to PSDM primary data */ - private final PrimaryDataFactory factory; - - public ExtPrimaryData(ActorRef dataService, ActorRef extSimAdapter, PrimaryDataFactory factory) { - this.dataService = dataService; - this.extSimAdapter = extSimAdapter; - this.factory = factory; - } - - /** Provide primary data from an external simulation in one tick. */ - public void providePrimaryData(Long tick, Map primaryData) { - Map convertedMap = new HashMap<>(); - primaryData.forEach( - (k, v) -> { - try { - convertedMap.put(UUID.fromString(k), factory.convert(v)); - } catch (ConvertionException e) { - throw new RuntimeException(e); - } - }); - sendExtMsg(new ProvidePrimaryData(tick, convertedMap)); - } - - /** - * 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, ActorRef.noSender()); - // we need to schedule data receiver activation with scheduler - extSimAdapter.tell(new ScheduleDataServiceMessage(dataService), ActorRef.noSender()); - } -} 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 new file mode 100644 index 0000000..10fe184 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnection.java @@ -0,0 +1,81 @@ +/* + * © 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.ScheduleDataServiceMessage; +import edu.ie3.simona.api.data.primarydata.ontology.PrimaryDataMessageFromExt; +import edu.ie3.simona.api.data.primarydata.ontology.ProvidePrimaryData; +import java.util.*; +import java.util.stream.Collectors; +import org.apache.pekko.actor.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, ActorRef.noSender()); + // we need to schedule data receiver activation with scheduler + extSimAdapter.tell(new ScheduleDataServiceMessage(dataService), ActorRef.noSender()); + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataSimulation.java b/src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataSimulation.java deleted file mode 100644 index d217209..0000000 --- a/src/main/java/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataSimulation.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.primarydata; - -import edu.ie3.simona.api.data.ExtDataSimulation; - -/** - * An external simulation that provides primary data should implement this interface and handle the - * ExtPrimaryData that is handed over. - */ -public interface ExtPrimaryDataSimulation extends ExtDataSimulation { - - /** Hand over an ExtPrimaryData which enables communication regarding primary data. */ - void setExtPrimaryData(ExtPrimaryData extPrimaryData); - - /** Should implement the convertion of the external format to the PSDM format of primary data. */ - PrimaryDataFactory getPrimaryDataFactory(); -} diff --git a/src/main/java/edu/ie3/simona/api/data/primarydata/PrimaryDataFactory.java b/src/main/java/edu/ie3/simona/api/data/primarydata/PrimaryDataFactory.java deleted file mode 100644 index 9ff74d5..0000000 --- a/src/main/java/edu/ie3/simona/api/data/primarydata/PrimaryDataFactory.java +++ /dev/null @@ -1,17 +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.exceptions.ConvertionException; - -/** Interface that should be implemented by an external simulation. */ -public interface PrimaryDataFactory { - - /** Should convert an object to a primary data value with a check if the object is primary data */ - Value convert(Object entity) throws ConvertionException; -} diff --git a/src/main/java/edu/ie3/simona/api/data/primarydata/ontology/ProvidePrimaryData.java b/src/main/java/edu/ie3/simona/api/data/primarydata/ontology/ProvidePrimaryData.java index a87d9f7..2c31632 100644 --- a/src/main/java/edu/ie3/simona/api/data/primarydata/ontology/ProvidePrimaryData.java +++ b/src/main/java/edu/ie3/simona/api/data/primarydata/ontology/ProvidePrimaryData.java @@ -8,8 +8,10 @@ import edu.ie3.datamodel.models.value.Value; import java.util.Map; +import java.util.Optional; import java.util.UUID; /** Message that provides primary data from an external primary data simulation */ -public record ProvidePrimaryData(long tick, Map primaryData) +public record ProvidePrimaryData( + long tick, Map primaryData, Optional maybeNextTick) implements PrimaryDataMessageFromExt {} 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 new file mode 100644 index 0000000..531faf7 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/results/ExtResultContainer.java @@ -0,0 +1,110 @@ +/* + * © 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.ModelResultEntity; +import edu.ie3.datamodel.models.result.NodeResult; +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/ExtResultData.java b/src/main/java/edu/ie3/simona/api/data/results/ExtResultDataConnection.java similarity index 51% rename from src/main/java/edu/ie3/simona/api/data/results/ExtResultData.java rename to src/main/java/edu/ie3/simona/api/data/results/ExtResultDataConnection.java index ae5d0ea..de90fa4 100644 --- a/src/main/java/edu/ie3/simona/api/data/results/ExtResultData.java +++ b/src/main/java/edu/ie3/simona/api/data/results/ExtResultDataConnection.java @@ -7,62 +7,101 @@ package edu.ie3.simona.api.data.results; import edu.ie3.datamodel.models.result.ModelResultEntity; -import edu.ie3.simona.api.data.ExtData; +import edu.ie3.datamodel.models.result.NodeResult; +import edu.ie3.datamodel.models.result.system.SystemParticipantResult; +import edu.ie3.simona.api.data.ExtOutputDataConnection; 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.exceptions.ConvertionException; 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.ActorRef; -public class ExtResultData implements ExtData { +/** 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 ev data within SIMONA */ - private final ActorRef dataService; + /** 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 final ActorRef extSimAdapter; + 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; - private final ResultDataFactory factory; + public ExtResultDataConnection( + Map participantResultAssetMapping, Map gridResultAssetMapping) { + this.participantResultAssetMapping = participantResultAssetMapping; + this.gridResultAssetMapping = gridResultAssetMapping; + } - public ExtResultData(ActorRef dataService, ActorRef extSimAdapter, ResultDataFactory factory) { - this.dataService = dataService; + /** + * 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; - this.factory = factory; + } + + 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. */ - public List requestResults() throws InterruptedException { - sendExtMsg(new RequestResultEntities()); + 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 requestResultObjects() - throws ConvertionException, InterruptedException { - return convertResultsList(requestResults()); + public Map requestResults(long tick) throws InterruptedException { + return createResultMap(requestResultList(tick)); } - protected Map convertResultsList(List results) - throws ConvertionException { - Map resultsMap = new HashMap<>(); - Object convertedResult; - for (ModelResultEntity res : results) { - convertedResult = factory.convert(res); - resultsMap.put(res.getInputModel().toString(), convertedResult); - } - return resultsMap; + 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; } /** @@ -73,9 +112,9 @@ protected Map convertResultsList(List results * @param msg the data/information that is sent to SIMONA's result data service */ public void sendExtMsg(ResultDataMessageFromExt msg) { - dataService.tell(msg, ActorRef.noSender()); + extResultDataService.tell(msg, ActorRef.noSender()); // we need to schedule data receiver activation with scheduler - extSimAdapter.tell(new ScheduleDataServiceMessage(dataService), ActorRef.noSender()); + extSimAdapter.tell(new ScheduleDataServiceMessage(dataServiceActivation), ActorRef.noSender()); } /** Queues message from SIMONA that should be handled by the external simulation. */ diff --git a/src/main/java/edu/ie3/simona/api/data/results/ExtResultDataSimulation.java b/src/main/java/edu/ie3/simona/api/data/results/ExtResultDataSimulation.java deleted file mode 100644 index 31ea7ac..0000000 --- a/src/main/java/edu/ie3/simona/api/data/results/ExtResultDataSimulation.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.results; - -import edu.ie3.simona.api.data.ExtDataSimulation; - -/** - * An external simulation that needs results from SIMONA should implement this interface and handle - * the ExtResultsData that is handed over. - */ -public interface ExtResultDataSimulation extends ExtDataSimulation { - - /** Hand over an ExtPrimaryData which enables communication regarding primary data. */ - void setExtResultData(ExtResultData extResultData); - - /** Should implement the convertion of the PSDM format to the external format of result data. */ - ResultDataFactory getResultDataFactory(); -} diff --git a/src/main/java/edu/ie3/simona/api/data/results/ResultDataFactory.java b/src/main/java/edu/ie3/simona/api/data/results/ResultDataFactory.java deleted file mode 100644 index 1632944..0000000 --- a/src/main/java/edu/ie3/simona/api/data/results/ResultDataFactory.java +++ /dev/null @@ -1,19 +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.ResultEntity; -import edu.ie3.simona.api.exceptions.ConvertionException; - -public interface ResultDataFactory { - - /** - * Should convert a result entity to an object, that can be read by the external simulation, with - * a check if the object is primary data - */ - Object convert(ResultEntity entity) throws ConvertionException; -} diff --git a/src/main/java/edu/ie3/simona/api/data/results/ontology/ProvideResultEntities.java b/src/main/java/edu/ie3/simona/api/data/results/ontology/ProvideResultEntities.java index 7c11cfb..525d702 100644 --- a/src/main/java/edu/ie3/simona/api/data/results/ontology/ProvideResultEntities.java +++ b/src/main/java/edu/ie3/simona/api/data/results/ontology/ProvideResultEntities.java @@ -8,7 +8,13 @@ import edu.ie3.datamodel.models.result.ModelResultEntity; import java.util.List; +import java.util.Map; +import java.util.UUID; /** Provides a list of results from SIMONA to an external simulation. */ public record ProvideResultEntities(List results) - implements ResultDataResponseMessageToExt {} + implements ResultDataResponseMessageToExt { + public ProvideResultEntities(Map resultMap) { + this(resultMap.values().stream().toList()); + } +} 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 05f8aae..8ef656b 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 @@ -7,4 +7,4 @@ package edu.ie3.simona.api.data.results.ontology; /** Request calculated results from SIMONA in the current tick */ -public record RequestResultEntities() implements ResultDataMessageFromExt {} +public record RequestResultEntities(Long tick) implements ResultDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/data/results/ontology/ResultDataMessageFromExt.java b/src/main/java/edu/ie3/simona/api/data/results/ontology/ResultDataMessageFromExt.java index 60ebff4..83b2e31 100644 --- a/src/main/java/edu/ie3/simona/api/data/results/ontology/ResultDataMessageFromExt.java +++ b/src/main/java/edu/ie3/simona/api/data/results/ontology/ResultDataMessageFromExt.java @@ -6,5 +6,7 @@ package edu.ie3.simona.api.data.results.ontology; +import edu.ie3.simona.api.data.ontology.DataMessageFromExt; + /** Messages that are sent from an external simulation to the SIMONA */ -public interface ResultDataMessageFromExt {} +public interface ResultDataMessageFromExt extends DataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/exceptions/NoExtSimulationException.java b/src/main/java/edu/ie3/simona/api/exceptions/NoExtSimulationException.java new file mode 100644 index 0000000..f4a242b --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/exceptions/NoExtSimulationException.java @@ -0,0 +1,20 @@ +/* + * © 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.ExtLinkInterface; + +public class NoExtSimulationException extends RuntimeException { + + public NoExtSimulationException(Class linkClass) { + this("No external simulation was set up in ExtLinkInterface: ." + linkClass.getSimpleName()); + } + + public NoExtSimulationException(final String message) { + super(message); + } +} diff --git a/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java b/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java new file mode 100644 index 0000000..f07ec9b --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java @@ -0,0 +1,178 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.simulation; + +import edu.ie3.datamodel.models.result.ModelResultEntity; +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 java.util.Map; +import java.util.Optional; +import java.util.UUID; +import org.slf4j.Logger; + +/** + * 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. + */ +public abstract class ExtCoSimulation extends ExtSimulation { + + /** Queue for the data connection from the external co-simulation to SimonaAPI */ + protected final DataQueueExtSimulationExtSimulator + dataQueueExtCoSimulatorToSimonaApi; + + /** Queue for the data connection from SimonaAPI to the external co-simulation */ + protected final DataQueueExtSimulationExtSimulator + dataQueueSimonaApiToExtCoSimulator; + + /** Name of the external co-simulation */ + protected final String extSimulatorName; + + protected ExtCoSimulation(String simulationName, String extSimulatorName) { + super(simulationName); + this.extSimulatorName = extSimulatorName; + this.dataQueueExtCoSimulatorToSimonaApi = new DataQueueExtSimulationExtSimulator<>(); + this.dataQueueSimonaApiToExtCoSimulator = new DataQueueExtSimulationExtSimulator<>(); + } + + /** + * Builds an {@link ExtPrimaryDataConnection}. + * + * @param mapping between the external simulation and SIMONA. + * @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."); + } else { + log.info("Primary connection with {} entities created.", primaryMapping.size()); + } + + return extPrimaryDataConnection; + } + + /** + * Builds an {@link ExtEmDataConnection}. + * + * @param mapping between the external simulation and SIMONA. + * @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."); + } else { + log.info("Em connection with {} entities created.", emMapping.size()); + } + + return extEmDataConnection; + } + + /** + * Builds an {@link ExtResultDataConnection}. + * + * @param mapping between the external simulation and SIMONA. + * @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."); + } else { + log.info( + "Result connection with {} participants and {} grid assets created.", + resultParticipantMapping.size(), + resultGridMapping.size()); + } + + return extResultDataConnection; + } + + /** + * Function to send primary data to SIMONA using ExtPrimaryData + * + * @param extPrimaryDataConnection the connection to SIMONA + * @param tick for which data is sent + * @param dataMap map: id to value + * @param maybeNextTick option for the next tick data is sent + * @param log logger + */ + protected void sendPrimaryDataToSimona( + ExtPrimaryDataConnection extPrimaryDataConnection, + long tick, + 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); + log.debug("Provided Primary Data to SIMONA!"); + } + + /** + * Function to send em data to SIMONA using ExtPrimaryData nextTick is necessary, because the em + * agents have an own scheduler that should know, when the next set point arrives. + * + * @param extEmDataConnection the connection to SIMONA + * @param tick for which data is sent + * @param dataMap map: id to value + * @param maybeNextTick option for the next tick data is sent + * @param log logger + */ + protected void sendEmDataToSimona( + ExtEmDataConnection extEmDataConnection, + long tick, + Map dataMap, + Optional maybeNextTick, + Logger log) { + log.debug("Received EmData from {}", extSimulatorName); + extEmDataConnection.convertAndSend(tick, dataMap, maybeNextTick, log); + log.debug("Provided EmData to SIMONA!"); + } + + /** + * Function to get result data from SIMONA using the available {@link ExtResultDataConnection} + * + * @param connection the connection to SIMONA + * @param tick for which data is received + * @param maybeNextTick option for the next tick data is received + * @param log logger + */ + protected void sendDataToExt( + ExtResultDataConnection connection, long tick, Optional maybeNextTick, Logger log) + throws InterruptedException { + log.debug("Request results from SIMONA!"); + Map resultsToBeSend = connection.requestResults(tick); + log.debug("Received results from SIMONA!"); + dataQueueSimonaApiToExtCoSimulator.queueData( + new ExtResultContainer(tick, resultsToBeSend, maybeNextTick)); + log.debug("Sent results to {}", extSimulatorName); + } +} diff --git a/src/main/java/edu/ie3/simona/api/simulation/ExtSimAdapterData.java b/src/main/java/edu/ie3/simona/api/simulation/ExtSimAdapterData.java index 20004bc..e0906ce 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/ExtSimAdapterData.java +++ b/src/main/java/edu/ie3/simona/api/simulation/ExtSimAdapterData.java @@ -17,7 +17,7 @@ public class ExtSimAdapterData { public final LinkedBlockingQueue receiveMessageQueue = new LinkedBlockingQueue<>(); - /** Actor reference to adapter that handles scheduler control flow in SIMONA */ + /** Actor reference to the adapter for the phases that handles scheduler control flow in SIMONA */ private final ActorRef extSimAdapter; /** CLI arguments with which SIMONA is initiated */ @@ -30,6 +30,10 @@ public ExtSimAdapterData(ActorRef extSimAdapter, String[] mainArgs) { this.mainArgs = mainArgs; } + public ActorRef getAdapter() { + return extSimAdapter; + } + /** * Called within SIMONA to queue messages for the external simulation * 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 ae900cd..0e52a81 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/ExtSimulation.java +++ b/src/main/java/edu/ie3/simona/api/simulation/ExtSimulation.java @@ -6,13 +6,10 @@ package edu.ie3.simona.api.simulation; -import edu.ie3.simona.api.data.ExtData; -import edu.ie3.simona.api.data.ev.ExtEvData; -import edu.ie3.simona.api.data.ev.ExtEvSimulation; +import edu.ie3.simona.api.data.ExtDataConnection; import edu.ie3.simona.api.simulation.ontology.*; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; +import java.util.Set; /** * Every external simulation must extend this class in order to get triggered by the main @@ -20,9 +17,13 @@ */ public abstract class ExtSimulation implements Runnable { + protected String simulationName; + private ExtSimAdapterData data; - protected ExtSimulation() {} + protected ExtSimulation(String simulationName) { + this.simulationName = simulationName; + } public void run() { try { @@ -100,22 +101,14 @@ protected void terminate(Boolean simulationSuccessful) { // to be overwritten in subclass } - public final List> getRequiredAdapters() { - ArrayList> classes = new ArrayList<>(); - - if (this instanceof ExtEvSimulation) classes.add(ExtEvData.class); - - return classes; - } - - public final void setup(ExtSimAdapterData data, List adapters) { + /** + * Method to set the external simulation adapter data. This method should be called during {@link + * edu.ie3.simona.api.ExtLinkInterface#setup(ExtSimAdapterData)}. + * + * @param data to set up + */ + public final void setAdapterData(ExtSimAdapterData data) { this.data = data; - - // todo sanity check if all required data is available - for (ExtData adapter : adapters) { - if (adapter instanceof ExtEvData && this instanceof ExtEvSimulation) - ((ExtEvSimulation) this).setExtEvData((ExtEvData) adapter); - } } /** @@ -126,4 +119,12 @@ public final void setup(ExtSimAdapterData data, List adapters) { protected String[] getMainArgs() { return data.getMainArgs(); } + + /** Returns the name of this external simulation. */ + public final String getSimulationName() { + return simulationName; + } + + /** Returns all {@link ExtDataConnection} of this simulation. */ + public abstract Set getDataConnections(); } diff --git a/src/main/java/edu/ie3/simona/api/simulation/mapping/DataType.java b/src/main/java/edu/ie3/simona/api/simulation/mapping/DataType.java new file mode 100644 index 0000000..67bfaa1 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/simulation/mapping/DataType.java @@ -0,0 +1,32 @@ +/* + * © 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.simulation.mapping; + +import edu.ie3.datamodel.exceptions.ParsingException; + +public enum DataType { + EXT_PRIMARY_INPUT("primary_input"), + EXT_EM_INPUT("em_input"), + EXT_GRID_RESULT("grid_result"), + EXT_PARTICIPANT_RESULT("participant_result"); + + public final String type; + + DataType(String type) { + this.type = type; + } + + public static DataType parse(String type) throws ParsingException { + return switch (type) { + case "primary_input" -> EXT_PRIMARY_INPUT; + case "em_input" -> EXT_EM_INPUT; + case "grid_result" -> EXT_GRID_RESULT; + case "participant_result" -> EXT_PARTICIPANT_RESULT; + default -> throw new ParsingException("Data type " + type + " is not supported!"); + }; + } +} 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 new file mode 100644 index 0000000..1444c3e --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityEntry.java @@ -0,0 +1,43 @@ +/* + * © 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.simulation.mapping; + +import edu.ie3.datamodel.io.naming.timeseries.ColumnScheme; +import edu.ie3.datamodel.models.input.InputEntity; +import java.util.UUID; + +/** + * Container for an external asset with all information for the mapping + * + * @param uuid SIMONA uuid + * @param id external id + * @param columnScheme data types the external asset expects + * @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) + implements InputEntity { + + public String toString() { + return "ExtEntityEntry={" + + "UUID=" + + uuid + + ", " + + "extId=" + + id + + ", " + + "columnScheme=" + + columnScheme + + ", " + + "dataType=" + + dataType + + "}"; + } +} 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 new file mode 100644 index 0000000..9a82025 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityFactory.java @@ -0,0 +1,56 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.simulation.mapping; + +import edu.ie3.datamodel.exceptions.FactoryException; +import edu.ie3.datamodel.exceptions.ParsingException; +import edu.ie3.datamodel.io.factory.EntityData; +import edu.ie3.datamodel.io.factory.EntityFactory; +import edu.ie3.datamodel.io.naming.timeseries.ColumnScheme; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** Class to build a mapping entry from the external simulation to SIMONA */ +public class ExtEntityFactory extends EntityFactory { + + public static final String SIMONA_UUID = "uuid"; + public static final String EXT_ID = "id"; + public static final String COLUMN_SCHEME = "columnScheme"; + public static final String DATA_TYPE = "dataType"; + + public ExtEntityFactory() { + super(ExtEntityEntry.class); + } + + @Override + protected List> getFields(Class entityClass) { + return Collections.singletonList( + Stream.of(SIMONA_UUID, EXT_ID, COLUMN_SCHEME, DATA_TYPE).collect(Collectors.toSet())); + } + + @Override + protected ExtEntityEntry buildModel(EntityData data) { + UUID simonaUuid = data.getUUID(SIMONA_UUID); + String extId = data.getField(EXT_ID); + Optional columnScheme = ColumnScheme.parse(data.getField(COLUMN_SCHEME)); + + DataType inputType; + try { + inputType = DataType.parse(data.getField(DATA_TYPE)); + } catch (ParsingException e) { + throw new FactoryException(e); + } + + return new ExtEntityEntry( + simonaUuid, + extId, + columnScheme + .orElseThrow(), // FIXME: Interim version -> ColumnScheme should handle more data types + 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 new file mode 100644 index 0000000..8fd033e --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMapping.java @@ -0,0 +1,43 @@ +/* + * © 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.simulation.mapping; + +import java.util.*; +import java.util.stream.Collectors; + +/** Contains the mapping between SIMONA uuid, the external id and the data type the assets hold */ +public class ExtEntityMapping { + + private final Map> extEntities; + + public ExtEntityMapping(List extEntityEntryList) { + this.extEntities = + extEntityEntryList.stream().collect(Collectors.groupingBy(ExtEntityEntry::dataType)); + } + + /** + * Mapping external id to SIMONA uuid + * + * @param dataType data type the external asset expects + * @return Mapping external id to SIMONA uuid + */ + public Map getExtId2UuidMapping(DataType dataType) { + return extEntities.getOrDefault(dataType, Collections.emptyList()).stream() + .collect(Collectors.toMap(ExtEntityEntry::id, ExtEntityEntry::uuid)); + } + + /** + * Mapping SIMONA uuid to external id + * + * @param dataType data type the external asset expects + * @return Mapping SIMONA uuid to external id + */ + public Map getExtUuid2IdMapping(DataType dataType) { + return extEntities.getOrDefault(dataType, Collections.emptyList()).stream() + .collect(Collectors.toMap(ExtEntityEntry::uuid, ExtEntityEntry::id)); + } +} diff --git a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingSource.java b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingSource.java new file mode 100644 index 0000000..1d24d7f --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingSource.java @@ -0,0 +1,88 @@ +/* + * © 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.simulation.mapping; + +import edu.ie3.datamodel.exceptions.SourceException; +import edu.ie3.datamodel.io.factory.EntityData; +import edu.ie3.datamodel.io.naming.EntityPersistenceNamingStrategy; +import edu.ie3.datamodel.io.naming.FileNamingStrategy; +import edu.ie3.datamodel.io.source.DataSource; +import edu.ie3.datamodel.io.source.csv.CsvDataSource; +import edu.ie3.datamodel.models.Entity; +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; + +/** Source for external entity mapping. */ +public class ExtEntityMappingSource { + + protected final DataSource dataSource; + protected final ExtEntityFactory factory; + + protected ExtEntityMappingSource(DataSource dataSource) { + this.dataSource = dataSource; + this.factory = new ExtEntityFactory(); + } + + /** + * Creates an {@link ExtEntityMapping} from a given file. + * + * @param filepath path to the file including its name + * @return a new mapping + * @throws SourceException if an error occurred + */ + public static ExtEntityMapping fromFile(Path filepath) throws SourceException { + String filename = filepath.getFileName().toString(); + Path directoryPath; + + if (!filename.contains(".csv")) { + directoryPath = filepath; + } else { + directoryPath = filepath.getParent(); + } + + ExtEntityNaming naming = new ExtEntityNaming(filename); + CsvDataSource source = new CsvDataSource(",", directoryPath, new FileNamingStrategy(naming)); + + return new ExtEntityMappingSource(source).getMapping(); + } + + /** Return the mapping from a given {@link DataSource}. */ + public ExtEntityMapping getMapping() throws SourceException { + return new ExtEntityMapping( + dataSource.getSourceData(ExtEntityEntry.class).map(this::createExtEntityEntry).toList()); + } + + /** + * Creates an ext entity entry from a given map. + * + * @param fieldToValues map: field name to value + * @return a new {@link ExtEntityEntry} + */ + private ExtEntityEntry createExtEntityEntry(Map fieldToValues) { + return factory.get(new EntityData(fieldToValues, ExtEntityEntry.class)).getOrThrow(); + } + + /** Csv naming for ext entity mapping. */ + private static class ExtEntityNaming extends EntityPersistenceNamingStrategy { + + private final String filename; + + private ExtEntityNaming(String filename) { + this.filename = filename.replace(".csv", ""); + } + + @Override + public Optional getEntityName(Class cls) { + if (ExtEntityEntry.class.isAssignableFrom(cls)) { + return Optional.of(filename); + } else { + return super.getEntityName(cls); + } + } + } +} diff --git a/src/main/java/edu/ie3/simona/api/simulation/ontology/ControlMessageToExt.java b/src/main/java/edu/ie3/simona/api/simulation/ontology/ControlMessageToExt.java index 42826eb..601b162 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/ontology/ControlMessageToExt.java +++ b/src/main/java/edu/ie3/simona/api/simulation/ontology/ControlMessageToExt.java @@ -6,4 +6,5 @@ package edu.ie3.simona.api.simulation.ontology; +/** Interface for control messages from the SIMONA to the external simulation */ public interface ControlMessageToExt {} 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 new file mode 100644 index 0000000..d8f798f --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/em/ExtEmDataConnectionTest.groovy @@ -0,0 +1,98 @@ +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.ScheduleDataServiceMessage +import edu.ie3.simona.api.data.primarydata.ontology.ProvidePrimaryData +import edu.ie3.simona.api.test.common.DataServiceTestData +import org.apache.pekko.actor.ActorSystem +import org.apache.pekko.testkit.TestProbe +import org.apache.pekko.testkit.javadsl.TestKit +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import spock.lang.Shared +import spock.lang.Specification + +class ExtEmDataConnectionTest extends Specification implements DataServiceTestData { + + @Shared + ActorSystem actorSystem + + @Shared + Map extEmDataMapping = Map.of( + "Em", + inputUuid + ) + + def setupSpec() { + actorSystem = ActorSystem.create() + } + + def cleanupSpec() { + TestKit.shutdownActorSystem(actorSystem) + actorSystem = null + } + + def "ExtEmDataConnection should provide em data correctly"() { + given: + def dataService = new TestProbe(actorSystem) + def extSimAdapter = new TestProbe(actorSystem) + 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.expectMsg(new ProvideEmSetPointData(0, convertedEmData, Optional.of(900L))) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(dataService.ref())) + } + + def "ExtEmDataConnection should convert input data correctly"() { + given: + def dataService = new TestProbe(actorSystem) + def extSimAdapter = new TestProbe(actorSystem) + 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.expectMsg(new ProvideEmSetPointData(0L, Map.of(inputUuid, pValue), Optional.of(900L))) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(dataService.ref())) + } + + def "ExtEmDataConnection should send no message, if input data for a not requested asset was provided"() { + given: + def dataService = new TestProbe(actorSystem) + def extSimAdapter = new TestProbe(actorSystem) + 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/data/ev/ExtEvDataTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/ev/ExtEvDataConnectionTest.groovy similarity index 66% rename from src/test/groovy/edu/ie3/simona/api/data/ev/ExtEvDataTest.groovy rename to src/test/groovy/edu/ie3/simona/api/data/ev/ExtEvDataConnectionTest.groovy index 63441e4..8487efc 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/ev/ExtEvDataTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/ev/ExtEvDataConnectionTest.groovy @@ -15,7 +15,7 @@ import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage import spock.lang.Shared import spock.lang.Specification -class ExtEvDataTest extends Specification { +class ExtEvDataConnectionTest extends Specification { @Shared ActorSystem actorSystem @@ -29,18 +29,19 @@ class ExtEvDataTest extends Specification { actorSystem = null } - def "ExtEvData should request and receive free evcs lots correctly"() { + def "ExtEvDataConnection should request and receive free evcs lots correctly"() { given: def dataService = new TestProbe(actorSystem) def extSimAdapter = new TestProbe(actorSystem) - def extEvData = new ExtEvData(dataService.ref(), extSimAdapter.ref()) + def extEvDataConnection = new ExtEvDataConnection() + extEvDataConnection.setActorRefs(dataService.ref(), extSimAdapter.ref()) def sentMsg = new ProvideEvcsFreeLots() when: // we need to queue the msg beforehand because the receive method is blocking - extEvData.queueExtResponseMsg(sentMsg) - def actualReceivedEvcs = extEvData.requestAvailablePublicEvcs() + extEvDataConnection.queueExtResponseMsg(sentMsg) + def actualReceivedEvcs = extEvDataConnection.requestAvailablePublicEvcs() then: dataService.expectMsg(new RequestEvcsFreeLots()) @@ -48,18 +49,19 @@ class ExtEvDataTest extends Specification { actualReceivedEvcs == sentMsg.evcs() } - def "ExtEvData should request and receive current charging prices correctly"() { + def "ExtEvDataConnection should request and receive current charging prices correctly"() { given: def dataService = new TestProbe(actorSystem) def extSimAdapter = new TestProbe(actorSystem) - def extEvData = new ExtEvData(dataService.ref(), extSimAdapter.ref()) + def extEvDataConnection = new ExtEvDataConnection() + extEvDataConnection.setActorRefs(dataService.ref(), extSimAdapter.ref()) def sentMsg = new ProvideCurrentPrices() when: // we need to queue the msg beforehand because the receive method is blocking - extEvData.queueExtResponseMsg(sentMsg) - def actualReceivedPrices = extEvData.requestCurrentPrices() + extEvDataConnection.queueExtResponseMsg(sentMsg) + def actualReceivedPrices = extEvDataConnection.requestCurrentPrices() then: dataService.expectMsg(new RequestCurrentPrices()) @@ -67,11 +69,12 @@ class ExtEvDataTest extends Specification { actualReceivedPrices == sentMsg.prices() } - def "ExtEvData should request and receive departing EVs correctly"() { + def "ExtEvDataConnection should request and receive departing EVs correctly"() { given: def dataService = new TestProbe(actorSystem) def extSimAdapter = new TestProbe(actorSystem) - def extEvData = new ExtEvData(dataService.ref(), extSimAdapter.ref()) + def extEvDataConnection = new ExtEvDataConnection() + extEvDataConnection.setActorRefs(dataService.ref(), extSimAdapter.ref()) def requestedDepartingEvs = new HashMap>() requestedDepartingEvs.put(UUID.randomUUID(), new ArrayList()) @@ -79,8 +82,8 @@ class ExtEvDataTest extends Specification { when: // we need to queue the msg beforehand because the receive method is blocking - extEvData.queueExtResponseMsg(sentMsg) - def actualReceivedEvs = extEvData.requestDepartingEvs(requestedDepartingEvs) + extEvDataConnection.queueExtResponseMsg(sentMsg) + def actualReceivedEvs = extEvDataConnection.requestDepartingEvs(requestedDepartingEvs) then: dataService.expectMsg(new RequestDepartingEvs(requestedDepartingEvs)) @@ -88,35 +91,37 @@ class ExtEvDataTest extends Specification { actualReceivedEvs == sentMsg.departedEvs() } - def "ExtEvData should provide arriving EVs correctly"() { + def "ExtEvDataConnection should provide arriving EVs correctly"() { given: def dataService = new TestProbe(actorSystem) def extSimAdapter = new TestProbe(actorSystem) - def extEvData = new ExtEvData(dataService.ref(), extSimAdapter.ref()) + def extEvDataConnection = new ExtEvDataConnection() + extEvDataConnection.setActorRefs(dataService.ref(), extSimAdapter.ref()) def arrivingEvs = new HashMap>() arrivingEvs.put(UUID.randomUUID(), new ArrayList()) when: - extEvData.provideArrivingEvs(arrivingEvs, Optional.of(60L)) + extEvDataConnection.provideArrivingEvs(arrivingEvs, Optional.of(60L)) then: dataService.expectMsg(new ProvideArrivingEvs(arrivingEvs, Optional.of(60L))) extSimAdapter.expectMsg(new ScheduleDataServiceMessage(dataService.ref())) } - def "ExtEvData should fail if wrong response is sent"() { + def "ExtEvDataConnection should fail if wrong response is sent"() { given: def dataService = new TestProbe(actorSystem) def extSimAdapter = new TestProbe(actorSystem) - def extEvData = new ExtEvData(dataService.ref(), extSimAdapter.ref()) + def extEvDataConnection = new ExtEvDataConnection() + extEvDataConnection.setActorRefs(dataService.ref(), extSimAdapter.ref()) def unexpectedMsg = new ProvideCurrentPrices() when: // we need to queue the msg beforehand because the receive method is blocking - extEvData.queueExtResponseMsg(unexpectedMsg) - extEvData.requestAvailablePublicEvcs() + extEvDataConnection.queueExtResponseMsg(unexpectedMsg) + extEvDataConnection.requestAvailablePublicEvcs() then: dataService.expectMsg(new RequestEvcsFreeLots()) diff --git a/src/test/groovy/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnectionTest.groovy new file mode 100644 index 0000000..5aba77d --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataConnectionTest.groovy @@ -0,0 +1,93 @@ +package edu.ie3.simona.api.data.primarydata + +import edu.ie3.datamodel.models.value.Value +import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.api.data.primarydata.ontology.ProvidePrimaryData +import edu.ie3.simona.api.test.common.DataServiceTestData +import org.apache.pekko.actor.ActorSystem +import org.apache.pekko.testkit.TestProbe +import org.apache.pekko.testkit.javadsl.TestKit +import spock.lang.Shared +import spock.lang.Specification + +class ExtPrimaryDataConnectionTest extends Specification implements DataServiceTestData { + + @Shared + ActorSystem actorSystem + + @Shared + Map extPrimaryDataMapping = Map.of( + "Pv", + inputUuid + ) + + def setupSpec() { + actorSystem = ActorSystem.create() + } + + def cleanupSpec() { + TestKit.shutdownActorSystem(actorSystem) + actorSystem = null + } + + def "ExtPrimaryDataConnection should provide primary data correctly"() { + given: + def dataService = new TestProbe(actorSystem) + def extSimAdapter = new TestProbe(actorSystem) + def extPrimaryDataConnection = new ExtPrimaryDataConnection(extPrimaryDataMapping) + 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.expectMsg(new ProvidePrimaryData(0L, convertedPrimaryData, Optional.of(900L))) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(dataService.ref())) + } + + def "ExtPrimaryDataConnection should convert input data correctly"() { + given: + def dataService = new TestProbe(actorSystem) + def extSimAdapter = new TestProbe(actorSystem) + def extPrimaryDataConnection = new ExtPrimaryDataConnection(extPrimaryDataMapping) + extPrimaryDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + def inputDataMap = Map.of("Pv", pValue) + + when: + extPrimaryDataConnection.convertAndSend(0L, inputDataMap, Optional.of(900L), log) + + then: + dataService.expectMsg(new ProvidePrimaryData(0L, Map.of(inputUuid, pValue), Optional.of(900L))) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(dataService.ref())) + } + + def "ExtPrimaryDataConnection should send no message, if input data for a not requested asset was provided"() { + given: + def dataService = new TestProbe(actorSystem) + def extSimAdapter = new TestProbe(actorSystem) + def extPrimaryDataConnection = new ExtPrimaryDataConnection(extPrimaryDataMapping) + extPrimaryDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + def inputDataMap = Map.of("Load", pValue) + + when: + extPrimaryDataConnection.convertAndSend(0L, inputDataMap, Optional.empty(), log) + + then: + dataService.expectNoMessage() + } +} diff --git a/src/test/groovy/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataTest.groovy deleted file mode 100644 index 7163f0b..0000000 --- a/src/test/groovy/edu/ie3/simona/api/data/primarydata/ExtPrimaryDataTest.groovy +++ /dev/null @@ -1,62 +0,0 @@ -package edu.ie3.simona.api.data.primarydata - -import edu.ie3.datamodel.models.StandardUnits -import edu.ie3.datamodel.models.value.PValue -import edu.ie3.datamodel.models.value.Value -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.api.data.primarydata.ontology.ProvidePrimaryData -import edu.ie3.simona.api.exceptions.ConvertionException -import org.apache.pekko.actor.ActorSystem -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 - -class ExtPrimaryDataTest extends Specification { - - @Shared - ActorSystem actorSystem - - - class PValuePrimaryDataFactory implements PrimaryDataFactory { - - @Override - Value convert(Object entity) throws ConvertionException { - if (entity.getClass() == PValue) { - return (PValue) entity - } else { - throw new ConvertionException("This factory can convert PValue entities only!") - } - } - } - - def setupSpec() { - actorSystem = ActorSystem.create() - } - - def cleanupSpec() { - TestKit.shutdownActorSystem(actorSystem) - actorSystem = null - } - - def "ExtPrimaryData should provide primary data correctly"() { - given: - def dataService = new TestProbe(actorSystem) - def extSimAdapter = new TestProbe(actorSystem) - def extPrimaryData = new ExtPrimaryData(dataService.ref(), extSimAdapter.ref(), new PValuePrimaryDataFactory()) - - def primaryData = new HashMap() - def uuid = UUID.randomUUID() - primaryData.put(uuid.toString(), new PValue(Quantities.getQuantity(500.0, StandardUnits.ACTIVE_POWER_IN))) - - def convertedPrimaryData = Map.of(uuid, new PValue(Quantities.getQuantity(500.0, StandardUnits.ACTIVE_POWER_IN))) - - when: - extPrimaryData.providePrimaryData(0, primaryData) - - then: - dataService.expectMsg(new ProvidePrimaryData(0, convertedPrimaryData)) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(dataService.ref())) - } -} diff --git a/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultContainerTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultContainerTest.groovy new file mode 100644 index 0000000..b86f6ae --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultContainerTest.groovy @@ -0,0 +1,95 @@ +package edu.ie3.simona.api.data.results + +import edu.ie3.datamodel.models.StandardUnits +import edu.ie3.datamodel.models.result.NodeResult +import edu.ie3.simona.api.test.common.DataServiceTestData +import edu.ie3.util.quantities.PowerSystemUnits +import spock.lang.Shared +import spock.lang.Specification +import tech.units.indriya.quantity.Quantities + +import java.time.ZonedDateTime + +class ExtResultContainerTest extends Specification implements DataServiceTestData { + + @Shared + UUID nodeUuid = UUID.fromString("55b97041-64be-4e6b-983a-72dbde6eddf4") + + @Shared + NodeResult nodeResult = new NodeResult( + ZonedDateTime.parse("2020-01-30T17:26:44Z"), + nodeUuid, + Quantities.getQuantity(0.95, PowerSystemUnits.PU), + Quantities.getQuantity(45, StandardUnits.VOLTAGE_ANGLE) + ) + + def "ExtResultContainer should return voltage deviation correctly"() { + given: + def resultMap = Map.of( + "Node", nodeResult + ) + def extResultContainer = new ExtResultContainer(0L, resultMap) + + when: + def calculatedVoltageDeviation = extResultContainer.getVoltageDeviation("Node") + + then: + calculatedVoltageDeviation == -0.05d + } + + def "ExtResultContainer should throw an exception, if voltage deviation was requested for a not NodeResult"() { + given: + def resultMap = Map.of( + "Load", loadResult + ) + def extResultContainer = new ExtResultContainer(0L, resultMap) + + when: + extResultContainer.getVoltageDeviation("Load") + + then: + thrown IllegalArgumentException + } + + def "ExtResultContainer should return active power correctly"() { + given: + def resultMap = Map.of( + "Load", loadResult + ) + def extResultContainer = new ExtResultContainer(0L, resultMap) + + when: + def returnedActivePower = extResultContainer.getActivePower("Load") + + then: + returnedActivePower == 10d + } + + def "ExtResultContainer should return reactive power correctly"() { + given: + def resultMap = Map.of( + "Load", loadResult + ) + def extResultContainer = new ExtResultContainer(0L, resultMap) + + when: + def returnedReactivePower = extResultContainer.getReactivePower("Load") + + then: + returnedReactivePower == 5d + } + + def "ExtResultContainer should throw an exception, if active power was requested for a not SystemParticipantResult"() { + given: + def resultMap = Map.of( + "Node", nodeResult + ) + def extResultContainer = new ExtResultContainer(0L, resultMap) + + when: + extResultContainer.getActivePower("Node") + + then: + thrown IllegalArgumentException + } +} \ No newline at end of file diff --git a/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultDataConnectionTest.groovy new file mode 100644 index 0000000..7dc0dc2 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultDataConnectionTest.groovy @@ -0,0 +1,123 @@ +package edu.ie3.simona.api.data.results + +import edu.ie3.datamodel.models.StandardUnits +import edu.ie3.datamodel.models.result.connector.LineResult +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.test.common.DataServiceTestData +import org.apache.pekko.actor.ActorSystem +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 { + + @Shared + ActorSystem actorSystem + + @Shared + Map participantResultAssetMapping = Map.of(inputUuid, "Load") + + @Shared + Map gridResultAssetMapping = [:] + + class WrongResultDataResponseMessageToExt implements ResultDataResponseMessageToExt {} + + def setupSpec() { + actorSystem = ActorSystem.create() + } + + def cleanupSpec() { + TestKit.shutdownActorSystem(actorSystem) + actorSystem = null + } + + def "ExtResultsData should request and receive results correctly as ModelResultEntity"() { + given: + def dataService = new TestProbe(actorSystem) + def dataServiceActivation = new TestProbe(actorSystem) + def extSimAdapter = new TestProbe(actorSystem) + def extResultDataConnection = new ExtResultDataConnection(participantResultAssetMapping, gridResultAssetMapping) + extResultDataConnection.setActorRefs( + dataService.ref(), + dataServiceActivation.ref(), + extSimAdapter.ref() + ) + + def sentMsg = new ProvideResultEntities([loadResult]) + + when: + // we need to queue the msg beforehand because the receive method is blocking + extResultDataConnection.queueExtResponseMsg(sentMsg) + def receivedResults = extResultDataConnection.requestResults(0L) + + then: + dataService.expectMsg(new RequestResultEntities(0L)) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(dataServiceActivation.ref())) + receivedResults.get("Load") == loadResult + } + + def "ExtResultsData should fail if wrong response is sent"() { + given: + def dataService = new TestProbe(actorSystem) + def dataServiceActivation = new TestProbe(actorSystem) + def extSimAdapter = new TestProbe(actorSystem) + def extResultDataConnection = new ExtResultDataConnection(participantResultAssetMapping, gridResultAssetMapping) + extResultDataConnection.setActorRefs( + dataService.ref(), + dataServiceActivation.ref(), + extSimAdapter.ref() + ) + + def unexpectedMsg = new WrongResultDataResponseMessageToExt() + + when: + // we need to queue the msg beforehand because the receive method is blocking + extResultDataConnection.queueExtResponseMsg(unexpectedMsg) + extResultDataConnection.requestResults(0L) + + then: + dataService.expectMsg(new RequestResultEntities(0L)) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(dataServiceActivation.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) + + 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 + } +} \ No newline at end of file diff --git a/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultDataTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultDataTest.groovy deleted file mode 100644 index 5b0ee7c..0000000 --- a/src/test/groovy/edu/ie3/simona/api/data/results/ExtResultDataTest.groovy +++ /dev/null @@ -1,130 +0,0 @@ -package edu.ie3.simona.api.data.results - -import edu.ie3.datamodel.models.StandardUnits -import edu.ie3.datamodel.models.result.ResultEntity -import edu.ie3.datamodel.models.result.system.LoadResult -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.exceptions.ConvertionException -import org.apache.pekko.actor.ActorSystem -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 java.time.ZonedDateTime - -class ExtResultDataTest extends Specification { - - @Shared - ActorSystem actorSystem - - @Shared - UUID loadUuid = UUID.fromString("22bea5fc-2cb2-4c61-beb9-b476e0107f52") - - @Shared - LoadResult loadResult = new LoadResult( - ZonedDateTime.parse("2020-01-30T17:26:44Z[UTC]"), - loadUuid, - Quantities.getQuantity(10, StandardUnits.ACTIVE_POWER_IN), - Quantities.getQuantity(10, StandardUnits.REACTIVE_POWER_IN) - ) - - class DefaultResultFactory implements ResultDataFactory { - - @Override - Object convert(ResultEntity entity) throws ConvertionException { - if (entity instanceof LoadResult) { - return "{\"p\":\"" + entity.p.toString() + ",\"q\":\"" + entity.q.toString() + "\"}" - } else { - throw new ConvertionException("This factory can convert LoadResult's only!") - } - } - } - - class WrongResultDataResponseMessageToExt implements ResultDataResponseMessageToExt {} - - def setupSpec() { - actorSystem = ActorSystem.create() - } - - def cleanupSpec() { - TestKit.shutdownActorSystem(actorSystem) - actorSystem = null - } - - def "ExtResultsData should request and receive results correctly as Object"() { - given: - def dataService = new TestProbe(actorSystem) - def extSimAdapter = new TestProbe(actorSystem) - def resultDataFactory = new DefaultResultFactory() - def extResultData = new ExtResultData(dataService.ref(), extSimAdapter.ref(), resultDataFactory) - - def sentMsg = new ProvideResultEntities([loadResult]) - - when: - // we need to queue the msg beforehand because the receive method is blocking - extResultData.queueExtResponseMsg(sentMsg) - def receivedResults = extResultData.requestResultObjects() - - then: - dataService.expectMsg(new RequestResultEntities()) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(dataService.ref())) - receivedResults.get(loadUuid.toString()) == resultDataFactory.convert(loadResult) - } - - def "ExtResultsData should request and receive results correctly as a list of results entities"() { - given: - def dataService = new TestProbe(actorSystem) - def extSimAdapter = new TestProbe(actorSystem) - def extResultData = new ExtResultData(dataService.ref(), extSimAdapter.ref(), new DefaultResultFactory()) - - def sentMsg = new ProvideResultEntities([loadResult]) - - when: - // we need to queue the msg beforehand because the receive method is blocking - extResultData.queueExtResponseMsg(sentMsg) - def receivedResults = extResultData.requestResults() - - then: - dataService.expectMsg(new RequestResultEntities()) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(dataService.ref())) - receivedResults == sentMsg.results() - } - - def "ExtResultsData should fail if wrong response is sent"() { - given: - def dataService = new TestProbe(actorSystem) - def extSimAdapter = new TestProbe(actorSystem) - def extResultData = new ExtResultData(dataService.ref(), extSimAdapter.ref(), new DefaultResultFactory()) - - def unexpectedMsg = new WrongResultDataResponseMessageToExt() - - when: - // we need to queue the msg beforehand because the receive method is blocking - extResultData.queueExtResponseMsg(unexpectedMsg) - extResultData.requestResults() - - then: - dataService.expectMsg(new RequestResultEntities()) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(dataService.ref())) - thrown RuntimeException - } - - def "ExtResultData should convert a list of result entities correctly to a map of objects"() { - given: - def dataService = new TestProbe(actorSystem) - def extSimAdapter = new TestProbe(actorSystem) - def extResultData = new ExtResultData(dataService.ref(), extSimAdapter.ref(), new DefaultResultFactory()) - - when: - def mapOfResults = extResultData.convertResultsList([loadResult]) - - then: - mapOfResults.size() == 1 - mapOfResults.get(loadUuid.toString()) == "{\"p\":\"10 kW,\"q\":\"10 kvar\"}" - } -} diff --git a/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy b/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy new file mode 100644 index 0000000..2903384 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy @@ -0,0 +1,74 @@ +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 org.slf4j.Logger +import org.slf4j.LoggerFactory +import spock.lang.Shared +import spock.lang.Specification + +class ExtCoSimulationTest extends Specification { + + @Shared + private static final Logger log = LoggerFactory.getLogger(ExtCoSimulationTest) + + def "An ExtCoSimulation can build a primary data connection correctly"() { + 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), + ]) + + when: + def actual = ExtCoSimulation.buildPrimaryConnection(mapping, log) + + then: + actual.getPrimaryDataAssets() == [uuid3, uuid1] + } + + 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), + ]) + + when: + def actual = ExtCoSimulation.buildEmConnection(mapping, log) + + then: + actual.getControlledEms() == [uuid1, uuid2] + } + + 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), + ]) + + when: + def actual = ExtCoSimulation.buildResultConnection(mapping, log) + + then: + actual.getGridResultDataAssets() == [uuid1] + actual.getParticipantResultDataAssets() == [uuid2] + } +} 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 c1fd13b..029056f 100644 --- a/src/test/groovy/edu/ie3/simona/api/simulation/ExtSimulationSpec.groovy +++ b/src/test/groovy/edu/ie3/simona/api/simulation/ExtSimulationSpec.groovy @@ -3,7 +3,7 @@ package edu.ie3.simona.api.simulation import org.apache.pekko.actor.ActorSystem import org.apache.pekko.testkit.TestProbe import org.apache.pekko.testkit.javadsl.TestKit -import edu.ie3.simona.api.data.ExtData +import edu.ie3.simona.api.data.ExtDataConnection import edu.ie3.simona.api.simulation.ontology.ActivationMessage import edu.ie3.simona.api.simulation.ontology.CompletionMessage import edu.ie3.simona.api.simulation.ontology.ControlMessageToExt @@ -32,6 +32,7 @@ class ExtSimulationSpec extends Specification { private Optional activationReturnTick TestSimulation(Long initReturnTick, Optional activationReturnTick) { + super("TestSimulation") this.initReturnTick = initReturnTick this.activationReturnTick = activationReturnTick } @@ -45,6 +46,11 @@ class ExtSimulationSpec extends Specification { protected Optional doActivity(long tick) { return this.activationReturnTick } + + @Override + Set getDataConnections() { + return [] + } } def setupSpec() { @@ -62,86 +68,86 @@ class ExtSimulationSpec extends Specification { def "An ExtSimulation should handle initialization"() { given: - def tick = -1L - def newTick = 0L - def testProbe = new TestProbe(actorSystem) - def extSimData = new ExtSimAdapterData(testProbe.ref(), new String[0]) - def extSim = new TestSimulation(newTick, Optional.of(-2L)) - extSim.setup(extSimData, new ArrayList()) + def tick = -1L + def newTick = 0L + def extSimAdapter = new TestProbe(actorSystem) + def extSimData = new ExtSimAdapterData(extSimAdapter.ref(), new String[0]) + def extSim = new TestSimulation(newTick, Optional.of(-2L)) + extSim.setAdapterData(extSimData) when: - extSimData.queueExtMsg(new ActivationMessage(tick)) - def finishedActual = handleMessage.invoke(extSim) + extSimData.queueExtMsg(new ActivationMessage(tick)) + def finishedActual = handleMessage.invoke(extSim) then: - finishedActual == false - testProbe.expectMsg(new CompletionMessage(Optional.of(newTick))) + finishedActual == false + extSimAdapter.expectMsg(new CompletionMessage(Optional.of(newTick))) } def "An ExtSimulation should handle activation and return given new triggers"() { given: - def testProbe = new TestProbe(actorSystem) - def extSimData = new ExtSimAdapterData(testProbe.ref(), new String[0]) - def newTickOpt = newTick.isEmpty() ? + def extSimAdapter = new TestProbe(actorSystem) + def extSimData = new ExtSimAdapterData(extSimAdapter.ref(), new String[0]) + def newTickOpt = newTick.isEmpty() ? Optional.empty() : Optional.of(newTick.first()) - def extSim = new TestSimulation(-2L, newTickOpt) - extSim.setup(extSimData, new ArrayList()) + def extSim = new TestSimulation(-2L, newTickOpt) + extSim.setAdapterData(extSimData) when: - extSimData.queueExtMsg(new ActivationMessage(tick)) - def finishedActual = handleMessage.invoke(extSim) + extSimData.queueExtMsg(new ActivationMessage(tick)) + def finishedActual = handleMessage.invoke(extSim) then: - finishedActual == finished - testProbe.expectMsg(new CompletionMessage(newTickOpt)) + finishedActual == finished + extSimAdapter.expectMsg(new CompletionMessage(newTickOpt)) where: - tick | newTick || finished - 0L | [900L] || false - 3600L | [7200L] || false - 7200L | [] || true - 10800L | [] || true + tick | newTick || finished + 0L | [900L] || false + 3600L | [7200L] || false + 7200L | [] || true + 10800L | [] || true } def "An ExtSimulation should handle termination properly"() { given: - def testProbe = new TestProbe(actorSystem) - def extSimData = new ExtSimAdapterData(testProbe.ref(), new String[0]) - def extSim = new TestSimulation(-1L, Optional.empty()) - extSim.setup(extSimData, new ArrayList()) + def extSimAdapter = new TestProbe(actorSystem) + def extSimData = new ExtSimAdapterData(extSimAdapter.ref(), new String[0]) + def extSim = new TestSimulation(-1L, Optional.empty()) + extSim.setAdapterData(extSimData) when: - extSimData.queueExtMsg(new TerminationMessage(simlulationSuccessful)) - def finishedActual = handleMessage.invoke(extSim) + extSimData.queueExtMsg(new TerminationMessage(simlulationSuccessful)) + def finishedActual = handleMessage.invoke(extSim) then: - finishedActual == finished - testProbe.expectMsg(new TerminationCompleted()) + finishedActual == finished + extSimAdapter.expectMsg(new TerminationCompleted()) where: - simlulationSuccessful || finished - false || true - true || true + simlulationSuccessful || finished + false || true + true || true } class UnknownMessage implements ControlMessageToExt {} def "An ExtSimulation should handle unknown messages by throwing an exception"() { given: - def testProbe = new TestProbe(actorSystem) - def extSimData = new ExtSimAdapterData(testProbe.ref(), new String[0]) - def extSim = new TestSimulation(-1L, Optional.empty()) - extSim.setup(extSimData, new ArrayList()) + def extSimAdapter = new TestProbe(actorSystem) + def extSimData = new ExtSimAdapterData(extSimAdapter.ref(), new String[0]) + def extSim = new TestSimulation(-1L, Optional.empty()) + extSim.setAdapterData(extSimData) when: - extSimData.queueExtMsg(new UnknownMessage()) - handleMessage.invoke(extSim) + extSimData.queueExtMsg(new UnknownMessage()) + handleMessage.invoke(extSim) then: - Exception ex = thrown() - // since we call a private method through reflection, - // our expected exception is wrapped in an InvocationTargetException - ex.getCause().getClass() == IllegalArgumentException - testProbe.expectNoMessage() + Exception ex = thrown() + // since we call a private method through reflection, + // our expected exception is wrapped in an InvocationTargetException + ex.getCause().getClass() == IllegalArgumentException + extSimAdapter.expectNoMessage() } } 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 new file mode 100644 index 0000000..2589e21 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingSourceTest.groovy @@ -0,0 +1,36 @@ +package edu.ie3.simona.api.simulation.mapping + +import edu.ie3.datamodel.models.input.NodeInput +import spock.lang.Specification + +import java.nio.file.Path + +class ExtEntityMappingSourceTest extends Specification { + + def "An ExtEntityMappingSource can create a naming correctly"() { + given: + def naming = new ExtEntityMappingSource.ExtEntityNaming("ext_entity_mapping") + + when: + def ext_entity_naming = naming.getEntityName(ExtEntityEntry) + def other = naming.getEntityName(NodeInput) + + then: + ext_entity_naming == Optional.of("ext_entity_mapping") + other == Optional.of("node_input") + } + + def "An ExtEntityMappingSource can read a mapping from file correctly"() { + given: + Path filePath = Path.of(ExtEntityMappingSourceTest.getResource("ext_entity_mapping.csv").toURI()) + + when: + def actual = ExtEntityMappingSource.fromFile(filePath) + + then: + actual.getExtUuid2IdMapping(DataType.EXT_PRIMARY_INPUT).size() == 2 + actual.getExtUuid2IdMapping(DataType.EXT_EM_INPUT).size() == 0 + actual.getExtUuid2IdMapping(DataType.EXT_GRID_RESULT).size() == 2 + actual.getExtUuid2IdMapping(DataType.EXT_PARTICIPANT_RESULT).size() == 0 + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..9f49f61 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingTest.groovy @@ -0,0 +1,52 @@ +package edu.ie3.simona.api.simulation.mapping + +import edu.ie3.datamodel.io.naming.timeseries.ColumnScheme +import spock.lang.Shared +import spock.lang.Specification + +class ExtEntityMappingTest extends Specification { + @Shared + UUID loadUuid = UUID.fromString("22bea5fc-2cb2-4c61-beb9-b476e0107f52") + + @Shared + ExtEntityEntry extResultEntry = new ExtEntityEntry( + loadUuid, + "Load", + ColumnScheme.parse("p").get(), + DataType.EXT_PARTICIPANT_RESULT + ) + + @Shared + ExtEntityEntry extInputEntry = new ExtEntityEntry( + loadUuid, + "Load", + ColumnScheme.parse("p").get(), + DataType.EXT_PRIMARY_INPUT + ) + + def "ExtEntityMapping should return SIMONA uuid mapping correctly"() { + given: + def extAssetList = List.of(extResultEntry, extInputEntry) + def extEntryMapping = new ExtEntityMapping(extAssetList) + + when: + def inputMap = extEntryMapping.getExtId2UuidMapping(DataType.EXT_PRIMARY_INPUT) + + then: + inputMap.size() == 1 + inputMap.get("Load") == loadUuid + } + + def "ExtEntityMapping should return external id mapping correctly"() { + given: + def extAssetList = List.of(extResultEntry, extInputEntry) + def extEntryMapping = new ExtEntityMapping(extAssetList) + + when: + def inputMap = extEntryMapping.getExtUuid2IdMapping(DataType.EXT_PRIMARY_INPUT) + + then: + inputMap.size() == 1 + inputMap.get(loadUuid) == "Load" + } +} \ No newline at end of file diff --git a/src/test/groovy/edu/ie3/simona/api/test/common/DataServiceTestData.groovy b/src/test/groovy/edu/ie3/simona/api/test/common/DataServiceTestData.groovy new file mode 100644 index 0000000..fa8e537 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/test/common/DataServiceTestData.groovy @@ -0,0 +1,24 @@ +package edu.ie3.simona.api.test.common + +import edu.ie3.datamodel.models.StandardUnits +import edu.ie3.datamodel.models.result.system.LoadResult +import edu.ie3.datamodel.models.value.PValue +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import tech.units.indriya.quantity.Quantities + +import java.time.ZonedDateTime + +trait DataServiceTestData { + Logger log = LoggerFactory.getLogger(DataServiceTestData) + + UUID inputUuid = UUID.fromString("22bea5fc-2cb2-4c61-beb9-b476e0107f52") + PValue pValue = new PValue(Quantities.getQuantity(500.0, StandardUnits.ACTIVE_POWER_IN)) + + LoadResult loadResult = new LoadResult( + ZonedDateTime.parse("2020-01-30T17:26:44Z[UTC]"), + inputUuid, + Quantities.getQuantity(10, StandardUnits.ACTIVE_POWER_IN), + Quantities.getQuantity(5, StandardUnits.REACTIVE_POWER_IN) + ) +} \ No newline at end of file diff --git a/src/test/resources/edu/ie3/simona/api/simulation/mapping/ext_entity_mapping.csv b/src/test/resources/edu/ie3/simona/api/simulation/mapping/ext_entity_mapping.csv new file mode 100644 index 0000000..2163547 --- /dev/null +++ b/src/test/resources/edu/ie3/simona/api/simulation/mapping/ext_entity_mapping.csv @@ -0,0 +1,5 @@ +uuid,id,column_scheme,data_type +00d03670-7833-47ee-ad52-04d18d1c64fd,NS_Node_1,p,grid_result +dfae9806-9b44-4995-ba27-d66d8e4a43e0,NS_Node_2,p,grid_result +4dca3b1d-5d24-444a-b4df-f4fa23b9ef1b,LOAD_NS_Node_1,pq,primary_input +9c5991bc-24df-496b-b4ce-5ec27657454c,LOAD_NS_Node_2,pq,primary_input \ No newline at end of file diff --git a/version.properties b/version.properties index 4fd9773..142da6e 100644 --- a/version.properties +++ b/version.properties @@ -1,8 +1,8 @@ #Generated by the Semver Plugin for Gradle -#Thu Nov 30 15:04:34 CET 2023 +#Fri Aug 09 15:42:54 CEST 2024 version.buildmeta= version.major=0 -version.minor=5 +version.minor=6 version.patch=0 version.prerelease= -version.semver=0.5.0 +version.semver=0.6.0