diff --git a/CHANGELOG.md b/CHANGELOG.md index 52e41c8..5ebe65e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased/Snapshot] +### Added +- Added support for external em communication [#304](https://github.com/ie3-institute/simonaAPI/issues/304) + ### Changed - Removed Jenkinsfile [#290](https://github.com/ie3-institute/simonaAPI/issues/290) - Adapted dependabot workflow and added CODEOWNERS [#294](https://github.com/ie3-institute/simonaAPI/issues/294) diff --git a/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java index ba5d3fe..1bf1143 100644 --- a/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtEmDataConnection.java @@ -7,11 +7,14 @@ package edu.ie3.simona.api.data.connection; import edu.ie3.simona.api.data.model.em.EmSetPoint; -import edu.ie3.simona.api.ontology.em.EmDataMessageFromExt; -import edu.ie3.simona.api.ontology.em.EmDataResponseMessageToExt; -import edu.ie3.simona.api.ontology.em.ProvideEmSetPointData; +import edu.ie3.simona.api.data.model.em.ExtendedFlexOptionsResult; +import edu.ie3.simona.api.data.model.em.FlexOptionRequest; +import edu.ie3.simona.api.data.model.em.FlexOptions; +import edu.ie3.simona.api.ontology.em.*; import java.util.*; +import javax.measure.quantity.Time; import org.slf4j.Logger; +import tech.units.indriya.ComparableQuantity; /** Enables data connection of em data between SIMONA and SimonaAPI */ public final class ExtEmDataConnection @@ -22,11 +25,23 @@ public final class ExtEmDataConnection /** Assets that are controlled by external simulation */ private final List controlled; + private final Optional> maxDelay; + public ExtEmDataConnection(List controlled, EmMode mode) { super(); this.mode = mode; this.controlled = controlled; + this.maxDelay = Optional.empty(); + } + + public ExtEmDataConnection( + List controlled, EmMode mode, Optional> maxDelay) { + super(); + + this.mode = mode; + this.controlled = controlled; + this.maxDelay = maxDelay; } /** Returns a list of the uuids of the em agents that expect external set points */ @@ -34,11 +49,52 @@ public List getControlledEms() { return new ArrayList<>(controlled); } + /** Returns the maximal delay, that is allowed for a message when using the em communication. */ + public Optional> getMaxDelay() { + return maxDelay; + } + + /** + * Sends the em flex requests to SIMONA. + * + * @param tick current tick + * @param data receiver to flex request, that should be sent to SIMONA + * @param maybeNextTick option for the next tick in the simulation + * @param log logger + */ + public void sendFlexRequests( + long tick, Map data, Optional maybeNextTick, Logger log) { + if (data.isEmpty()) { + log.debug("No em flex requests found! Sending no em data to SIMONA for tick {}.", tick); + } else { + log.debug("Provided SIMONA with em flex requests."); + sendExtMsg(new ProvideFlexRequestData(tick, data, maybeNextTick)); + } + } + + /** + * Sends the em flex options to SIMONA. + * + * @param tick current tick + * @param data receiver to flex options, that should be sent to SIMONA + * @param maybeNextTick option for the next tick in the simulation + * @param log logger + */ + public void sendFlexOptions( + long tick, Map> data, Optional maybeNextTick, Logger log) { + if (data.isEmpty()) { + log.debug("No em flex options found! Sending no em data to SIMONA for tick {}.", tick); + } else { + log.debug("Provided SIMONA with em flex options."); + sendExtMsg(new ProvideEmFlexOptionData(tick, data, maybeNextTick)); + } + } + /** * Sends the em set points to SIMONA. * * @param tick current tick - * @param setPoints to be sent + * @param setPoints receiver to set point, that should be sent to SIMONA * @param maybeNextTick option for the next tick in the simulation * @param log logger */ @@ -52,6 +108,31 @@ public void sendSetPoints( } } + /** + * Method to request em flexibility options from SIMONA. + * + * @param tick for which set points are requested + * @param emEntities for which set points are requested + * @return an {@link FlexOptionsResponse} message + * @throws InterruptedException - on interruptions + */ + public Map requestEmFlexResults( + long tick, List emEntities, boolean disaggregated) throws InterruptedException { + sendExtMsg(new RequestEmFlexResults(tick, emEntities, disaggregated)); + return receiveWithType(FlexOptionsResponse.class).receiverToFlexOptions(); + } + + /** + * Method to request the completion of the em service in SIMONA for the given tick. + * + * @param tick for which the em service should stop + * @return an option for the next tick in SIMONA + */ + public Optional requestCompletion(long tick) throws InterruptedException { + sendExtMsg(new RequestEmCompletion(tick)); + return receiveWithType(EmCompletion.class).maybeNextTick(); + } + /** Mode of the em connection */ public enum EmMode { BASE, diff --git a/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java b/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java index e75a978..23f4425 100644 --- a/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java +++ b/src/main/java/edu/ie3/simona/api/data/connection/ExtPrimaryDataConnection.java @@ -19,9 +19,9 @@ public final class ExtPrimaryDataConnection extends ExtInputDataConnection { - private final Map> valueClasses; + private final Map> valueClasses; - public ExtPrimaryDataConnection(Map> valueClasses) { + public ExtPrimaryDataConnection(Map> valueClasses) { this.valueClasses = valueClasses; } @@ -34,7 +34,7 @@ public List getPrimaryDataAssets() { * @param uuid of the model * @return an option for the value class associated with the model. */ - public Optional> getValueClass(UUID uuid) { + public Optional> getValueClass(UUID uuid) { return Optional.ofNullable(valueClasses.get(uuid)); } diff --git a/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java b/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java index c58f822..1f8f8d2 100644 --- a/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java +++ b/src/main/java/edu/ie3/simona/api/data/container/ExtInputContainer.java @@ -92,6 +92,10 @@ public void addRequest(UUID receiver, Optional sender) { flexRequests.put(receiver, new FlexOptionRequest(receiver, sender)); } + public void addRequest(UUID receiver, FlexOptionRequest request) { + flexRequests.put(receiver, request); + } + /** * Method for adding flex options to a given receiver. * diff --git a/src/main/java/edu/ie3/simona/api/data/container/ExtResultContainer.java b/src/main/java/edu/ie3/simona/api/data/container/ExtResultContainer.java index 6f0077e..9ef5df3 100644 --- a/src/main/java/edu/ie3/simona/api/data/container/ExtResultContainer.java +++ b/src/main/java/edu/ie3/simona/api/data/container/ExtResultContainer.java @@ -6,20 +6,11 @@ package edu.ie3.simona.api.data.container; -import static edu.ie3.util.quantities.PowerSystemUnits.PU; - -import edu.ie3.datamodel.models.result.NodeResult; import edu.ie3.datamodel.models.result.ResultEntity; -import edu.ie3.datamodel.models.result.connector.LineResult; -import edu.ie3.datamodel.models.result.system.SystemParticipantResult; -import edu.ie3.util.quantities.PowerSystemUnits; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.UUID; -import javax.measure.quantity.Dimensionless; -import tech.units.indriya.ComparableQuantity; -import tech.units.indriya.quantity.Quantities; /** Contains all SIMONA results for a certain tick. */ public final class ExtResultContainer implements ExtDataContainer { @@ -103,61 +94,4 @@ public Optional getMaybeNextTick() { public ResultEntity getResult(UUID assetId) { return resultMap.get(assetId); } - - /** - * Returns the voltage deviation in pu for a certain asset, if this asset provided a {@link - * NodeResult} - */ - public double getVoltageDeviation(UUID assetId) { - if (resultMap.get(assetId) instanceof NodeResult nodeResult) { - ComparableQuantity vMagDev = - Quantities.getQuantity(-1.0, PU).add(nodeResult.getvMag()); - return vMagDev.getValue().doubleValue(); - } else { - throw new IllegalArgumentException( - "Voltage deviation is only available for NodeResult's! AssetId: " + assetId); - } - } - - /** - * Returns the voltage deviation for certain asset, if this asset provided a {@link NodeResult} - */ - public double getVoltage(UUID assetId) { - if (resultMap.get(assetId) instanceof NodeResult nodeResult) { - return nodeResult.getvMag().getValue().doubleValue(); - } else { - throw new IllegalArgumentException("Voltage is only available for NodeResult's!"); - } - } - - /** - * Returns the active power in MW for certain asset, if this asset provided a {@link - * SystemParticipantResult} - */ - public double getActivePower(UUID assetId) { - if (resultMap.get(assetId) instanceof SystemParticipantResult systemParticipantResult) { - return systemParticipantResult.getP().to(PowerSystemUnits.MEGAWATT).getValue().doubleValue(); - } else { - throw new IllegalArgumentException( - "Active power is only available for SystemParticipantResult's!"); - } - } - - /** - * Returns the reactive power in MVAr for certain asset, if this asset provided a {@link - * SystemParticipantResult} - */ - public double getReactivePower(UUID assetId) { - if (resultMap.get(assetId) instanceof SystemParticipantResult systemParticipantResult) { - return systemParticipantResult.getQ().to(PowerSystemUnits.MEGAVAR).getValue().doubleValue(); - } else { - throw new IllegalArgumentException( - "Reactive power is only available for SystemParticipantResult's!"); - } - } - - /** Returns the line loading for certain asset, if this asset provided a {@link LineResult} */ - public double getLineLoading(UUID assetId) { - throw new IllegalArgumentException("Line loading is not implemented yet!"); - } } diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/EmMessageBase.java b/src/main/java/edu/ie3/simona/api/data/model/em/EmMessageBase.java new file mode 100644 index 0000000..d8156d7 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/model/em/EmMessageBase.java @@ -0,0 +1,66 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.model.em; + +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import javax.measure.quantity.Time; +import tech.units.indriya.ComparableQuantity; + +/** Base class for messages used during communication. */ +public abstract sealed class EmMessageBase permits EmSetPoint, FlexOptionRequest, FlexOptions { + + /** The receiver of the message. */ + public final UUID receiver; + + /** An option for the delay of this message. */ + public final Optional> delay; + + /** + * Base constructor without {@link #delay}. + * + * @param receiver of the message + */ + protected EmMessageBase(UUID receiver) { + this.receiver = receiver; + this.delay = Optional.empty(); + } + + /** + * Base constructor with {@link #delay}. + * + * @param receiver of this message + * @param delay of this message + */ + protected EmMessageBase(UUID receiver, Optional> delay) { + this.receiver = receiver; + this.delay = delay; + } + + /** Returns {@code true}, if there is a delay. */ + public boolean hasDelay() { + return delay.isPresent(); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + EmMessageBase that = (EmMessageBase) o; + return Objects.equals(receiver, that.receiver) && Objects.equals(delay, that.delay); + } + + @Override + public int hashCode() { + return Objects.hash(receiver, delay); + } + + @Override + public String toString() { + return "EmMessageBase{" + "receiver=" + receiver + ", delay=" + delay + '}'; + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java b/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java index 1093dda..7314e6f 100644 --- a/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java +++ b/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPoint.java @@ -11,42 +11,77 @@ import java.util.Optional; import java.util.UUID; import javax.measure.quantity.Power; +import javax.measure.quantity.Time; import tech.units.indriya.ComparableQuantity; /** Energy management set point that will be sent to SIMONA. */ -public final class EmSetPoint { - public final UUID receiver; +public final class EmSetPoint extends EmMessageBase { + + /** An option for the em set point. */ public final Optional power; + /** + * Constructor for {@link EmSetPoint}. + * + *

