From 20be2482342fe14be5878ef4bd0f0dbff76f7120 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 08:31:58 +0000 Subject: [PATCH 1/4] Bump com.github.ie3-institute:simonaAPI from 0.11.0 to 0.12.0 Bumps [com.github.ie3-institute:simonaAPI](https://github.com/ie3-institute/simonaAPI) from 0.11.0 to 0.12.0. - [Release notes](https://github.com/ie3-institute/simonaAPI/releases) - [Changelog](https://github.com/ie3-institute/simonaAPI/blob/dev/CHANGELOG.md) - [Commits](https://github.com/ie3-institute/simonaAPI/compare/0.11.0...0.12.0) --- updated-dependencies: - dependency-name: com.github.ie3-institute:simonaAPI dependency-version: 0.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2f87fadce4..53d9cf4e04 100644 --- a/build.gradle +++ b/build.gradle @@ -92,7 +92,7 @@ dependencies { exclude group: 'edu.ie3' } - implementation('com.github.ie3-institute:simonaAPI:0.11.0') { + implementation('com.github.ie3-institute:simonaAPI:0.12.0') { exclude group: 'org.apache.logging.log4j' exclude group: 'org.slf4j' /* Exclude our own nested dependencies */ From 086756dd558e6a655439bc6900570e8c11f00f28 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 9 Dec 2025 13:00:25 +0100 Subject: [PATCH 2/4] Adapting to changes in simonaAPI version 0.12.0 --- .../participant/evcs/EvModelWrapper.scala | 2 +- .../evcs/EvcsChargingProperties.scala | 2 +- .../ontology/messages/ServiceMessage.scala | 4 +- .../ie3/simona/service/ExtDataSupport.scala | 7 +- .../simona/service/em/EmServiceBaseCore.scala | 321 +++++++++++------- .../ie3/simona/service/em/EmServiceCore.scala | 124 ++++--- .../simona/service/em/ExtEmDataService.scala | 31 +- .../simona/service/ev/ExtEvDataService.scala | 3 +- .../edu/ie3/simona/util/CollectionUtils.scala | 13 + .../edu/ie3/simona/util/ReceiveDataMap.scala | 3 + .../simona/test/common/model/MockEvModel.java | 10 +- .../participant/evcs/EvcsModelSpec.scala | 2 +- .../evcs/EvcsPowerLimitFlexModelSpec.scala | 2 +- .../evcs/MaximumPowerChargingSpec.scala | 2 +- .../service/em/EmServiceBaseCoreSpec.scala | 75 ++-- .../service/em/ExtEmDataServiceSpec.scala | 38 ++- 16 files changed, 378 insertions(+), 261 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/evcs/EvModelWrapper.scala b/src/main/scala/edu/ie3/simona/model/participant/evcs/EvModelWrapper.scala index 601078bc1d..98f94a42cc 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/evcs/EvModelWrapper.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/evcs/EvModelWrapper.scala @@ -35,7 +35,7 @@ final case class EvModelWrapper( def uuid: UUID = original.getUuid def id: String = original.getId - lazy val pRatedAc: Power = original.getPRatedAC.toSquants + lazy val sRatedAc: Power = original.getSRatedAC.toSquants lazy val pRatedDc: Power = original.getPRatedDC.toSquants lazy val eStorage: Energy = original.getEStorage.toSquants diff --git a/src/main/scala/edu/ie3/simona/model/participant/evcs/EvcsChargingProperties.scala b/src/main/scala/edu/ie3/simona/model/participant/evcs/EvcsChargingProperties.scala index 024f847e62..f85502e89f 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/evcs/EvcsChargingProperties.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/evcs/EvcsChargingProperties.scala @@ -39,7 +39,7 @@ trait EvcsChargingProperties { ): Power = { val evPower = currentType match { case ElectricCurrentType.AC => - ev.pRatedAc + ev.sRatedAc case ElectricCurrentType.DC => ev.pRatedDc } diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/ServiceMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/ServiceMessage.scala index a1500a9374..73f28133f8 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/ServiceMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/ServiceMessage.scala @@ -69,8 +69,8 @@ object ServiceMessage { final case class EmServiceRegistration( requestingActor: ActorRef[EmAgent.Message], inputUuid: UUID, - parentEm: Option[ActorRef[FlexResponse]], - parentUuid: Option[UUID], + parentEm: Option[ActorRef[FlexResponse]] = None, + parentUuid: Option[UUID] = None, ) extends ServiceRegistrationMessage /** Message to register with a primary data service. diff --git a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala index 672e0b3aab..175be58b1d 100644 --- a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala +++ b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala @@ -46,8 +46,8 @@ trait ExtDataSupport { idle(using updatedStateData, scheduler) - case (_, extResponseMsg: ServiceResponseMessage) => - val updatedStateData = handleDataResponseMessage(extResponseMsg) + case (ctx, extResponseMsg: ServiceResponseMessage) => + val updatedStateData = handleDataResponseMessage(extResponseMsg, ctx) idle(using updatedStateData, scheduler) } @@ -75,6 +75,7 @@ trait ExtDataSupport { * the updated state data */ protected def handleDataResponseMessage( - extResponseMsg: ServiceResponseMessage + extResponseMsg: ServiceResponseMessage, + ctx: ActorContext[Message], )(using serviceStateData: S): S } diff --git a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala index b1db55d428..fd18315b91 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -7,45 +7,46 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.model.em.ExtendedFlexOptionsResult +import edu.ie3.simona.api.data.model.em +import edu.ie3.simona.api.data.model.em.{EmSetPoint, FlexOptions} import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration import edu.ie3.simona.ontology.messages.flex.FlexType.PowerLimit import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.flex.PowerLimitFlexOptions +import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.ReceiveDataMap -import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK -import edu.ie3.simona.util.TickUtil.TickLong +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger -import java.time.ZonedDateTime import java.util.UUID -import scala.jdk.CollectionConverters.{ - ListHasAsScala, - MapHasAsJava, - MapHasAsScala, - SetHasAsScala, -} +import scala.jdk.CollectionConverters.MapHasAsScala /** Basic service core for an [[ExtEmDataService]]. * @param lastFinishedTick * The last tick that was completed. * @param uuidToAgent * Map: uuid to em agent reference. - * @param flexOptions - * ReceiveDataMap: uuid to flex option result. - * @param allFlexOptions - * Map: uuid to flex option result. - * @param completions - * ReceiveDataMap: uuid to completions. - * @param structure + * @param agentToUuid + * Map: em agent reference to uuid. + * @param uuidToInferior * A map that contains information about uuids of inferior em agents. This * information is used to determine the disaggregated flex options. + * @param uuidToParent + * A map: uuid to parent uuid. + * @param completions + * ReceiveDataMap: uuid to completions. + * @param nextActivation + * A map: uuid to next activation tick. + * @param allFlexOptions + * Map: uuid to flex option result. + * @param flexOptions + * ReceiveDataMap: uuid to flex option result. * @param disaggregated * A map: uuid of em agent to boolean. It defines for which em agent we - * should return disaggregated flex optios. + * should return disaggregated flex options. * @param sendOptionsToExt * True, if flex options should be sent to the external simulation. * @param canHandleSetPoints @@ -56,83 +57,70 @@ import scala.jdk.CollectionConverters.{ * Option for em set points that needs to be handled at a later time. */ final case class EmServiceBaseCore( - override val lastFinishedTick: Long = PRE_INIT_TICK, + override val lastFinishedTick: Long = INIT_SIM_TICK, override val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, - flexOptions: ReceiveDataMap[UUID, ExtendedFlexOptionsResult] = - ReceiveDataMap.empty, - override val allFlexOptions: Map[UUID, ExtendedFlexOptionsResult] = - Map.empty, + override val agentToUuid: Map[ + ActorRef[FlexRequest] | ActorRef[FlexResponse], + UUID, + ] = Map.empty, + override val uncontrolled: Set[UUID] = Set.empty, + override val uuidToInferior: Map[UUID, Set[UUID]] = Map.empty, + override val uuidToParent: Map[UUID, UUID] = Map.empty, override val completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, - structure: Map[UUID, Set[UUID]] = Map.empty, + override val nextActivation: Map[UUID, Long] = Map.empty, + override val allFlexOptions: Map[UUID, FlexOptions] = Map.empty, + flexOptions: ReceiveDataMap[UUID, FlexOptions] = ReceiveDataMap.empty, disaggregated: Map[UUID, Boolean] = Map.empty, sendOptionsToExt: Boolean = false, canHandleSetPoints: Boolean = false, - setPointOption: Option[ProvideEmSetPointData] = None, + setPointOption: Option[Map[UUID, EmSetPoint]] = None, + internal: Set[UUID] = Set.empty, ) extends EmServiceCore { - override def handleRegistration( + def handleRegistration( emServiceRegistration: EmServiceRegistration ): EmServiceBaseCore = { + val uuid = emServiceRegistration.inputUuid val ref = emServiceRegistration.requestingActor - val modelUuid = emServiceRegistration.inputUuid - val parentUuid = emServiceRegistration.parentUuid - - val updatedStructure = parentUuid match { - case Some(parent) => - structure.get(parent) match { - case Some(subEms) => - val allSubEms = subEms + modelUuid - structure ++ Map(parent -> allSubEms) - case None => - structure ++ Map(parent -> Set(modelUuid)) - } - case _ => - // since the given em agent has no parent, no changes to the parent structure are needed - // the actual em agent is added to the structure later - structure - } + val (updatedUncontrolled, updatedInferior, updatedUuidToParent) = + emServiceRegistration.parentUuid match { + case Some(parent) => + val inferior = uuidToInferior.get(parent) match { + case Some(inferiorUuids) => + inferiorUuids ++ Seq(uuid) + case None => + Set(uuid) + } + + ( + uncontrolled, + uuidToInferior.updated(parent, inferior), + uuidToParent.updated(uuid, parent), + ) + case None => + (uncontrolled + uuid, uuidToInferior, uuidToParent) + } copy( - uuidToAgent = uuidToAgent + (modelUuid -> ref), - completions = completions.addExpectedKeys(Set(modelUuid)), - structure = updatedStructure ++ Map(modelUuid -> Set.empty[UUID]), + uuidToAgent = uuidToAgent.updated(uuid, ref), + agentToUuid = agentToUuid.updated(ref, uuid), + uncontrolled = updatedUncontrolled, + uuidToInferior = updatedInferior, + uuidToParent = updatedUuidToParent, + completions = completions.addExpectedKey(uuid), + nextActivation = nextActivation.updated(uuid, -1), ) } override def handleExtMessage(tick: Long, extMsg: EmDataMessageFromExt)(using log: Logger ): (EmServiceBaseCore, Option[EmDataResponseMessageToExt]) = extMsg match { - case requestEmFlexResults: RequestEmFlexResults => - val tick = requestEmFlexResults.tick - val emEntities = requestEmFlexResults.emEntities.asScala - val disaggregatedFlex = requestEmFlexResults.disaggregated - - val requests = emEntities.flatMap { entity => - uuidToAgent.get(entity).map { ref => - ref ! FlexActivation(tick, PowerLimit) - - entity -> disaggregatedFlex - } - }.toMap - - ( - copy( - flexOptions = ReceiveDataMap(emEntities.toSet), - disaggregated = disaggregated ++ requests, - sendOptionsToExt = true, - ), - None, - ) - case provideEmData: ProvideEmData => - if !provideEmData.flexOptions.isEmpty || !provideEmData - .setPoints() - .isEmpty - then { + if !provideEmData.flexOptions.isEmpty then { log.warn( - s"We received the following data '$provideEmData'. The base service can currently not handle the provided flex options and set points." + s"We received the following data '$provideEmData'. The base service can currently not handle the provided flex options." ) } @@ -146,41 +134,67 @@ final case class EmServiceBaseCore( } }.toMap - ( - copy( - flexOptions = ReceiveDataMap(flexRequests.keySet), - disaggregated = disaggregated ++ flexRequests, - sendOptionsToExt = true, - ), - None, + val updatedState = copy( + flexOptions = ReceiveDataMap(flexRequests.keySet), + completions = completions.addExpectedKeys(flexRequests.keySet), + disaggregated = disaggregated ++ flexRequests, + sendOptionsToExt = flexRequests.nonEmpty, ) - case provideEmSetPoints: ProvideEmSetPointData => - if canHandleSetPoints then { - handleSetPoint(tick, provideEmSetPoints, log) + // handle set points + val setPoints = provideEmData.setPoints().asScala.toMap - (this, None) - } else { - val tick = provideEmSetPoints.tick - val emEntities = provideEmSetPoints.emSetPoints.keySet.asScala - - emEntities.foreach { entity => - uuidToAgent.get(entity) match { - case Some(ref) => - // activate the necessary em agent, this is needed, because an em agent needs to know - // its current flex option to properly handle the given set point - ref ! FlexActivation(tick, PowerLimit) - case None => - log.warn(s"Received entity: $entity") + if setPoints.nonEmpty then { + + if canHandleSetPoints then { + handleSetPoint(tick, setPoints, log) + + (updatedState, None) + } else { + val entities = setPoints.keySet + + entities.foreach { entity => + uuidToAgent.get(entity) match { + case Some(ref) => + // activate the necessary em agent, this is needed, because an em agent needs to know + // its current flex option to properly handle the given set point + ref ! FlexActivation(tick, PowerLimit) + case None => + log.warn(s"Received entity: $entity") + } } + + ( + updatedState.copy( + flexOptions = updatedState.flexOptions.addExpectedKeys(entities), + completions = updatedState.completions.addExpectedKeys(entities), + setPointOption = Some(setPoints), + ), + None, + ) } + } else (updatedState, None) + + case requestEmCompletion: RequestEmCompletion => + // finish tick and return next tick + val extTick = requestEmCompletion.tick + + if extTick != tick then { + throw new CriticalFailureException( + s"Received completion request for tick '$extTick', while being in tick '$tick'." + ) + } else { + log.info(s"Request to finish for tick '$tick' received.") + + // deactivate agents by sending an IssueNoControl message + // activatedAgents.map(uuidToAgent).foreach(_ ! IssueNoControl(tick)) + + val nextTick = getMaybeNextTick + ( - copy( - flexOptions = ReceiveDataMap(emEntities.toSet), - setPointOption = Some(provideEmSetPoints), - ), - None, + copy(lastFinishedTick = tick), + Some(new EmCompletion(nextTick)), ) } @@ -195,26 +209,33 @@ final case class EmServiceBaseCore( flexResponse: FlexResponse, receiver: Either[UUID, ActorRef[FlexResponse]], )(using - startTime: ZonedDateTime, - log: Logger, + log: Logger ): (EmServiceBaseCore, Option[EmDataResponseMessageToExt]) = { - receiver.foreach(_ ! flexResponse) + + val receiverUuid = receiver match { + case Right(ref) => + ref ! flexResponse + agentToUuid(ref) + case Left(uuid) => + uuid + } flexResponse match { case provideFlexOptions: ProvideFlexOptions => val (updated, updatedAdditional) = - handleFlexOptions(tick, provideFlexOptions) + handleFlexOptions(tick, receiverUuid, provideFlexOptions) if updated.isComplete then { // we received all flex options + val data = updated.receivedData data.foreach { case (uuid, flexOption) => - if disaggregated.contains(uuid) then { + if disaggregated.getOrElse(uuid, false) then { // we add the disaggregated flex options addDisaggregatingFlexOptions( flexOption, - structure.getOrElse(uuid, Set.empty), + uuidToInferior.getOrElse(uuid, Set.empty), ) } } @@ -225,11 +246,21 @@ final case class EmServiceBaseCore( canHandleSetPoints = true, ) - if sendOptionsToExt then { + if internal.nonEmpty then { + internal.map(uuidToAgent).foreach(_ ! IssueNoControl(tick)) + + (updatedCore, None) + + } else if sendOptionsToExt then { + val dataToSend = data.map { case (uuid, option) => + uuid -> List(option) + } + // we have received an option request, that will now be answered - (updatedCore, Some(new FlexOptionsResponse(data.asJava))) + (updatedCore, Some(new FlexOptionsResponse(dataToSend.asJava))) } else { + setPointOption match { case Some(setPoints) => // we have received new set points, that are not handled yet => we will handle them now @@ -243,6 +274,8 @@ final case class EmServiceBaseCore( } } else { + log.debug(s"Missing flex options for: ${updated.getExpectedKeys}") + ( copy( flexOptions = updated, @@ -253,23 +286,48 @@ final case class EmServiceBaseCore( } case completion: FlexCompletion => - val (updated, extMsgOption, finished) = + val (updated, extMsgOption, nextTick, finished) = handleCompletion(tick, completion) if finished then { + // the next activations + val updatedNextActivation = + nextActivation ++ updated.receivedData.flatMap { case (uuid, msg) => + msg.requestAtTick.map(uuid -> _) + } + + val expectedCompletions = nextTick match { + case Some(t) => + val keys = updatedNextActivation.filter { case (_, activation) => + activation == t + }.keySet + ReceiveDataMap[UUID, FlexCompletion](keys) + case None => + updated + } + + val msgToExt = if internal.nonEmpty then { + Some(new EmCompletion(updatedNextActivation.values.minOption)) + } else extMsgOption + ( copy( lastFinishedTick = tick, - completions = updated, - allFlexOptions = Map.empty, + completions = expectedCompletions, disaggregated = Map.empty, sendOptionsToExt = false, canHandleSetPoints = false, + nextActivation = updatedNextActivation, + internal = Set.empty, ), - extMsgOption, + msgToExt, ) - } else (copy(completions = updated), extMsgOption) + } else { + log.debug(s"Missing completion for: ${updated.getExpectedKeys}") + + (copy(completions = updated), extMsgOption) + } case _ => (this, None) @@ -280,38 +338,39 @@ final case class EmServiceBaseCore( flexRequest: FlexRequest, receiver: ActorRef[FlexRequest], )(using - startTime: ZonedDateTime, - log: Logger, + log: Logger ): (EmServiceBaseCore, Option[EmDataResponseMessageToExt]) = { log.debug(s"$receiver: $flexRequest") receiver ! flexRequest - (this, None) + val uuid = agentToUuid(receiver) + (copy(completions = completions.addExpectedKey(uuid)), None) } /** Method to handle flex options. * @param tick * Current tick of the service. + * @param receiver + * The receiver of the flex options. * @param provideFlexOptions * The provided flex options. - * @param startTime - * The start time of the simulation. * @return * An updated service core and a map: uuid to flex options */ private def handleFlexOptions( tick: Long, + receiver: UUID, provideFlexOptions: ProvideFlexOptions, - )(using startTime: ZonedDateTime): ( - ReceiveDataMap[UUID, ExtendedFlexOptionsResult], - Map[UUID, ExtendedFlexOptionsResult], + ): ( + ReceiveDataMap[UUID, FlexOptions], + Map[UUID, FlexOptions], ) = provideFlexOptions match { case ProvideFlexOptions( modelUuid: UUID, PowerLimitFlexOptions(ref, min, max), ) => - val result = new ExtendedFlexOptionsResult( - tick.toDateTime, + val result = new em.PowerLimitFlexOptions( + receiver, modelUuid, min.toQuantity, ref.toQuantity, @@ -321,7 +380,7 @@ final case class EmServiceBaseCore( if flexOptions.expects(modelUuid) then { ( flexOptions.addData(modelUuid, result), - allFlexOptions, + allFlexOptions.updated(modelUuid, result), ) } else { ( @@ -338,5 +397,15 @@ final case class EmServiceBaseCore( object EmServiceBaseCore { - def empty: EmServiceBaseCore = EmServiceBaseCore() + def apply(core: EmServiceCore): EmServiceBaseCore = EmServiceBaseCore( + core.lastFinishedTick, + core.uuidToAgent, + core.agentToUuid, + core.uncontrolled, + core.uuidToInferior, + core.uuidToParent, + core.completions, + core.nextActivation, + core.allFlexOptions, + ) } diff --git a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala index 4c5752f24b..16d7432ddc 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -6,10 +6,9 @@ package edu.ie3.simona.service.em -import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.{PValue, SValue} import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.model.em.ExtendedFlexOptionsResult +import edu.ie3.simona.api.data.model.em.{EmSetPoint, FlexOptions} import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.ontology.messages.ServiceMessage.{ EmFlexMessage, @@ -27,9 +26,8 @@ import squants.Power import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime -import java.util.UUID +import java.util.{Optional, UUID} import javax.measure.quantity.Power as PsdmPower -import scala.jdk.CollectionConverters.MapHasAsScala import scala.jdk.OptionConverters.{RichOption, RichOptional} /** Trait for all em service cores. @@ -44,21 +42,37 @@ trait EmServiceCore { */ val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] + val agentToUuid: Map[ActorRef[FlexRequest] | ActorRef[FlexResponse], UUID] + + val uncontrolled: Set[UUID] + + val uuidToInferior: Map[UUID, Set[UUID]] + + val uuidToParent: Map[UUID, UUID] + /** Map: uuid to flex option result. */ - val allFlexOptions: Map[UUID, FlexOptionsResult] + val allFlexOptions: Map[UUID, FlexOptions] /** ReceiveDataMap: uuid to completions. */ val completions: ReceiveDataMap[UUID, FlexCompletion] + val nextActivation: Map[UUID, Long] + /** Extension to convert a squants power value to a psdm power value. */ extension (value: Power) { def toQuantity: ComparableQuantity[PsdmPower] = value.toMegawatts.asMegaWatt } + given Conversion[Optional[java.lang.Long], Option[Long]] = + (x: Optional[java.lang.Long]) => x.toScala.map(Long2long) + given Conversion[Option[Long], Optional[java.lang.Long]] = + (x: Option[Long]) => x.map(long2Long).toJava + /** Method to handle a registration message. + * * @param emServiceRegistration * The registration to handle. * @return @@ -107,7 +121,7 @@ trait EmServiceCore { log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = responseMsg match { case EmFlexMessage(flexRequest: FlexRequest, receiver) => - log.warn(s"$receiver <- $flexRequest") + log.debug(s"$receiver <- $flexRequest") receiver match { case ref: ActorRef[FlexRequest] => @@ -126,7 +140,7 @@ trait EmServiceCore { } case EmFlexMessage(flexResponse: FlexResponse, receiver) => - log.warn(s"$receiver <- $flexResponse") + log.debug(s"$receiver <- $flexResponse") receiver match { case uuid: UUID => @@ -145,43 +159,40 @@ trait EmServiceCore { /** Method to handle the set points provided by the external simulation. * @param tick * Current tick of the service. - * @param provideEmSetPoints + * @param setPoints * The set points to handle. * @param log * Logger for logging messages. */ final def handleSetPoint( tick: Long, - provideEmSetPoints: ProvideEmSetPointData, + setPoints: Map[UUID, EmSetPoint], log: Logger, ): Unit = { - log.info(s"Handling of: $provideEmSetPoints") - - provideEmSetPoints.emSetPoints.asScala - .foreach { case (agent, setPoint) => - uuidToAgent.get(agent) match { - case Some(receiver) => - val (pOption, qOption) = setPoint.power.toScala match { - case Some(sValue: SValue) => - (sValue.getP.toScala, sValue.getQ.toScala) - case Some(pValue: PValue) => - (pValue.getP.toScala, None) - case None => - (None, None) - } - - (pOption, qOption) match { - case (Some(activePower), _) => - receiver ! IssuePowerControl(tick, activePower.toSquants) - - case (None, _) => - receiver ! IssueNoControl(tick) - } - - case None => - log.warn(s"No em agent with uuid '$agent' registered!") - } + setPoints.foreach { case (agent, setPoint) => + uuidToAgent.get(agent) match { + case Some(receiver) => + val (pOption, qOption) = setPoint.power.toScala match { + case Some(sValue: SValue) => + (sValue.getP.toScala, sValue.getQ.toScala) + case Some(pValue: PValue) => + (pValue.getP.toScala, None) + case None => + (None, None) + } + + (pOption, qOption) match { + case (Some(activePower), _) => + receiver ! IssuePowerControl(tick, activePower.toSquants) + + case (None, _) => + receiver ! IssueNoControl(tick) + } + + case None => + log.warn(s"No em agent with uuid '$agent' registered!") } + } } /** Method to handle flex responses from the em agents. @@ -191,8 +202,6 @@ trait EmServiceCore { * From the agent to handle. * @param receiver * The receiver of the agent. - * @param startTime - * The start time of the simulation. * @param log * Logger for logging messages. * @return @@ -203,18 +212,13 @@ trait EmServiceCore { tick: Long, flexResponse: FlexResponse, receiver: Either[UUID, ActorRef[FlexResponse]], - )(using - startTime: ZonedDateTime, - log: Logger, - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) + )(using log: Logger): (EmServiceCore, Option[EmDataResponseMessageToExt]) /** Method to handle flex requests to the em agents. * @param flexRequest * That is sent to an agents. * @param receiver * Of the flex request. - * @param startTime - * The start time of the simulation. * @param log * Logger for logging messages. * @return @@ -224,10 +228,7 @@ trait EmServiceCore { def handleFlexRequest( flexRequest: FlexRequest, receiver: ActorRef[FlexRequest], - )(using - startTime: ZonedDateTime, - log: Logger, - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) + )(using log: Logger): (EmServiceCore, Option[EmDataResponseMessageToExt]) /** Method to add disaggregated flex options to given data. * @param flexOption @@ -236,7 +237,7 @@ trait EmServiceCore { * To derive the needed disaggregated data. */ final def addDisaggregatingFlexOptions( - flexOption: ExtendedFlexOptionsResult, + flexOption: FlexOptions, inferiorAgents: Set[UUID], ): Unit = { inferiorAgents.foreach { inferior => @@ -257,34 +258,31 @@ trait EmServiceCore { final def handleCompletion(tick: Long, completion: FlexCompletion): ( ReceiveDataMap[UUID, FlexCompletion], Option[EmDataResponseMessageToExt], + Option[Long], Boolean, ) = { val updated = completions.addData(completion.modelUuid, completion) if updated.isComplete then { - val allKeys = updated.receivedData.keySet - - val extMsgOption = if tick != INIT_SIM_TICK then { + val (extMsgOption, nextTickOption) = if tick != INIT_SIM_TICK then { // send completion message to external simulation, if we aren't in the INIT_SIM_TICK - Some(new EmCompletion(getMaybeNextTick)) - } else None + val option = getMaybeNextTick + + (Some(new EmCompletion(option)), option) + } else (None, None) // every em agent has sent a completion message - (ReceiveDataMap(allKeys), extMsgOption, true) + (updated, extMsgOption, nextTickOption, true) - } else (updated, None, false) + } else (updated, None, None, false) } /** Method to calculate the next tick option. * @return * An option for the next activation tick. */ - private final def getMaybeNextTick: java.util.Optional[java.lang.Long] = - completions.receivedData - .flatMap { case (_, completion) => - completion.requestAtTick - } - .minOption - .map(long2Long) - .toJava + final def getMaybeNextTick: Option[Long] = + completions.receivedData.flatMap { case (_, completion) => + completion.requestAtTick + }.minOption } diff --git a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala index ac077ad4f3..262c76f191 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -8,7 +8,6 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.connection.ExtEmDataConnection -import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException @@ -22,7 +21,10 @@ import edu.ie3.simona.service.ServiceStateData.{ ServiceBaseStateData, } import edu.ie3.simona.service.{ExtDataSupport, SimonaService} -import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import edu.ie3.simona.util.SimonaConstants.{ + FIRST_TICK_IN_SIMULATION, + INIT_SIM_TICK, +} import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} import org.slf4j.{Logger, LoggerFactory} @@ -115,17 +117,13 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { log.debug(s"Received response message: $scheduleFlexActivation") receiver match { - case uuid: UUID => + case _: UUID => log.debug(s"Unlocking msg: $scheduleFlexActivation") scheduleFlexActivation.scheduleKey.foreach(_.unlock()) case ref: ActorRef[EmAgent.Message] => log.debug(s"Forwarding the message to: $ref") ref ! scheduleFlexActivation - - case _ => - // this should not happen - log.warn(s"No receiver found for msg: $serviceResponse") } } @@ -134,10 +132,7 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { )(using log: Logger): Try[(ExtEmDataStateData, Option[Long])] = initServiceData match { case InitExtEmData(extEmDataConnection, startTime) => - val serviceCore = extEmDataConnection.mode match { - case EmMode.BASE => - EmServiceBaseCore.empty - } + val serviceCore = EmServiceBaseCore() val emDataInitializedStateData = ExtEmDataStateData(extEmDataConnection, startTime, serviceCore) @@ -215,9 +210,7 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { ) val (updatedCore, msgToExt) = - serviceStateData.serviceCore.handleExtMessage(tick, extMsg)(using - ctx.log - ) + serviceStateData.serviceCore.handleExtMessage(tick, extMsg) msgToExt.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) @@ -246,18 +239,22 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { } override protected def handleDataResponseMessage( - extResponseMsg: ServiceResponseMessage + extResponseMsg: ServiceResponseMessage, + ctx: ActorContext[Message], )(using serviceStateData: ExtEmDataStateData ): ExtEmDataStateData = { + val tick = serviceStateData.tick val (updatedCore, extMsg) = serviceStateData.serviceCore.handleDataResponseMessage( - serviceStateData.tick, + tick, extResponseMsg, )(using serviceStateData.startTime, log) - extMsg.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) + if tick >= FIRST_TICK_IN_SIMULATION then { + extMsg.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) + } serviceStateData.copy(serviceCore = updatedCore) } diff --git a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala index 83d4f07e53..94c500d51c 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -333,7 +333,8 @@ object ExtEvDataService extends SimonaService with ExtDataSupport { } override protected def handleDataResponseMessage( - extResponseMsg: ServiceResponseMessage + extResponseMsg: ServiceResponseMessage, + ctx: ActorContext[Message], )(using serviceStateData: ExtEvStateData): ExtEvStateData = { extResponseMsg match { case DepartingEvsResponse(evcs, evModels) => diff --git a/src/main/scala/edu/ie3/simona/util/CollectionUtils.scala b/src/main/scala/edu/ie3/simona/util/CollectionUtils.scala index e84e14efa1..c70025707d 100644 --- a/src/main/scala/edu/ie3/simona/util/CollectionUtils.scala +++ b/src/main/scala/edu/ie3/simona/util/CollectionUtils.scala @@ -10,9 +10,22 @@ import squants.Quantity import scala.annotation.tailrec import scala.collection.immutable.HashSet +import scala.math.Ordering.Double +import scala.jdk.CollectionConverters.{SeqHasAsJava, MapHasAsJava} object CollectionUtils { + /** Extension to convert a map with nested collection to java. The nested + * collection will be converted to lists. + */ + extension [K, V](scalaMap: Map[K, Iterable[V]]) { + def asJava: java.util.Map[K, java.util.List[V]] = { + scalaMap.map { case (key, value) => + key -> value.toList.asJava + }.asJava + } + } + /** fast implementation to test if a list contains duplicates. See * https://stackoverflow.com/questions/3871491/functional-programming-does-a-list-only-contain-unique-items * for details diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala index 82c21534b0..ed8bbfeb82 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala @@ -46,6 +46,9 @@ final case class ReceiveDataMap[K, V]( def addExpectedKeys(keys: Set[K]): ReceiveDataMap[K, V] = copy(expectedKeys = expectedKeys ++ keys) + def addExpectedKey(key: K): ReceiveDataMap[K, V] = + copy(expectedKeys = expectedKeys + key) + def getExpectedKeys: Set[K] = expectedKeys } diff --git a/src/test/java/edu/ie3/simona/test/common/model/MockEvModel.java b/src/test/java/edu/ie3/simona/test/common/model/MockEvModel.java index dba782a611..d213214e1e 100644 --- a/src/test/java/edu/ie3/simona/test/common/model/MockEvModel.java +++ b/src/test/java/edu/ie3/simona/test/common/model/MockEvModel.java @@ -20,6 +20,7 @@ public class MockEvModel implements EvModel { private final String id; private final ComparableQuantity sRatedAC; private final ComparableQuantity sRatedDC; + private final double cosPhi; private final ComparableQuantity eStorage; private final ComparableQuantity storedEnergy; private final Long departureTick; @@ -36,6 +37,7 @@ public MockEvModel( this.id = id; this.sRatedAC = sRatedAC; this.sRatedDC = sRatedDC; + this.cosPhi = 1d; this.eStorage = eStorage; this.storedEnergy = storedEnergy; this.departureTick = departureTick; @@ -52,6 +54,7 @@ public MockEvModel( this.id = id; this.sRatedAC = sRatedAC; this.sRatedDC = sRatedDC; + this.cosPhi = 1d; this.eStorage = eStorage; this.storedEnergy = Quantities.getQuantity(0d, PowerSystemUnits.KILOWATTHOUR); this.departureTick = departureTick; @@ -68,7 +71,7 @@ public String getId() { } @Override - public ComparableQuantity getPRatedAC() { + public ComparableQuantity getSRatedAC() { return sRatedAC; } @@ -77,6 +80,11 @@ public ComparableQuantity getPRatedDC() { return sRatedDC; } + @Override + public double getCosPhiRated() { + return cosPhi; + } + @Override public ComparableQuantity getEStorage() { return eStorage; diff --git a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala index 6d94f1fc15..b515009dc5 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala @@ -188,7 +188,7 @@ class EvcsModelSpec actualEv.uuid shouldBe ev.uuid actualEv.id shouldBe ev.id - actualEv.pRatedAc shouldBe ev.pRatedAc + actualEv.sRatedAc shouldBe ev.sRatedAc actualEv.pRatedDc shouldBe ev.pRatedDc actualEv.eStorage shouldBe ev.eStorage actualEv.storedEnergy should approximate( diff --git a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsPowerLimitFlexModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsPowerLimitFlexModelSpec.scala index 8db09548fb..a22fb282ab 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsPowerLimitFlexModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsPowerLimitFlexModelSpec.scala @@ -259,7 +259,7 @@ class EvcsPowerLimitFlexModelSpec ) => refPower should approximate(Kilowatts(5.0)) // one hour left minPower should approximate(Kilowatts(0d)) // no v2g allowed! - maxPower should approximate(ev1.pRatedAc) + maxPower should approximate(ev1.sRatedAc) } } diff --git a/src/test/scala/edu/ie3/simona/model/participant/evcs/MaximumPowerChargingSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/evcs/MaximumPowerChargingSpec.scala index 27169eb721..737c0cd715 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/evcs/MaximumPowerChargingSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/evcs/MaximumPowerChargingSpec.scala @@ -75,7 +75,7 @@ class MaximumPowerChargingSpec extends UnitSpec with TableDrivenPropertyChecks { ) chargingMap shouldBe Map( - ev.uuid -> ev.pRatedAc + ev.uuid -> ev.sRatedAc ) } diff --git a/src/test/scala/edu/ie3/simona/service/em/EmServiceBaseCoreSpec.scala b/src/test/scala/edu/ie3/simona/service/em/EmServiceBaseCoreSpec.scala index 4fda181cb4..98ea2a50d5 100644 --- a/src/test/scala/edu/ie3/simona/service/em/EmServiceBaseCoreSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/EmServiceBaseCoreSpec.scala @@ -7,11 +7,8 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.model.em.EmSetPoint -import edu.ie3.simona.api.ontology.em.{ - ProvideEmSetPointData, - RequestEmFlexResults, -} +import edu.ie3.simona.api.data.model.em.{EmSetPoint, FlexOptionRequest} +import edu.ie3.simona.api.ontology.em.ProvideEmData import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration import edu.ie3.simona.ontology.messages.flex.FlexType.PowerLimit import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ @@ -26,19 +23,20 @@ import edu.ie3.simona.ontology.messages.flex.{ } import edu.ie3.simona.test.common.{ConfigTestData, UnitSpec} import edu.ie3.simona.util.ReceiveDataMap -import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK +import edu.ie3.simona.api.data.model.em +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe, } import org.slf4j.{Logger, LoggerFactory} -import edu.ie3.util.quantities.QuantityUtils.asKiloWatt +import edu.ie3.util.quantities.QuantityUtils.{asKiloWatt, asMegaWatt} import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import squants.energy.{Kilowatts, Power, Watts} import java.time.ZonedDateTime -import java.util.{Optional, UUID} -import scala.jdk.CollectionConverters.{MapHasAsJava, SeqHasAsJava} +import java.util.UUID +import scala.jdk.CollectionConverters.MapHasAsJava class EmServiceBaseCoreSpec extends ScalaTestWithActorTestKit @@ -52,7 +50,7 @@ class EmServiceBaseCoreSpec given Power = Watts(1e-3) "handle registration of parentless em agent correctly" in { - val emptyCore = EmServiceBaseCore.empty + val emptyCore = EmServiceBaseCore() val emAgent = TestProbe[EmAgent.Message]("emAgent").ref val emUuid = UUID.randomUUID() @@ -61,12 +59,12 @@ class EmServiceBaseCoreSpec EmServiceRegistration(emAgent, emUuid, None, None) ) - updatedCore.lastFinishedTick shouldBe PRE_INIT_TICK + updatedCore.lastFinishedTick shouldBe INIT_SIM_TICK updatedCore.uuidToAgent shouldBe Map(emUuid -> emAgent) updatedCore.flexOptions shouldBe ReceiveDataMap.empty updatedCore.allFlexOptions shouldBe empty updatedCore.completions shouldBe ReceiveDataMap(Set(emUuid)) - updatedCore.structure shouldBe Map(emUuid -> Set.empty) + updatedCore.uuidToInferior shouldBe Map.empty updatedCore.disaggregated shouldBe Map.empty updatedCore.sendOptionsToExt shouldBe false updatedCore.canHandleSetPoints shouldBe false @@ -74,7 +72,7 @@ class EmServiceBaseCoreSpec } "handle registration of em agent with parent correctly" in { - val emptyCore = EmServiceBaseCore.empty + val emptyCore = EmServiceBaseCore() val emAgent = TestProbe[EmAgent.Message]("emAgent").ref val emUuid = UUID.randomUUID() @@ -91,15 +89,12 @@ class EmServiceBaseCoreSpec ) ) - updatedCore.lastFinishedTick shouldBe PRE_INIT_TICK + updatedCore.lastFinishedTick shouldBe INIT_SIM_TICK updatedCore.uuidToAgent shouldBe Map(emUuid -> emAgent) updatedCore.flexOptions shouldBe ReceiveDataMap.empty updatedCore.allFlexOptions shouldBe empty updatedCore.completions shouldBe ReceiveDataMap(Set(emUuid)) - updatedCore.structure shouldBe Map( - emUuid -> Set.empty, - parentEmUuid -> Set(emUuid), - ) + updatedCore.uuidToInferior shouldBe Map(parentEmUuid -> Set(emUuid)) updatedCore.disaggregated shouldBe Map.empty updatedCore.sendOptionsToExt shouldBe false updatedCore.canHandleSetPoints shouldBe false @@ -114,11 +109,15 @@ class EmServiceBaseCoreSpec EmServiceRegistration(emAgent.ref, emUuid, None, None) ) - val (updatedCore, msgToExt) = core.handleExtMessage( + val flexRequests = new ProvideEmData( 0L, - RequestEmFlexResults(0L, List(emUuid).asJava, true), + Map(emUuid -> new FlexOptionRequest(emUuid, true)).asJava, + Map.empty.asJava, + Map.empty.asJava, ) + val (updatedCore, msgToExt) = core.handleExtMessage(0L, flexRequests) + // the agent should receive a flex option request emAgent.expectMessage(FlexActivation(0L, PowerLimit)) @@ -131,7 +130,7 @@ class EmServiceBaseCoreSpec updatedCore.flexOptions shouldBe ReceiveDataMap(Set(emUuid)) updatedCore.allFlexOptions shouldBe empty updatedCore.completions shouldBe ReceiveDataMap(Set(emUuid)) - updatedCore.structure shouldBe Map(emUuid -> Set.empty) + updatedCore.uuidToInferior shouldBe Map.empty updatedCore.disaggregated shouldBe Map( emUuid -> true ) // since we requested disaggregated flex options @@ -148,10 +147,13 @@ class EmServiceBaseCoreSpec EmServiceRegistration(emAgent.ref, emUuid, None, None) ) - val setPointData = ProvideEmSetPointData( + val setPoints = Map(emUuid -> new EmSetPoint(emUuid, 5.asKiloWatt)) + + val setPointData = new ProvideEmData( 0L, - Map(emUuid -> new EmSetPoint(emUuid, 5.asKiloWatt)).asJava, - Optional.of(900L), + Map.empty.asJava, + Map.empty.asJava, + setPoints.asJava, ) val (updatedCore, msgToExt) = core.handleExtMessage(0L, setPointData) @@ -169,12 +171,12 @@ class EmServiceBaseCoreSpec updatedCore.flexOptions shouldBe ReceiveDataMap(Set(emUuid)) updatedCore.allFlexOptions shouldBe empty updatedCore.completions shouldBe ReceiveDataMap(Set(emUuid)) - updatedCore.structure shouldBe Map(emUuid -> Set.empty) + updatedCore.uuidToInferior shouldBe Map.empty updatedCore.disaggregated shouldBe Map.empty updatedCore.sendOptionsToExt shouldBe false // since we didn't receive a flex option request updatedCore.canHandleSetPoints shouldBe false updatedCore.setPointOption shouldBe Some( - setPointData + setPoints ) // save the set point data until we can handle it // handle flex options @@ -204,11 +206,19 @@ class EmServiceBaseCoreSpec emUuid -> emAgent.ref ) coreAfterFlexOptionProvision.flexOptions shouldBe ReceiveDataMap.empty // empty, since we received all flex options - coreAfterFlexOptionProvision.allFlexOptions shouldBe empty + coreAfterFlexOptionProvision.allFlexOptions shouldBe Map( + emUuid -> new em.PowerLimitFlexOptions( + emUuid, + emUuid, + 0.asMegaWatt, + 0.asMegaWatt, + 0.asMegaWatt, + ) + ) coreAfterFlexOptionProvision.completions shouldBe ReceiveDataMap( Set(emUuid) ) - coreAfterFlexOptionProvision.structure shouldBe Map(emUuid -> Set.empty) + coreAfterFlexOptionProvision.uuidToInferior shouldBe Map.empty coreAfterFlexOptionProvision.disaggregated shouldBe Map.empty coreAfterFlexOptionProvision.sendOptionsToExt shouldBe false // since we didn't receive a flex option request coreAfterFlexOptionProvision.canHandleSetPoints shouldBe true // since all agents have provided flex options @@ -217,13 +227,18 @@ class EmServiceBaseCoreSpec "handle flex requests correctly" in { val emAgent = TestProbe[EmAgent.Message]("emAgent") - val core = EmServiceBaseCore.empty + val emUuid = UUID.randomUUID() + val core = EmServiceBaseCore().handleRegistration( + EmServiceRegistration(emAgent.ref, emUuid) + ) val msg = IssueNoControl(0L) val (updatedCore, msgToExt) = core.handleFlexRequest(msg, emAgent.ref) // since we don't update the core - updatedCore shouldBe core + updatedCore shouldBe core.copy(completions = + core.completions.addExpectedKey(emUuid) + ) // we should have no message for the external simulation msgToExt shouldBe None diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala index ddf67ab947..167a8ef88b 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -9,9 +9,14 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.connection.ExtEmDataConnection import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode -import edu.ie3.simona.api.data.model.em.{EmSetPoint, ExtendedFlexOptionsResult} +import edu.ie3.simona.api.data.model.em.{ + EmSetPoint, + ExtendedFlexOptionsResult, + FlexOptionRequest, +} import edu.ie3.simona.api.ontology.ScheduleDataServiceMessage import edu.ie3.simona.api.ontology.em.* +import edu.ie3.simona.api.data.model.em import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, @@ -51,6 +56,7 @@ import java.util.UUID import scala.concurrent.duration.DurationInt import scala.jdk.CollectionConverters.* import scala.jdk.OptionConverters.RichOption +import edu.ie3.simona.util.CollectionUtils.asJava class ExtEmDataServiceSpec extends ScalaTestWithActorTestKit @@ -264,10 +270,13 @@ class ExtEmDataServiceSpec // scheduler.expectMessage(Completion(emService)) extEmDataConnection.sendExtMsg( - new RequestEmFlexResults( - 0, - List(emAgent1UUID).asJava, - false, + new ProvideEmData( + 0L, + Map( + emAgent1UUID -> new FlexOptionRequest(emAgent1UUID, false) + ).asJava, + Map.empty.asJava, + Map.empty.asJava, ) ) @@ -303,12 +312,14 @@ class ExtEmDataServiceSpec extEmDataConnection.receiveTriggerQueue .take() shouldBe new FlexOptionsResponse( Map( - emAgent1UUID -> new ExtendedFlexOptionsResult( - simulationStart, - emAgent1UUID, - 0.asMegaWatt, - 0.005.asMegaWatt, - 0.01.asMegaWatt, + emAgent1UUID -> List( + new em.PowerLimitFlexOptions( + emAgent1UUID, + emAgent1UUID, + 0.asMegaWatt, + 0.005.asMegaWatt, + 0.01.asMegaWatt, + ) ) ).asJava ) @@ -373,8 +384,10 @@ class ExtEmDataServiceSpec ) extEmDataConnection.sendExtMsg( - new ProvideEmSetPointData( + new ProvideEmData( 0, + Map.empty.asJava, + Map.empty.asJava, Map( emAgent1UUID -> new EmSetPoint( emAgent1UUID, @@ -385,7 +398,6 @@ class ExtEmDataServiceSpec 0d.asKiloWatt, ), ).asJava, - None.toJava, ) ) From 228f9427dc916f333b955cd219bca0da7bf6e889 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 10 Dec 2025 10:24:05 +0100 Subject: [PATCH 3/4] Including reviewer's comments. --- .../simona/model/participant/evcs/EvModelWrapper.scala | 10 ++++++++-- .../participant/evcs/EvcsChargingProperties.scala | 2 +- .../edu/ie3/simona/service/em/EmServiceBaseCore.scala | 5 +++-- .../edu/ie3/simona/test/common/model/MockEvModel.java | 4 ++++ .../simona/model/participant/evcs/EvcsModelSpec.scala | 2 ++ .../participant/evcs/EvcsPowerLimitFlexModelSpec.scala | 2 +- .../participant/evcs/MaximumPowerChargingSpec.scala | 2 +- 7 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/evcs/EvModelWrapper.scala b/src/main/scala/edu/ie3/simona/model/participant/evcs/EvModelWrapper.scala index 98f94a42cc..3e082a0fda 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/evcs/EvModelWrapper.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/evcs/EvModelWrapper.scala @@ -9,7 +9,11 @@ package edu.ie3.simona.model.participant.evcs import edu.ie3.simona.api.data.model.ev.EvModel import edu.ie3.util.quantities.PowerSystemUnits.* import edu.ie3.util.quantities.QuantityUtils.asKiloWattHour -import edu.ie3.util.scala.quantities.QuantityConversionUtils.toSquants +import edu.ie3.util.scala.quantities.ApparentPower +import edu.ie3.util.scala.quantities.QuantityConversionUtils.{ + toApparent, + toSquants, +} import squants.Power import squants.energy.{Energy, KilowattHours} @@ -35,7 +39,9 @@ final case class EvModelWrapper( def uuid: UUID = original.getUuid def id: String = original.getId - lazy val sRatedAc: Power = original.getSRatedAC.toSquants + lazy val cosPhi: Double = original.getCosPhiRated + lazy val sRatedAc: ApparentPower = original.getSRatedAC.toApparent + lazy val pRatedAc: Power = original.getPRatedDC.toSquants lazy val pRatedDc: Power = original.getPRatedDC.toSquants lazy val eStorage: Energy = original.getEStorage.toSquants diff --git a/src/main/scala/edu/ie3/simona/model/participant/evcs/EvcsChargingProperties.scala b/src/main/scala/edu/ie3/simona/model/participant/evcs/EvcsChargingProperties.scala index f85502e89f..024f847e62 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/evcs/EvcsChargingProperties.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/evcs/EvcsChargingProperties.scala @@ -39,7 +39,7 @@ trait EvcsChargingProperties { ): Power = { val evPower = currentType match { case ElectricCurrentType.AC => - ev.sRatedAc + ev.pRatedAc case ElectricCurrentType.DC => ev.pRatedDc } diff --git a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala index fd18315b91..4ff7722b4e 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -41,9 +41,10 @@ import scala.jdk.CollectionConverters.MapHasAsScala * @param nextActivation * A map: uuid to next activation tick. * @param allFlexOptions - * Map: uuid to flex option result. + * Map: uuid to flex options. This map stores all flex options received for + * the current tick. * @param flexOptions - * ReceiveDataMap: uuid to flex option result. + * ReceiveDataMap: uuid to flex option. * @param disaggregated * A map: uuid of em agent to boolean. It defines for which em agent we * should return disaggregated flex options. diff --git a/src/test/java/edu/ie3/simona/test/common/model/MockEvModel.java b/src/test/java/edu/ie3/simona/test/common/model/MockEvModel.java index d213214e1e..56724b54c5 100644 --- a/src/test/java/edu/ie3/simona/test/common/model/MockEvModel.java +++ b/src/test/java/edu/ie3/simona/test/common/model/MockEvModel.java @@ -75,6 +75,10 @@ public ComparableQuantity getSRatedAC() { return sRatedAC; } + public ComparableQuantity getPRatedAC() { + return sRatedAC.multiply(cosPhi); + } + @Override public ComparableQuantity getPRatedDC() { return sRatedDC; diff --git a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala index b515009dc5..7e35c9a5ad 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala @@ -188,7 +188,9 @@ class EvcsModelSpec actualEv.uuid shouldBe ev.uuid actualEv.id shouldBe ev.id + actualEv.cosPhi shouldBe ev.cosPhi actualEv.sRatedAc shouldBe ev.sRatedAc + actualEv.pRatedAc shouldBe ev.pRatedAc actualEv.pRatedDc shouldBe ev.pRatedDc actualEv.eStorage shouldBe ev.eStorage actualEv.storedEnergy should approximate( diff --git a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsPowerLimitFlexModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsPowerLimitFlexModelSpec.scala index a22fb282ab..8db09548fb 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsPowerLimitFlexModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsPowerLimitFlexModelSpec.scala @@ -259,7 +259,7 @@ class EvcsPowerLimitFlexModelSpec ) => refPower should approximate(Kilowatts(5.0)) // one hour left minPower should approximate(Kilowatts(0d)) // no v2g allowed! - maxPower should approximate(ev1.sRatedAc) + maxPower should approximate(ev1.pRatedAc) } } diff --git a/src/test/scala/edu/ie3/simona/model/participant/evcs/MaximumPowerChargingSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/evcs/MaximumPowerChargingSpec.scala index 737c0cd715..27169eb721 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/evcs/MaximumPowerChargingSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/evcs/MaximumPowerChargingSpec.scala @@ -75,7 +75,7 @@ class MaximumPowerChargingSpec extends UnitSpec with TableDrivenPropertyChecks { ) chargingMap shouldBe Map( - ev.uuid -> ev.sRatedAc + ev.uuid -> ev.pRatedAc ) } From 885aa4f78ff866f441262b24bee7bea92098ad90 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 10 Dec 2025 10:53:25 +0100 Subject: [PATCH 4/4] Fixing failing tests. --- .../edu/ie3/simona/model/participant/evcs/EvModelWrapper.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/evcs/EvModelWrapper.scala b/src/main/scala/edu/ie3/simona/model/participant/evcs/EvModelWrapper.scala index 3e082a0fda..9599170173 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/evcs/EvModelWrapper.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/evcs/EvModelWrapper.scala @@ -41,7 +41,7 @@ final case class EvModelWrapper( lazy val cosPhi: Double = original.getCosPhiRated lazy val sRatedAc: ApparentPower = original.getSRatedAC.toApparent - lazy val pRatedAc: Power = original.getPRatedDC.toSquants + lazy val pRatedAc: Power = sRatedAc.toActivePower(cosPhi) lazy val pRatedDc: Power = original.getPRatedDC.toSquants lazy val eStorage: Energy = original.getEStorage.toSquants