Note: Using this constructor will signal SIMONA, that the current set point should be kept. + * + * @param receiver of the set point. + */ public EmSetPoint(UUID receiver) { - this.receiver = receiver; + super(receiver); this.power = Optional.empty(); } + /** + * Constructor for {@link EmSetPoint}. + * + * @param receiver of the set point. + * @param p power value of the set point + */ public EmSetPoint(UUID receiver, ComparableQuantity p) { - this.receiver = receiver; + super(receiver); this.power = Optional.of(new PValue(p)); } + /** + * Constructor for {@link EmSetPoint}. + * + * @param receiver of the set point. + * @param power value of the set point + */ public EmSetPoint(UUID receiver, PValue power) { - this.receiver = receiver; + super(receiver); this.power = Optional.of(power); } + /** + * Constructor for {@link EmSetPoint}. + * + * @param receiver of the set point. + * @param power option for the set point + * @param delay option for the delay of this message + */ + public EmSetPoint( + UUID receiver, Optional power, Optional> delay) { + super(receiver, delay); + this.power = power; + } + @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; EmSetPoint that = (EmSetPoint) o; - return Objects.equals(receiver, that.receiver) && Objects.equals(power, that.power); + return Objects.equals(power, that.power); } @Override public int hashCode() { - return Objects.hash(receiver, power); + return Objects.hash(super.hashCode(), power); } @Override public String toString() { - return "EmSetPoint{" + "receiver=" + receiver + ", power=" + power + '}'; + return "EmSetPoint{" + "receiver=" + receiver + ", power=" + power + ", delay=" + delay + '}'; } } diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPointResult.java b/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPointResult.java new file mode 100644 index 0000000..3061134 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/model/em/EmSetPointResult.java @@ -0,0 +1,67 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.model.em; + +import edu.ie3.datamodel.models.result.ResultEntity; +import edu.ie3.datamodel.models.value.PValue; +import java.time.ZonedDateTime; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +/** Em set point result. */ +public final class EmSetPointResult extends ResultEntity { + + private final Map receiverToSetPoints; + + /** + * Basic constructor of an em set point result. + * + * @param time date and time when the result is produced + * @param sender uuid of the sending model + * @param receiverToSetPoints map: uuid to set point + */ + public EmSetPointResult(ZonedDateTime time, UUID sender, Map receiverToSetPoints) { + super(time, sender); + this.receiverToSetPoints = receiverToSetPoints; + } + + /** Returns the sender of the results. */ + public UUID getSender() { + return getInputModel(); + } + + /** Returns the mapped (receiver to set point) set point. */ + public Map getReceiverToSetPoint() { + return receiverToSetPoints; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EmSetPointResult that = (EmSetPointResult) o; + return Objects.equals(receiverToSetPoints, that.receiverToSetPoints); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), receiverToSetPoints); + } + + @Override + public String toString() { + return "EmSetPointResult{" + + "time=" + + getTime() + + ", sender=" + + getInputModel() + + ", receiverToSetPoints=" + + receiverToSetPoints + + '}'; + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/ExtendedFlexOptionsResult.java b/src/main/java/edu/ie3/simona/api/data/model/em/ExtendedFlexOptionsResult.java new file mode 100644 index 0000000..af9f438 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/model/em/ExtendedFlexOptionsResult.java @@ -0,0 +1,207 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.model.em; + +import edu.ie3.datamodel.models.result.system.FlexOptionsResult; +import java.time.ZonedDateTime; +import java.util.*; +import javax.measure.quantity.Power; +import org.slf4j.Logger; +import tech.units.indriya.ComparableQuantity; + +/** + * Extended {@link FlexOptionsResult}, that contains the receiver of the flex options. This models + * may also contain a disaggregation of the total flex options. + */ +public final class ExtendedFlexOptionsResult extends FlexOptionsResult { + + /** The receiver of the message. */ + private final UUID receiver; + + /** The disaggregated flex option results. */ + private final Map disaggregated; + + /** + * Standard constructor for {@link ExtendedFlexOptionsResult}. + * + * @param time date and time when the result is produced + * @param sender uuid of the input model that produces the result + * @param receiver uuid of the receiver that will receive this result + * @param pRef active power that was suggested for regular usage by the system participant + * @param pMin active minimal power that was determined by the system participant + * @param pMax active maximum power that was determined by the system participant + */ + public ExtendedFlexOptionsResult( + ZonedDateTime time, + UUID sender, + UUID receiver, + ComparableQuantity pRef, + ComparableQuantity pMin, + ComparableQuantity pMax) { + super(time, sender, pRef, pMin, pMax); + this.receiver = receiver; + this.disaggregated = new HashMap<>(); + } + + /** + * Constructor for {@link ExtendedFlexOptionsResult} with disaggregated flex options. + * + * @param time date and time when the result is produced + * @param sender uuid of the input model that produces the result + * @param receiver uuid of the receiver that will receive this result + * @param pRef active power that was suggested for regular usage by the system participant + * @param pMin active minimal power that was determined by the system participant + * @param pMax active maximum power that was determined by the system participant + */ + public ExtendedFlexOptionsResult( + ZonedDateTime time, + UUID sender, + UUID receiver, + ComparableQuantity pRef, + ComparableQuantity pMin, + ComparableQuantity pMax, + Map disaggregated) { + super(time, sender, pRef, pMin, pMax); + this.receiver = receiver; + this.disaggregated = disaggregated; + } + + /** + * Method for adding disaggregated flex option results to this object. + * + *

Note: This method does not check, if the disaggregated flex options match the total flex + * options. To do this, please use the method {@link #checkFlexOptions(Logger)}. + * + * @param uuid of the inferior model + * @param flexOptionsResult the flex options of the inferior model + */ + public void addDisaggregated(UUID uuid, FlexOptionsResult flexOptionsResult) { + this.disaggregated.put(uuid, flexOptionsResult); + } + + /** Returns the uuid of the sender ({@link #getInputModel()}) of the results. */ + public UUID getSender() { + return getInputModel(); + } + + /** Returns the uuid of the receiver. */ + public UUID getReceiver() { + return receiver; + } + + /** Returns {@code true}, if disaggregated flex option are available. */ + public boolean hasDisaggregated() { + return !disaggregated.isEmpty(); + } + + /** + * Returns a map: uuid to disaggregated flex options. + * + *

Note: If no disaggregated flex options are present (see: {@link #hasDisaggregated()}), the + * map will be empty. + */ + public Map getDisaggregated() { + return Collections.unmodifiableMap(disaggregated); + } + + /** + * Method for checking if the disaggregated flex options match the total flex options. + * + * @param log used for logging + * @return {@code true} if the flex options match, else {@code false} + */ + public boolean checkFlexOptions(Logger log) { + List> refs = new ArrayList<>(); + List> mins = new ArrayList<>(); + List> maxs = new ArrayList<>(); + + disaggregated.forEach( + (uuid, flexOptionsResult) -> { + refs.add(flexOptionsResult.getpRef()); + mins.add(flexOptionsResult.getpMin()); + maxs.add(flexOptionsResult.getpMax()); + }); + + ComparableQuantity ref = getpRef(); + ComparableQuantity min = getpMin(); + ComparableQuantity max = getpMax(); + + Optional> refSum = refs.stream().reduce(ComparableQuantity::add); + Optional> minSum = mins.stream().reduce(ComparableQuantity::add); + Optional> maxSum = maxs.stream().reduce(ComparableQuantity::add); + + boolean isRefValid = false; + boolean isMinValid = false; + boolean isMaxValid = false; + + if (refSum.isPresent()) { + isRefValid = refSum.get().isEquivalentTo(ref); + + if (!isRefValid) { + log.warn("Disaggregated reference power does not match total reference power."); + } + } else { + log.warn("Cannot check disaggregated reference power."); + } + + if (minSum.isPresent()) { + isMinValid = minSum.get().isEquivalentTo(min); + + if (!isMinValid) { + log.warn("Disaggregated minimum power does not match total minimum power."); + } + } else { + log.warn("Cannot check disaggregated minimum power."); + } + + if (maxSum.isPresent()) { + isMaxValid = maxSum.get().isEquivalentTo(max); + + if (!isMaxValid) { + log.warn("Disaggregated maximum power does not match total maximum power."); + } + } else { + log.warn("Cannot check disaggregated maximum power."); + } + + return isRefValid && isMinValid && isMaxValid; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtendedFlexOptionsResult that = (ExtendedFlexOptionsResult) o; + return Objects.equals(receiver, that.receiver) + && Objects.equals(disaggregated, that.disaggregated); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), receiver, disaggregated); + } + + @Override + public String toString() { + return "ExtendedFlexOptionsResult{" + + "time=" + + getTime() + + ", sender=" + + getSender() + + ", receiver=" + + receiver + + ", pRef=" + + getpRef() + + ", pMin=" + + getpMin() + + ", pMax=" + + getpMax() + + ", disaggregated=" + + disaggregated + + '}'; + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java index 492838d..f1b1e30 100644 --- a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java +++ b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequest.java @@ -6,13 +6,97 @@ package edu.ie3.simona.api.data.model.em; +import java.util.Objects; import java.util.Optional; import java.util.UUID; +import javax.measure.quantity.Time; +import tech.units.indriya.ComparableQuantity; -/** - * Flex option request that will be sent to SIMONA. - * - * @param receiver uuid of the agent, that will receive the request - * @param sender option for the uuid of the agent, that sent the request - */ -public record FlexOptionRequest(UUID receiver, Optional sender) {} +/** Energy management flex option request that will be sent to SIMONA. */ +public final class FlexOptionRequest extends EmMessageBase { + + /** The sender of the request. */ + public final Optional sender; + + /** + * Constructor for {@link FlexOptionRequest}. Equals {@code new FlexOptionRequest(receiver, + * Optional.empty())}. + * + * @param receiver of the request + */ + public FlexOptionRequest(UUID receiver) { + this(receiver, Optional.empty()); + } + + /** + * Constructor for {@link FlexOptionRequest}. + * + * @param receiver of the request + * @param sender of the request + */ + public FlexOptionRequest(UUID receiver, UUID sender) { + super(receiver); + this.sender = Optional.ofNullable(sender); + } + + /** + * Constructor for {@link FlexOptionRequest}. + * + * @param receiver of the request + * @param sender of the request + */ + public FlexOptionRequest(UUID receiver, Optional sender) { + super(receiver); + this.sender = sender; + } + + /** + * Constructor for {@link FlexOptionRequest}. + * + * @param receiver of the request + * @param sender of the request + * @param delay option for the delay of this message + */ + public FlexOptionRequest(UUID receiver, UUID sender, Optional> delay) { + super(receiver, delay); + this.sender = Optional.ofNullable(sender); + } + + /** + * Constructor for {@link FlexOptionRequest}. + * + * @param receiver of the request + * @param sender option for the sender of the request + * @param delay option for the delay of this message + */ + public FlexOptionRequest( + UUID receiver, Optional sender, Optional> delay) { + super(receiver, delay); + this.sender = sender; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + FlexOptionRequest that = (FlexOptionRequest) o; + return Objects.equals(sender, that.sender); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), delay); + } + + @Override + public String toString() { + return "FlexOptionRequest{" + + "receiver=" + + receiver + + ", sender=" + + sender + + ", delay=" + + delay + + '}'; + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequestResult.java b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequestResult.java new file mode 100644 index 0000000..d0a7068 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptionRequestResult.java @@ -0,0 +1,68 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.data.model.em; + +import edu.ie3.datamodel.models.result.ResultEntity; +import java.time.ZonedDateTime; +import java.util.*; + +/** Em flex request result. */ +public final class FlexOptionRequestResult extends ResultEntity { + + /** The uuids of the receivers. */ + private final List receivers; + + /** + * Constructor of a {@link FlexOptionRequest}. + * + * @param time date and time when the result is produced + * @param sender uuid of the input model that produces the result + * @param receivers a collection of receivers + */ + public FlexOptionRequestResult(ZonedDateTime time, UUID sender, Collection receivers) { + super(time, sender); + this.receivers = new ArrayList<>(receivers); + } + + /** Returns the uuid of the sender. */ + public UUID getSender() { + return getInputModel(); + } + + /** + * Returns the list of the uuids of all receivers, that should receive a request to provide flex + * options. + */ + public List getReceivers() { + return receivers; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FlexOptionRequestResult that = (FlexOptionRequestResult) o; + return Objects.equals(receivers, that.receivers); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), receivers); + } + + @Override + public String toString() { + return "FlexRequestResult{" + + "time=" + + getTime() + + ", inputModel=" + + getInputModel() + + ", receivers=" + + receivers + + '}'; + } +} diff --git a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java index c10b648..ce04bfa 100644 --- a/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java +++ b/src/main/java/edu/ie3/simona/api/data/model/em/FlexOptions.java @@ -6,22 +6,113 @@ package edu.ie3.simona.api.data.model.em; +import java.util.Objects; +import java.util.Optional; import java.util.UUID; import javax.measure.quantity.Power; +import javax.measure.quantity.Time; import tech.units.indriya.ComparableQuantity; -/** - * Flex option that will be sent to SIMONA. - * - * @param receiver uuid of the flex options - * @param sender uuid of the flex options - * @param pMin minimal active power - * @param pRef current active power - * @param pMax maximal active power - */ -public record FlexOptions( - UUID receiver, - UUID sender, - ComparableQuantity pMin, - ComparableQuantity pRef, - ComparableQuantity pMax) {} +/** Flex option that will be sent to SIMONA. */ +public final class FlexOptions extends EmMessageBase { + + /** The sender of the request. */ + public final UUID sender; + + /** Active power (might be negative, thus feed-in) that was suggested for regular usage. */ + public final ComparableQuantity pRef; + + /** + * Minimal active power to which the sender can be reduced (might be negative, thus feed-in), that + * was determined by the system. Therefore equates to lower bound of possible flexibility + * provision. + */ + public final ComparableQuantity pMin; + + /** + * Maximum active power to which the sender can be increased (might be negative, thus feed-in), + * that was determined by the system. Therefore equates to upper bound of possible flexibility + * provision. + */ + public final ComparableQuantity pMax; + + /** + * Flex option that will be sent to SIMONA. + * + * @param receiver uuid of the flex options + * @param sender uuid of the flex options + * @param pRef current active power + * @param pMin minimal active power + * @param pMax maximal active power + */ + public FlexOptions( + UUID receiver, + UUID sender, + ComparableQuantity pRef, + ComparableQuantity pMin, + ComparableQuantity pMax) { + super(receiver); + this.sender = sender; + this.pRef = pRef; + this.pMin = pMin; + this.pMax = pMax; + } + + /** + * Flex option that will be sent to SIMONA. + * + * @param receiver uuid of the flex options + * @param sender uuid of the flex options + * @param pRef current active power + * @param pMin minimal active power + * @param pMax maximal active power + * @param delay option for the delay of the message + */ + public FlexOptions( + UUID receiver, + UUID sender, + ComparableQuantity pRef, + ComparableQuantity pMin, + ComparableQuantity pMax, + Optional> delay) { + super(receiver, delay); + this.sender = sender; + this.pRef = pRef; + this.pMin = pMin; + this.pMax = pMax; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + FlexOptions that = (FlexOptions) o; + return Objects.equals(sender, that.sender) + && Objects.equals(pRef, that.pRef) + && Objects.equals(pMin, that.pMin) + && Objects.equals(pMax, that.pMax); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), sender, pRef, pMin, pMax); + } + + @Override + public String toString() { + return "FlexOptions{" + + "receiver=" + + receiver + + ", sender=" + + sender + + ", pRef=" + + pRef + + ", pMin=" + + pMin + + ", pMax=" + + pMax + + ", delay=" + + delay + + '}'; + } +} diff --git a/src/main/java/edu/ie3/simona/api/mapping/DataType.java b/src/main/java/edu/ie3/simona/api/mapping/DataType.java index ea6d2d4..45f1cbb 100644 --- a/src/main/java/edu/ie3/simona/api/mapping/DataType.java +++ b/src/main/java/edu/ie3/simona/api/mapping/DataType.java @@ -11,6 +11,7 @@ public enum DataType { EXT_PRIMARY_INPUT("primary_input"), EXT_EM_INPUT("em_input"), + EXT_EM_COMMUNICATION("em_communication"), EXT_GRID_RESULT("grid_result"), EXT_PARTICIPANT_RESULT("participant_result"), EXT_FLEX_OPTIONS_RESULT("flex_options_result"); @@ -25,6 +26,7 @@ public static DataType parse(String type) throws ParsingException { return switch (type) { case "primary_input" -> EXT_PRIMARY_INPUT; case "em_input" -> EXT_EM_INPUT; + case "em_communication" -> EXT_EM_COMMUNICATION; case "grid_result" -> EXT_GRID_RESULT; case "participant_result" -> EXT_PARTICIPANT_RESULT; case "flex_options_result" -> EXT_FLEX_OPTIONS_RESULT; diff --git a/src/main/java/edu/ie3/simona/api/mapping/ExtEntityMapping.java b/src/main/java/edu/ie3/simona/api/mapping/ExtEntityMapping.java new file mode 100644 index 0000000..301dd99 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/mapping/ExtEntityMapping.java @@ -0,0 +1,104 @@ +/* + * © 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.mapping; + +import edu.ie3.simona.api.simulation.mapping.ExtEntityEntry; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** 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)); + } + + /** Returns the data types of this mapping. */ + public Set getDataTypes() { + return extEntities.keySet(); + } + + /** + * Method for getting the external entity entries for a specific data type. + * + * @param dataType for which entries should be returned + * @return a list containing all entries or an empty list + */ + public List getEntries(DataType dataType) { + return extEntities.getOrDefault(dataType, Collections.emptyList()); + } + + /** + * Returns the full mapping external id to SIMONA uuid. Equals {@code + * getExtId2UuidMapping(DataType.values())}. + */ + public Map getExtId2UuidMapping() { + return extEntities.values().stream() + .flatMap(Collection::stream) + .collect(Collectors.toMap(ExtEntityEntry::id, ExtEntityEntry::uuid)); + } + + /** + * Returns the full mapping SIMONA uuid to external id. Equals {@code + * getExtUuid2IdMapping(DataType.values())}. + */ + public Map getExtUuid2IdMapping() { + return extEntities.values().stream() + .flatMap(Collection::stream) + .collect(Collectors.toMap(ExtEntityEntry::uuid, ExtEntityEntry::id)); + } + + /** + * 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 external id to SIMONA uuid. + * + * @param dataTypes the external asset expects + * @return mapping external id to SIMONA uuid + */ + public Map getExtId2UuidMapping(DataType... dataTypes) { + return Stream.of(dataTypes) + .flatMap(type -> extEntities.getOrDefault(type, 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)); + } + + /** + * Mapping SIMONA uuid to external id. + * + * @param dataTypes data types the external asset expects + * @return mapping SIMONA uuid to external id + */ + public Map getExtUuid2IdMapping(DataType... dataTypes) { + return Stream.of(dataTypes) + .flatMap(type -> extEntities.getOrDefault(type, Collections.emptyList()).stream()) + .collect(Collectors.toMap(ExtEntityEntry::uuid, ExtEntityEntry::id)); + } +} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/EmCompletion.java b/src/main/java/edu/ie3/simona/api/ontology/em/EmCompletion.java new file mode 100644 index 0000000..12bc170 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/EmCompletion.java @@ -0,0 +1,16 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.ontology.em; + +import java.util.Optional; + +/** + * Response send from SIMONA after the em service is finished. + * + * @param maybeNextTick option for the next tick in SIMONA + */ +public record EmCompletion(Optional maybeNextTick) implements EmDataResponseMessageToExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/EmSetPointDataResponse.java b/src/main/java/edu/ie3/simona/api/ontology/em/EmSetPointDataResponse.java new file mode 100644 index 0000000..c7a6c6e --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/EmSetPointDataResponse.java @@ -0,0 +1,15 @@ +/* + * © 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.ontology.em; + +import edu.ie3.simona.api.data.model.em.EmSetPointResult; +import java.util.Map; +import java.util.UUID; + +/** Message that provides em data (set points) to an external simulation. */ +public record EmSetPointDataResponse(Map emData) + implements EmDataResponseMessageToExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/FlexOptionsResponse.java b/src/main/java/edu/ie3/simona/api/ontology/em/FlexOptionsResponse.java new file mode 100644 index 0000000..4985243 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/FlexOptionsResponse.java @@ -0,0 +1,15 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.ontology.em; + +import edu.ie3.simona.api.data.model.em.ExtendedFlexOptionsResult; +import java.util.Map; +import java.util.UUID; + +/** Message that provides em data (flexibility options) to an external simulation. */ +public record FlexOptionsResponse(Map receiverToFlexOptions) + implements EmDataResponseMessageToExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/FlexRequestResponse.java b/src/main/java/edu/ie3/simona/api/ontology/em/FlexRequestResponse.java new file mode 100644 index 0000000..a2739e0 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/FlexRequestResponse.java @@ -0,0 +1,15 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.ontology.em; + +import edu.ie3.simona.api.data.model.em.FlexOptionRequestResult; +import java.util.Map; +import java.util.UUID; + +/** Message that provides em data (flexibility requests) to an external simulation. */ +public record FlexRequestResponse(Map flexRequests) + implements EmDataResponseMessageToExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/ProvideEmFlexOptionData.java b/src/main/java/edu/ie3/simona/api/ontology/em/ProvideEmFlexOptionData.java new file mode 100644 index 0000000..3d8d0e5 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/ProvideEmFlexOptionData.java @@ -0,0 +1,18 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.ontology.em; + +import edu.ie3.simona.api.data.model.em.FlexOptions; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** Message that provides em data (flexibility options) from an external simulation. */ +public record ProvideEmFlexOptionData( + long tick, Map> flexOptions, Optional maybeNextTick) + implements EmDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/ProvideFlexRequestData.java b/src/main/java/edu/ie3/simona/api/ontology/em/ProvideFlexRequestData.java new file mode 100644 index 0000000..4013e74 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/ProvideFlexRequestData.java @@ -0,0 +1,17 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.ontology.em; + +import edu.ie3.simona.api.data.model.em.FlexOptionRequest; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** Message that provides em data (flex requests) from an external simulation. */ +public record ProvideFlexRequestData( + long tick, Map flexRequests, Optional maybeNextTick) + implements EmDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/RequestEmCompletion.java b/src/main/java/edu/ie3/simona/api/ontology/em/RequestEmCompletion.java new file mode 100644 index 0000000..fc6f5b1 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/RequestEmCompletion.java @@ -0,0 +1,14 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.ontology.em; + +/** + * Request send to SIMONA to finish the em service. + * + * @param tick for which the em service should be finished + */ +public record RequestEmCompletion(long tick) implements EmDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/ontology/em/RequestEmFlexResults.java b/src/main/java/edu/ie3/simona/api/ontology/em/RequestEmFlexResults.java new file mode 100644 index 0000000..47d41f9 --- /dev/null +++ b/src/main/java/edu/ie3/simona/api/ontology/em/RequestEmFlexResults.java @@ -0,0 +1,14 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.api.ontology.em; + +import java.util.List; +import java.util.UUID; + +/** Request em set points from SIMONA in the given tick. */ +public record RequestEmFlexResults(long tick, List emEntities, boolean disaggregated) + implements EmDataMessageFromExt {} diff --git a/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java b/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java index 445c0e9..4932799 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java +++ b/src/main/java/edu/ie3/simona/api/simulation/ExtCoSimulation.java @@ -19,11 +19,10 @@ import edu.ie3.simona.api.data.model.em.EmSetPoint; import edu.ie3.simona.api.exceptions.ExtDataConnectionException; import edu.ie3.simona.api.mapping.DataType; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; +import java.util.*; +import javax.measure.quantity.Time; import org.slf4j.Logger; +import tech.units.indriya.ComparableQuantity; /** * Abstract class for an external co-simulation with bidirectional communication with SIMONA. @@ -59,7 +58,7 @@ protected ExtCoSimulation(String simulationName, String extSimulatorName) { * @return an ext primary data connection */ public static ExtPrimaryDataConnection buildPrimaryConnection( - Map> assetToValueClasses, Logger log) { + Map> assetToValueClasses, Logger log) { if (assetToValueClasses.isEmpty()) { log.warn("No primary data connection was created."); @@ -75,11 +74,19 @@ public static ExtPrimaryDataConnection buildPrimaryConnection( * Builds an {@link ExtEmDataConnection}. * * @param controlled uuids for controlled em agents. + * @param maxDelay the maximal delay used in em communication mode * @param log logger * @return an ext em data connection */ public static ExtEmDataConnection buildEmConnection( - List controlled, ExtEmDataConnection.EmMode mode, Logger log) { + List controlled, + ExtEmDataConnection.EmMode mode, + Optional> maxDelay, + Logger log) { + if (maxDelay.isEmpty() && mode == ExtEmDataConnection.EmMode.EM_COMMUNICATION) { + log.info("Using em communication without a maximum delay."); + } + if (controlled.isEmpty()) { log.warn("Em data connection with 0 controlled entities created. This might lead to errors!"); throw new ExtDataConnectionException(ExtEmDataConnection.class); @@ -89,7 +96,7 @@ public static ExtEmDataConnection buildEmConnection( mode, controlled.size()); - return new ExtEmDataConnection(controlled, mode); + return new ExtEmDataConnection(controlled, mode, maxDelay); } } @@ -120,6 +127,8 @@ public static ExtResultDataConnection buildResultConnection( } } + // primary data methods + /** * Function to send primary data to SIMONA using ExtPrimaryData * @@ -141,6 +150,8 @@ protected void sendPrimaryDataToSimona( log.debug("Provided Primary Data to SIMONA!"); } + // energy management data methods + /** * 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. @@ -162,6 +173,8 @@ protected void sendEmSetPointsToSimona( log.debug("Provided em set points to SIMONA!"); } + // result data methods + /** * Function to get result data from SIMONA using the available {@link ExtResultDataConnection} * 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 deleted file mode 100644 index 6976be0..0000000 --- a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMapping.java +++ /dev/null @@ -1,47 +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.simulation.mapping; - -import edu.ie3.simona.api.mapping.DataType; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.stream.Collectors; - -/** Contains the mapping between SIMONA uuid, the external id and the data type the assets hold */ -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 index 1d24d7f..db03893 100644 --- a/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingSource.java +++ b/src/main/java/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingSource.java @@ -13,6 +13,7 @@ import edu.ie3.datamodel.io.source.DataSource; import edu.ie3.datamodel.io.source.csv.CsvDataSource; import edu.ie3.datamodel.models.Entity; +import edu.ie3.simona.api.mapping.ExtEntityMapping; import java.nio.file.Path; import java.util.Map; import java.util.Optional; diff --git a/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy index b9e9251..4a26a52 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/connection/ExtEmDataConnectionTest.groovy @@ -1,15 +1,19 @@ package edu.ie3.simona.api.data.connection import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode -import edu.ie3.simona.api.data.model.em.EmSetPoint +import edu.ie3.simona.api.data.model.em.ExtendedFlexOptionsResult +import edu.ie3.simona.api.data.model.em.FlexOptionRequest +import edu.ie3.simona.api.data.model.em.FlexOptions import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.api.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.api.ontology.em.ProvideEmSetPointData +import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.api.test.common.DataServiceTestData import org.apache.pekko.actor.testkit.typed.javadsl.ActorTestKit import spock.lang.Shared import spock.lang.Specification +import java.time.ZonedDateTime + class ExtEmDataConnectionTest extends Specification implements DataServiceTestData { @Shared @@ -27,7 +31,45 @@ class ExtEmDataConnectionTest extends Specification implements DataServiceTestDa testKit = null } - def "ExtEmDataConnection should provide em data correctly"() { + def "ExtEmDataConnection should provide em flex requests correctly"() { + given: + def dataService = testKit.createTestProbe(DataMessageFromExt) + def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.BASE) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + + def emData = Map.of(inputUuid, new FlexOptionRequest(inputUuid)) + + when: + extEmDataConnection.sendFlexRequests(0L, emData, Optional.of(900L), log) + + then: + dataService.expectMessage(new ProvideFlexRequestData(0, emData, Optional.of(900L))) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) + } + + def "ExtEmDataConnection should send no message, if no flex requests are given"() { + given: + def dataService = testKit.createTestProbe(DataMessageFromExt) + def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.BASE) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + def inputDataMap = [:] as Map + + when: + extEmDataConnection.sendFlexRequests(0L, inputDataMap, Optional.of(900L), log) + + then: + dataService.expectNoMessage() + } + + def "ExtEmDataConnection should provide em flex options correctly"() { given: def dataService = testKit.createTestProbe(DataMessageFromExt) def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) @@ -37,16 +79,34 @@ class ExtEmDataConnectionTest extends Specification implements DataServiceTestDa extSimAdapter.ref() ) - def emData = Map.of(inputUuid, new EmSetPoint(inputUuid, pValue)) + def emData = Map.of(inputUuid, [new FlexOptions(inputUuid, UUID.randomUUID(), power, power, power)]) when: - extEmDataConnection.sendSetPoints(0L, emData, Optional.of(900L), log) + extEmDataConnection.sendFlexOptions(0L, emData, Optional.of(900L), log) then: - dataService.expectMessage(new ProvideEmSetPointData(0, emData, Optional.of(900L))) + dataService.expectMessage(new ProvideEmFlexOptionData(0, emData, Optional.of(900L))) extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) } + def "ExtEmDataConnection should send no message, if no flex options are given"() { + given: + def dataService = testKit.createTestProbe(DataMessageFromExt) + def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.BASE) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + def inputDataMap = [:] as Map + + when: + extEmDataConnection.sendFlexRequests(0L, inputDataMap, Optional.of(900L), log) + + then: + dataService.expectNoMessage() + } + def "ExtEmDataConnection should send no message, if input data is empty"() { given: def dataService = testKit.createTestProbe(DataMessageFromExt) @@ -65,4 +125,70 @@ class ExtEmDataConnectionTest extends Specification implements DataServiceTestDa dataService.expectNoMessage() } + def "ExtEmDataConnection should send no message, if no em set points are given"() { + given: + def dataService = testKit.createTestProbe(DataMessageFromExt) + def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.BASE) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + def inputDataMap = [:] as Map + + when: + extEmDataConnection.sendSetPoints(0L, inputDataMap, Optional.of(900L), log) + + then: + dataService.expectNoMessage() + } + + def "ExtEmDataConnection should request and receive flex options correctly"() { + given: + def dataService = testKit.createTestProbe(DataMessageFromExt) + def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.BASE) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + + def sendMsg = new FlexOptionsResponse([(inputUuid): new ExtendedFlexOptionsResult(ZonedDateTime.now(), inputUuid, UUID.randomUUID(), power, power, power)]) + + when: + // we need to queue the msg beforehand because the receive method is blocking + extEmDataConnection.queueExtResponseMsg(sendMsg) + + def response = extEmDataConnection.requestEmFlexResults(0L, [inputUuid], false) + + then: + dataService.expectMessage(new RequestEmFlexResults(0L, [inputUuid], false)) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) + response == sendMsg.receiverToFlexOptions() + } + + def "ExtEmDataConnection should request and receive flex completion correctly"() { + given: + def dataService = testKit.createTestProbe(DataMessageFromExt) + def extSimAdapter = testKit.createTestProbe(ScheduleDataServiceMessage) + def extEmDataConnection = new ExtEmDataConnection(controlled, EmMode.BASE) + extEmDataConnection.setActorRefs( + dataService.ref(), + extSimAdapter.ref() + ) + + def sendMsg = new EmCompletion(Optional.of(900L)) + + when: + // we need to queue the msg beforehand because the receive method is blocking + extEmDataConnection.queueExtResponseMsg(sendMsg) + + def response = extEmDataConnection.requestCompletion(0L) + + then: + dataService.expectMessage(new RequestEmCompletion(0L)) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(dataService.ref())) + response == sendMsg.maybeNextTick() + } + } diff --git a/src/test/groovy/edu/ie3/simona/api/data/container/ExtResultContainerTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/container/ExtResultContainerTest.groovy index 5838da4..6dff294 100644 --- a/src/test/groovy/edu/ie3/simona/api/data/container/ExtResultContainerTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/data/container/ExtResultContainerTest.groovy @@ -57,66 +57,4 @@ class ExtResultContainerTest extends Specification implements DataServiceTestDat loadResults == [(inputUuid): loadResult] flexOptionsResults == [:] } - - def "ExtResultContainer should return voltage deviation correctly"() { - given: - def resultMap = Map.of(nodeUuid, nodeResult) - def extResultContainer = new ExtResultContainer(0L, resultMap) - - when: - def calculatedVoltageDeviation = extResultContainer.getVoltageDeviation(nodeUuid) - - then: - calculatedVoltageDeviation == -0.05d - } - - def "ExtResultContainer should throw an exception, if voltage deviation was requested for a not NodeResult"() { - given: - def resultMap = Map.of(inputUuid, loadResult) - def extResultContainer = new ExtResultContainer(0L, resultMap) - - when: - extResultContainer.getVoltageDeviation(inputUuid) - - then: - thrown IllegalArgumentException - } - - def "ExtResultContainer should return active power correctly"() { - given: - def resultMap = Map.of(inputUuid, loadResult) - def extResultContainer = new ExtResultContainer(0L, resultMap) - - when: - def returnedActivePower = extResultContainer.getActivePower(inputUuid) - - then: - returnedActivePower == 10d - } - - def "ExtResultContainer should return reactive power correctly"() { - given: - def resultMap = Map.of(inputUuid, loadResult) - def extResultContainer = new ExtResultContainer(0L, resultMap) - - when: - def returnedReactivePower = extResultContainer.getReactivePower(inputUuid) - - then: - returnedReactivePower == 5d - } - - def "ExtResultContainer should throw an exception, if active power was requested for a not SystemParticipantResult"() { - given: - def resultMap = Map.of( - nodeUuid, nodeResult - ) - def extResultContainer = new ExtResultContainer(0L, resultMap) - - when: - extResultContainer.getActivePower(nodeUuid) - - then: - thrown IllegalArgumentException - } } \ No newline at end of file diff --git a/src/test/groovy/edu/ie3/simona/api/data/model/em/EmSetPointResultTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/model/em/EmSetPointResultTest.groovy new file mode 100644 index 0000000..c760ad0 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/model/em/EmSetPointResultTest.groovy @@ -0,0 +1,52 @@ +package edu.ie3.simona.api.data.model.em + +import edu.ie3.datamodel.models.value.PValue +import edu.ie3.util.quantities.PowerSystemUnits +import spock.lang.Shared +import spock.lang.Specification +import tech.units.indriya.ComparableQuantity +import tech.units.indriya.quantity.Quantities + +import javax.measure.quantity.Power +import java.time.ZonedDateTime + +class EmSetPointResultTest extends Specification { + + @Shared + ZonedDateTime time = ZonedDateTime.now() + + @Shared + UUID senderUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + @Shared + ComparableQuantity power = Quantities.getQuantity(10, PowerSystemUnits.KILOWATT) + + + def "An EmSetPoint can be constructed without set points correctly"() { + when: + def setPoint = new EmSetPointResult(time, senderUuid, [:]) + + then: + setPoint.time == time + setPoint.inputModel == senderUuid + setPoint.sender == senderUuid + setPoint.receiverToSetPoint == [:] + } + + def "An EmSetPoint can be constructed with set points correctly"() { + given: + def receiverUuid = UUID.fromString("81c7c8de-0f01-4559-97bb-844259e467b5") + + def setPoints = [(receiverUuid): new PValue(power)] + + when: + def setPoint = new EmSetPointResult(time, senderUuid, setPoints) + + then: + setPoint.time == time + setPoint.inputModel == senderUuid + setPoint.sender == senderUuid + setPoint.receiverToSetPoint == setPoints + } + +} diff --git a/src/test/groovy/edu/ie3/simona/api/data/model/em/EmSetPointTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/model/em/EmSetPointTest.groovy new file mode 100644 index 0000000..8192ccd --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/model/em/EmSetPointTest.groovy @@ -0,0 +1,67 @@ +package edu.ie3.simona.api.data.model.em + +import edu.ie3.datamodel.models.value.PValue +import edu.ie3.util.quantities.PowerSystemUnits +import spock.lang.Shared +import spock.lang.Specification +import tech.units.indriya.ComparableQuantity +import tech.units.indriya.quantity.Quantities + +import javax.measure.quantity.Power + +class EmSetPointTest extends Specification { + + @Shared + UUID receiverUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + @Shared + ComparableQuantity power = Quantities.getQuantity(10, PowerSystemUnits.KILOWATT) + + + def "An empty EmSetPoint can be constructed correctly"() { + when: + def setPoint = new EmSetPoint(receiverUuid) + + then: + setPoint.receiver == receiverUuid + setPoint.power == Optional.empty() + setPoint.delay == Optional.empty() + !setPoint.hasDelay() + } + + def "An EmSetPoint can be constructed without delay correctly"() { + given: + def pValue = new PValue(power) + + when: + def setPoint1 = new EmSetPoint(receiverUuid, power) + def setPoint2 = new EmSetPoint(receiverUuid, pValue) + + then: + setPoint1.receiver == receiverUuid + setPoint1.power == Optional.of(pValue) + setPoint1.delay == Optional.empty() + !setPoint1.hasDelay() + + setPoint2.receiver == receiverUuid + setPoint2.power == Optional.of(pValue) + setPoint2.delay == Optional.empty() + !setPoint2.hasDelay() + } + + def "An EmSetPoint can be constructed with delay correctly"() { + given: + def pValue = new PValue(power) + def delay = Quantities.getQuantity(10, PowerSystemUnits.MILLISECOND) + + when: + def setPoint = new EmSetPoint(receiverUuid, Optional.of(pValue), Optional.of(delay)) + + then: + setPoint.receiver == receiverUuid + setPoint.power == Optional.of(pValue) + setPoint.delay == Optional.of(delay) + setPoint.hasDelay() + } + +} diff --git a/src/test/groovy/edu/ie3/simona/api/data/model/em/ExtendedFlexOptionsResultTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/model/em/ExtendedFlexOptionsResultTest.groovy new file mode 100644 index 0000000..3f359f7 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/model/em/ExtendedFlexOptionsResultTest.groovy @@ -0,0 +1,140 @@ +package edu.ie3.simona.api.data.model.em + +import edu.ie3.datamodel.models.result.system.FlexOptionsResult +import edu.ie3.util.quantities.PowerSystemUnits +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import spock.lang.Shared +import spock.lang.Specification +import tech.units.indriya.ComparableQuantity +import tech.units.indriya.quantity.Quantities + +import javax.measure.quantity.Power +import java.time.ZonedDateTime + +class ExtendedFlexOptionsResultTest extends Specification { + + @Shared + Logger logger = LoggerFactory.getLogger(ExtendedFlexOptionsResultTest) + + @Shared + ZonedDateTime time = ZonedDateTime.now() + + @Shared + UUID receiverUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + @Shared + UUID senderUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + @Shared + ComparableQuantity pRef = Quantities.getQuantity(7, PowerSystemUnits.KILOWATT) + + @Shared + ComparableQuantity pMin = Quantities.getQuantity(0, PowerSystemUnits.KILOWATT) + + @Shared + ComparableQuantity pMax = Quantities.getQuantity(10, PowerSystemUnits.KILOWATT) + + + def "The ExtendedFlexOptionsResult can be constructed without disaggregated flex options"() { + when: + def result = new ExtendedFlexOptionsResult(time, senderUuid, receiverUuid, pRef, pMin, pMax, [:]) + + then: + result.time == time + result.inputModel == senderUuid + result.sender == senderUuid + result.receiver == receiverUuid + result.pRef == pRef + result.pMin == pMin + result.pMax == pMax + result.disaggregated == [:] + } + + def "The ExtendedFlexOptionsResult can be constructed with disaggregated flex options"() { + given: + def dis1 = UUID.fromString("a246eee3-405c-4af1-9ad2-69ecad2bfb65") + def dis2 = UUID.fromString("78676121-f154-4f70-ad50-4384ddf8deed") + + def disaggregated = [ + (dis1): new FlexOptionsResult(time, dis1, pRef, pMin, pMax), + (dis2): new FlexOptionsResult(time, dis2, pMin, pMin, pMin) + ] + + when: + def result = new ExtendedFlexOptionsResult(time, senderUuid, receiverUuid, pRef, pMin, pMax, disaggregated) + + then: + result.time == time + result.inputModel == senderUuid + result.sender == senderUuid + result.receiver == receiverUuid + result.pRef == pRef + result.pMin == pMin + result.pMax == pMax + result.disaggregated == disaggregated + } + + def "The ExtendedFlexOptionsResult should specify if there are disaggregated flex options correctly"() { + when: + def result = new ExtendedFlexOptionsResult(time, senderUuid, receiverUuid, pRef, pMin, pMax, diagregatedMap) + + then: + result.hasDisaggregated() == expectedResult + + where: + diagregatedMap | expectedResult + [:] as Map | false + [ + (UUID.fromString("a246eee3-405c-4af1-9ad2-69ecad2bfb65")): new FlexOptionsResult(time, UUID.fromString("a246eee3-405c-4af1-9ad2-69ecad2bfb65"), pRef, pMin, pMax), + (UUID.fromString("78676121-f154-4f70-ad50-4384ddf8deed")): new FlexOptionsResult(time, UUID.fromString("78676121-f154-4f70-ad50-4384ddf8deed"), pMin, pMin, pMin) + ] | true + } + + def "The ExtendedFlexOptionsResult should add disaggregated flex options correctly"() { + given: + def result = new ExtendedFlexOptionsResult(time, senderUuid, receiverUuid, pRef, pMin, pMax) + def inferiorUuid1 = UUID.fromString("a246eee3-405c-4af1-9ad2-69ecad2bfb65") + def inferiorUuid2 = UUID.fromString("78676121-f154-4f70-ad50-4384ddf8deed") + + def inferiorOptions1 = new FlexOptionsResult(time, inferiorUuid1, pRef, pMin, pMax) + def inferiorOptions2 = new FlexOptionsResult(time, inferiorUuid2, pMin, pMin, pMin) + + when: + result.addDisaggregated(inferiorUuid1, inferiorOptions1) + result.addDisaggregated(inferiorUuid2, inferiorOptions2) + + then: + result.hasDisaggregated() + result.disaggregated.size() == 2 + result.disaggregated.get(inferiorUuid1) == inferiorOptions1 + result.disaggregated.get(inferiorUuid2) == inferiorOptions2 + } + + def "The ExtendedFlexOptionsResult should check if disaggregated flex options match total flex options correctly"() { + given: + def result = new ExtendedFlexOptionsResult(time, senderUuid, receiverUuid, pRef, pMin, pMax) + def inferiorUuid1 = UUID.fromString("a246eee3-405c-4af1-9ad2-69ecad2bfb65") + def inferiorUuid2 = UUID.fromString("78676121-f154-4f70-ad50-4384ddf8deed") + def inferiorUuid3 = UUID.randomUUID() + + def inferiorOptions1 = new FlexOptionsResult(time, inferiorUuid1, pRef, pMin, pMin) + def inferiorOptions2 = new FlexOptionsResult(time, inferiorUuid2, pMin, pMin, pMax) + def inferiorOptions3 = new FlexOptionsResult(time, inferiorUuid3, pRef, pRef, pMax) + + when: + result.addDisaggregated(inferiorUuid1, inferiorOptions1) + boolean withOnly1 = result.checkFlexOptions(logger) + + result.addDisaggregated(inferiorUuid2, inferiorOptions2) + boolean with1And2 = result.checkFlexOptions(logger) + + result.addDisaggregated(inferiorUuid3, inferiorOptions3) + boolean with1And2And3 = result.checkFlexOptions(logger) + + then: + !withOnly1 + with1And2 + !with1And2And3 + } +} diff --git a/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionRequestResultTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionRequestResultTest.groovy new file mode 100644 index 0000000..a232cfe --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionRequestResultTest.groovy @@ -0,0 +1,30 @@ +package edu.ie3.simona.api.data.model.em + +import spock.lang.Shared +import spock.lang.Specification + +import java.time.ZonedDateTime + +class FlexOptionRequestResultTest extends Specification { + + @Shared + ZonedDateTime time = ZonedDateTime.now() + + @Shared + UUID receiverUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + @Shared + UUID senderUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + def "An FlexOptionRequestResult can be constructed correctly"() { + when: + def result = new FlexOptionRequestResult(time, senderUuid, [receiverUuid]) + + then: + result.time == time + result.inputModel == senderUuid + result.sender == senderUuid + result.receivers == [receiverUuid] + } + +} diff --git a/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionRequestTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionRequestTest.groovy new file mode 100644 index 0000000..68689b8 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionRequestTest.groovy @@ -0,0 +1,63 @@ +package edu.ie3.simona.api.data.model.em + +import edu.ie3.util.quantities.PowerSystemUnits +import spock.lang.Shared +import spock.lang.Specification +import tech.units.indriya.quantity.Quantities + +class FlexOptionRequestTest extends Specification { + + @Shared + UUID receiverUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + @Shared + UUID senderUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + def "The FlexOptionRequest can be constructed without sender and delay correctly"() { + when: + def request = new FlexOptionRequest(receiverUuid) + + then: + request.receiver == receiverUuid + request.sender == Optional.empty() + request.delay == Optional.empty() + !request.hasDelay() + } + + def "The FlexOptionRequest can be constructed with sender and without delay correctly"() { + when: + def request = new FlexOptionRequest(receiverUuid, senderUuid) + + then: + request.receiver == receiverUuid + request.sender == Optional.of(senderUuid) + request.delay == Optional.empty() + !request.hasDelay() + } + + def "The FlexOptionRequest can be constructed with sender as optional and without delay correctly"() { + when: + def request = new FlexOptionRequest(receiverUuid, Optional.of(senderUuid)) + + then: + request.receiver == receiverUuid + request.sender == Optional.of(senderUuid) + request.delay == Optional.empty() + !request.hasDelay() + } + + def "The FlexOptionRequest can be constructed with sender and delay correctly"() { + given: + def delay = Quantities.getQuantity(10, PowerSystemUnits.MILLISECOND) + + when: + def request = new FlexOptionRequest(receiverUuid, Optional.of(senderUuid), Optional.of(delay)) + + then: + request.receiver == receiverUuid + request.sender == Optional.of(senderUuid) + request.delay == Optional.of(delay) + request.hasDelay() + } + +} diff --git a/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionsTest.groovy b/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionsTest.groovy new file mode 100644 index 0000000..02ee987 --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/data/model/em/FlexOptionsTest.groovy @@ -0,0 +1,59 @@ +package edu.ie3.simona.api.data.model.em + +import edu.ie3.util.quantities.PowerSystemUnits +import spock.lang.Shared +import spock.lang.Specification +import tech.units.indriya.ComparableQuantity +import tech.units.indriya.quantity.Quantities + +import javax.measure.quantity.Power + +class FlexOptionsTest extends Specification { + + @Shared + UUID receiverUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + @Shared + UUID senderUuid = UUID.fromString("978554e5-32cc-4221-bd39-84beac60f327") + + @Shared + ComparableQuantity pRef = Quantities.getQuantity(7, PowerSystemUnits.KILOWATT) + + @Shared + ComparableQuantity pMin = Quantities.getQuantity(0, PowerSystemUnits.KILOWATT) + + @Shared + ComparableQuantity pMax = Quantities.getQuantity(10, PowerSystemUnits.KILOWATT) + + def "FlexOptions can be constructed without delay correctly"() { + when: + def flexOptions = new FlexOptions(receiverUuid, senderUuid, pRef, pMin, pMax) + + then: + flexOptions.receiver == receiverUuid + flexOptions.sender == senderUuid + flexOptions.pRef == pRef + flexOptions.pMin == pMin + flexOptions.pMax == pMax + flexOptions.delay == Optional.empty() + !flexOptions.hasDelay() + } + + def "FlexOptions can be constructed with delay correctly"() { + given: + def delay = Quantities.getQuantity(10, PowerSystemUnits.MILLISECOND) + + when: + def flexOptions = new FlexOptions(receiverUuid, senderUuid, pRef, pMin, pMax, Optional.of(delay)) + + then: + flexOptions.receiver == receiverUuid + flexOptions.sender == senderUuid + flexOptions.pRef == pRef + flexOptions.pMin == pMin + flexOptions.pMax == pMax + flexOptions.delay == Optional.of(delay) + flexOptions.hasDelay() + } + +} diff --git a/src/test/groovy/edu/ie3/simona/api/mapping/ExtEntityMappingTest.groovy b/src/test/groovy/edu/ie3/simona/api/mapping/ExtEntityMappingTest.groovy new file mode 100644 index 0000000..c4b4f3b --- /dev/null +++ b/src/test/groovy/edu/ie3/simona/api/mapping/ExtEntityMappingTest.groovy @@ -0,0 +1,158 @@ +package edu.ie3.simona.api.mapping + +import edu.ie3.datamodel.io.naming.timeseries.ColumnScheme +import edu.ie3.simona.api.simulation.mapping.ExtEntityEntry +import spock.lang.Shared +import spock.lang.Specification + +class ExtEntityMappingTest extends Specification { + @Shared + UUID loadUuid = UUID.fromString("22bea5fc-2cb2-4c61-beb9-b476e0107f52") + + @Shared + UUID pvUuid = UUID.fromString("12f5e864-2464-4e43-9a38-5753a439d45f") + + @Shared + UUID emUuid = UUID.fromString("60dbc7e4-9718-4bbd-913a-dd26925e68a3") + + @Shared + ExtEntityEntry extResultEntry = new ExtEntityEntry( + loadUuid, + "Load", + Optional.empty(), + DataType.EXT_PARTICIPANT_RESULT + ) + + @Shared + ExtEntityEntry extInputEntry = new ExtEntityEntry( + pvUuid, + "PV", + ColumnScheme.parse("p"), + DataType.EXT_PRIMARY_INPUT + ) + + @Shared + ExtEntityEntry extEmInputEntry = new ExtEntityEntry( + emUuid, + "Em", + Optional.empty(), + DataType.EXT_EM_INPUT + ) + + def "ExtEntityMapping should return the data types correctly"() { + when: + def extEntryMapping = new ExtEntityMapping(assets) + def types = extEntryMapping.dataTypes + + then: + types == expectedTypes as Set + + where: + assets | expectedTypes + [extResultEntry] | [DataType.EXT_PARTICIPANT_RESULT] + [extInputEntry] | [DataType.EXT_PRIMARY_INPUT] + [extEmInputEntry] | [DataType.EXT_EM_INPUT] + [extResultEntry, extInputEntry] | [DataType.EXT_PARTICIPANT_RESULT, DataType.EXT_PRIMARY_INPUT] + [extInputEntry, extEmInputEntry] | [DataType.EXT_PRIMARY_INPUT, DataType.EXT_EM_INPUT] + [extResultEntry, extInputEntry, extEmInputEntry] | [DataType.EXT_PARTICIPANT_RESULT, DataType.EXT_PRIMARY_INPUT, DataType.EXT_EM_INPUT] + } + + def "ExtEntityMapping should return the entries correctly"() { + when: + def extEntryMapping = new ExtEntityMapping(assets) + def types = extEntryMapping.getEntries(dataType) + + then: + types == expectedEntries + + where: + assets | dataType | expectedEntries + [extResultEntry, extInputEntry, extEmInputEntry] | DataType.EXT_PARTICIPANT_RESULT | [extResultEntry] + [extResultEntry, extInputEntry, extEmInputEntry] | DataType.EXT_PRIMARY_INPUT | [extInputEntry] + [extResultEntry, extInputEntry, extEmInputEntry] | DataType.EXT_EM_INPUT | [extEmInputEntry] + } + + def "ExtEntityMapping should return all SIMONA uuid mapping correctly"() { + given: + def extAssetList = List.of(extResultEntry, extInputEntry, extEmInputEntry) + def extEntryMapping = new ExtEntityMapping(extAssetList) + + when: + def inputMap = extEntryMapping.getExtId2UuidMapping() + + then: + inputMap.size() == 3 + inputMap.get("Load") == loadUuid + inputMap.get("PV") == pvUuid + inputMap.get("Em") == emUuid + } + + def "ExtEntityMapping should return SIMONA uuid mapping correctly"() { + given: + def extAssetList = List.of(extResultEntry, extInputEntry, extEmInputEntry) + def extEntryMapping = new ExtEntityMapping(extAssetList) + + when: + def inputMap = extEntryMapping.getExtId2UuidMapping(DataType.EXT_PRIMARY_INPUT) + + then: + inputMap.size() == 1 + inputMap.get("PV") == pvUuid + } + + def "ExtEntityMapping should return multiple SIMONA uuid mapping correctly"() { + given: + def extAssetList = List.of(extResultEntry, extInputEntry, extEmInputEntry) + def extEntryMapping = new ExtEntityMapping(extAssetList) + + when: + def inputMap = extEntryMapping.getExtId2UuidMapping(DataType.EXT_PRIMARY_INPUT, DataType.EXT_EM_INPUT) + + then: + inputMap.size() == 2 + inputMap.get("PV") == pvUuid + inputMap.get("Em") == emUuid + } + + def "ExtEntityMapping should return all external id mapping correctly"() { + given: + def extAssetList = List.of(extResultEntry, extInputEntry, extEmInputEntry) + def extEntryMapping = new ExtEntityMapping(extAssetList) + + when: + def inputMap = extEntryMapping.getExtUuid2IdMapping() + + then: + inputMap.size() == 3 + inputMap.get(loadUuid) == "Load" + inputMap.get(pvUuid) == "PV" + inputMap.get(emUuid) == "Em" + } + + def "ExtEntityMapping should return external id mapping correctly"() { + given: + def extAssetList = List.of(extResultEntry, extInputEntry, extEmInputEntry) + def extEntryMapping = new ExtEntityMapping(extAssetList) + + when: + def inputMap = extEntryMapping.getExtUuid2IdMapping(DataType.EXT_PRIMARY_INPUT) + + then: + inputMap.size() == 1 + inputMap.get(pvUuid) == "PV" + } + + def "ExtEntityMapping should return multiple external id mapping correctly"() { + given: + def extAssetList = List.of(extResultEntry, extInputEntry, extEmInputEntry) + def extEntryMapping = new ExtEntityMapping(extAssetList) + + when: + def inputMap = extEntryMapping.getExtUuid2IdMapping(DataType.EXT_PRIMARY_INPUT, DataType.EXT_EM_INPUT) + + then: + inputMap.size() == 2 + inputMap.get(pvUuid) == "PV" + inputMap.get(emUuid) == "Em" + } +} \ No newline at end of file diff --git a/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy b/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy index 30a768f..178ef60 100644 --- a/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy +++ b/src/test/groovy/edu/ie3/simona/api/simulation/ExtCoSimulationTest.groovy @@ -50,7 +50,7 @@ class ExtCoSimulationTest extends Specification { def controlled = [uuid1, uuid2] when: - def actual = ExtCoSimulation.buildEmConnection(controlled, EmMode.BASE, log) + def actual = ExtCoSimulation.buildEmConnection(controlled, EmMode.BASE, Optional.empty(), log) then: actual.getControlledEms() == [uuid1, uuid2] @@ -58,7 +58,7 @@ class ExtCoSimulationTest extends Specification { def "An ExtCoSimulation throws an ExtDataConnectionException while trying to build an empty em data connection"() { when: - ExtCoSimulation.buildEmConnection([], EmMode.BASE, log) + ExtCoSimulation.buildEmConnection([], EmMode.BASE, Optional.empty(), log) then: ExtDataConnectionException ex = thrown(ExtDataConnectionException) 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 deleted file mode 100644 index dea3928..0000000 --- a/src/test/groovy/edu/ie3/simona/api/simulation/mapping/ExtEntityMappingTest.groovy +++ /dev/null @@ -1,53 +0,0 @@ -package edu.ie3.simona.api.simulation.mapping - -import edu.ie3.datamodel.io.naming.timeseries.ColumnScheme -import edu.ie3.simona.api.mapping.DataType -import spock.lang.Shared -import spock.lang.Specification - -class ExtEntityMappingTest extends Specification { - @Shared - UUID loadUuid = UUID.fromString("22bea5fc-2cb2-4c61-beb9-b476e0107f52") - - @Shared - ExtEntityEntry extResultEntry = new ExtEntityEntry( - loadUuid, - "Load", - ColumnScheme.parse("p"), - DataType.EXT_PARTICIPANT_RESULT - ) - - @Shared - ExtEntityEntry extInputEntry = new ExtEntityEntry( - loadUuid, - "Load", - ColumnScheme.parse("p"), - 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 index 88a7af5..748b95e 100644 --- a/src/test/groovy/edu/ie3/simona/api/test/common/DataServiceTestData.groovy +++ b/src/test/groovy/edu/ie3/simona/api/test/common/DataServiceTestData.groovy @@ -5,15 +5,20 @@ 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.ComparableQuantity import tech.units.indriya.quantity.Quantities +import javax.measure.quantity.Power 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)) + + ComparableQuantity power = Quantities.getQuantity(500.0, StandardUnits.ACTIVE_POWER_IN) + + PValue pValue = new PValue(power) LoadResult loadResult = new LoadResult( ZonedDateTime.parse("2020-01-30T17:26:44Z[UTC]"),