From e316d887809d16421473cd96bb26e8bfa16edb91 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 24 Feb 2025 11:39:41 +0100 Subject: [PATCH 001/125] Adding services. --- .../event/listener/DelayedStopHelper.scala | 2 + .../messages/flex/FlexibilityMessage.scala | 6 + .../messages/services/EvMessage.scala | 7 +- .../messages/services/ServiceMessage.scala | 20 +- .../ie3/simona/service/ExtDataSupport.scala | 3 +- .../simona/service/em/ExtEmDataService.scala | 279 +++++++++++++ .../simona/service/ev/ExtEvDataService.scala | 7 +- .../primary/ExtPrimaryDataService.scala | 235 +++++++++++ .../service/primary/PrimaryServiceProxy.scala | 90 +++- .../results/ExtResultDataProvider.scala | 388 ++++++++++++++++++ .../ie3/simona/sim/setup/ExtSimSetup.scala | 160 +++++++- .../sim/setup/SimonaStandaloneSetup.scala | 1 + .../edu/ie3/simona/util/ReceiveDataMap.scala | 2 + .../primary/ExtPrimaryDataServiceSpec.scala | 125 ++++++ .../primary/PrimaryServiceProxySpec.scala | 51 ++- .../primary/PrimaryServiceProxySqlIT.scala | 1 + 16 files changed, 1341 insertions(+), 36 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala create mode 100644 src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala create mode 100644 src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala create mode 100644 src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala diff --git a/src/main/scala/edu/ie3/simona/event/listener/DelayedStopHelper.scala b/src/main/scala/edu/ie3/simona/event/listener/DelayedStopHelper.scala index c6d8cea4d5..3c3a84f3ca 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/DelayedStopHelper.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/DelayedStopHelper.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.event.listener +import edu.ie3.simona.service.results.ExtResultDataProvider import org.apache.pekko.actor.typed.Behavior import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} @@ -23,6 +24,7 @@ object DelayedStopHelper { sealed trait StoppingMsg extends ResultEventListener.Request with RuntimeEventListener.Request + with ExtResultDataProvider.Request /** Message indicating that [[RuntimeEventListener]] should stop. Instead of * using [[org.apache.pekko.actor.typed.scaladsl.ActorContext.stop]], this diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala index 58b95e3e1d..8a8e93bc6e 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala @@ -37,6 +37,12 @@ object FlexibilityMessage { val modelUuid: UUID } + final case class SetPointFlexRequest( + tick: Long, + setPower: Power, + nextSetPointTick: Option[Long], + ) extends FlexRequest + /** Message that registers a controlled asset model with an * [[edu.ie3.simona.agent.em.EmAgent]]. * diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/EvMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/EvMessage.scala index 0c1cca56ba..b93c33e55d 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/EvMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/EvMessage.scala @@ -8,7 +8,10 @@ package edu.ie3.simona.ontology.messages.services import edu.ie3.simona.agent.participant.data.Data.SecondaryData import edu.ie3.simona.model.participant.evcs.EvModelWrapper -import edu.ie3.simona.ontology.messages.services.ServiceMessage.ServiceRegistrationMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + DataResponseMessage, + ServiceRegistrationMessage, +} import java.util.UUID @@ -54,7 +57,7 @@ object EvMessage { arrivals: Seq[EvModelWrapper] ) extends EvData {} - trait EvResponseMessage extends EvMessage + trait EvResponseMessage extends EvMessage with DataResponseMessage final case class FreeLotsResponse( evcs: UUID, diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala index 43ba7240cd..4840bff37d 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala @@ -6,10 +6,13 @@ package edu.ie3.simona.ontology.messages.services -import org.apache.pekko.actor.ActorRef +import edu.ie3.simona.agent.em.EmAgent + +import org.apache.pekko.actor.{ActorRef => ClassicRef} +import org.apache.pekko.actor.typed.ActorRef import java.util.UUID -import edu.ie3.simona.agent.participant.data.Data +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.FlexRequest import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey /** Collections of all messages, that are send to and from the different @@ -23,13 +26,15 @@ object ServiceMessage { */ trait ServiceRegistrationMessage extends ServiceMessage + trait DataResponseMessage extends ServiceMessage + /** Message to register with a primary data service. * * @param inputModelUuid * Identifier of the input model */ final case class PrimaryServiceRegistrationMessage( - requestingActor: ActorRef, + requestingActor: ClassicRef, inputModelUuid: UUID, ) extends ServiceRegistrationMessage @@ -40,12 +45,17 @@ object ServiceMessage { * @param requestingActor * Reference to the requesting actor */ - final case class WorkerRegistrationMessage(requestingActor: ActorRef) + final case class WorkerRegistrationMessage(requestingActor: ClassicRef) extends ServiceRegistrationMessage + final case class ExtEmDataServiceRegistrationMessage( + modelUuid: UUID, + requestingActor: ActorRef[EmAgent.Request], + flexAdapter: ActorRef[FlexRequest], + ) extends ServiceRegistrationMessage + final case class ScheduleServiceActivation( tick: Long, unlockKey: ScheduleKey, ) - } diff --git a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala index 31800da210..339f684101 100644 --- a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala +++ b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala @@ -8,6 +8,7 @@ package edu.ie3.simona.service import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.ontology.messages.services.EvMessage.EvResponseMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage.DataResponseMessage import edu.ie3.simona.service.ServiceStateData.ServiceBaseStateData trait ExtDataSupport[ @@ -49,6 +50,6 @@ trait ExtDataSupport[ * the updated state data */ protected def handleDataResponseMessage( - extResponseMsg: EvResponseMessage + extResponseMsg: DataResponseMessage )(implicit serviceStateData: S): S } diff --git a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala new file mode 100644 index 0000000000..c966215e20 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -0,0 +1,279 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.em + +import edu.ie3.datamodel.models.value.PValue +import edu.ie3.simona.agent.em.EmAgent +import edu.ie3.simona.agent.participant2.ParticipantAgent.{ + RegistrationResponseMessage, + RegistrationSuccessfulMessage, +} +import edu.ie3.simona.api.data.em.ExtEmDataConnection +import edu.ie3.simona.api.data.em.ontology.{ + EmDataMessageFromExt, + ProvideEmSetPointData, +} +import edu.ie3.simona.api.data.ontology.DataMessageFromExt +import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException +import edu.ie3.simona.exceptions.{InitializationException, ServiceException} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ + FlexRequest, + IssuePowerControl, + SetPointFlexRequest, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + DataResponseMessage, + ExtEmDataServiceRegistrationMessage, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, +} +import edu.ie3.simona.service.em.ExtEmDataService.{ + ExtEmDataStateData, + InitExtEmData, + WrappedRegistrationResponse, +} +import edu.ie3.simona.service.{ExtDataSupport, SimonaService} +import org.apache.pekko.actor.typed.ActorRef +import org.apache.pekko.actor.{ActorContext, Props, ActorRef => ClassicRef} +import squants.Power +import squants.energy.Kilowatts + +import java.util.UUID +import scala.jdk.CollectionConverters.{CollectionHasAsScala, MapHasAsScala} +import scala.jdk.OptionConverters.RichOptional +import scala.util.{Failure, Success, Try} + +object ExtEmDataService { + + def props(scheduler: ClassicRef): Props = + Props( + new ExtEmDataService(scheduler: ClassicRef) + ) + + final case class ExtEmDataStateData( + extEmData: ExtEmDataConnection, + subscribers: List[UUID] = List.empty, + uuidToActorRef: Map[UUID, ActorRef[EmAgent.Request]] = + Map.empty[UUID, ActorRef[EmAgent.Request]], // subscribers in SIMONA + uuidToAdapterRef: Map[UUID, ActorRef[FlexRequest]] = + Map.empty[UUID, ActorRef[FlexRequest]], // subscribers in SIMONA + extEmDataMessage: Option[EmDataMessageFromExt] = None, + ) extends ServiceBaseStateData + + case class InitExtEmData( + extEmData: ExtEmDataConnection + ) extends InitializeServiceStateData + + final case class WrappedIssuePowerControl( + issuePowerControl: IssuePowerControl + ) extends EmAgent.Request + + final case class WrappedRegistrationResponse( + registrationResponse: RegistrationResponseMessage + ) extends EmAgent.Request +} + +final case class ExtEmDataService( + override val scheduler: ClassicRef +) extends SimonaService[ExtEmDataStateData](scheduler) + with ExtDataSupport[ExtEmDataStateData] { + + /** Initialize the concrete service implementation using the provided + * initialization data. This method should perform all heavyweight tasks + * before the actor becomes ready. The return values are a) the state data of + * the initialized service and b) optional triggers that should be send to + * the [[edu.ie3.simona.scheduler.Scheduler]] together with the completion + * message that is send in response to the trigger that is send to start the + * initialization process + * + * @param initServiceData + * the data that should be used for initialization + * @return + * the state data of this service and optional tick that should be included + * in the completion message + */ + override def init( + initServiceData: InitializeServiceStateData + ): Try[(ExtEmDataStateData, Option[Long])] = initServiceData match { + case InitExtEmData(extEmData) => + val emDataInitializedStateData = ExtEmDataStateData( + extEmData, + subscribers = extEmData.getControlledEms.asScala.toList, + ) + Success( + emDataInitializedStateData, + None, + ) + + case invalidData => + Failure( + new InitializationException( + s"Provided init data '${invalidData.getClass.getSimpleName}' for ExtEmDataService are invalid!" + ) + ) + } + + /** Handle a request to register for information from this service + * + * @param registrationMessage + * registration message to handle + * @param serviceStateData + * current state data of the actor + * @return + * the service stata data that should be used in the next state (normally + * with updated values) + */ + override protected def handleRegistrationRequest( + registrationMessage: ServiceMessage.ServiceRegistrationMessage + )(implicit serviceStateData: ExtEmDataStateData): Try[ExtEmDataStateData] = + registrationMessage match { + case ExtEmDataServiceRegistrationMessage( + modelUuid, + requestingActor, + flexAdapter, + ) => + Success( + handleEmRegistrationRequest(modelUuid, requestingActor, flexAdapter) + ) + case invalidMessage => + Failure( + InvalidRegistrationRequestException( + s"A primary service provider is not able to handle registration request '$invalidMessage'." + ) + ) + } + + private def handleEmRegistrationRequest( + modelUuid: UUID, + modelActorRef: ActorRef[EmAgent.Request], + flexAdapterRef: ActorRef[FlexRequest], + )(implicit serviceStateData: ExtEmDataStateData): ExtEmDataStateData = { + if (serviceStateData.subscribers.contains(modelUuid)) { + modelActorRef ! WrappedRegistrationResponse( + RegistrationSuccessfulMessage(self, 0L) + ) + serviceStateData.copy( + uuidToActorRef = + serviceStateData.uuidToActorRef + (modelUuid -> modelActorRef), + uuidToAdapterRef = + serviceStateData.uuidToAdapterRef + (modelUuid -> flexAdapterRef), + ) + } else { + serviceStateData + } + } + + /** Send out the information to all registered recipients + * + * @param tick + * current tick data should be announced for + * @param serviceStateData + * the current state data of this service + * @return + * the service stata data that should be used in the next state (normally + * with updated values) together with the completion message that is send + * in response to the trigger that was sent to start this announcement + */ + override protected def announceInformation(tick: Long)(implicit + serviceStateData: ExtEmDataStateData, + ctx: ActorContext, + ): (ExtEmDataStateData, Option[Long]) = { + serviceStateData.extEmDataMessage.getOrElse( + throw ServiceException( + "ExtPrimaryDataService was triggered without ExtEmDataMessage available" + ) + ) match { + case providedEmData: ProvideEmSetPointData => + announceEmData(tick, providedEmData)( + serviceStateData, + ctx, + ) + } + } + + private def announceEmData( + tick: Long, + emDataMessage: ProvideEmSetPointData, + )(implicit serviceStateData: ExtEmDataStateData, ctx: ActorContext): ( + ExtEmDataStateData, + Option[Long], + ) = { + val actorToEmData = emDataMessage.emData.asScala.flatMap { + case (agent, emDataPerAgent) => + serviceStateData.uuidToAdapterRef + .get(agent) + .map((_, convertToSetPoint(emDataPerAgent))) + .orElse { + log.warning( + "A corresponding actor ref for UUID {} could not be found", + agent, + ) + None + } + } + + val maybeNextTick = emDataMessage.maybeNextTick.toScala.map(Long2long) + + if (actorToEmData.nonEmpty) { + actorToEmData.foreach { case (actor, setPoint) => + actor ! SetPointFlexRequest( + tick, + setPoint, + maybeNextTick, + ) + } + } + ( + serviceStateData.copy(extEmDataMessage = None), + None, + ) + } + + private def convertToSetPoint( + value: PValue + ): Power = { + Kilowatts(value.getP.get.getValue.doubleValue()) + } + + /** Handle a message from outside the simulation + * + * @param extMsg + * the external incoming message + * @param serviceStateData + * the current state data of this service + * @return + * the updated state data + */ + override protected def handleDataMessage( + extMsg: DataMessageFromExt + )(implicit serviceStateData: ExtEmDataStateData): ExtEmDataStateData = { + extMsg match { + case extEmDataMessage: EmDataMessageFromExt => + serviceStateData.copy( + extEmDataMessage = Some(extEmDataMessage) + ) + } + } + + /** Handle a message from inside SIMONA sent to external + * + * @param extResponseMsg + * the external incoming message + * @param serviceStateData + * the current state data of this service + * @return + * the updated state data + */ + override protected def handleDataResponseMessage( + extResponseMsg: DataResponseMessage + )(implicit + serviceStateData: ExtEmDataStateData + ): ExtEmDataStateData = serviceStateData +} 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 a777cdfc07..b8d3790511 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -22,7 +22,10 @@ import edu.ie3.simona.exceptions.{ } import edu.ie3.simona.model.participant.evcs.EvModelWrapper import edu.ie3.simona.ontology.messages.services.EvMessage._ -import edu.ie3.simona.ontology.messages.services.ServiceMessage.ServiceRegistrationMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + DataResponseMessage, + ServiceRegistrationMessage, +} import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceBaseStateData, @@ -351,7 +354,7 @@ class ExtEvDataService(override val scheduler: ActorRef) } override protected def handleDataResponseMessage( - extResponseMsg: EvResponseMessage + extResponseMsg: DataResponseMessage )(implicit serviceStateData: ExtEvStateData): ExtEvStateData = { extResponseMsg match { case DepartingEvsResponse(evcs, evModels) => diff --git a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala new file mode 100644 index 0000000000..b1869bb4bf --- /dev/null +++ b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala @@ -0,0 +1,235 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.primary + +import edu.ie3.simona.agent.participant.data.Data.PrimaryData.RichValue +import edu.ie3.simona.agent.participant2.ParticipantAgent.{ + DataProvision, + RegistrationSuccessfulMessage, +} +import edu.ie3.simona.api.data.ontology.DataMessageFromExt +import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection +import edu.ie3.simona.api.data.primarydata.ontology.{ + PrimaryDataMessageFromExt, + ProvidePrimaryData, +} +import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException +import edu.ie3.simona.exceptions.{InitializationException, ServiceException} +import edu.ie3.simona.ontology.messages.services.ServiceMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + DataResponseMessage, + PrimaryServiceRegistrationMessage, +} +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, +} +import edu.ie3.simona.service.primary.ExtPrimaryDataService.{ + ExtPrimaryDataStateData, + InitExtPrimaryData, +} +import edu.ie3.simona.service.{ExtDataSupport, ServiceStateData, SimonaService} +import org.apache.pekko.actor.{ActorContext, ActorRef, Props} + +import java.util.UUID +import scala.jdk.CollectionConverters.MapHasAsScala +import scala.jdk.OptionConverters.RichOptional +import scala.util.{Failure, Success, Try} + +object ExtPrimaryDataService { + + def props(scheduler: ActorRef): Props = + Props( + new ExtPrimaryDataService(scheduler: ActorRef) + ) + + final case class ExtPrimaryDataStateData( + extPrimaryData: ExtPrimaryDataConnection, + subscribers: List[UUID] = List.empty, + uuidToActorRef: Map[UUID, ActorRef] = + Map.empty[UUID, ActorRef], // subscribers in SIMONA + extPrimaryDataMessage: Option[PrimaryDataMessageFromExt] = None, + maybeNextTick: Option[Long] = None, + ) extends ServiceBaseStateData + + case class InitExtPrimaryData( + extPrimaryData: ExtPrimaryDataConnection + ) extends InitializeServiceStateData + +} +final case class ExtPrimaryDataService( + override val scheduler: ActorRef +) extends SimonaService[ExtPrimaryDataStateData](scheduler) + with ExtDataSupport[ExtPrimaryDataStateData] { + + override def init( + initServiceData: ServiceStateData.InitializeServiceStateData + ): Try[(ExtPrimaryDataStateData, Option[Long])] = initServiceData match { + case InitExtPrimaryData(extPrimaryData) => + val primaryDataInitializedStateData = ExtPrimaryDataStateData( + extPrimaryData + ) + Success( + primaryDataInitializedStateData, + None, + ) + + case invalidData => + Failure( + new InitializationException( + s"Provided init data '${invalidData.getClass.getSimpleName}' for ExtPrimaryService are invalid!" + ) + ) + } + + override protected def handleRegistrationRequest( + registrationMessage: ServiceMessage.ServiceRegistrationMessage + )(implicit + serviceStateData: ExtPrimaryDataStateData + ): Try[ExtPrimaryDataStateData] = registrationMessage match { + case PrimaryServiceRegistrationMessage( + requestingActor, + modelUuid, + ) => + Success(handleRegistrationRequest(requestingActor, modelUuid)) + case invalidMessage => + Failure( + InvalidRegistrationRequestException( + s"A primary service provider is not able to handle registration request '$invalidMessage'." + ) + ) + } + + private def handleRegistrationRequest( + agentToBeRegistered: ActorRef, + agentUUID: UUID, + )(implicit + serviceStateData: ExtPrimaryDataStateData + ): ExtPrimaryDataStateData = { + serviceStateData.uuidToActorRef.get(agentUUID) match { + case None => + // Actor is not registered yet + agentToBeRegistered ! RegistrationSuccessfulMessage(self, 0L) + log.info(s"Successful registration for $agentUUID") + serviceStateData.copy( + uuidToActorRef = + serviceStateData.uuidToActorRef + (agentUUID -> agentToBeRegistered) + ) + case Some(_) => + // actor is already registered, do nothing + log.warning( + "Sending actor {} is already registered", + agentToBeRegistered, + ) + serviceStateData + } + } + + /** Send out the information to all registered recipients + * + * @param tick + * current tick data should be announced for + * @param serviceStateData + * the current state data of this service + * @return + * the service stata data that should be used in the next state (normally + * with updated values) together with the completion message that is send + * in response to the trigger that was sent to start this announcement + */ + override protected def announceInformation( + tick: Long + )(implicit + serviceStateData: ExtPrimaryDataStateData, + ctx: ActorContext, + ): (ExtPrimaryDataStateData, Option[Long]) = { // We got activated for this tick, so we expect incoming primary data + serviceStateData.extPrimaryDataMessage.getOrElse( + throw ServiceException( + "ExtPrimaryDataService was triggered without ExtPrimaryDataMessage available" + ) + ) match { + case providedPrimaryData: ProvidePrimaryData => + processDataAndAnnounce(tick, providedPrimaryData)( + serviceStateData, + ctx, + ) + } + } + + private def processDataAndAnnounce( + tick: Long, + primaryDataMessage: ProvidePrimaryData, + )(implicit + serviceStateData: ExtPrimaryDataStateData, + ctx: ActorContext, + ): ( + ExtPrimaryDataStateData, + Option[Long], + ) = { + log.debug(s"Got activation to distribute primaryData = $primaryDataMessage") + val actorToPrimaryData = primaryDataMessage.primaryData.asScala.flatMap { + case (agent, primaryDataPerAgent) => + serviceStateData.uuidToActorRef + .get(agent) + .map((_, primaryDataPerAgent)) + .orElse { + log.warning( + "A corresponding actor ref for UUID {} could not be found", + agent, + ) + None + } + } + + val maybeNextTick = primaryDataMessage.maybeNextTick.toScala.map(Long2long) + + // Distribute Primary Data + if (actorToPrimaryData.nonEmpty) { + actorToPrimaryData.foreach { case (actor, value) => + value.toPrimaryData match { + case Success(primaryData) => + actor ! DataProvision( + tick, + self, + primaryData, + maybeNextTick, + ) + case Failure(exception) => + /* Processing of data failed */ + log.warning( + "Unable to convert received value to primary data. Skipped that data." + + "\nException: {}", + exception, + ) + } + } + } + + ( + serviceStateData.copy(extPrimaryDataMessage = None), + None, + ) + } + + override protected def handleDataMessage( + extMsg: DataMessageFromExt + )(implicit + serviceStateData: ExtPrimaryDataStateData + ): ExtPrimaryDataStateData = { + extMsg match { + case extPrimaryDataMessage: PrimaryDataMessageFromExt => + serviceStateData.copy( + extPrimaryDataMessage = Some(extPrimaryDataMessage) + ) + } + } + + override protected def handleDataResponseMessage( + extResponseMsg: DataResponseMessage + )(implicit + serviceStateData: ExtPrimaryDataStateData + ): ExtPrimaryDataStateData = serviceStateData +} diff --git a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala index 67b8ddbffb..e406ac6a1d 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala @@ -6,8 +6,6 @@ package edu.ie3.simona.service.primary -import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps -import org.apache.pekko.actor.{Actor, ActorRef, PoisonPill, Props} import edu.ie3.datamodel.io.connectors.SqlConnector import edu.ie3.datamodel.io.csv.CsvIndividualTimeSeriesMetaInformation import edu.ie3.datamodel.io.naming.timeseries.IndividualTimeSeriesMetaInformation @@ -30,6 +28,7 @@ import edu.ie3.datamodel.io.source.{ } import edu.ie3.datamodel.models.value.Value import edu.ie3.simona.agent.participant2.ParticipantAgent.RegistrationFailedMessage +import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.config.SimonaConfig.PrimaryDataCsvParams import edu.ie3.simona.config.SimonaConfig.Simona.Input.Primary.SqlParams import edu.ie3.simona.config.SimonaConfig.Simona.Input.{ @@ -47,7 +46,6 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ WorkerRegistrationMessage, } import edu.ie3.simona.scheduler.ScheduleLock -import edu.ie3.simona.service.{ServiceStateData, SimonaService} import edu.ie3.simona.service.ServiceStateData.InitializeServiceStateData import edu.ie3.simona.service.primary.PrimaryServiceProxy.{ InitPrimaryServiceProxyStateData, @@ -59,7 +57,10 @@ import edu.ie3.simona.service.primary.PrimaryServiceWorker.{ InitPrimaryServiceStateData, SqlInitPrimaryServiceStateData, } +import edu.ie3.simona.service.{ServiceStateData, SimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps +import org.apache.pekko.actor.{Actor, ActorRef, PoisonPill, Props} import java.nio.file.Paths import java.text.SimpleDateFormat @@ -107,6 +108,7 @@ case class PrimaryServiceProxy( prepareStateData( initStateData.primaryConfig, initStateData.simulationStart, + initStateData.extSimulationData, ) match { case Success(stateData) => scheduler ! Completion(self.toTyped) @@ -139,6 +141,7 @@ case class PrimaryServiceProxy( private def prepareStateData( primaryConfig: PrimaryConfig, simulationStart: ZonedDateTime, + extSimulationData: Map[ExtPrimaryDataConnection, ActorRef], ): Try[PrimaryServiceStateData] = { createSources(primaryConfig).map { case (mappingSource, metaInformationSource) => @@ -167,13 +170,30 @@ case class PrimaryServiceProxy( } } .toMap - PrimaryServiceStateData( - modelToTimeSeries, - timeSeriesToSourceRef, - simulationStart, - primaryConfig, - mappingSource, - ) + if (extSimulationData.nonEmpty) { + val extSubscribersToService = extSimulationData.flatMap { + case (connection, ref) => + connection.getPrimaryDataAssets.asScala.map(id => id -> ref) + } + + // Ask ExtPrimaryDataService which UUIDs should be substituted + PrimaryServiceStateData( + modelToTimeSeries, + timeSeriesToSourceRef, + simulationStart, + primaryConfig, + mappingSource, + extSubscribersToService, + ) + } else { + PrimaryServiceStateData( + modelToTimeSeries, + timeSeriesToSourceRef, + simulationStart, + primaryConfig, + mappingSource, + ) + } } } @@ -246,21 +266,34 @@ case class PrimaryServiceProxy( private def onMessage(stateData: PrimaryServiceStateData): Receive = { case PrimaryServiceRegistrationMessage(requestingActor, modelUuid) => /* Try to register for this model */ - stateData.modelToTimeSeries.get(modelUuid) match { - case Some(timeSeriesUuid) => - /* There is a time series apparent for this model, try to get a worker for it */ - handleCoveredModel( - modelUuid, - timeSeriesUuid, - stateData, - requestingActor, - ) + stateData.extSubscribersToService.get(modelUuid) match { + case Some(_) => + /* There is external data apparent for this model */ + handleExternalModel(modelUuid, stateData, requestingActor) + case None => log.debug( - s"There is no time series apparent for the model with uuid '{}'.", + s"There is no external data apparent for the model with uuid '{}'.", modelUuid, ) - requestingActor ! RegistrationFailedMessage(self) + + stateData.modelToTimeSeries.get(modelUuid) match { + case Some(timeSeriesUuid) => + /* There is a time series apparent for this model, try to get a worker for it */ + handleCoveredModel( + modelUuid, + timeSeriesUuid, + stateData, + requestingActor, + ) + + case None => + log.debug( + s"There is no time series apparent for the model with uuid '{}'.", + modelUuid, + ) + requestingActor ! RegistrationFailedMessage(self) + } } case x => log.error( @@ -325,6 +358,19 @@ case class PrimaryServiceProxy( } } + protected def handleExternalModel( + modelUuid: UUID, + stateData: PrimaryServiceStateData, + requestingActor: ActorRef, + ): Unit = { + stateData.extSubscribersToService.foreach { case (_, ref) => + ref ! PrimaryServiceRegistrationMessage( + requestingActor, + modelUuid, + ) + } + } + /** Instantiate a new [[PrimaryServiceWorker]] and send initialization * information * @@ -510,6 +556,7 @@ object PrimaryServiceProxy { final case class InitPrimaryServiceProxyStateData( primaryConfig: PrimaryConfig, simulationStart: ZonedDateTime, + extSimulationData: Map[ExtPrimaryDataConnection, ActorRef], ) extends InitializeServiceStateData /** Holding the state of an initialized proxy. @@ -531,6 +578,7 @@ object PrimaryServiceProxy { simulationStart: ZonedDateTime, primaryConfig: PrimaryConfig, mappingSource: TimeSeriesMappingSource, + extSubscribersToService: Map[UUID, ActorRef] = Map.empty, ) extends ServiceStateData /** Giving reference to the target time series and source worker. diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala new file mode 100644 index 0000000000..62b59e772b --- /dev/null +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala @@ -0,0 +1,388 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.results + +import edu.ie3.datamodel.models.result.ModelResultEntity +import edu.ie3.simona.api.data.results.ExtResultDataConnection +import edu.ie3.simona.api.data.results.ontology.{ + ProvideResultEntities, + RequestResultEntities, + ResultDataMessageFromExt, +} +import edu.ie3.simona.event.listener.DelayedStopHelper +import edu.ie3.simona.exceptions.ServiceException +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.ScheduleServiceActivation +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey +import edu.ie3.simona.util.ReceiveDataMap +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import org.apache.pekko.actor.typed.scaladsl.{Behaviors, StashBuffer} +import org.apache.pekko.actor.typed.{ActorRef, Behavior} + +import java.util.UUID +import scala.jdk.CollectionConverters._ + +object ExtResultDataProvider { + + trait Request + + final case class WrappedActivation(activation: Activation) extends Request + + /** ExtSimulation -> ExtResultDataProvider */ + final case class WrappedResultDataMessageFromExt( + extResultDataMessageFromExt: ResultDataMessageFromExt + ) extends Request + final case class WrappedScheduleServiceActivationAdapter( + scheduleServiceActivationMsg: ScheduleServiceActivation + ) extends Request + + final case class RequestDataMessageAdapter( + sender: ActorRef[ActorRef[ResultDataMessageFromExt]] + ) extends Request + + final case class RequestScheduleActivationAdapter( + sender: ActorRef[ActorRef[ScheduleServiceActivation]] + ) extends Request + + /** ResultEventListener -> ExtResultDataProvider */ + final case class ResultResponseMessage( + result: ModelResultEntity, + tick: Long, + nextTick: Option[Long], + ) extends Request + + /** ExtResultDataProvider -> ExtResultDataProvider */ + final case class ResultRequestMessage( + currentTick: Long + ) extends Request + + final case class Create( + initializeStateData: InitExtResultData, + unlockKey: ScheduleKey, + ) extends Request + + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + def apply( + scheduler: ActorRef[SchedulerMessage] + ): Behavior[Request] = Behaviors.withStash(5000) { buffer => + Behaviors.setup[Request] { ctx => + val activationAdapter: ActorRef[Activation] = + ctx.messageAdapter[Activation](msg => WrappedActivation(msg)) + val resultDataMessageFromExtAdapter: ActorRef[ResultDataMessageFromExt] = + ctx.messageAdapter[ResultDataMessageFromExt](msg => + WrappedResultDataMessageFromExt(msg) + ) + val scheduleServiceActivationAdapter + : ActorRef[ScheduleServiceActivation] = + ctx.messageAdapter[ScheduleServiceActivation](msg => + WrappedScheduleServiceActivationAdapter(msg) + ) + + uninitialized( + scheduler, + activationAdapter, + resultDataMessageFromExtAdapter, + scheduleServiceActivationAdapter, + buffer, + ) + } + } + + private def uninitialized(implicit + scheduler: ActorRef[SchedulerMessage], + activationAdapter: ActorRef[Activation], + resultDataMessageFromExtAdapter: ActorRef[ResultDataMessageFromExt], + scheduleServiceActivationAdapter: ActorRef[ScheduleServiceActivation], + buffer: StashBuffer[Request], + ): Behavior[Request] = Behaviors.receiveMessagePartial { + case RequestDataMessageAdapter(sender) => + sender ! resultDataMessageFromExtAdapter + Behaviors.same + + case RequestScheduleActivationAdapter(sender) => + sender ! scheduleServiceActivationAdapter + Behaviors.same + + case Create( + initializeStateData: InitExtResultData, + unlockKey: ScheduleKey, + ) => + scheduler ! ScheduleActivation( + activationAdapter, + INIT_SIM_TICK, + Some(unlockKey), + ) + + initializing(initializeStateData) + } + + private def initializing( + initServiceData: InitExtResultData + )(implicit + scheduler: ActorRef[SchedulerMessage], + activationAdapter: ActorRef[Activation], + resultDataMessageFromExtAdapter: ActorRef[ResultDataMessageFromExt], + buffer: StashBuffer[Request], + ): Behavior[Request] = { + Behaviors.receivePartial { + case (_, WrappedActivation(Activation(INIT_SIM_TICK))) => + val initGridSubscribers = + initServiceData.extResultData.getGridResultDataAssets.asScala.toList + val initParticipantSubscribers = + initServiceData.extResultData.getParticipantResultDataAssets.asScala.toList + + var initResultScheduleMap = Map.empty[Long, Set[UUID]] + initResultScheduleMap = + initResultScheduleMap + (0L -> initParticipantSubscribers.toSet) // First result for system participants expected for tick 0 + initResultScheduleMap = + initResultScheduleMap + (initServiceData.powerFlowResolution -> initGridSubscribers.toSet) // First result for grid expected for tick powerflowresolution + + val resultInitializedStateData = ExtResultStateData( + extResultData = initServiceData.extResultData, + currentTick = INIT_SIM_TICK, + extResultSchedule = ExtResultSchedule( + scheduleMap = initResultScheduleMap + ), + ) + scheduler ! Completion( + activationAdapter, + None, + ) + idle(resultInitializedStateData) + } + } + + private def idle(serviceStateData: ExtResultStateData)(implicit + scheduler: ActorRef[SchedulerMessage], + activationAdapter: ActorRef[Activation], + resultDataMessageFromExtAdapter: ActorRef[ResultDataMessageFromExt], + buffer: StashBuffer[Request], + ): Behavior[Request] = Behaviors + .receivePartial[Request] { + case (ctx, WrappedActivation(activation: Activation)) => + var updatedStateData = serviceStateData.handleActivation(activation) + // ctx.log.info(s"+++++++ Received Activation for tick ${updatedStateData.currentTick} +++++++") + + serviceStateData.extResultsMessage.getOrElse( + throw ServiceException( + "ExtResultDataService was triggered without ResultDataMessageFromExt available" + ) // this should not be possible because the external simulation schedules this service + ) match { + case msg: RequestResultEntities => // ExtResultDataProvider wurde aktiviert und es wurden Nachrichten von ExtSimulation angefragt + // ctx.log.info(s"[${updatedStateData.currentTick}] [requestResults] resultStorage = ${updatedStateData.resultStorage}\n extResultScheduler ${updatedStateData.extResultScheduler}") + val currentTick = updatedStateData.currentTick + if (msg.tick == currentTick) { // check, if we are in the right tick + val expectedKeys = + serviceStateData.extResultSchedule.getExpectedKeys( + currentTick + ) // Expected keys are for this tick scheduled and not scheduled + val receiveDataMap = + ReceiveDataMap[UUID, ModelResultEntity](expectedKeys) + val updatedSchedule = + serviceStateData.extResultSchedule.handleActivation(currentTick) + + // ctx.log.info(s"[${updatedStateData.currentTick}] [requestResults] updatedSchedule = $updatedSchedule \n receiveDataMap = $receiveDataMap") + + if (receiveDataMap.isComplete) { + // --- There are no expected results for this tick! Send the send right away! + // ctx.log.info(s"[requestResults] tick ${msg.tick} -> ReceiveDataMap is complete -> send it right away: ${serviceStateData.resultStorage}") + + serviceStateData.extResultData.queueExtResponseMsg( + new ProvideResultEntities( + serviceStateData.resultStorage.asJava + ) + ) + updatedStateData = updatedStateData.copy( + extResultsMessage = None, + receiveDataMap = None, + extResultSchedule = updatedSchedule, + ) + scheduler ! Completion(activationAdapter, None) + } else { + // We got an activation and we are waiting for some results -> trigger ourself to process + // ctx.log.info(s"[requestResults] receiveDataMap was built -> now sending ResultRequestMessage") + ctx.self ! ResultRequestMessage(msg.tick) + updatedStateData = updatedStateData.copy( + extResultsMessage = None, + receiveDataMap = Some(receiveDataMap), + extResultSchedule = updatedSchedule, + ) + } + } else { + throw ServiceException( + s"Results for the wrong tick ${msg.tick} requested! We are currently in tick ${updatedStateData.currentTick}" + ) + } + } + // scheduler ! Completion(activationAdapter, None) + idle(updatedStateData) + + case ( + _, + scheduleServiceActivationMsg: WrappedScheduleServiceActivationAdapter, + ) => + scheduler ! ScheduleActivation( + activationAdapter, + scheduleServiceActivationMsg.scheduleServiceActivationMsg.tick, + Some( + scheduleServiceActivationMsg.scheduleServiceActivationMsg.unlockKey + ), + ) + Behaviors.same + + case ( + _, + resultDataMessageFromExt: WrappedResultDataMessageFromExt, + ) => // Received a request for results before activation -> save it and answer later + idle( + serviceStateData.copy( + extResultsMessage = + Some(resultDataMessageFromExt.extResultDataMessageFromExt) + ) + ) + + case ( + _, + extResultResponseMsg: ResultResponseMessage, + ) => // Received result from ResultEventListener + serviceStateData.receiveDataMap.fold { + // result arrived before activation -> stash them away + buffer.stash(extResultResponseMsg) + idle(serviceStateData) + } { dataMap => + if ( + dataMap.getExpectedKeys.contains( + extResultResponseMsg.result.getInputModel + ) + ) { // Received a result for external entity + // ctx.log.info(s"[${serviceStateData.currentTick}] Process ResultsResponseMsg = ${extResultResponseMsg.result.getInputModel}\n receiveDataMap ${serviceStateData.receiveDataMap}\n MsgTick=${extResultResponseMsg.tick}, ServiceStateDataTick=${serviceStateData.currentTick}, nextTick = ${extResultResponseMsg.nextTick}") + + if ( + extResultResponseMsg.tick == serviceStateData.currentTick | extResultResponseMsg.tick == -1L + ) { // Received a result for the current tick -> process it + // FIXME Not expected results are unconsidered + val updatedReceiveDataMap = dataMap.addData( + extResultResponseMsg.result.getInputModel, + extResultResponseMsg.result, + ) + + // ctx.log.info("[hDRM] AddData to RecentResults -> updatedReceivedResults = " + updatedReceiveDataMap) + + val updatedResultStorage = + serviceStateData.resultStorage + (extResultResponseMsg.result.getInputModel -> extResultResponseMsg.result) + val updatedResultSchedule = + serviceStateData.extResultSchedule.handleResult( + extResultResponseMsg + ) + // ctx.log.info(s"[hDRM] updatedResultSchedule = $updatedResultSchedule") + // ctx.log.info(s"[hDRM] updatedResultStorage = $updatedResultStorage") + + if (updatedReceiveDataMap.nonComplete) { // There are still results missing... + // ctx.log.info(s"[${serviceStateData.currentTick}] There are still results missing...") + idle( + serviceStateData.copy( + receiveDataMap = Some(updatedReceiveDataMap), + resultStorage = updatedResultStorage, + extResultSchedule = updatedResultSchedule, + ) + ) + } else { // all responses received, forward them to external simulation in a bundle + // ctx.log.info(s"\u001b[0;34m[${serviceStateData.currentTick}] Got all ResultResponseMessage -> Now forward to external simulation in a bundle: $updatedResultStorage\u001b[0;0m") + serviceStateData.extResultData.queueExtResponseMsg( + new ProvideResultEntities(updatedResultStorage.asJava) + ) + // ctx.log.info("++++++++++++++++++ sended ExtResultData +++++++++++++++++++++++") + scheduler ! Completion(activationAdapter, None) + idle( + serviceStateData.copy( + receiveDataMap = None, + resultStorage = updatedResultStorage, + extResultSchedule = updatedResultSchedule, + ) + ) + } + } else { // Received a result for another tick -> ignore it + idle(serviceStateData) + } + } else { // Received a result for internal entity -> ignore it + idle(serviceStateData) + } + } + + case ( + _, + _: ResultRequestMessage, + ) => // Received internal result request -> unstash messages + // ctx.log.info(s"[handleDataResponseMessage] Received ResultRequestMessage $msg -> Now unstash all buffered messages!") + buffer.unstashAll(idle(serviceStateData)) + + case (ctx, msg: DelayedStopHelper.StoppingMsg) => + DelayedStopHelper.handleMsg((ctx, msg)) + } + + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + final case class ExtResultStateData( + extResultData: ExtResultDataConnection, + currentTick: Long, + extResultSchedule: ExtResultSchedule, + extResultsMessage: Option[ResultDataMessageFromExt] = None, + resultStorage: Map[UUID, ModelResultEntity] = Map.empty, + receiveDataMap: Option[ReceiveDataMap[UUID, ModelResultEntity]] = None, + ) { + def handleActivation(activation: Activation): ExtResultStateData = { + copy( + currentTick = activation.tick + ) + } + } + final case class InitExtResultData( + extResultData: ExtResultDataConnection, + powerFlowResolution: Long, + ) + + final case class ExtResultSchedule( + scheduleMap: Map[Long, Set[UUID]] = Map.empty, + unscheduledList: Set[UUID] = Set.empty, + ) { + def getExpectedKeys(tick: Long): Set[UUID] = { + scheduleMap.getOrElse( + tick, + Set(), + ) ++ unscheduledList + } + + private def getScheduledKeys(tick: Long): Set[UUID] = { + scheduleMap.getOrElse(tick, Set[UUID]()) + } + + def handleActivation(tick: Long): ExtResultSchedule = { + copy( + scheduleMap = scheduleMap.-(tick) + ) + } + + def handleResult(msg: ResultResponseMessage): ExtResultSchedule = { + msg.nextTick.fold { + copy() + } { // the next tick was provided -> update schedule + newTick => // ctx.log.info(s"[hDRM] update schedule = $newTick, uuid = ${extResultResponseMsg.result.getInputModel}") + copy( + scheduleMap = scheduleMap.updated( + newTick, + getScheduledKeys(newTick) + msg.result.getInputModel, + ) + ) + } + } + } + +} diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index cd31338709..4350c6bdbf 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -8,30 +8,49 @@ package edu.ie3.simona.sim.setup import edu.ie3.simona.actor.SimonaActorNaming.RichActorRefFactory import edu.ie3.simona.api.data.ExtInputDataConnection +import edu.ie3.simona.api.data.em.ExtEmDataConnection import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection +import edu.ie3.simona.api.data.results.ExtResultDataConnection +import edu.ie3.simona.api.data.results.ontology.ResultDataMessageFromExt import edu.ie3.simona.api.simulation.{ExtSimAdapterData, ExtSimulation} import edu.ie3.simona.api.{ExtLinkInterface, ExtSimAdapter} import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.exceptions.ServiceException import edu.ie3.simona.ontology.messages.SchedulerMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage.ScheduleServiceActivation import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceStateData.InitializeServiceStateData import edu.ie3.simona.service.SimonaService +import edu.ie3.simona.service.em.ExtEmDataService +import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData import edu.ie3.simona.service.ev.ExtEvDataService import edu.ie3.simona.service.ev.ExtEvDataService.InitExtEvData +import edu.ie3.simona.service.primary.ExtPrimaryDataService +import edu.ie3.simona.service.primary.ExtPrimaryDataService.InitExtPrimaryData +import edu.ie3.simona.service.results.ExtResultDataProvider +import edu.ie3.simona.service.results.ExtResultDataProvider.{ + InitExtResultData, + RequestDataMessageAdapter, + RequestScheduleActivationAdapter, +} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK -import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext +import org.apache.pekko.actor.typed.scaladsl.AskPattern.Askable import org.apache.pekko.actor.typed.scaladsl.adapter.{ TypedActorContextOps, TypedActorRefOps, } +import org.apache.pekko.actor.typed.{ActorRef, Scheduler} import org.apache.pekko.actor.{Props, ActorRef => ClassicRef} +import org.apache.pekko.util.{Timeout => PekkoTimeout} import org.slf4j.{Logger, LoggerFactory} import java.util.UUID +import scala.concurrent.Await +import scala.concurrent.duration.DurationInt import scala.jdk.CollectionConverters.{ListHasAsScala, SetHasAsScala} +import scala.jdk.DurationConverters.ScalaDurationOps import scala.util.{Failure, Success, Try} object ExtSimSetup { @@ -142,6 +161,32 @@ object ExtSimSetup { val updatedSetupData = connections.foldLeft(extSimSetupData) { case (setupData, connection) => connection match { + case extPrimaryDataConnection: ExtPrimaryDataConnection => + extPrimaryDataSetup(setupData, extPrimaryDataConnection) + + case extEmDataConnection: ExtEmDataConnection => + if (setupData.emDataConnection.nonEmpty) { + throw ServiceException( + s"Trying to connect another EmDataConnection. Currently only one is allowed." + ) + } + + if (extEmDataConnection.getControlledEms.isEmpty) { + log.warn( + s"External em connection $extEmDataConnection is not used, because there are no controlled ems present!" + ) + setupData + } else { + val serviceRef = setupInputService( + extEmDataConnection, + ExtEmDataService.props, + "ExtEmDataService", + InitExtEmData, + ) + + extSimSetupData.update(extEmDataConnection, serviceRef) + } + case extEvDataConnection: ExtEvDataConnection => if (setupData.evDataConnection.nonEmpty) { throw ServiceException( @@ -158,6 +203,9 @@ object ExtSimSetup { extSimSetupData.update(extEvDataConnection, serviceRef) + case extResultDataConnection: ExtResultDataConnection => + extResultDataSetup(setupData, extResultDataConnection) + case otherConnection => log.warn( s"There is currently no implementation for the connection: $otherConnection." @@ -225,6 +273,116 @@ object ExtSimSetup { extDataService } + /** Method to set up an external primary data service. + * @param extSimSetupData + * that contains information about all external simulations + * @param extPrimaryDataConnection + * the data connection + * @param context + * the actor context of this actor system + * @param scheduler + * the scheduler of simona + * @param extSimAdapter + * the adapter for the external simulation + * @return + * an updated [[ExtSimSetupData]] + */ + private[setup] def extPrimaryDataSetup( + extSimSetupData: ExtSimSetupData, + extPrimaryDataConnection: ExtPrimaryDataConnection, + )(implicit + context: ActorContext[_], + scheduler: ActorRef[SchedulerMessage], + extSimAdapter: ClassicRef, + ): ExtSimSetupData = { + val extPrimaryDataService = context.toClassic.simonaActorOf( + ExtPrimaryDataService.props(scheduler.toClassic), + "ExtPrimaryDataService", + ) + + extPrimaryDataService ! SimonaService.Create( + InitExtPrimaryData(extPrimaryDataConnection), + ScheduleLock.singleKey( + context, + scheduler, + INIT_SIM_TICK, + ), + ) + + extPrimaryDataConnection.setActorRefs(extPrimaryDataService, extSimAdapter) + + extSimSetupData update (extPrimaryDataConnection, extPrimaryDataService) + } + + /** Method to set up an external result data service. + * + * @param extSimSetupData + * that contains information about all external simulations + * @param extResultDataConnection + * the data connection + * @param context + * the actor context of this actor system + * @param scheduler + * the scheduler of simona + * @param extSimAdapter + * the adapter for the external simulation + * @param simonaConfig + * the config + * @return + * an updated [[ExtSimSetupData]] + */ + private[setup] def extResultDataSetup( + extSimSetupData: ExtSimSetupData, + extResultDataConnection: ExtResultDataConnection, + )(implicit + context: ActorContext[_], + scheduler: ActorRef[SchedulerMessage], + extSimAdapter: ClassicRef, + simonaConfig: SimonaConfig, + ): ExtSimSetupData = { + val extResultDataProvider = + context.spawn( + ExtResultDataProvider(scheduler), + s"ExtResultDataProvider", + ) + + val timeout: PekkoTimeout = PekkoTimeout.create(5.seconds.toJava) + val scheduler2: Scheduler = context.system.scheduler + + val adapterRef = Await.result( + extResultDataProvider.ask[ActorRef[ResultDataMessageFromExt]](ref => + RequestDataMessageAdapter(ref) + )(timeout, scheduler2), + timeout.duration, + ) + val adapterScheduleRef = Await.result( + extResultDataProvider.ask[ActorRef[ScheduleServiceActivation]](ref => + RequestScheduleActivationAdapter(ref) + )(timeout, scheduler2), + timeout.duration, + ) + + extResultDataConnection.setActorRefs( + adapterRef.toClassic, + adapterScheduleRef.toClassic, + extSimAdapter, + ) + + val powerFlowResolution = + simonaConfig.simona.powerflow.resolution.toSeconds + + extResultDataProvider ! ExtResultDataProvider.Create( + InitExtResultData(extResultDataConnection, powerFlowResolution), + ScheduleLock.singleKey( + context, + scheduler, + INIT_SIM_TICK, + ), + ) + + extSimSetupData update (extResultDataConnection, extResultDataProvider) + } + /** Method for validating the external primary data connections. * @param extPrimaryDataConnection * all external primary data connections diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index 125388699f..a5204d127a 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -164,6 +164,7 @@ class SimonaStandaloneSetup( InitPrimaryServiceProxyStateData( simonaConfig.simona.input.primary, simulationStart, + extSimSetupData.extPrimaryDataServices, ), simulationStart, ), diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala index 1f7fca229e..4550f9e1de 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala @@ -41,6 +41,8 @@ final case class ReceiveDataMap[K, V]( ) } + def getExpectedKeys: Set[K] = expectedKeys + } object ReceiveDataMap { diff --git a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala new file mode 100644 index 0000000000..edc341ac3a --- /dev/null +++ b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala @@ -0,0 +1,125 @@ +/* + * © 2021. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.primary + +import com.typesafe.config.ConfigFactory +import edu.ie3.simona.agent.participant.data.Data.PrimaryData +import edu.ie3.simona.agent.participant2.ParticipantAgent.{ + DataProvision, + RegistrationSuccessfulMessage, +} +import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection +import edu.ie3.simona.ontology.messages.Activation +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.PrimaryServiceRegistrationMessage +import edu.ie3.simona.ontology.messages.services.WeatherMessage.RegisterForWeatherMessage +import edu.ie3.simona.scheduler.ScheduleLock +import edu.ie3.simona.service.SimonaService +import edu.ie3.simona.service.primary.ExtPrimaryDataService.InitExtPrimaryData +import edu.ie3.simona.test.common.{TestKitWithShutdown, TestSpawnerClassic} +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import org.apache.pekko.actor.ActorSystem +import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps +import org.apache.pekko.testkit.{TestActorRef, TestProbe} + +import java.util.UUID +import scala.concurrent.duration.DurationInt +import scala.jdk.CollectionConverters._ + +class ExtPrimaryDataServiceSpec + extends TestKitWithShutdown( + ActorSystem( + "ExtPrimaryDataServiceSpec", + ConfigFactory + .parseString(""" + |pekko.loggers = ["org.apache.pekko.testkit.TestEventListener"] + |pekko.loglevel = "INFO" + |""".stripMargin), + ) + ) + with TestSpawnerClassic { + + private val scheduler = TestProbe("scheduler") + private val extSimAdapter = TestProbe("extSimAdapter") + + private val extPrimaryDataConnection = new ExtPrimaryDataConnection( + Map.empty[String, UUID].asJava + ) + + "An uninitialized external primary data service" must { + + "send correct completion message after initialisation" in { + val primaryDataService = TestActorRef( + new ExtPrimaryDataService( + scheduler.ref + ) + ) + + val key = + ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) + scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + + extPrimaryDataConnection.setActorRefs( + primaryDataService, + extSimAdapter.ref, + ) + + scheduler.send( + primaryDataService, + SimonaService.Create( + InitExtPrimaryData(extPrimaryDataConnection), + key, + ), + ) + scheduler.expectMsg( + ScheduleActivation(primaryDataService.toTyped, INIT_SIM_TICK, Some(key)) + ) + + scheduler.send(primaryDataService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(primaryDataService.toTyped)) + } + } + + "An external primary service actor" should { + val serviceRef = + TestActorRef( + new ExtPrimaryDataService( + scheduler.ref + ) + ) + + "refuse registration for wrong registration request" in { + serviceRef ! RegisterForWeatherMessage(51.4843281, 7.4116482) + expectNoMessage() + } + + val systemParticipant: TestProbe = TestProbe("dummySystemParticipant") + "correctly register a forwarded request" ignore { + serviceRef ! PrimaryServiceRegistrationMessage( + systemParticipant.ref, + UUID.randomUUID(), + ) + println("Try to register") + + /* Wait for request approval */ + val msg = systemParticipant.expectMsgType[RegistrationSuccessfulMessage]( + 10.seconds + ) + msg.serviceRef shouldBe serviceRef.ref + msg.firstDataTick shouldBe 0L + + /* We cannot directly check, if the requesting actor is among the subscribers, therefore we ask the actor to + * provide data to all subscribed actors and check, if the subscribed probe gets one */ + scheduler.send(serviceRef, Activation(0)) + scheduler.expectMsgType[Completion] + systemParticipant.expectMsgAllClassOf(classOf[DataProvision[PrimaryData]]) + } + } +} diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala index 8debb0cbd8..79497b01ba 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala @@ -6,10 +6,6 @@ package edu.ie3.simona.service.primary -import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps -import org.apache.pekko.actor.{ActorRef, ActorSystem, PoisonPill} -import org.apache.pekko.testkit.{TestActorRef, TestProbe} -import org.apache.pekko.util.Timeout import com.typesafe.config.ConfigFactory import edu.ie3.datamodel.io.csv.CsvIndividualTimeSeriesMetaInformation import edu.ie3.datamodel.io.naming.FileNamingStrategy @@ -18,6 +14,7 @@ import edu.ie3.datamodel.io.source.TimeSeriesMappingSource import edu.ie3.datamodel.io.source.csv.CsvTimeSeriesMappingSource import edu.ie3.datamodel.models.value.{SValue, Value} import edu.ie3.simona.agent.participant2.ParticipantAgent.RegistrationFailedMessage +import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.config.SimonaConfig.PrimaryDataCsvParams import edu.ie3.simona.config.SimonaConfig.Simona.Input.Primary.{ CouchbaseParams, @@ -53,6 +50,10 @@ import edu.ie3.simona.test.common.input.TimeSeriesTestData import edu.ie3.simona.test.common.{AgentSpec, TestSpawnerClassic} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.TimeUtil +import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps +import org.apache.pekko.actor.{ActorRef, ActorSystem, PoisonPill} +import org.apache.pekko.testkit.{TestActorRef, TestProbe} +import org.apache.pekko.util.Timeout import org.scalatest.PartialFunctionValues import org.scalatest.prop.TableDrivenPropertyChecks @@ -61,6 +62,7 @@ import java.time.ZonedDateTime import java.util.concurrent.TimeUnit import java.util.{Objects, UUID} import scala.concurrent.ExecutionContext.Implicits.global +import scala.jdk.CollectionConverters._ import scala.util.{Failure, Success, Try} class PrimaryServiceProxySpec @@ -129,6 +131,18 @@ class PrimaryServiceProxySpec private val scheduler: TestProbe = TestProbe("scheduler") + private val validExtPrimaryDataService = TestActorRef( + new ExtPrimaryDataService( + scheduler.ref + ) + ) + + private val extEntityId = + UUID.fromString("07bbe1aa-1f39-4dfb-b41b-339dec816ec4") + private val extPrimaryDataConnection = new ExtPrimaryDataConnection( + Map(extEntityId.toString -> extEntityId).asJava + ) + "Testing a primary service config" should { "lead to complaining about too much source definitions" in { val maliciousConfig = PrimaryConfig( @@ -239,6 +253,7 @@ class PrimaryServiceProxySpec InitPrimaryServiceProxyStateData( validPrimaryConfig, simulationStart, + Map.empty, ) val proxyRef: TestActorRef[PrimaryServiceProxy] = TestActorRef( new PrimaryServiceProxy(scheduler.ref, initStateData, simulationStart) @@ -260,6 +275,7 @@ class PrimaryServiceProxySpec proxy invokePrivate prepareStateData( maliciousConfig, simulationStart, + Map.empty, ) match { case Success(_) => fail("Building state data with missing config should fail") @@ -280,6 +296,7 @@ class PrimaryServiceProxySpec proxy invokePrivate prepareStateData( maliciousConfig, simulationStart, + Map.empty, ) match { case Success(_) => fail("Building state data with missing config should fail") @@ -293,6 +310,7 @@ class PrimaryServiceProxySpec proxy invokePrivate prepareStateData( validPrimaryConfig, simulationStart, + Map.empty, ) match { case Success( PrimaryServiceStateData( @@ -301,6 +319,7 @@ class PrimaryServiceProxySpec simulationStart, primaryConfig, mappingSource, + _, ) ) => modelToTimeSeries shouldBe Map( @@ -338,6 +357,29 @@ class PrimaryServiceProxySpec ) } } + + "build proxy correctly when there is an external simulation" in { + proxy invokePrivate prepareStateData( + validPrimaryConfig, + simulationStart, + Map(extPrimaryDataConnection -> validExtPrimaryDataService), + ) match { + case Success( + PrimaryServiceStateData( + _, + _, + _, + _, + _, + extSubscribersToService, + ) + ) => + extSubscribersToService shouldBe Map( + extEntityId -> validExtPrimaryDataService + ) + } + } + } "Sending initialization information to an uninitialized actor" should { @@ -562,6 +604,7 @@ class PrimaryServiceProxySpec simulationStart, primaryConfig, mappingSource, + _, ) => modelToTimeSeries shouldBe proxyStateData.modelToTimeSeries timeSeriesToSourceRef shouldBe Map( diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySqlIT.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySqlIT.scala index d946ea8ee8..3f349c483d 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySqlIT.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySqlIT.scala @@ -101,6 +101,7 @@ class PrimaryServiceProxySqlIT sqlParams = Some(sqlParams), ), simulationStart, + Map.empty, ) TestActorRef( From 893eec204c619c22a48acae5b3e6dc5204691f80 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 24 Feb 2025 13:32:49 +0100 Subject: [PATCH 002/125] Fixing some bugs. --- .../edu/ie3/simona/config/ArgsParser.scala | 10 ++++ .../edu/ie3/simona/config/SimonaConfig.scala | 7 ++- .../event/listener/ResultEventListener.scala | 56 ++++++++++++++++--- .../primary/ExtPrimaryDataService.scala | 25 +++++++-- .../results/ExtResultDataProvider.scala | 48 ++++++++++------ .../ie3/simona/sim/setup/ExtSimSetup.scala | 2 +- .../simona/sim/setup/ExtSimSetupData.scala | 7 ++- .../sim/setup/SimonaStandaloneSetup.scala | 3 +- 8 files changed, 124 insertions(+), 34 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/config/ArgsParser.scala b/src/main/scala/edu/ie3/simona/config/ArgsParser.scala index 22891d8ace..d480de5c5a 100644 --- a/src/main/scala/edu/ie3/simona/config/ArgsParser.scala +++ b/src/main/scala/edu/ie3/simona/config/ArgsParser.scala @@ -29,6 +29,7 @@ object ArgsParser extends LazyLogging { seedAddress: Option[String] = None, useLocalWorker: Option[Boolean] = None, tArgs: Map[String, String] = Map.empty, + extAddress: Option[String] = None, ) { val useCluster: Boolean = clusterType.isDefined } @@ -100,6 +101,15 @@ object ArgsParser extends LazyLogging { "If cluster is specified then this defaults to false and must be explicitly set to true. " + "NOTE: For cluster, this will ONLY be checked if cluster-type=master" ) + opt[String]("ext-address") + .action((value, args) => args.copy(extAddress = Option(value))) + .validate(value => + if (value.trim.isEmpty) failure("ext-address cannot be empty") + else success + ) + .text( + "Comma separated list (no whitespaces!) of initial addresses used for the rest of the cluster to bootstrap" + ) checkConfig(args => if ( diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index cfa11f4dce..5f85788241 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -8,11 +8,13 @@ package edu.ie3.simona.config import com.typesafe.config.{Config, ConfigRenderOptions} import edu.ie3.simona.exceptions.CriticalFailureException +import edu.ie3.util.TimeUtil import pureconfig._ import pureconfig.error._ import pureconfig.generic.ProductHint import pureconfig.generic.auto._ +import java.time.ZonedDateTime import scala.concurrent.duration.{DurationInt, FiniteDuration} import scala.language.implicitConversions @@ -379,6 +381,9 @@ object SimonaConfig { endDateTime: String = "2011-05-01T01:00:00Z", schedulerReadyCheckWindow: Option[Int] = None, startDateTime: String = "2011-05-01T00:00:00Z", - ) + ) { + def startTime: ZonedDateTime = + TimeUtil.withDefaults.toZonedDateTime(startDateTime) + } } } diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index d9c910ef1a..db23a703fc 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -6,11 +6,14 @@ package edu.ie3.simona.event.listener -import org.apache.pekko.actor.typed.scaladsl.Behaviors -import org.apache.pekko.actor.typed.{Behavior, PostStop} import edu.ie3.datamodel.io.processor.result.ResultEntityProcessor -import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} +import edu.ie3.datamodel.models.result.{ + ModelResultEntity, + NodeResult, + ResultEntity, +} import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult +import edu.ie3.simona.api.data.results.ExtResultDataConnection import edu.ie3.simona.event.ResultEvent.{ FlexOptionsResultEvent, ParticipantResultEvent, @@ -22,7 +25,11 @@ import edu.ie3.simona.exceptions.{ ProcessResultEventException, } import edu.ie3.simona.io.result._ +import edu.ie3.simona.service.results.ExtResultDataProvider +import edu.ie3.simona.service.results.ExtResultDataProvider.ResultResponseMessage import edu.ie3.simona.util.ResultFileHierarchy +import org.apache.pekko.actor.typed.scaladsl.Behaviors +import org.apache.pekko.actor.typed.{ActorRef, Behavior, PostStop} import org.slf4j.Logger import scala.concurrent.ExecutionContext.Implicits.global @@ -46,9 +53,14 @@ object ResultEventListener extends Transformer3wResultSupport { * @param classToSink * a map containing the sink for each class that should be processed by the * listener + * @param extResultListeners + * actors for external result data services */ private final case class BaseData( classToSink: Map[Class[_], ResultEntitySink], + extResultListeners: Map[ExtResultDataConnection, ActorRef[ + ExtResultDataProvider.Request + ]], threeWindingResults: Map[ Transformer3wKey, AggregatedTransformer3wResult, @@ -160,7 +172,16 @@ object ResultEventListener extends Transformer3wResultSupport { baseData: BaseData, log: Logger, ): BaseData = { + // log.info("Got Result " + resultEntity) handOverToSink(resultEntity, baseData.classToSink, log) + + if (baseData.extResultListeners.nonEmpty) { + handOverToExternalService( + resultEntity, + baseData.extResultListeners.values, + ) + } + baseData } @@ -235,8 +256,25 @@ object ResultEventListener extends Transformer3wResultSupport { log.error("Error while writing result event: ", exception) } + private def handOverToExternalService( + resultEntity: ResultEntity, + extResultListeners: Iterable[ActorRef[ExtResultDataProvider.Request]], + ): Unit = Try { + resultEntity match { + case modelResultEntity: ModelResultEntity => + extResultListeners.foreach( + _ ! ResultResponseMessage(modelResultEntity) + ) + case _ => + throw new Exception("Wrong data type!") + } + } + def apply( - resultFileHierarchy: ResultFileHierarchy + resultFileHierarchy: ResultFileHierarchy, + extResultListeners: Map[ExtResultDataConnection, ActorRef[ + ExtResultDataProvider.Request + ]] = Map.empty, ): Behavior[Request] = Behaviors.setup[Request] { ctx => ctx.log.debug("Starting initialization!") resultFileHierarchy.resultSinkType match { @@ -260,14 +298,18 @@ object ResultEventListener extends Transformer3wResultSupport { case Success(result) => SinkResponse(result.toMap) } - init() + init(extResultListeners) } - private def init(): Behavior[Request] = Behaviors.withStash(200) { buffer => + private def init( + extResultListeners: Map[ExtResultDataConnection, ActorRef[ + ExtResultDataProvider.Request + ]] + ): Behavior[Request] = Behaviors.withStash(200) { buffer => Behaviors.receive[Request] { case (ctx, SinkResponse(response)) => ctx.log.debug("Initialization complete!") - buffer.unstashAll(idle(BaseData(response))) + buffer.unstashAll(idle(BaseData(response, extResultListeners))) case (ctx, InitFailed(ex)) => ctx.log.error("Unable to setup ResultEventListener.", ex) diff --git a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala index b1869bb4bf..616d7bfe6a 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala @@ -6,10 +6,11 @@ package edu.ie3.simona.service.primary +import edu.ie3.simona.agent.participant.data.Data.PrimaryData import edu.ie3.simona.agent.participant.data.Data.PrimaryData.RichValue import edu.ie3.simona.agent.participant2.ParticipantAgent.{ DataProvision, - RegistrationSuccessfulMessage, + PrimaryRegistrationSuccessfulMessage, } import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection @@ -112,13 +113,29 @@ final case class ExtPrimaryDataService( ): ExtPrimaryDataStateData = { serviceStateData.uuidToActorRef.get(agentUUID) match { case None => - // Actor is not registered yet - agentToBeRegistered ! RegistrationSuccessfulMessage(self, 0L) + // checks if a value class was specified for the agent + val valueClass = serviceStateData.extPrimaryData + .getValueClass(agentUUID) + .toScala + .getOrElse( + throw InvalidRegistrationRequestException( + s"A primary service provider is not able to handle registration request, because there was no value class specified for the agent with id: '$agentUUID'." + ) + ) + + agentToBeRegistered ! PrimaryRegistrationSuccessfulMessage( + self, + 0L, + PrimaryData.getPrimaryDataExtra(valueClass), + ) log.info(s"Successful registration for $agentUUID") + serviceStateData.copy( + subscribers = serviceStateData.subscribers :+ agentUUID, uuidToActorRef = - serviceStateData.uuidToActorRef + (agentUUID -> agentToBeRegistered) + serviceStateData.uuidToActorRef + (agentUUID -> agentToBeRegistered), ) + case Some(_) => // actor is already registered, do nothing log.warning( diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala index 62b59e772b..d9b6c4b6c1 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala @@ -24,9 +24,11 @@ import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import edu.ie3.util.TimeUtil import org.apache.pekko.actor.typed.scaladsl.{Behaviors, StashBuffer} import org.apache.pekko.actor.typed.{ActorRef, Behavior} +import java.time.ZonedDateTime import java.util.UUID import scala.jdk.CollectionConverters._ @@ -54,10 +56,14 @@ object ExtResultDataProvider { /** ResultEventListener -> ExtResultDataProvider */ final case class ResultResponseMessage( - result: ModelResultEntity, - tick: Long, - nextTick: Option[Long], - ) extends Request + result: ModelResultEntity + ) extends Request { + def tick(implicit startTime: ZonedDateTime): Long = + TimeUtil.withDefaults.zonedDateTimeDifferenceInSeconds( + startTime, + result.getTime, + ) + } /** ExtResultDataProvider -> ExtResultDataProvider */ final case class ResultRequestMessage( @@ -72,7 +78,8 @@ object ExtResultDataProvider { // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- def apply( - scheduler: ActorRef[SchedulerMessage] + scheduler: ActorRef[SchedulerMessage], + startTime: ZonedDateTime, ): Behavior[Request] = Behaviors.withStash(5000) { buffer => Behaviors.setup[Request] { ctx => val activationAdapter: ActorRef[Activation] = @@ -93,6 +100,7 @@ object ExtResultDataProvider { resultDataMessageFromExtAdapter, scheduleServiceActivationAdapter, buffer, + startTime, ) } } @@ -103,6 +111,7 @@ object ExtResultDataProvider { resultDataMessageFromExtAdapter: ActorRef[ResultDataMessageFromExt], scheduleServiceActivationAdapter: ActorRef[ScheduleServiceActivation], buffer: StashBuffer[Request], + startTime: ZonedDateTime, ): Behavior[Request] = Behaviors.receiveMessagePartial { case RequestDataMessageAdapter(sender) => sender ! resultDataMessageFromExtAdapter @@ -132,6 +141,7 @@ object ExtResultDataProvider { activationAdapter: ActorRef[Activation], resultDataMessageFromExtAdapter: ActorRef[ResultDataMessageFromExt], buffer: StashBuffer[Request], + startTime: ZonedDateTime, ): Behavior[Request] = { Behaviors.receivePartial { case (_, WrappedActivation(Activation(INIT_SIM_TICK))) => @@ -148,6 +158,7 @@ object ExtResultDataProvider { val resultInitializedStateData = ExtResultStateData( extResultData = initServiceData.extResultData, + powerFlowResolution = initServiceData.powerFlowResolution, currentTick = INIT_SIM_TICK, extResultSchedule = ExtResultSchedule( scheduleMap = initResultScheduleMap @@ -166,6 +177,7 @@ object ExtResultDataProvider { activationAdapter: ActorRef[Activation], resultDataMessageFromExtAdapter: ActorRef[ResultDataMessageFromExt], buffer: StashBuffer[Request], + startTime: ZonedDateTime, ): Behavior[Request] = Behaviors .receivePartial[Request] { case (ctx, WrappedActivation(activation: Activation)) => @@ -281,7 +293,8 @@ object ExtResultDataProvider { serviceStateData.resultStorage + (extResultResponseMsg.result.getInputModel -> extResultResponseMsg.result) val updatedResultSchedule = serviceStateData.extResultSchedule.handleResult( - extResultResponseMsg + extResultResponseMsg, + extResultResponseMsg.tick + serviceStateData.powerFlowResolution, ) // ctx.log.info(s"[hDRM] updatedResultSchedule = $updatedResultSchedule") // ctx.log.info(s"[hDRM] updatedResultStorage = $updatedResultStorage") @@ -332,6 +345,7 @@ object ExtResultDataProvider { // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- final case class ExtResultStateData( extResultData: ExtResultDataConnection, + powerFlowResolution: Long, currentTick: Long, extResultSchedule: ExtResultSchedule, extResultsMessage: Option[ResultDataMessageFromExt] = None, @@ -370,18 +384,16 @@ object ExtResultDataProvider { ) } - def handleResult(msg: ResultResponseMessage): ExtResultSchedule = { - msg.nextTick.fold { - copy() - } { // the next tick was provided -> update schedule - newTick => // ctx.log.info(s"[hDRM] update schedule = $newTick, uuid = ${extResultResponseMsg.result.getInputModel}") - copy( - scheduleMap = scheduleMap.updated( - newTick, - getScheduledKeys(newTick) + msg.result.getInputModel, - ) - ) - } + def handleResult( + msg: ResultResponseMessage, + nextTick: Long, + ): ExtResultSchedule = { + copy( + scheduleMap = scheduleMap.updated( + nextTick, + getScheduledKeys(nextTick) + msg.result.getInputModel, + ) + ) } } diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index 4350c6bdbf..dc375bc8b1 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -342,7 +342,7 @@ object ExtSimSetup { ): ExtSimSetupData = { val extResultDataProvider = context.spawn( - ExtResultDataProvider(scheduler), + ExtResultDataProvider(scheduler, simonaConfig.simona.time.startTime), s"ExtResultDataProvider", ) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index 68b13e9b3c..e3fd35f4df 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -11,6 +11,7 @@ import edu.ie3.simona.api.data.em.ExtEmDataConnection import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.api.data.results.ExtResultDataConnection +import edu.ie3.simona.service.results.ExtResultDataProvider import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.{ActorRef => ClassicRef} @@ -30,7 +31,9 @@ final case class ExtSimSetupData( extSimAdapters: Iterable[ClassicRef], extPrimaryDataServices: Map[ExtPrimaryDataConnection, ClassicRef], extDataServices: Map[ExtInputDataConnection, ClassicRef], - extResultListeners: Map[ExtResultDataConnection, ActorRef[_]], + extResultListeners: Map[ExtResultDataConnection, ActorRef[ + ExtResultDataProvider.Request + ]], ) { private[setup] def update( @@ -53,7 +56,7 @@ final case class ExtSimSetupData( private[setup] def update( connection: ExtResultDataConnection, - ref: ActorRef[_], + ref: ActorRef[ExtResultDataProvider.Request], ): ExtSimSetupData = copy(extResultListeners = extResultListeners ++ Map(connection -> ref)) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index a5204d127a..39232ec3ea 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -270,7 +270,8 @@ class SimonaStandaloneSetup( context .spawn( ResultEventListener( - resultFileHierarchy + resultFileHierarchy, + extSimSetupData.extResultListeners, ), ResultEventListener.getClass.getSimpleName, ) From 7be910ecac7cc2714efe93a12d00cba520ab5b54 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 24 Feb 2025 15:58:06 +0100 Subject: [PATCH 003/125] Fixing config. --- .../edu/ie3/simona/config/RuntimeConfig.scala | 26 ++++++++++++------- .../edu/ie3/simona/config/SimonaConfig.scala | 2 +- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/config/RuntimeConfig.scala b/src/main/scala/edu/ie3/simona/config/RuntimeConfig.scala index 6e70a48302..3fc18073c6 100644 --- a/src/main/scala/edu/ie3/simona/config/RuntimeConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/RuntimeConfig.scala @@ -25,7 +25,7 @@ import scala.language.implicitConversions */ final case class RuntimeConfig( listener: Listener = Listener(), - participant: Participant = Participant.empty(), + participant: Participant = Participant.default, selectedSubgrids: Option[List[Int]] = None, selectedVoltLvls: Option[List[VoltLvlConfig]] = None, ) @@ -34,6 +34,12 @@ object RuntimeConfig { implicit def productHint[T]: ProductHint[T] = ProductHint[T](ConfigFieldMapping(CamelCase, CamelCase)) + private val defaultUuids = List("default") + + /** Returns the default runtime configuration. + */ + def default: RuntimeConfig = RuntimeConfig() + /** Wraps an [[BaseRuntimeConfig]] with a [[ParticipantRuntimeConfigs]]. * * @param config @@ -89,7 +95,7 @@ object RuntimeConfig { /** Returns a [[Participant]] object with default values. */ - def empty(): Participant = Participant( + def default: Participant = Participant( em = EmRuntimeConfig(), evcs = EvcsRuntimeConfig(), fixedFeedIn = FixedFeedInRuntimeConfig(), @@ -139,7 +145,7 @@ object RuntimeConfig { final case class EvcsRuntimeConfig( override val calculateMissingReactivePowerWithModel: Boolean = false, override val scaling: Double = 1.0, - override val uuids: List[String] = List.empty, + override val uuids: List[String] = defaultUuids, chargingStrategy: String = "maxPower", lowestEvSoc: Double = 0.2, ) extends BaseRuntimeConfig @@ -161,7 +167,7 @@ object RuntimeConfig { final case class EmRuntimeConfig( override val calculateMissingReactivePowerWithModel: Boolean = false, override val scaling: Double = 1.0, - override val uuids: List[String] = List.empty, + override val uuids: List[String] = defaultUuids, aggregateFlex: String = "SELF_OPT_EXCL_REG", curtailRegenerative: Boolean = false, ) extends BaseRuntimeConfig @@ -179,7 +185,7 @@ object RuntimeConfig { final case class FixedFeedInRuntimeConfig( override val calculateMissingReactivePowerWithModel: Boolean = false, override val scaling: Double = 1.0, - override val uuids: List[String] = List.empty, + override val uuids: List[String] = defaultUuids, ) extends BaseRuntimeConfig /** Runtime configuration for heat pumps. @@ -195,7 +201,7 @@ object RuntimeConfig { final case class HpRuntimeConfig( override val calculateMissingReactivePowerWithModel: Boolean = false, override val scaling: Double = 1.0, - override val uuids: List[String] = List.empty, + override val uuids: List[String] = defaultUuids, ) extends BaseRuntimeConfig /** Runtime configuration for loads. @@ -216,7 +222,7 @@ object RuntimeConfig { final case class LoadRuntimeConfig( override val calculateMissingReactivePowerWithModel: Boolean = false, override val scaling: Double = 1.0, - override val uuids: List[String] = List.empty, + override val uuids: List[String] = defaultUuids, modelBehaviour: String = "fix", reference: String = "power", ) extends BaseRuntimeConfig @@ -234,7 +240,7 @@ object RuntimeConfig { final case class PvRuntimeConfig( override val calculateMissingReactivePowerWithModel: Boolean = false, override val scaling: Double = 1.0, - override val uuids: List[String] = List.empty, + override val uuids: List[String] = defaultUuids, ) extends BaseRuntimeConfig /** Runtime configuration for electrical storages. @@ -254,7 +260,7 @@ object RuntimeConfig { final case class StorageRuntimeConfig( override val calculateMissingReactivePowerWithModel: Boolean = false, override val scaling: Double = 1.0, - override val uuids: List[String] = List.empty, + override val uuids: List[String] = defaultUuids, initialSoc: Double = 0d, targetSoc: Option[Double] = None, ) extends BaseRuntimeConfig @@ -272,6 +278,6 @@ object RuntimeConfig { final case class WecRuntimeConfig( override val calculateMissingReactivePowerWithModel: Boolean = false, override val scaling: Double = 1.0, - override val uuids: List[String] = List.empty, + override val uuids: List[String] = defaultUuids, ) extends BaseRuntimeConfig } diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index 5f85788241..02d9bab949 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -180,7 +180,7 @@ object SimonaConfig { input: Simona.Input, output: Simona.Output, powerflow: Simona.Powerflow, - runtime: RuntimeConfig, + runtime: RuntimeConfig = RuntimeConfig.default, simulationName: String, time: Simona.Time = Simona.Time(), ) From 0e455c29630d2a83a1c78db62c373b6bbc0a801c Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 27 Feb 2025 12:39:17 +0100 Subject: [PATCH 004/125] Adapting tests. --- .../primary/ExtPrimaryDataServiceSpec.scala | 16 +++++----------- .../simona/sim/setup/ExtSimSetupDataSpec.scala | 4 ++-- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala index edc341ac3a..085af06e4c 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala @@ -8,22 +8,16 @@ package edu.ie3.simona.service.primary import com.typesafe.config.ConfigFactory import edu.ie3.simona.agent.participant.data.Data.PrimaryData -import edu.ie3.simona.agent.participant2.ParticipantAgent.{ - DataProvision, - RegistrationSuccessfulMessage, -} +import edu.ie3.simona.agent.participant2.ParticipantAgent.{DataProvision, RegistrationSuccessfulMessage} import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.ontology.messages.Activation -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} import edu.ie3.simona.ontology.messages.services.ServiceMessage.PrimaryServiceRegistrationMessage import edu.ie3.simona.ontology.messages.services.WeatherMessage.RegisterForWeatherMessage import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.SimonaService import edu.ie3.simona.service.primary.ExtPrimaryDataService.InitExtPrimaryData -import edu.ie3.simona.test.common.{TestKitWithShutdown, TestSpawnerClassic} +import edu.ie3.simona.test.common.{AgentSpec, TestSpawnerClassic} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.ActorSystem import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps @@ -34,7 +28,7 @@ import scala.concurrent.duration.DurationInt import scala.jdk.CollectionConverters._ class ExtPrimaryDataServiceSpec - extends TestKitWithShutdown( + extends AgentSpec( ActorSystem( "ExtPrimaryDataServiceSpec", ConfigFactory @@ -96,7 +90,7 @@ class ExtPrimaryDataServiceSpec ) "refuse registration for wrong registration request" in { - serviceRef ! RegisterForWeatherMessage(51.4843281, 7.4116482) + serviceRef ! RegisterForWeatherMessage(self, 51.4843281, 7.4116482) expectNoMessage() } diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala index 0eb413fb9d..99ad59b832 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala @@ -113,7 +113,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val extSimSetupData = ExtSimSetupData() val resultConnection = - new ExtResultDataConnection(emptyMapResult, emptyMapResult) + new ExtResultDataConnection(emptyMapResult, emptyMapResult, emptyMapResult) val resultRef = TestProbe("result_service").ref val updated = extSimSetupData.update(resultConnection, resultRef) @@ -137,7 +137,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val emRef = TestProbe("em_service").ref.toClassic val resultConnection = - new ExtResultDataConnection(emptyMapResult, emptyMapResult) + new ExtResultDataConnection(emptyMapResult, emptyMapResult, emptyMapResult) val resultRef = TestProbe("result_service").ref val updated = extSimSetupData From 9f80d809598554465006fb2d4546d2c8df507770 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 4 Mar 2025 14:02:09 +0100 Subject: [PATCH 005/125] Some adaptions. --- .../event/listener/ResultEventListener.scala | 8 ++------ .../service/primary/PrimaryServiceProxy.scala | 6 +++--- .../service/results/ExtResultDataProvider.scala | 10 +++++----- .../scala/edu/ie3/simona/sim/SimonaSim.scala | 10 ++++++++-- .../edu/ie3/simona/sim/setup/ExtSimSetup.scala | 17 +++++++++++------ .../ie3/simona/sim/setup/ExtSimSetupData.scala | 4 +++- .../edu/ie3/simona/sim/setup/SimonaSetup.scala | 3 ++- .../sim/setup/SimonaStandaloneSetup.scala | 6 ++++-- .../primary/ExtPrimaryDataServiceSpec.scala | 10 ++++++++-- .../simona/sim/setup/ExtSimSetupDataSpec.scala | 12 ++++++++++-- 10 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index db23a703fc..1eeffa3a0b 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -7,11 +7,7 @@ package edu.ie3.simona.event.listener import edu.ie3.datamodel.io.processor.result.ResultEntityProcessor -import edu.ie3.datamodel.models.result.{ - ModelResultEntity, - NodeResult, - ResultEntity, -} +import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult import edu.ie3.simona.api.data.results.ExtResultDataConnection import edu.ie3.simona.event.ResultEvent.{ @@ -261,7 +257,7 @@ object ResultEventListener extends Transformer3wResultSupport { extResultListeners: Iterable[ActorRef[ExtResultDataProvider.Request]], ): Unit = Try { resultEntity match { - case modelResultEntity: ModelResultEntity => + case modelResultEntity: ResultEntity => extResultListeners.foreach( _ ! ResultResponseMessage(modelResultEntity) ) diff --git a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala index e406ac6a1d..967d78ed53 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala @@ -141,7 +141,7 @@ case class PrimaryServiceProxy( private def prepareStateData( primaryConfig: PrimaryConfig, simulationStart: ZonedDateTime, - extSimulationData: Map[ExtPrimaryDataConnection, ActorRef], + extSimulationData: Seq[(ExtPrimaryDataConnection, ActorRef)], ): Try[PrimaryServiceStateData] = { createSources(primaryConfig).map { case (mappingSource, metaInformationSource) => @@ -183,7 +183,7 @@ case class PrimaryServiceProxy( simulationStart, primaryConfig, mappingSource, - extSubscribersToService, + extSubscribersToService.toMap, ) } else { PrimaryServiceStateData( @@ -556,7 +556,7 @@ object PrimaryServiceProxy { final case class InitPrimaryServiceProxyStateData( primaryConfig: PrimaryConfig, simulationStart: ZonedDateTime, - extSimulationData: Map[ExtPrimaryDataConnection, ActorRef], + extSimulationData: Seq[(ExtPrimaryDataConnection, ActorRef)], ) extends InitializeServiceStateData /** Holding the state of an initialized proxy. diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala index d9b6c4b6c1..d602a177a7 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala @@ -6,7 +6,7 @@ package edu.ie3.simona.service.results -import edu.ie3.datamodel.models.result.ModelResultEntity +import edu.ie3.datamodel.models.result.ResultEntity import edu.ie3.simona.api.data.results.ExtResultDataConnection import edu.ie3.simona.api.data.results.ontology.{ ProvideResultEntities, @@ -56,7 +56,7 @@ object ExtResultDataProvider { /** ResultEventListener -> ExtResultDataProvider */ final case class ResultResponseMessage( - result: ModelResultEntity + result: ResultEntity ) extends Request { def tick(implicit startTime: ZonedDateTime): Long = TimeUtil.withDefaults.zonedDateTimeDifferenceInSeconds( @@ -198,7 +198,7 @@ object ExtResultDataProvider { currentTick ) // Expected keys are for this tick scheduled and not scheduled val receiveDataMap = - ReceiveDataMap[UUID, ModelResultEntity](expectedKeys) + ReceiveDataMap[UUID, ResultEntity](expectedKeys) val updatedSchedule = serviceStateData.extResultSchedule.handleActivation(currentTick) @@ -349,8 +349,8 @@ object ExtResultDataProvider { currentTick: Long, extResultSchedule: ExtResultSchedule, extResultsMessage: Option[ResultDataMessageFromExt] = None, - resultStorage: Map[UUID, ModelResultEntity] = Map.empty, - receiveDataMap: Option[ReceiveDataMap[UUID, ModelResultEntity]] = None, + resultStorage: Map[UUID, ResultEntity] = Map.empty, + receiveDataMap: Option[ReceiveDataMap[UUID, ResultEntity]] = None, ) { def handleActivation(activation: Activation): ExtResultStateData = { copy( diff --git a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala index c748313b16..dfbee6cd8e 100644 --- a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala +++ b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala @@ -9,7 +9,11 @@ package edu.ie3.simona.sim import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.api.ExtSimAdapter import edu.ie3.simona.event.RuntimeEvent -import edu.ie3.simona.event.listener.{DelayedStopHelper, RuntimeEventListener} +import edu.ie3.simona.event.listener.{ + DelayedStopHelper, + ResultEventListener, + RuntimeEventListener, +} import edu.ie3.simona.main.RunSimona.SimonaEnded import edu.ie3.simona.scheduler.TimeAdvancer import edu.ie3.simona.sim.setup.SimonaSetup @@ -71,7 +75,6 @@ object SimonaSim { ): Behavior[Request] = Behaviors .receivePartial[Request] { case (ctx, Start(_)) => - val resultEventListeners = simonaSetup.resultEventListener(ctx) val runtimeEventListener = simonaSetup.runtimeEventListener(ctx) val timeAdvancer = @@ -86,6 +89,9 @@ object SimonaSim { val extSimulationData = simonaSetup.extSimulations(ctx, scheduler, extSimDir) + val resultEventListeners = + simonaSetup.resultEventListener(ctx, extSimulationData) + /* start services */ // primary service proxy val primaryServiceProxy = diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index aff323d805..b30bc1fc63 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -45,6 +45,7 @@ import org.apache.pekko.actor.{Props, ActorRef => ClassicRef} import org.apache.pekko.util.{Timeout => PekkoTimeout} import org.slf4j.{Logger, LoggerFactory} +import java.time.ZonedDateTime import java.util.UUID import scala.concurrent.Await import scala.concurrent.duration.DurationInt @@ -80,6 +81,7 @@ object ExtSimSetup { )(implicit context: ActorContext[_], scheduler: ActorRef[SchedulerMessage], + startTime: ZonedDateTime, resolution: FiniteDuration, ): ExtSimSetupData = extLinks.zipWithIndex.foldLeft(ExtSimSetupData()) { case (extSimSetupData, (extLink, index)) => @@ -148,6 +150,7 @@ object ExtSimSetup { context: ActorContext[_], scheduler: ActorRef[SchedulerMessage], extSimAdapterData: ExtSimAdapterData, + startTime: ZonedDateTime, resolution: FiniteDuration, ): ExtSimSetupData = { implicit val extSimAdapter: ClassicRef = extSimAdapterData.getAdapter @@ -327,8 +330,10 @@ object ExtSimSetup { * the scheduler of simona * @param extSimAdapter * the adapter for the external simulation - * @param simonaConfig - * the config + * @param startTime + * Of the simulation. + * @param resolution + * Of the power flow. * @return * an updated [[ExtSimSetupData]] */ @@ -339,11 +344,12 @@ object ExtSimSetup { context: ActorContext[_], scheduler: ActorRef[SchedulerMessage], extSimAdapter: ClassicRef, - simonaConfig: SimonaConfig, + startTime: ZonedDateTime, + resolution: FiniteDuration, ): ExtSimSetupData = { val extResultDataProvider = context.spawn( - ExtResultDataProvider(scheduler, simonaConfig.simona.time.startTime), + ExtResultDataProvider(scheduler, startTime), s"ExtResultDataProvider", ) @@ -369,8 +375,7 @@ object ExtSimSetup { extSimAdapter, ) - val powerFlowResolution = - simonaConfig.simona.powerflow.resolution.toSeconds + val powerFlowResolution = resolution.toSeconds extResultDataProvider ! ExtResultDataProvider.Create( InitExtResultData(extResultDataConnection, powerFlowResolution), diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index 55057ae706..713cd5c8a1 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -31,7 +31,9 @@ final case class ExtSimSetupData( extSimAdapters: Iterable[ClassicRef], extPrimaryDataServices: Seq[(ExtPrimaryDataConnection, ClassicRef)], extDataServices: Seq[(ExtInputDataConnection, ClassicRef)], - extResultListeners: Seq[(ExtResultDataConnection, ActorRef[_])], + extResultListeners: Seq[ + (ExtResultDataConnection, ActorRef[ExtResultDataProvider.Request]) + ], ) { private[setup] def update( diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala index e458c46315..77dc626fbc 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -66,7 +66,8 @@ trait SimonaSetup { * A sequence of actor references to result event listeners */ def resultEventListener( - context: ActorContext[_] + context: ActorContext[_], + extSimSetupData: ExtSimSetupData, ): Seq[ActorRef[ResultEventListener.Request]] /** Creates a primary service proxy. The proxy is the first instance to ask diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index 174be1a603..bbb82351c7 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -210,6 +210,7 @@ class SimonaStandaloneSetup( setupExtSim(extLinks, args)( context, scheduler, + simonaConfig.simona.time.startTime, simonaConfig.simona.powerflow.resolution, ) } @@ -262,7 +263,8 @@ class SimonaStandaloneSetup( ) override def resultEventListener( - context: ActorContext[_] + context: ActorContext[_], + extSimSetupData: ExtSimSetupData, ): Seq[ActorRef[ResultEventListener.Request]] = { // append ResultEventListener as well to write raw output files Seq( @@ -270,7 +272,7 @@ class SimonaStandaloneSetup( .spawn( ResultEventListener( resultFileHierarchy, - extSimSetupData.extResultListeners, + extSimSetupData.extResultListeners.toMap, ), ResultEventListener.getClass.getSimpleName, ) diff --git a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala index 085af06e4c..3b57bd2bce 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala @@ -8,10 +8,16 @@ package edu.ie3.simona.service.primary import com.typesafe.config.ConfigFactory import edu.ie3.simona.agent.participant.data.Data.PrimaryData -import edu.ie3.simona.agent.participant2.ParticipantAgent.{DataProvision, RegistrationSuccessfulMessage} +import edu.ie3.simona.agent.participant2.ParticipantAgent.{ + DataProvision, + RegistrationSuccessfulMessage, +} import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.ontology.messages.Activation -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} import edu.ie3.simona.ontology.messages.services.ServiceMessage.PrimaryServiceRegistrationMessage import edu.ie3.simona.ontology.messages.services.WeatherMessage.RegisterForWeatherMessage import edu.ie3.simona.scheduler.ScheduleLock diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala index 8046d694d6..ecda681924 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala @@ -113,7 +113,11 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val extSimSetupData = ExtSimSetupData() val resultConnection = - new ExtResultDataConnection(emptyMapResult, emptyMapResult, emptyMapResult) + new ExtResultDataConnection( + emptyMapResult, + emptyMapResult, + emptyMapResult, + ) val resultRef = TestProbe("result_service").ref val updated = extSimSetupData.update(resultConnection, resultRef) @@ -137,7 +141,11 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val emRef = TestProbe("em_service").ref.toClassic val resultConnection = - new ExtResultDataConnection(emptyMapResult, emptyMapResult, emptyMapResult) + new ExtResultDataConnection( + emptyMapResult, + emptyMapResult, + emptyMapResult, + ) val resultRef = TestProbe("result_service").ref val updated = extSimSetupData From 34679c3864701ab4c978ceb80c64c46cd7bd3f0b Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 4 Mar 2025 14:09:33 +0100 Subject: [PATCH 006/125] Updating to PSDM version `6.0.0`. --- .../ie3/simona/service/primary/PrimaryServiceProxySpec.scala | 2 +- .../ie3/simona/service/primary/PrimaryServiceProxySqlIT.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala index 79497b01ba..3691b3efde 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala @@ -253,7 +253,7 @@ class PrimaryServiceProxySpec InitPrimaryServiceProxyStateData( validPrimaryConfig, simulationStart, - Map.empty, + Seq.empty, ) val proxyRef: TestActorRef[PrimaryServiceProxy] = TestActorRef( new PrimaryServiceProxy(scheduler.ref, initStateData, simulationStart) diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySqlIT.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySqlIT.scala index 3f349c483d..33939cfb7d 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySqlIT.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySqlIT.scala @@ -101,7 +101,7 @@ class PrimaryServiceProxySqlIT sqlParams = Some(sqlParams), ), simulationStart, - Map.empty, + Seq.empty, ) TestActorRef( From ff6f7393a1d5893cd0d35feca05210527aa391a9 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 5 Mar 2025 17:28:22 +0100 Subject: [PATCH 007/125] Adapting some parts. --- .../messages/flex/FlexibilityMessage.scala | 7 +- .../flex/MinMaxFixFlexibilityMessage.scala | 127 ++++++++++++++++++ .../simona/service/em/ExtEmDataService.scala | 14 +- 3 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala index 8a8e93bc6e..9e009e12b0 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala @@ -39,11 +39,16 @@ object FlexibilityMessage { final case class SetPointFlexRequest( tick: Long, + controlSignal: Boolean, setPower: Power, nextSetPointTick: Option[Long], ) extends FlexRequest - /** Message that registers a controlled asset model with an + final case class FlexOptionsRequest( + tick: Long + ) extends FlexRequest + + /** Message that registers a flex options provider with an * [[edu.ie3.simona.agent.em.EmAgent]]. * * @param participant diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala new file mode 100644 index 0000000000..6d42c70d0a --- /dev/null +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala @@ -0,0 +1,127 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.ontology.messages.flex + +import edu.ie3.simona.exceptions.CriticalFailureException +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.ProvideFlexOptions +import edu.ie3.util.scala.quantities.DefaultQuantities._ +import squants.Power + +import java.util.UUID + +/** Messages that communicate interval-based flexibility with minimum, reference + * and maximum power + */ +object MinMaxFixFlexibilityMessage { + + /** Message that provides flexibility options using reference, minimum and + * maximum power. It is possible that the power values are either all + * negative or all positive, meaning that feed-in or load is mandatory. + * + * @param modelUuid + * The UUID of the flex options provider asset model + * @param ref + * The reference active power that the flex options provider would + * produce/consume regularly at the current tick, i.e. if it was not + * flex-controlled + * @param min + * The minimum active power that the flex options provider allows at the + * current tick + * @param max + * The maximum active power that the flex options provider allows at the + * current tick + */ + final case class ProvideMinMaxFixFlexOptions private ( + override val modelUuid: UUID, + ref: Power, + min: Power, + max: Power, + fix: Power, + ) extends ProvideFlexOptions + + object ProvideMinMaxFixFlexOptions { + + implicit class RichIterable( + private val flexOptions: Iterable[ProvideMinMaxFixFlexOptions] + ) extends AnyVal { + def flexSum: (Power, Power, Power, Power) = + flexOptions.foldLeft((zeroKW, zeroKW, zeroKW, zeroKW)) { + case ( + (sumRef, sumMin, sumMax, sumFix), + ProvideMinMaxFixFlexOptions(_, addRef, addMin, addMax, addFix), + ) => + ( + sumRef + addRef, + sumMin + addMin, + sumMax + addMax, + sumFix + addFix, + ) + } + } + + /** Creates a + * [[edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions]] + * message with sanity checks regarding the power values + * + * @param modelUuid + * The UUID of the flex options provider asset model + * @param ref + * The reference active power that the flex options provider would + * produce/consume regularly at the current tick, i.e. if it was not + * flex-controlled + * @param min + * The minimum active power that the flex options provider allows at the + * current tick + * @param max + * The maximum active power that the flex options provider allows at the + * current tick + * @return + * The + * [[edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions]] + * message + */ + def apply( + modelUuid: UUID, + ref: Power, + min: Power, + max: Power, + fix: Power, + ): ProvideMinMaxFixFlexOptions = { + if (min > ref) + throw new CriticalFailureException( + s"Minimum power $min is greater than reference power $ref" + ) + + if (ref > max) + throw new CriticalFailureException( + s"Reference power $ref is greater than maximum power $max" + ) + + new ProvideMinMaxFixFlexOptions(modelUuid, ref, min, max, fix) + } + + /** Creates a + * [[edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions]] + * message that does not allow any flexibility, meaning that min = ref = + * max power. + * + * @param modelUuid + * The UUID of the flex provider asset model + * @param power + * The active power that the flex provider requires + * @return + * The corresponding + * [[edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions]] + * message + */ + def noFlexOption( + modelUuid: UUID, + power: Power, + ): ProvideMinMaxFixFlexOptions = + ProvideMinMaxFixFlexOptions(modelUuid, power, power, power, zeroKW) + } +} 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 c966215e20..74ad2dd71b 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -12,11 +12,11 @@ import edu.ie3.simona.agent.participant2.ParticipantAgent.{ RegistrationResponseMessage, RegistrationSuccessfulMessage, } -import edu.ie3.simona.api.data.em.ExtEmDataConnection import edu.ie3.simona.api.data.em.ontology.{ EmDataMessageFromExt, ProvideEmSetPointData, } +import edu.ie3.simona.api.data.em.{ExtEmDataConnection, NoSetPointValue} import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{InitializationException, ServiceException} @@ -25,11 +25,11 @@ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ IssuePowerControl, SetPointFlexRequest, } +import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ DataResponseMessage, ExtEmDataServiceRegistrationMessage, } -import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceBaseStateData, @@ -222,9 +222,10 @@ final case class ExtEmDataService( val maybeNextTick = emDataMessage.maybeNextTick.toScala.map(Long2long) if (actorToEmData.nonEmpty) { - actorToEmData.foreach { case (actor, setPoint) => + actorToEmData.foreach { case (actor, (controlSignal, setPoint)) => actor ! SetPointFlexRequest( tick, + controlSignal, setPoint, maybeNextTick, ) @@ -238,8 +239,11 @@ final case class ExtEmDataService( private def convertToSetPoint( value: PValue - ): Power = { - Kilowatts(value.getP.get.getValue.doubleValue()) + ): (Boolean, Power) = { + value match { + case _: NoSetPointValue => (false, Kilowatts(0.0)) + case _ => (true, Kilowatts(value.getP.get.getValue.doubleValue())) + } } /** Handle a message from outside the simulation From 9d40d87602d1ca1baa4754bfe5ab4dee521b45df Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 6 Mar 2025 15:30:12 +0100 Subject: [PATCH 008/125] Refactoring external data connections. --- src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala | 2 +- src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index b30bc1fc63..9bb3f5413a 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -244,7 +244,7 @@ object ExtSimSetup { * @return * The reference to the service. */ - private[setup] def setupInputService[T <: ExtInputDataConnection]( + private[setup] def setupInputService[T <: ExtInputDataConnection[_]]( extInputDataConnection: T, props: ClassicRef => Props, name: String, diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index 713cd5c8a1..4451bb72d4 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -30,7 +30,7 @@ import org.apache.pekko.actor.{ActorRef => ClassicRef} final case class ExtSimSetupData( extSimAdapters: Iterable[ClassicRef], extPrimaryDataServices: Seq[(ExtPrimaryDataConnection, ClassicRef)], - extDataServices: Seq[(ExtInputDataConnection, ClassicRef)], + extDataServices: Seq[(ExtInputDataConnection[_], ClassicRef)], extResultListeners: Seq[ (ExtResultDataConnection, ActorRef[ExtResultDataProvider.Request]) ], @@ -45,7 +45,7 @@ final case class ExtSimSetupData( ) private[setup] def update( - connection: ExtInputDataConnection, + connection: ExtInputDataConnection[_], ref: ClassicRef, ): ExtSimSetupData = connection match { case primaryConnection: ExtPrimaryDataConnection => From 1ea5a839ec6a73fee0e927508df4ac7313e1666f Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 7 Mar 2025 14:48:59 +0100 Subject: [PATCH 009/125] Adding `EmDataService` to `EmAgent`. --- .../ie3/simona/agent/EnvironmentRefs.scala | 3 + .../edu/ie3/simona/agent/em/EmAgent.scala | 162 +++++++++- .../agent/grid/GridAgentController.scala | 11 + .../messages/services/EmMessage.scala | 39 +++ .../messages/services/ServiceMessage.scala | 9 +- .../ie3/simona/service/ExtDataSupport.scala | 2 +- .../simona/service/em/ExtEmDataService.scala | 277 +++++++++++------- .../scala/edu/ie3/simona/sim/SimonaSim.scala | 7 +- 8 files changed, 383 insertions(+), 127 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala diff --git a/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala b/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala index 4b5c6c920d..f22facf5d5 100644 --- a/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala +++ b/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala @@ -22,6 +22,8 @@ import org.apache.pekko.actor.{ActorRef => ClassicRef} * Reference to the primary service proxy * @param weather * Reference to the service, that provides weather information + * @param emDataService + * An energy management service. * @param evDataService * Reference to the EV data service, if existing */ @@ -30,5 +32,6 @@ final case class EnvironmentRefs( runtimeEventListener: ActorRef[RuntimeEvent], primaryServiceProxy: ClassicRef, weather: ClassicRef, + emDataService: Option[ClassicRef], evDataService: Option[ClassicRef], ) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index a2df2748f1..51940f8aa9 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -25,14 +25,21 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ } import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + IssueFlexControlResponse, + SimonaFlexOptionsResponse, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import edu.ie3.util.scala.quantities.DefaultQuantities._ -import org.apache.pekko.actor.typed.scaladsl.Behaviors +import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} import org.apache.pekko.actor.typed.{ActorRef, Behavior} +import org.apache.pekko.actor.{ActorRef => ClassicRef} import java.time.ZonedDateTime +import java.util.UUID /** Energy management agent that receives flex options from and issues control * messages to connected agents @@ -91,6 +98,8 @@ object EmAgent { * that is activating this agent * @param listener * A collection of result event listeners + * @param emDataService + * An energy management service. */ def apply( inputModel: EmInput, @@ -100,6 +109,7 @@ object EmAgent { simulationStartDate: ZonedDateTime, parent: Either[ActorRef[SchedulerMessage], ActorRef[FlexResponse]], listener: Iterable[ActorRef[ResultEvent]], + emDataService: Option[ClassicRef], ): Behavior[Request] = Behaviors.setup[Request] { ctx => val constantData = EmData( outputConfig, @@ -125,6 +135,7 @@ object EmAgent { } }, listener, + emDataService, ) val modelShell = EmModelShell( @@ -134,6 +145,15 @@ object EmAgent { modelConfig, ) + // register for ext em service, if service is present + emDataService.foreach(service => + registerForEmService( + service, + constantData, + modelShell.uuid, + )(ctx) + ) + inactive( constantData, modelShell, @@ -141,6 +161,25 @@ object EmAgent { ) } + private def registerForEmService( + emDataService: ClassicRef, + emData: EmData, + uuid: UUID, + )(ctx: ActorContext[Request]): Unit = { + val flexAdapter = emData.parentData match { + case Left(_) => + ctx.messageAdapter[FlexRequest](Flex) + case Right(value) => + value.flexAdapter + } + + emDataService ! RegisterForEmDataService( + uuid, + ctx.self, + flexAdapter, + ) + } + /** Behavior of an inactive [[EmAgent]], which waits for an activation or flex * request to be activated. */ @@ -222,8 +261,8 @@ object EmAgent { emData: EmData, modelShell: EmModelShell, flexOptionsCore: EmDataCore.AwaitingFlexOptions, - ): Behavior[Request] = Behaviors.receiveMessagePartial { - case flexOptions: ProvideFlexOptions => + ): Behavior[Request] = Behaviors.receivePartial { + case (ctx, flexOptions: ProvideFlexOptions) => val updatedCore = flexOptionsCore.handleFlexOptions(flexOptions) if (updatedCore.isComplete) { @@ -233,24 +272,22 @@ object EmAgent { val (emRef, emMin, emMax) = modelShell.aggregateFlexOptions(allFlexOptions) - if (emData.outputConfig.flexResult) { - val flexResult = new FlexOptionsResult( - flexOptionsCore.activeTick.toDateTime( - emData.simulationStartDate - ), - modelShell.uuid, - emRef.toMegawatts.asMegaWatt, - emMin.toMegawatts.asMegaWatt, - emMax.toMegawatts.asMegaWatt, - ) + val flexResult = new FlexOptionsResult( + flexOptionsCore.activeTick.toDateTime(emData.simulationStartDate), + modelShell.uuid, + emRef.toMegawatts.asMegaWatt, + emMin.toMegawatts.asMegaWatt, + emMax.toMegawatts.asMegaWatt, + ) + if (emData.outputConfig.flexResult) { emData.listener.foreach { _ ! FlexOptionsResultEvent(flexResult) } } - emData.parentData match { - case Right(flexStateData) => + (emData.parentData, emData.emDataService) match { + case (Right(flexStateData), None) => // provide aggregate flex options to parent val flexMessage = ProvideMinMaxFlexOptions( modelShell.uuid, @@ -271,7 +308,27 @@ object EmAgent { awaitingFlexCtrl(updatedEmData, modelShell, updatedCore) - case Left(_) => + case (Right(flexStateData), Some(emService)) => + // if we have an external data service, it will replace the default agent communication + emService ! SimonaFlexOptionsResponse( + flexStateData.emAgent, + flexResult, + ) + + awaitingFlexCtrlWithService( + emData, + emService, + ProvideMinMaxFlexOptions( + modelShell.uuid, + emRef, + emMin, + emMax, + ), + modelShell, + flexOptionsCore, + ) + + case (Left(_), None) => // We're not em-controlled ourselves, // always desire to come as close as possible to 0 kW val setPower = zeroKW @@ -289,6 +346,26 @@ object EmAgent { } awaitingCompletions(emData, modelShell, newCore) + + case (Left(_), Some(emService)) => + // if we have an external data service, it will replace the default agent communication + emService ! SimonaFlexOptionsResponse( + ctx.self, + flexResult, + ) + + awaitingFlexCtrlWithService( + emData, + emService, + ProvideMinMaxFlexOptions( + modelShell.uuid, + emRef, + emMin, + emMax, + ), + modelShell, + flexOptionsCore, + ) } } else { @@ -353,6 +430,56 @@ object EmAgent { awaitingCompletions(emData, modelShell, newCore) } + /** Behavior of an [[EmAgent]] waiting for a flex control message to be + * received in order to transition to the next behavior. This behavior should + * only be used by EmAgents that are themselves EM-controlled. + */ + private def awaitingFlexCtrlWithService( + emData: EmData, + emService: ClassicRef, + ownFlexOptions: ProvideFlexOptions, + modelShell: EmModelShell, + flexOptionsCore: EmDataCore.AwaitingFlexOptions, + ): Behavior[Request] = Behaviors.receiveMessagePartial { + case Flex(flexCtrl: IssueFlexControl) => + val setPointActivePower = EmTools.determineFlexPower( + ownFlexOptions, + flexCtrl, + ) + + // flex options calculated by connected agents + val receivedFlexOptions = flexOptionsCore.getFlexOptions + + val ctrlSetPoints = + modelShell.determineFlexControl( + receivedFlexOptions, + setPointActivePower, + ) + + val (allFlexMsgs, newCore) = flexOptionsCore + .handleFlexCtrl(ctrlSetPoints) + .fillInMissingIssueCtrl() + .complete() + + allFlexMsgs.foreach { case (receiver, msg) => + val time = msg.tick.toDateTime(emData.simulationStartDate) + + val setPoint = msg match { + case IssuePowerControl(_, setPower) => Some(setPower) + case _ => None + } + + emService ! IssueFlexControlResponse( + receiver, + time, + modelShell.uuid, + setPoint, + ) + } + + awaitingCompletions(emData, modelShell, newCore) + } + /** Behavior of an [[EmAgent]] waiting for completions messages to be received * in order to transition to the inactive behavior. */ @@ -454,12 +581,15 @@ object EmAgent { * em-controlled, or a [[Left]] with [[SchedulerData]] * @param listener * A collection of result event listeners + * @param emDataService + * An energy management service. */ private final case class EmData( outputConfig: NotifierConfig, simulationStartDate: ZonedDateTime, parentData: Either[SchedulerData, FlexControlledData], listener: Iterable[ActorRef[ResultEvent]], + emDataService: Option[ClassicRef], ) /** The existence of this data object indicates that the corresponding agent diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala index ddf5a0be43..e64216b4e1 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala @@ -106,6 +106,7 @@ class GridAgentController( EmConfigUtil(emConfigs), outputConfigUtil, firstLevelEms, + emDataService = environmentRefs.emDataService, ) /* Browse through all system participants, build actors and map their node's UUID to the actor references */ @@ -252,6 +253,8 @@ class GridAgentController( * higher levels * @param previousLevelEms * EMs that have been built by the previous recursion level + * @param emDataService + * An energy management service. * @return * Map from model UUID to EmAgent ActorRef */ @@ -260,6 +263,7 @@ class GridAgentController( outputConfigUtil: OutputConfigUtil, emInputs: Map[UUID, EmInput], previousLevelEms: Map[UUID, ActorRef[FlexResponse]] = Map.empty, + emDataService: Option[ClassicRef], ): Map[UUID, ActorRef[FlexResponse]] = { // For the current level, split controlled and uncontrolled EMs. // Uncontrolled EMs can be built right away. @@ -273,6 +277,7 @@ class GridAgentController( emConfigUtil.getOrDefault(uuid), outputConfigUtil.getOrDefault(NotifierIdentifier.Em), maybeControllingEm = None, + emDataService, ) Right(uuid -> actor) } @@ -295,6 +300,7 @@ class GridAgentController( outputConfigUtil, controllingEms, previousLevelAndUncontrolledEms, + emDataService, ) val controlledEms = controlledEmInputs.map { case (uuid, emInput) => @@ -314,6 +320,7 @@ class GridAgentController( emConfigUtil.getOrDefault(uuid), outputConfigUtil.getOrDefault(NotifierIdentifier.Em), maybeControllingEm = controllingEm, + emDataService, ) }.toMap @@ -630,6 +637,8 @@ class GridAgentController( * Configuration for output notification * @param maybeControllingEm * The parent EmAgent, if applicable + * @param emDataService + * An energy management service. * @return * The [[EmAgent]] 's [[ActorRef]] */ @@ -638,6 +647,7 @@ class GridAgentController( modelConfiguration: EmRuntimeConfig, outputConfig: NotifierConfig, maybeControllingEm: Option[ActorRef[FlexResponse]], + emDataService: Option[ClassicRef], ): ActorRef[FlexResponse] = gridAgentContext.spawn( EmAgent( @@ -650,6 +660,7 @@ class GridAgentController( environmentRefs.scheduler ), listener, + emDataService, ), actorName(classOf[EmAgent.type], emInput.getId), ) diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala new file mode 100644 index 0000000000..bace7b252b --- /dev/null +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala @@ -0,0 +1,39 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.ontology.messages.services + +import edu.ie3.datamodel.models.result.system.FlexOptionsResult +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ + FlexRequest, + FlexResponse, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.DataResponseMessage +import org.apache.pekko.actor.typed.ActorRef +import squants.Power + +import java.time.ZonedDateTime +import java.util.UUID + +sealed trait EmMessage + +object EmMessage { + + trait EmResponseMessage extends EmMessage with DataResponseMessage + + final case class SimonaFlexOptionsResponse( + receiver: ActorRef[FlexResponse], + flexOptions: FlexOptionsResult, + ) extends EmResponseMessage + + final case class IssueFlexControlResponse( + receiver: ActorRef[FlexRequest], + time: ZonedDateTime, + model: UUID, + setPoint: Option[Power], + ) extends EmResponseMessage + +} diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala index 07bccaeeaf..5d45b0241a 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala @@ -7,13 +7,12 @@ package edu.ie3.simona.ontology.messages.services import edu.ie3.simona.agent.em.EmAgent - -import org.apache.pekko.actor.{ActorRef => ClassicRef} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.FlexRequest +import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import org.apache.pekko.actor.typed.ActorRef +import org.apache.pekko.actor.{ActorRef => ClassicRef} import java.util.UUID -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.FlexRequest -import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey /** Collections of all messages, that are send to and from the different * services @@ -50,7 +49,7 @@ object ServiceMessage { final case class WorkerRegistrationMessage(requestingActor: ClassicRef) extends ServiceRegistrationMessage - final case class ExtEmDataServiceRegistrationMessage( + final case class RegisterForEmDataService( modelUuid: UUID, requestingActor: ActorRef[EmAgent.Request], flexAdapter: ActorRef[FlexRequest], diff --git a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala index 339f684101..b1429736a4 100644 --- a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala +++ b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala @@ -21,7 +21,7 @@ trait ExtDataSupport[ val updatedStateData = handleDataMessage(extMsg)(stateData) context become idle(updatedStateData) - case extResponseMsg: EvResponseMessage => + case extResponseMsg: DataResponseMessage => val updatedStateData = handleDataResponseMessage(extResponseMsg)(stateData) context become idle(updatedStateData) 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 74ad2dd71b..68fb71713c 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -6,29 +6,25 @@ package edu.ie3.simona.service.em +import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.agent.participant2.ParticipantAgent.{ - RegistrationResponseMessage, - RegistrationSuccessfulMessage, -} -import edu.ie3.simona.api.data.em.ontology.{ - EmDataMessageFromExt, - ProvideEmSetPointData, -} +import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexOptionValue} +import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.api.data.em.{ExtEmDataConnection, NoSetPointValue} import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{InitializationException, ServiceException} -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ - FlexRequest, - IssuePowerControl, - SetPointFlexRequest, +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ +import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + IssueFlexControlResponse, + SimonaFlexOptionsResponse, } import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ DataResponseMessage, - ExtEmDataServiceRegistrationMessage, + RegisterForEmDataService, } import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, @@ -37,17 +33,26 @@ import edu.ie3.simona.service.ServiceStateData.{ import edu.ie3.simona.service.em.ExtEmDataService.{ ExtEmDataStateData, InitExtEmData, - WrappedRegistrationResponse, } import edu.ie3.simona.service.{ExtDataSupport, SimonaService} +import edu.ie3.simona.util.ReceiveDataMap +import edu.ie3.util.quantities.PowerSystemUnits.KILOWATT +import edu.ie3.util.quantities.QuantityUtils._ +import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.{ActorContext, Props, ActorRef => ClassicRef} import squants.Power import squants.energy.Kilowatts +import tech.units.indriya.ComparableQuantity import java.util.UUID -import scala.jdk.CollectionConverters.{CollectionHasAsScala, MapHasAsScala} -import scala.jdk.OptionConverters.RichOptional +import javax.measure.quantity.{Power => PsdmPower} +import scala.jdk.CollectionConverters.{ + ListHasAsScala, + MapHasAsJava, + MapHasAsScala, +} +import scala.jdk.OptionConverters.{RichOption, RichOptional} import scala.util.{Failure, Success, Try} object ExtEmDataService { @@ -58,26 +63,21 @@ object ExtEmDataService { ) final case class ExtEmDataStateData( - extEmData: ExtEmDataConnection, - subscribers: List[UUID] = List.empty, - uuidToActorRef: Map[UUID, ActorRef[EmAgent.Request]] = - Map.empty[UUID, ActorRef[EmAgent.Request]], // subscribers in SIMONA - uuidToAdapterRef: Map[UUID, ActorRef[FlexRequest]] = - Map.empty[UUID, ActorRef[FlexRequest]], // subscribers in SIMONA + extEmDataConnection: ExtEmDataConnection, + uuidToActorRef: Map[UUID, ActorRef[EmAgent.Request]] = Map.empty, + actorRefToUuid: Map[ActorRef[FlexResponse], UUID] = Map.empty, + uuidToAdapterRef: Map[UUID, ActorRef[FlexRequest]] = Map.empty, + flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, extEmDataMessage: Option[EmDataMessageFromExt] = None, + flexOptionResponse: ReceiveDataMap[UUID, FlexOptionsResult] = + ReceiveDataMap.empty, + setPointResponse: ReceiveDataMap[UUID, EmSetPointResult] = + ReceiveDataMap.empty, ) extends ServiceBaseStateData case class InitExtEmData( extEmData: ExtEmDataConnection ) extends InitializeServiceStateData - - final case class WrappedIssuePowerControl( - issuePowerControl: IssuePowerControl - ) extends EmAgent.Request - - final case class WrappedRegistrationResponse( - registrationResponse: RegistrationResponseMessage - ) extends EmAgent.Request } final case class ExtEmDataService( @@ -85,6 +85,16 @@ final case class ExtEmDataService( ) extends SimonaService[ExtEmDataStateData](scheduler) with ExtDataSupport[ExtEmDataStateData] { + implicit class SquantsToQuantity(private val value: Power) { + def toQuantity: ComparableQuantity[PsdmPower] = value.toKilowatts.asKiloWatt + } + + implicit class quantityToSquants( + private val value: ComparableQuantity[PsdmPower] + ) { + def toSquants: Power = Kilowatts(value.to(KILOWATT).getValue.doubleValue()) + } + /** Initialize the concrete service implementation using the provided * initialization data. This method should perform all heavyweight tasks * before the actor becomes ready. The return values are a) the state data of @@ -102,11 +112,8 @@ final case class ExtEmDataService( override def init( initServiceData: InitializeServiceStateData ): Try[(ExtEmDataStateData, Option[Long])] = initServiceData match { - case InitExtEmData(extEmData) => - val emDataInitializedStateData = ExtEmDataStateData( - extEmData, - subscribers = extEmData.getControlledEms.asScala.toList, - ) + case InitExtEmData(extEmDataConnection) => + val emDataInitializedStateData = ExtEmDataStateData(extEmDataConnection) Success( emDataInitializedStateData, None, @@ -134,7 +141,7 @@ final case class ExtEmDataService( registrationMessage: ServiceMessage.ServiceRegistrationMessage )(implicit serviceStateData: ExtEmDataStateData): Try[ExtEmDataStateData] = registrationMessage match { - case ExtEmDataServiceRegistrationMessage( + case RegisterForEmDataService( modelUuid, requestingActor, flexAdapter, @@ -145,7 +152,7 @@ final case class ExtEmDataService( case invalidMessage => Failure( InvalidRegistrationRequestException( - s"A primary service provider is not able to handle registration request '$invalidMessage'." + s"An external em service is not able to handle registration request '$invalidMessage'." ) ) } @@ -153,22 +160,18 @@ final case class ExtEmDataService( private def handleEmRegistrationRequest( modelUuid: UUID, modelActorRef: ActorRef[EmAgent.Request], - flexAdapterRef: ActorRef[FlexRequest], - )(implicit serviceStateData: ExtEmDataStateData): ExtEmDataStateData = { - if (serviceStateData.subscribers.contains(modelUuid)) { - modelActorRef ! WrappedRegistrationResponse( - RegistrationSuccessfulMessage(self, 0L) - ) - serviceStateData.copy( - uuidToActorRef = - serviceStateData.uuidToActorRef + (modelUuid -> modelActorRef), - uuidToAdapterRef = - serviceStateData.uuidToAdapterRef + (modelUuid -> flexAdapterRef), - ) - } else { - serviceStateData - } - } + flexAdapter: ActorRef[FlexRequest], + )(implicit serviceStateData: ExtEmDataStateData): ExtEmDataStateData = + serviceStateData.copy( + uuidToActorRef = + serviceStateData.uuidToActorRef + (modelUuid -> modelActorRef), + actorRefToUuid = + serviceStateData.actorRefToUuid + (modelActorRef -> modelUuid), + uuidToAdapterRef = + serviceStateData.uuidToAdapterRef + (modelUuid -> flexAdapter), + flexAdapterToUuid = + serviceStateData.flexAdapterToUuid + (flexAdapter -> modelUuid), + ) /** Send out the information to all registered recipients * @@ -185,65 +188,89 @@ final case class ExtEmDataService( serviceStateData: ExtEmDataStateData, ctx: ActorContext, ): (ExtEmDataStateData, Option[Long]) = { - serviceStateData.extEmDataMessage.getOrElse( + val updatedStateData = serviceStateData.extEmDataMessage.getOrElse( throw ServiceException( "ExtPrimaryDataService was triggered without ExtEmDataMessage available" ) ) match { - case providedEmData: ProvideEmSetPointData => - announceEmData(tick, providedEmData)( - serviceStateData, - ctx, + case requestEmFlexResults: RequestEmFlexResults => + val uuids = requestEmFlexResults.emEntities().asScala.toSet + + serviceStateData.copy( + extEmDataMessage = None, + flexOptionResponse = ReceiveDataMap(uuids), + ) + + case requestEmSetPoints: RequestEmSetPoints => + val uuids = requestEmSetPoints.emEntities().asScala.toSet + + serviceStateData.copy( + extEmDataMessage = None, + setPointResponse = ReceiveDataMap(uuids), ) + + case provideFlexOptions: ProvideEmFlexOptionData => + announceFlexOptions(provideFlexOptions) + + case providedEmData: ProvideEmSetPointData => + announceEmSetPoints(tick, providedEmData) } + + (updatedStateData, None) } - private def announceEmData( - tick: Long, - emDataMessage: ProvideEmSetPointData, - )(implicit serviceStateData: ExtEmDataStateData, ctx: ActorContext): ( - ExtEmDataStateData, - Option[Long], - ) = { - val actorToEmData = emDataMessage.emData.asScala.flatMap { - case (agent, emDataPerAgent) => - serviceStateData.uuidToAdapterRef - .get(agent) - .map((_, convertToSetPoint(emDataPerAgent))) - .orElse { - log.warning( - "A corresponding actor ref for UUID {} could not be found", - agent, + private def announceFlexOptions( + provideFlexOptions: ProvideEmFlexOptionData + )(implicit + serviceStateData: ExtEmDataStateData + ): ExtEmDataStateData = { + provideFlexOptions + .flexOptions() + .asScala + .foreach { case (agent, flexOption: FlexOptionValue) => + serviceStateData.uuidToActorRef.get(agent) match { + case Some(receiver) => + receiver ! ProvideMinMaxFlexOptions( + flexOption.sender, + flexOption.pRef.toSquants, + flexOption.pMin.toSquants, + flexOption.pMax.toSquants, ) - None - } - } - - val maybeNextTick = emDataMessage.maybeNextTick.toScala.map(Long2long) - if (actorToEmData.nonEmpty) { - actorToEmData.foreach { case (actor, (controlSignal, setPoint)) => - actor ! SetPointFlexRequest( - tick, - controlSignal, - setPoint, - maybeNextTick, - ) + case None => + log.warning(s"No em agent with uuid '$agent' registered!") + } } - } - ( - serviceStateData.copy(extEmDataMessage = None), - None, - ) + + serviceStateData.copy(extEmDataMessage = None) } - private def convertToSetPoint( - value: PValue - ): (Boolean, Power) = { - value match { - case _: NoSetPointValue => (false, Kilowatts(0.0)) - case _ => (true, Kilowatts(value.getP.get.getValue.doubleValue())) - } + private def announceEmSetPoints( + tick: Long, + provideEmSetPointData: ProvideEmSetPointData, + )(implicit serviceStateData: ExtEmDataStateData): ExtEmDataStateData = { + provideEmSetPointData + .emData() + .asScala + .foreach { case (agent, emSetPoint) => + serviceStateData.uuidToAdapterRef.get(agent) match { + case Some(receiver) => + emSetPoint match { + case _: NoSetPointValue => + receiver ! IssueNoControl(tick) + case _ => + val power = + emSetPoint.getP.toScala.map(_.toSquants).getOrElse(zeroKW) + + receiver ! IssuePowerControl(tick, power) + } + + case None => + log.warning(s"No em agent with uuid '$agent' registered!") + } + } + + serviceStateData.copy(extEmDataMessage = None) } /** Handle a message from outside the simulation @@ -279,5 +306,55 @@ final case class ExtEmDataService( extResponseMsg: DataResponseMessage )(implicit serviceStateData: ExtEmDataStateData - ): ExtEmDataStateData = serviceStateData + ): ExtEmDataStateData = extResponseMsg match { + case SimonaFlexOptionsResponse(receiver, provideFlexOptions) => + val uuid = serviceStateData.actorRefToUuid(receiver) + val updated = + serviceStateData.flexOptionResponse.addData(uuid, provideFlexOptions) + + if (updated.nonComplete) { + // responses are still incomplete + serviceStateData.copy( + flexOptionResponse = updated + ) + } else { + // all responses received, forward them to external simulation in a bundle + serviceStateData.extEmDataConnection.queueExtResponseMsg( + new FlexOptionsResponse(updated.receivedData.asJava) + ) + + serviceStateData.copy( + flexOptionResponse = ReceiveDataMap.empty + ) + } + + case IssueFlexControlResponse(receiver, time, model, setPoint) => + val uuid = serviceStateData.flexAdapterToUuid(receiver) + + val updated = serviceStateData.setPointResponse.addData( + uuid, + new EmSetPointResult( + time, + model, + setPoint.map(power => new PValue(power.toQuantity)).toJava, + ), + ) + + if (updated.nonComplete) { + // responses are still incomplete + serviceStateData.copy( + setPointResponse = updated + ) + } else { + // all responses received, forward them to external simulation in a bundle + serviceStateData.extEmDataConnection.queueExtResponseMsg( + new EmSetPointDataResponse(updated.receivedData.asJava) + ) + + serviceStateData.copy( + setPointResponse = ReceiveDataMap.empty + ) + } + + } } diff --git a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala index dfbee6cd8e..312cb2dd1c 100644 --- a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala +++ b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala @@ -9,11 +9,7 @@ package edu.ie3.simona.sim import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.api.ExtSimAdapter import edu.ie3.simona.event.RuntimeEvent -import edu.ie3.simona.event.listener.{ - DelayedStopHelper, - ResultEventListener, - RuntimeEventListener, -} +import edu.ie3.simona.event.listener.{DelayedStopHelper, RuntimeEventListener} import edu.ie3.simona.main.RunSimona.SimonaEnded import edu.ie3.simona.scheduler.TimeAdvancer import edu.ie3.simona.sim.setup.SimonaSetup @@ -106,6 +102,7 @@ object SimonaSim { runtimeEventListener.toClassic, primaryServiceProxy, weatherService, + extSimulationData.emDataService, extSimulationData.evDataService, ) From e37f50c6c31af4368eb9648b8dedbf7d3da9ad7e Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 10 Mar 2025 13:37:37 +0100 Subject: [PATCH 010/125] Refactoring `EmAgent`. Adding some tests for em agents with em service. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 231 ++---- .../messages/flex/FlexibilityMessage.scala | 5 +- .../messages/services/EmMessage.scala | 17 +- .../messages/services/EvMessage.scala | 5 +- .../messages/services/ServiceMessage.scala | 7 +- .../simona/service/em/ExtEmDataService.scala | 173 +++- .../edu/ie3/simona/agent/em/EmAgentIT.scala | 2 + .../edu/ie3/simona/agent/em/EmAgentSpec.scala | 4 + .../agent/em/EmAgentWithServiceSpec.scala | 738 ++++++++++++++++++ .../agent/grid/DBFSAlgorithmCenGridSpec.scala | 1 + .../DBFSAlgorithmFailedPowerFlowSpec.scala | 1 + .../grid/DBFSAlgorithmParticipantSpec.scala | 1 + .../agent/grid/DBFSAlgorithmSupGridSpec.scala | 1 + .../primary/PrimaryServiceProxySpec.scala | 8 +- .../edu/ie3/simona/sim/SimonaSimSpec.scala | 6 +- .../simona/sim/setup/SimonaSetupSpec.scala | 3 +- 16 files changed, 978 insertions(+), 225 deletions(-) create mode 100644 src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 51940f8aa9..ce3d0b6171 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -25,21 +25,18 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ } import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - IssueFlexControlResponse, - SimonaFlexOptionsResponse, -} import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.service.em.ExtEmDataService import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import edu.ie3.util.scala.quantities.DefaultQuantities._ -import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} +import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.{ActorRef, Behavior} import org.apache.pekko.actor.{ActorRef => ClassicRef} import java.time.ZonedDateTime -import java.util.UUID +import scala.jdk.OptionConverters.RichOptional /** Energy management agent that receives flex options from and issues control * messages to connected agents @@ -111,31 +108,66 @@ object EmAgent { listener: Iterable[ActorRef[ResultEvent]], emDataService: Option[ClassicRef], ): Behavior[Request] = Behaviors.setup[Request] { ctx => - val constantData = EmData( - outputConfig, - simulationStartDate, - parent - .map { parentEm => - val flexAdapter = ctx.messageAdapter[FlexRequest](Flex) + val flexAdapter = ctx.messageAdapter[FlexRequest](Flex) + + val parentData = emDataService match { + case Some(service) => + // since we have a service, it will replace the default agent communication + + val parentOption = parent.toOption - parentEm ! RegisterControlledAsset( + service ! RegisterForEmDataService( + inputModel.getUuid, + ctx.self, + flexAdapter, + parentOption, + inputModel.getControllingEm.toScala.map(_.getUuid), + ) + + val serviceRequestAdapter: ActorRef[FlexRequest] = + ExtEmDataService.emServiceRequestAdapter( + service, flexAdapter, - inputModel, - ) + )(ctx) - FlexControlledData(parentEm, flexAdapter) - } - .left - .map { scheduler => - { - val activationAdapter = ctx.messageAdapter[Activation] { msg => - EmActivation(msg.tick) + parentOption.foreach( + _ ! RegisterControlledAsset(serviceRequestAdapter, inputModel) + ) + + val serviceResponseAdapter: ActorRef[FlexResponse] = + ExtEmDataService.emServiceResponseAdapter( + service, + parentOption, + )(ctx) + + Right(FlexControlledData(serviceResponseAdapter, flexAdapter)) + + case None => + parent + .map { parentEm => + parentEm ! RegisterControlledAsset( + flexAdapter, + inputModel, + ) + + FlexControlledData(parentEm, flexAdapter) + } + .left + .map { scheduler => + { + val activationAdapter = ctx.messageAdapter[Activation] { msg => + EmActivation(msg.tick) + } + SchedulerData(scheduler, activationAdapter) } - SchedulerData(scheduler, activationAdapter) } - }, + } + + val constantData = EmData( + outputConfig, + simulationStartDate, + parentData, listener, - emDataService, ) val modelShell = EmModelShell( @@ -145,15 +177,6 @@ object EmAgent { modelConfig, ) - // register for ext em service, if service is present - emDataService.foreach(service => - registerForEmService( - service, - constantData, - modelShell.uuid, - )(ctx) - ) - inactive( constantData, modelShell, @@ -161,25 +184,6 @@ object EmAgent { ) } - private def registerForEmService( - emDataService: ClassicRef, - emData: EmData, - uuid: UUID, - )(ctx: ActorContext[Request]): Unit = { - val flexAdapter = emData.parentData match { - case Left(_) => - ctx.messageAdapter[FlexRequest](Flex) - case Right(value) => - value.flexAdapter - } - - emDataService ! RegisterForEmDataService( - uuid, - ctx.self, - flexAdapter, - ) - } - /** Behavior of an inactive [[EmAgent]], which waits for an activation or flex * request to be activated. */ @@ -261,8 +265,8 @@ object EmAgent { emData: EmData, modelShell: EmModelShell, flexOptionsCore: EmDataCore.AwaitingFlexOptions, - ): Behavior[Request] = Behaviors.receivePartial { - case (ctx, flexOptions: ProvideFlexOptions) => + ): Behavior[Request] = Behaviors.receiveMessagePartial { + case flexOptions: ProvideFlexOptions => val updatedCore = flexOptionsCore.handleFlexOptions(flexOptions) if (updatedCore.isComplete) { @@ -272,22 +276,24 @@ object EmAgent { val (emRef, emMin, emMax) = modelShell.aggregateFlexOptions(allFlexOptions) - val flexResult = new FlexOptionsResult( - flexOptionsCore.activeTick.toDateTime(emData.simulationStartDate), - modelShell.uuid, - emRef.toMegawatts.asMegaWatt, - emMin.toMegawatts.asMegaWatt, - emMax.toMegawatts.asMegaWatt, - ) - if (emData.outputConfig.flexResult) { + val flexResult = new FlexOptionsResult( + flexOptionsCore.activeTick.toDateTime( + emData.simulationStartDate + ), + modelShell.uuid, + emRef.toMegawatts.asMegaWatt, + emMin.toMegawatts.asMegaWatt, + emMax.toMegawatts.asMegaWatt, + ) + emData.listener.foreach { _ ! FlexOptionsResultEvent(flexResult) } } - (emData.parentData, emData.emDataService) match { - case (Right(flexStateData), None) => + emData.parentData match { + case Right(flexStateData) => // provide aggregate flex options to parent val flexMessage = ProvideMinMaxFlexOptions( modelShell.uuid, @@ -308,27 +314,7 @@ object EmAgent { awaitingFlexCtrl(updatedEmData, modelShell, updatedCore) - case (Right(flexStateData), Some(emService)) => - // if we have an external data service, it will replace the default agent communication - emService ! SimonaFlexOptionsResponse( - flexStateData.emAgent, - flexResult, - ) - - awaitingFlexCtrlWithService( - emData, - emService, - ProvideMinMaxFlexOptions( - modelShell.uuid, - emRef, - emMin, - emMax, - ), - modelShell, - flexOptionsCore, - ) - - case (Left(_), None) => + case Left(_) => // We're not em-controlled ourselves, // always desire to come as close as possible to 0 kW val setPower = zeroKW @@ -346,26 +332,6 @@ object EmAgent { } awaitingCompletions(emData, modelShell, newCore) - - case (Left(_), Some(emService)) => - // if we have an external data service, it will replace the default agent communication - emService ! SimonaFlexOptionsResponse( - ctx.self, - flexResult, - ) - - awaitingFlexCtrlWithService( - emData, - emService, - ProvideMinMaxFlexOptions( - modelShell.uuid, - emRef, - emMin, - emMax, - ), - modelShell, - flexOptionsCore, - ) } } else { @@ -430,56 +396,6 @@ object EmAgent { awaitingCompletions(emData, modelShell, newCore) } - /** Behavior of an [[EmAgent]] waiting for a flex control message to be - * received in order to transition to the next behavior. This behavior should - * only be used by EmAgents that are themselves EM-controlled. - */ - private def awaitingFlexCtrlWithService( - emData: EmData, - emService: ClassicRef, - ownFlexOptions: ProvideFlexOptions, - modelShell: EmModelShell, - flexOptionsCore: EmDataCore.AwaitingFlexOptions, - ): Behavior[Request] = Behaviors.receiveMessagePartial { - case Flex(flexCtrl: IssueFlexControl) => - val setPointActivePower = EmTools.determineFlexPower( - ownFlexOptions, - flexCtrl, - ) - - // flex options calculated by connected agents - val receivedFlexOptions = flexOptionsCore.getFlexOptions - - val ctrlSetPoints = - modelShell.determineFlexControl( - receivedFlexOptions, - setPointActivePower, - ) - - val (allFlexMsgs, newCore) = flexOptionsCore - .handleFlexCtrl(ctrlSetPoints) - .fillInMissingIssueCtrl() - .complete() - - allFlexMsgs.foreach { case (receiver, msg) => - val time = msg.tick.toDateTime(emData.simulationStartDate) - - val setPoint = msg match { - case IssuePowerControl(_, setPower) => Some(setPower) - case _ => None - } - - emService ! IssueFlexControlResponse( - receiver, - time, - modelShell.uuid, - setPoint, - ) - } - - awaitingCompletions(emData, modelShell, newCore) - } - /** Behavior of an [[EmAgent]] waiting for completions messages to be received * in order to transition to the inactive behavior. */ @@ -581,15 +497,12 @@ object EmAgent { * em-controlled, or a [[Left]] with [[SchedulerData]] * @param listener * A collection of result event listeners - * @param emDataService - * An energy management service. */ private final case class EmData( outputConfig: NotifierConfig, simulationStartDate: ZonedDateTime, parentData: Either[SchedulerData, FlexControlledData], listener: Iterable[ActorRef[ResultEvent]], - emDataService: Option[ClassicRef], ) /** The existence of this data object indicates that the corresponding agent diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala index 9e009e12b0..3f3e502c30 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala @@ -38,10 +38,7 @@ object FlexibilityMessage { } final case class SetPointFlexRequest( - tick: Long, - controlSignal: Boolean, - setPower: Power, - nextSetPointTick: Option[Long], + tick: Long ) extends FlexRequest final case class FlexOptionsRequest( diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala index bace7b252b..37a8c91f97 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala @@ -6,17 +6,12 @@ package edu.ie3.simona.ontology.messages.services -import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ FlexRequest, FlexResponse, } import edu.ie3.simona.ontology.messages.services.ServiceMessage.DataResponseMessage import org.apache.pekko.actor.typed.ActorRef -import squants.Power - -import java.time.ZonedDateTime -import java.util.UUID sealed trait EmMessage @@ -24,16 +19,14 @@ object EmMessage { trait EmResponseMessage extends EmMessage with DataResponseMessage - final case class SimonaFlexOptionsResponse( - receiver: ActorRef[FlexResponse], - flexOptions: FlexOptionsResult, + final case class WrappedFlexResponse( + flexResponse: FlexResponse, + receiver: Option[ActorRef[FlexResponse]], ) extends EmResponseMessage - final case class IssueFlexControlResponse( + final case class WrappedFlexRequest( + flexRequest: FlexRequest, receiver: ActorRef[FlexRequest], - time: ZonedDateTime, - model: UUID, - setPoint: Option[Power], ) extends EmResponseMessage } diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/EvMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/EvMessage.scala index d8fb1fa2d5..d570c166df 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/EvMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/EvMessage.scala @@ -9,7 +9,10 @@ package edu.ie3.simona.ontology.messages.services import edu.ie3.simona.agent.participant.data.Data.SecondaryData import edu.ie3.simona.agent.participant2.ParticipantAgent.ParticipantRequest import edu.ie3.simona.model.participant2.evcs.EvModelWrapper -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{DataResponseMessage, ServiceRegistrationMessage} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + DataResponseMessage, + ServiceRegistrationMessage, +} import org.apache.pekko.actor.ActorRef import java.util.UUID diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala index 5d45b0241a..e8c5d7297e 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala @@ -7,7 +7,10 @@ package edu.ie3.simona.ontology.messages.services import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.FlexRequest +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ + FlexRequest, + FlexResponse, +} import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.{ActorRef => ClassicRef} @@ -53,6 +56,8 @@ object ServiceMessage { modelUuid: UUID, requestingActor: ActorRef[EmAgent.Request], flexAdapter: ActorRef[FlexRequest], + parentEm: Option[ActorRef[FlexResponse]], + parentUuid: Option[UUID] = None, ) extends ServiceRegistrationMessage final case class ScheduleServiceActivation( 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 68fb71713c..4e21a31e80 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -6,10 +6,8 @@ package edu.ie3.simona.service.em -import edu.ie3.datamodel.models.result.system.FlexOptionsResult -import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexOptionValue} +import edu.ie3.simona.api.data.em.model.FlexOptionValue import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.api.data.em.{ExtEmDataConnection, NoSetPointValue} import edu.ie3.simona.api.data.ontology.DataMessageFromExt @@ -18,8 +16,8 @@ import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EmMessage.{ - IssueFlexControlResponse, - SimonaFlexOptionsResponse, + WrappedFlexRequest, + WrappedFlexResponse, } import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ @@ -40,6 +38,10 @@ import edu.ie3.util.quantities.PowerSystemUnits.KILOWATT import edu.ie3.util.quantities.QuantityUtils._ import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import org.apache.pekko.actor.typed.ActorRef +import org.apache.pekko.actor.typed.scaladsl.{ + Behaviors, + ActorContext => TypedContext, +} import org.apache.pekko.actor.{ActorContext, Props, ActorRef => ClassicRef} import squants.Power import squants.energy.Kilowatts @@ -47,12 +49,8 @@ import tech.units.indriya.ComparableQuantity import java.util.UUID import javax.measure.quantity.{Power => PsdmPower} -import scala.jdk.CollectionConverters.{ - ListHasAsScala, - MapHasAsJava, - MapHasAsScala, -} -import scala.jdk.OptionConverters.{RichOption, RichOptional} +import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsScala} +import scala.jdk.OptionConverters.RichOptional import scala.util.{Failure, Success, Try} object ExtEmDataService { @@ -62,19 +60,93 @@ object ExtEmDataService { new ExtEmDataService(scheduler: ClassicRef) ) + def emServiceResponseAdapter( + emService: ClassicRef, + receiver: Option[ActorRef[FlexResponse]], + )(implicit ctx: TypedContext[EmAgent.Request]): ActorRef[FlexResponse] = { + + val request = Behaviors.receiveMessagePartial[FlexResponse] { + case response: FlexResponse => + emService ! WrappedFlexResponse( + response, + receiver, + ) + + Behaviors.same + } + + ctx.spawn(request, "response-adapter") + } + + def emServiceRequestAdapter( + emService: ClassicRef, + receiver: ActorRef[FlexRequest], + )(implicit ctx: TypedContext[EmAgent.Request]): ActorRef[FlexRequest] = { + val response = Behaviors.receiveMessagePartial[FlexRequest] { + case request: FlexRequest => + emService ! WrappedFlexRequest( + request, + receiver, + ) + + Behaviors.same + } + + ctx.spawn(response, "request-adapter") + } + final case class ExtEmDataStateData( extEmDataConnection: ExtEmDataConnection, - uuidToActorRef: Map[UUID, ActorRef[EmAgent.Request]] = Map.empty, - actorRefToUuid: Map[ActorRef[FlexResponse], UUID] = Map.empty, - uuidToAdapterRef: Map[UUID, ActorRef[FlexRequest]] = Map.empty, + emHierarchy: EmHierarchy = EmHierarchy(), + uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = Map.empty, flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, extEmDataMessage: Option[EmDataMessageFromExt] = None, - flexOptionResponse: ReceiveDataMap[UUID, FlexOptionsResult] = + flexOptionResponse: ReceiveDataMap[UUID, ProvideFlexOptions] = ReceiveDataMap.empty, - setPointResponse: ReceiveDataMap[UUID, EmSetPointResult] = + setPointResponse: ReceiveDataMap[UUID, IssueFlexControl] = ReceiveDataMap.empty, ) extends ServiceBaseStateData + final case class EmHierarchy( + uncontrolledToRef: Map[UUID, ActorRef[EmAgent.Request]] = Map.empty, + refToUncontrolled: Map[ActorRef[FlexResponse], UUID] = Map.empty, + controlledToRef: Map[UUID, ActorRef[FlexResponse]] = Map.empty, + refToControlled: Map[ActorRef[FlexResponse], UUID] = Map.empty, + parentToControlled: Map[ActorRef[FlexResponse], List[UUID]] = Map.empty, + ) { + def add(model: UUID, ref: ActorRef[EmAgent.Request]): EmHierarchy = copy( + uncontrolledToRef = uncontrolledToRef + (model -> ref), + refToUncontrolled = refToUncontrolled + (ref -> model), + ) + + def add( + model: UUID, + ref: ActorRef[EmAgent.Request], + parent: ActorRef[FlexResponse], + ): EmHierarchy = { + val hierarchy = parentToControlled.getOrElse(parent, List.empty) + + copy( + controlledToRef = controlledToRef + (model -> ref), + refToControlled = refToControlled + (ref -> model), + parentToControlled = + parentToControlled + (parent -> (hierarchy ++ List(model))), + ) + } + + def getUuid(ref: ActorRef[FlexResponse]): UUID = + refToUncontrolled.getOrElse(ref, refToControlled(ref)) + + def getResponseRef(uuid: UUID): Option[ActorRef[FlexResponse]] = + uncontrolledToRef.get(uuid) match { + case Some(value) => + Some(value) + case None => + controlledToRef.get(uuid) + } + + } + case class InitExtEmData( extEmData: ExtEmDataConnection ) extends InitializeServiceStateData @@ -145,9 +217,16 @@ final case class ExtEmDataService( modelUuid, requestingActor, flexAdapter, + parentEm, + _, ) => Success( - handleEmRegistrationRequest(modelUuid, requestingActor, flexAdapter) + handleEmRegistrationRequest( + modelUuid, + requestingActor, + flexAdapter, + parentEm, + ) ) case invalidMessage => Failure( @@ -161,17 +240,25 @@ final case class ExtEmDataService( modelUuid: UUID, modelActorRef: ActorRef[EmAgent.Request], flexAdapter: ActorRef[FlexRequest], - )(implicit serviceStateData: ExtEmDataStateData): ExtEmDataStateData = + parentEm: Option[ActorRef[FlexResponse]], + )(implicit serviceStateData: ExtEmDataStateData): ExtEmDataStateData = { + val hierarchy = serviceStateData.emHierarchy + + val updatedHierarchy = parentEm match { + case Some(parent) => + hierarchy.add(modelUuid, modelActorRef, parent) + case None => + hierarchy.add(modelUuid, modelActorRef) + } + serviceStateData.copy( - uuidToActorRef = - serviceStateData.uuidToActorRef + (modelUuid -> modelActorRef), - actorRefToUuid = - serviceStateData.actorRefToUuid + (modelActorRef -> modelUuid), - uuidToAdapterRef = - serviceStateData.uuidToAdapterRef + (modelUuid -> flexAdapter), + emHierarchy = updatedHierarchy, + uuidToFlexAdapter = + serviceStateData.uuidToFlexAdapter + (modelUuid -> flexAdapter), flexAdapterToUuid = serviceStateData.flexAdapterToUuid + (flexAdapter -> modelUuid), ) + } /** Send out the information to all registered recipients * @@ -188,14 +275,19 @@ final case class ExtEmDataService( serviceStateData: ExtEmDataStateData, ctx: ActorContext, ): (ExtEmDataStateData, Option[Long]) = { + println("Here!!!") + val updatedStateData = serviceStateData.extEmDataMessage.getOrElse( throw ServiceException( - "ExtPrimaryDataService was triggered without ExtEmDataMessage available" + "ExtEMDataService was triggered without ExtEmDataMessage available" ) ) match { case requestEmFlexResults: RequestEmFlexResults => val uuids = requestEmFlexResults.emEntities().asScala.toSet + val uuidToRef = serviceStateData.uuidToFlexAdapter + uuids.map(uuidToRef).foreach(_ ! FlexActivation(tick)) + serviceStateData.copy( extEmDataMessage = None, flexOptionResponse = ReceiveDataMap(uuids), @@ -224,11 +316,13 @@ final case class ExtEmDataService( )(implicit serviceStateData: ExtEmDataStateData ): ExtEmDataStateData = { + val hierarchy = serviceStateData.emHierarchy + provideFlexOptions .flexOptions() .asScala .foreach { case (agent, flexOption: FlexOptionValue) => - serviceStateData.uuidToActorRef.get(agent) match { + hierarchy.getResponseRef(agent) match { case Some(receiver) => receiver ! ProvideMinMaxFlexOptions( flexOption.sender, @@ -249,11 +343,12 @@ final case class ExtEmDataService( tick: Long, provideEmSetPointData: ProvideEmSetPointData, )(implicit serviceStateData: ExtEmDataStateData): ExtEmDataStateData = { + provideEmSetPointData .emData() .asScala .foreach { case (agent, emSetPoint) => - serviceStateData.uuidToAdapterRef.get(agent) match { + serviceStateData.uuidToFlexAdapter.get(agent) match { case Some(receiver) => emSetPoint match { case _: NoSetPointValue => @@ -307,8 +402,11 @@ final case class ExtEmDataService( )(implicit serviceStateData: ExtEmDataStateData ): ExtEmDataStateData = extResponseMsg match { - case SimonaFlexOptionsResponse(receiver, provideFlexOptions) => - val uuid = serviceStateData.actorRefToUuid(receiver) + case WrappedFlexResponse( + provideFlexOptions: ProvideFlexOptions, + Some(receiver), + ) => + val uuid = serviceStateData.emHierarchy.refToControlled(receiver) val updated = serviceStateData.flexOptionResponse.addData(uuid, provideFlexOptions) @@ -319,25 +417,20 @@ final case class ExtEmDataService( ) } else { // all responses received, forward them to external simulation in a bundle - serviceStateData.extEmDataConnection.queueExtResponseMsg( - new FlexOptionsResponse(updated.receivedData.asJava) - ) + + // serviceStateData.extEmDataConnection.queueExtResponseMsg(new FlexOptionsResponse(updated.receivedData.asJava)) serviceStateData.copy( flexOptionResponse = ReceiveDataMap.empty ) } - case IssueFlexControlResponse(receiver, time, model, setPoint) => + case WrappedFlexRequest(issueFlexControl: IssueFlexControl, receiver) => val uuid = serviceStateData.flexAdapterToUuid(receiver) val updated = serviceStateData.setPointResponse.addData( uuid, - new EmSetPointResult( - time, - model, - setPoint.map(power => new PValue(power.toQuantity)).toJava, - ), + issueFlexControl, ) if (updated.nonComplete) { @@ -347,9 +440,7 @@ final case class ExtEmDataService( ) } else { // all responses received, forward them to external simulation in a bundle - serviceStateData.extEmDataConnection.queueExtResponseMsg( - new EmSetPointDataResponse(updated.receivedData.asJava) - ) + // serviceStateData.extEmDataConnection.queueExtResponseMsg(new EmSetPointDataResponse(updated.receivedData.asJava)) serviceStateData.copy( setPointResponse = ReceiveDataMap.empty diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala index 9d7f074628..b4845db7d6 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -130,6 +130,7 @@ class EmAgentIT simulationStartDate, parent = Left(scheduler.ref), listener = Iterable(resultListener.ref), + None, ), "EmAgent", ) @@ -375,6 +376,7 @@ class EmAgentIT simulationStartDate, parent = Left(scheduler.ref), listener = Iterable(resultListener.ref), + None, ), "EmAgent1", ) diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentSpec.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentSpec.scala index ce0948cbcd..ce40e07411 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentSpec.scala @@ -84,6 +84,7 @@ class EmAgentSpec simulationStartDate, parent = Left(scheduler.ref), listener = Iterable(resultListener.ref), + None, ) ) @@ -269,6 +270,7 @@ class EmAgentSpec simulationStartDate, parent = Left(scheduler.ref), listener = Iterable(resultListener.ref), + None, ) ) @@ -449,6 +451,7 @@ class EmAgentSpec simulationStartDate, parent = Left(scheduler.ref), listener = Iterable(resultListener.ref), + None, ) ) @@ -639,6 +642,7 @@ class EmAgentSpec simulationStartDate, parent = Right(parentEmAgent.ref), listener = Iterable(resultListener.ref), + None, ) ) diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala new file mode 100644 index 0000000000..30934016d0 --- /dev/null +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala @@ -0,0 +1,738 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.agent.em + +import edu.ie3.datamodel.models.result.system.EmResult +import edu.ie3.simona.agent.participant.data.Data.PrimaryData.ComplexPower +import edu.ie3.simona.config.RuntimeConfig.EmRuntimeConfig +import edu.ie3.simona.event.ResultEvent +import edu.ie3.simona.event.ResultEvent.{ + FlexOptionsResultEvent, + ParticipantResultEvent, +} +import edu.ie3.simona.event.notifier.NotifierConfig +import edu.ie3.simona.ontology.messages.SchedulerMessage +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ +import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + WrappedFlexRequest, + WrappedFlexResponse, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService +import edu.ie3.simona.test.common.input.EmInputTestData +import edu.ie3.simona.test.matchers.SquantsMatchers +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import edu.ie3.simona.util.TickUtil.TickLong +import edu.ie3.util.TimeUtil +import edu.ie3.util.quantities.QuantityMatchers.equalWithTolerance +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import edu.ie3.util.scala.quantities.{Kilovars, ReactivePower} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} +import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps +import org.scalatest.matchers.should +import org.scalatest.wordspec.AnyWordSpecLike +import org.scalatestplus.mockito.MockitoSugar +import squants.Power +import squants.energy.Kilowatts + +import java.time.ZonedDateTime +import java.util.UUID + +class EmAgentWithServiceSpec + extends ScalaTestWithActorTestKit + with AnyWordSpecLike + with should.Matchers + with EmInputTestData + with MockitoSugar + with SquantsMatchers { + + protected implicit val simulationStartDate: ZonedDateTime = + TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:00:00Z") + + private val outputConfig = NotifierConfig( + simulationResultInfo = true, + powerRequestReply = false, + flexResult = true, // also test FlexOptionsResult if EM-controlled + ) + + override protected val modelConfig: EmRuntimeConfig = EmRuntimeConfig( + calculateMissingReactivePowerWithModel = false, + scaling = 1, + uuids = List("default"), + aggregateFlex = "SELF_OPT_EXCL_REG", + curtailRegenerative = false, + ) + + private implicit val activePowerTolerance: Power = Kilowatts(1e-10) + private implicit val reactivePowerTolerance: ReactivePower = Kilovars(1e-10) + + "An EM-controlled EM agent with em service" should { + + "be initialized correctly and run through some activations" in { + val resultListener = TestProbe[ResultEvent]("ResultListener") + + val parentEmAgent = TestProbe[FlexResponse]("ParentEmAgent") + + val service = TestProbe[Any]("emService") + val serviceRef = service.ref.toClassic + + val emAgent = spawn( + EmAgent( + emInput, + modelConfig, + outputConfig, + "PRIORITIZED", + simulationStartDate, + parent = Right(parentEmAgent.ref), + listener = Iterable(resultListener.ref), + Some(serviceRef), + ) + ) + + val pvAgent = TestProbe[FlexRequest]("PvAgent") + emAgent ! RegisterControlledAsset(pvAgent.ref, pvInput) + emAgent ! ScheduleFlexActivation(pvInput.getUuid, INIT_SIM_TICK) + + val emAgentFlex = + service.expectMessageType[RegisterForEmDataService] match { + case RegisterForEmDataService( + modelUuid, + requestingActor, + flexAdapter, + parentEm, + parentUuid, + ) => + modelUuid shouldBe emInput.getUuid + requestingActor shouldBe emAgent + parentEm shouldBe Some(parentEmAgent.ref) + parentUuid shouldBe None + + flexAdapter + } + + parentEmAgent + .expectMessageType[RegisterControlledAsset] + .inputModel shouldBe emInput + + service.expectMessage( + WrappedFlexResponse( + ScheduleFlexActivation(emInput.getUuid, INIT_SIM_TICK), + Some(parentEmAgent.ref), + ) + ) + + val evcsAgent = TestProbe[FlexRequest]("EvcsAgent") + emAgent ! RegisterControlledAsset(evcsAgent.ref, evcsInput) + emAgent ! ScheduleFlexActivation(evcsInput.getUuid, INIT_SIM_TICK) + + // no additional scheduling message, since tick -1 has already been scheduled + service.expectNoMessage() + + /* TICK -1 */ + emAgentFlex ! FlexActivation(INIT_SIM_TICK) + + // expect flex activations + pvAgent.expectMessage(FlexActivation(INIT_SIM_TICK)) + evcsAgent.expectMessage(FlexActivation(INIT_SIM_TICK)) + + // receive flex completions + emAgent ! FlexCompletion( + modelUuid = pvInput.getUuid, + requestAtTick = Some(0), + ) + + service.expectNoMessage() + + emAgent ! FlexCompletion( + modelUuid = evcsInput.getUuid, + requestAtTick = Some(0), + ) + + // expect no results for init + resultListener.expectNoMessage() + // expect completion from EmAgent + service.expectMessage( + WrappedFlexResponse( + FlexCompletion( + modelUuid = emInput.getUuid, + requestAtTick = Some(0), + ), + Some(parentEmAgent.ref), + ) + ) + + /* TICK 0 */ + emAgentFlex ! FlexActivation(0) + + // expect activations and flex requests + pvAgent.expectMessage(FlexActivation(0)) + evcsAgent.expectMessage(FlexActivation(0)) + + // send flex options + emAgent ! ProvideMinMaxFlexOptions( + pvInput.getUuid, + Kilowatts(-5), + Kilowatts(-5), + Kilowatts(0), + ) + + pvAgent.expectNoMessage() + evcsAgent.expectNoMessage() + + emAgent ! ProvideMinMaxFlexOptions( + evcsInput.getUuid, + Kilowatts(2), + Kilowatts(-11), + Kilowatts(11), + ) + + resultListener.expectMessageType[FlexOptionsResultEvent] match { + case FlexOptionsResultEvent(flexResult) => + flexResult.getInputModel shouldBe emInput.getUuid + flexResult.getTime shouldBe 0.toDateTime(simulationStartDate) + flexResult.getpRef() should equalWithTolerance(0.asMegaWatt) + flexResult.getpMin() should equalWithTolerance(-.016.asMegaWatt) + flexResult.getpMax() should equalWithTolerance(.006.asMegaWatt) + } + + service.expectMessageType[WrappedFlexResponse] match { + case WrappedFlexResponse( + ProvideMinMaxFlexOptions( + modelUuid, + referencePower, + minPower, + maxPower, + ), + receiver, + ) => + modelUuid shouldBe emInput.getUuid + referencePower shouldBe Kilowatts(0) + minPower shouldBe Kilowatts(-16) + maxPower shouldBe Kilowatts(6) // hint: PV is not flexible + + receiver shouldBe Some(parentEmAgent.ref) + } + + // issue power control and expect EmAgent to distribute it + // we want max power = 6 kW + emAgentFlex ! IssuePowerControl(0, Kilowatts(6)) + + // expect issue power control + pvAgent.expectMessage(IssueNoControl(0)) + + emAgent ! FlexResult( + modelUuid = pvInput.getUuid, + result = ComplexPower(Kilowatts(-5), Kilovars(-.5)), + ) + emAgent ! FlexCompletion( + modelUuid = pvInput.getUuid, + requestAtTick = Some(600), + ) + + evcsAgent.expectMessageType[IssuePowerControl] match { + case IssuePowerControl(0, setPower) => + setPower should approximate(Kilowatts(11.0)) + } + + service.expectNoMessage() + + emAgent ! FlexResult( + modelUuid = evcsInput.getUuid, + result = ComplexPower(Kilowatts(11), Kilovars(1.1)), + ) + emAgent ! FlexCompletion( + modelUuid = evcsInput.getUuid, + requestAtTick = Some(300), + ) + + // expect correct results + resultListener.expectMessageType[ParticipantResultEvent] match { + case ParticipantResultEvent(emResult: EmResult) => + emResult.getInputModel shouldBe emInput.getUuid + emResult.getTime shouldBe 0.toDateTime(simulationStartDate) + emResult.getP should equalWithTolerance(.006.asMegaWatt) + emResult.getQ should equalWithTolerance(.0006.asMegaVar) + } + + service.expectMessageType[WrappedFlexResponse] match { + case WrappedFlexResponse(FlexResult(modelUuid, result), receiver) => + modelUuid shouldBe emInput.getUuid + result.p should approximate(Kilowatts(6)) + result.q should approximate(Kilovars(.6)) + + receiver shouldBe Some(parentEmAgent.ref) + } + + service.expectMessage( + WrappedFlexResponse( + FlexCompletion( + modelUuid = emInput.getUuid, + requestAtTick = Some(300), + ), + Some(parentEmAgent.ref), + ) + ) + + /* TICK 150 */ + // The mock parent EM now acts as if the situation changed before tick 300, + // so that the flex control changes before new flex option calculations are due + + // no control means reference power of the latest flex options = 0 kW + emAgentFlex ! IssueNoControl(150) + + // We already sent NoControl at last tick, so we're still at -5 kW + pvAgent.expectNoMessage() + + // We need 5 kW to compensate PV feed-in + evcsAgent.expectMessageType[IssuePowerControl] match { + case IssuePowerControl(150, setPower) => + setPower should approximate(Kilowatts(5.0)) + } + + service.expectNoMessage() + + emAgent ! FlexResult( + modelUuid = evcsInput.getUuid, + result = ComplexPower(Kilowatts(5.0), Kilovars(.5)), + ) + emAgent ! FlexCompletion( + modelUuid = evcsInput.getUuid, + requestAtTick = Some(700), + ) + + // expect correct results + resultListener.expectMessageType[ParticipantResultEvent] match { + case ParticipantResultEvent(emResult: EmResult) => + emResult.getInputModel shouldBe emInput.getUuid + emResult.getTime shouldBe 150.toDateTime(simulationStartDate) + emResult.getP should equalWithTolerance(0.asMegaWatt) + emResult.getQ should equalWithTolerance(0.asMegaVar) + } + + service.expectMessageType[WrappedFlexResponse] match { + case WrappedFlexResponse(FlexResult(modelUuid, result), receiver) => + modelUuid shouldBe emInput.getUuid + result.p should approximate(Kilowatts(0)) + result.q should approximate(Kilovars(0)) + + receiver shouldBe Some(parentEmAgent.ref) + } + service.expectMessage( + WrappedFlexResponse( + FlexCompletion( + modelUuid = emInput.getUuid, + requestAtTick = Some(600), + ), + Some(parentEmAgent.ref), + ) + ) + + } + + "communicate with parent em through em service" in { + val resultListener = TestProbe[ResultEvent]("ResultListener") + val scheduler = TestProbe[SchedulerMessage]("Scheduler") + + val service = TestProbe[Any]("emService") + val serviceRef = service.ref.toClassic + + val parentEmInput = emInput + .copy() + .uuid(UUID.randomUUID()) + .id("parent") + .build() + + val updatedEmInput = emInput.copy().parentEm(parentEmInput).build() + + val parentEmAgent = spawn( + EmAgent( + parentEmInput, + modelConfig, + outputConfig, + "PROPORTIONAL", + simulationStartDate, + parent = Left(scheduler.ref), + listener = Iterable(resultListener.ref), + Some(serviceRef), + ) + ) + + val parentEmAgentFlex = + service.expectMessageType[RegisterForEmDataService] match { + case RegisterForEmDataService( + modelUuid, + requestingActor, + flexAdapter, + parentEm, + parentUuid, + ) => + modelUuid shouldBe parentEmInput.getUuid + requestingActor shouldBe parentEmAgent + parentEm shouldBe None + parentUuid shouldBe None + + flexAdapter + } + + val emAgent = spawn( + EmAgent( + updatedEmInput, + modelConfig, + outputConfig, + "PRIORITIZED", + simulationStartDate, + parent = Right(parentEmAgent), + listener = Iterable(resultListener.ref), + Some(serviceRef), + ) + ) + + val pvAgent = TestProbe[FlexRequest]("PvAgent") + emAgent ! RegisterControlledAsset(pvAgent.ref, pvInput) + emAgent ! ScheduleFlexActivation(pvInput.getUuid, INIT_SIM_TICK) + + val emAgentFlex = + service.expectMessageType[RegisterForEmDataService] match { + case RegisterForEmDataService( + modelUuid, + requestingActor, + flexAdapter, + parentEm, + parentUuid, + ) => + modelUuid shouldBe updatedEmInput.getUuid + requestingActor shouldBe emAgent + parentEm shouldBe Some(parentEmAgent) + parentUuid shouldBe Some(parentEmInput.getUuid) + + flexAdapter + } + + service.expectMessage( + WrappedFlexResponse( + ScheduleFlexActivation(updatedEmInput.getUuid, INIT_SIM_TICK), + Some(parentEmAgent), + ) + ) + + parentEmAgent ! ScheduleFlexActivation( + updatedEmInput.getUuid, + INIT_SIM_TICK, + ) + + service.expectMessage( + WrappedFlexResponse( + ScheduleFlexActivation(parentEmInput.getUuid, INIT_SIM_TICK), + None, + ) + ) + + val evcsAgent = TestProbe[FlexRequest]("EvcsAgent") + emAgent ! RegisterControlledAsset(evcsAgent.ref, evcsInput) + emAgent ! ScheduleFlexActivation(evcsInput.getUuid, INIT_SIM_TICK) + + // no additional scheduling message, since tick -1 has already been scheduled + service.expectNoMessage() + + /* TICK -1 */ + parentEmAgentFlex ! FlexActivation(INIT_SIM_TICK) + + service.expectMessage( + WrappedFlexRequest( + FlexActivation(INIT_SIM_TICK), + emAgentFlex, + ) + ) + + emAgentFlex ! FlexActivation(INIT_SIM_TICK) + + // expect flex activations + pvAgent.expectMessage(FlexActivation(INIT_SIM_TICK)) + evcsAgent.expectMessage(FlexActivation(INIT_SIM_TICK)) + + // receive flex completions + emAgent ! FlexCompletion( + modelUuid = pvInput.getUuid, + requestAtTick = Some(0), + ) + + service.expectNoMessage() + + emAgent ! FlexCompletion( + modelUuid = evcsInput.getUuid, + requestAtTick = Some(0), + ) + + // expect no results for init + resultListener.expectNoMessage() + // expect completion from EmAgent + service.expectMessage( + WrappedFlexResponse( + FlexCompletion( + modelUuid = updatedEmInput.getUuid, + requestAtTick = Some(0), + ), + Some(parentEmAgent), + ) + ) + + parentEmAgent ! FlexCompletion( + modelUuid = updatedEmInput.getUuid, + requestAtTick = Some(0), + ) + + service.expectMessage( + WrappedFlexResponse( + FlexCompletion( + modelUuid = parentEmInput.getUuid, + requestAtTick = Some(0), + ), + None, + ) + ) + + /* TICK 0 */ + parentEmAgentFlex ! FlexActivation(0) + + service.expectMessage( + WrappedFlexRequest( + FlexActivation(0), + emAgentFlex, + ) + ) + + emAgentFlex ! FlexActivation(0) + + // expect activations and flex requests + pvAgent.expectMessage(FlexActivation(0)) + evcsAgent.expectMessage(FlexActivation(0)) + + // send flex options + emAgent ! ProvideMinMaxFlexOptions( + pvInput.getUuid, + Kilowatts(-5), + Kilowatts(-5), + Kilowatts(0), + ) + + pvAgent.expectNoMessage() + evcsAgent.expectNoMessage() + + emAgent ! ProvideMinMaxFlexOptions( + evcsInput.getUuid, + Kilowatts(2), + Kilowatts(-11), + Kilowatts(11), + ) + + resultListener.expectMessageType[FlexOptionsResultEvent] match { + case FlexOptionsResultEvent(flexResult) => + flexResult.getInputModel shouldBe updatedEmInput.getUuid + flexResult.getTime shouldBe 0.toDateTime(simulationStartDate) + flexResult.getpRef() should equalWithTolerance(0.asMegaWatt) + flexResult.getpMin() should equalWithTolerance(-.016.asMegaWatt) + flexResult.getpMax() should equalWithTolerance(.006.asMegaWatt) + } + + service.expectMessageType[WrappedFlexResponse] match { + case WrappedFlexResponse( + ProvideMinMaxFlexOptions( + modelUuid, + referencePower, + minPower, + maxPower, + ), + receiver, + ) => + modelUuid shouldBe updatedEmInput.getUuid + referencePower shouldBe Kilowatts(0) + minPower shouldBe Kilowatts(-16) + maxPower shouldBe Kilowatts(6) // hint: PV is not flexible + + receiver shouldBe Some(parentEmAgent) + } + + parentEmAgent ! ProvideMinMaxFlexOptions( + updatedEmInput.getUuid, + Kilowatts(0), + Kilowatts(-16), + Kilowatts(6), + ) + + service.expectMessageType[WrappedFlexResponse] match { + case WrappedFlexResponse( + ProvideMinMaxFlexOptions( + modelUuid, + referencePower, + minPower, + maxPower, + ), + receiver, + ) => + modelUuid shouldBe parentEmInput.getUuid + referencePower shouldBe Kilowatts(0) + minPower shouldBe Kilowatts(-16) + maxPower shouldBe Kilowatts(6) // hint: PV is not flexible + + receiver shouldBe None + } + + parentEmAgentFlex ! IssuePowerControl(0, Kilowatts(6)) + + service.expectMessage( + WrappedFlexRequest( + IssuePowerControl(0, Kilowatts(6)), + emAgentFlex, + ) + ) + + // issue power control and expect EmAgent to distribute it + // we want max power = 6 kW + emAgentFlex ! IssuePowerControl(0, Kilowatts(6)) + + // expect issue power control + pvAgent.expectMessage(IssueNoControl(0)) + + emAgent ! FlexResult( + modelUuid = pvInput.getUuid, + result = ComplexPower(Kilowatts(-5), Kilovars(-.5)), + ) + emAgent ! FlexCompletion( + modelUuid = pvInput.getUuid, + requestAtTick = Some(600), + ) + + evcsAgent.expectMessageType[IssuePowerControl] match { + case IssuePowerControl(0, setPower) => + setPower should approximate(Kilowatts(11.0)) + } + + service.expectNoMessage() + + emAgent ! FlexResult( + modelUuid = evcsInput.getUuid, + result = ComplexPower(Kilowatts(11), Kilovars(1.1)), + ) + emAgent ! FlexCompletion( + modelUuid = evcsInput.getUuid, + requestAtTick = Some(300), + ) + + // expect correct results + resultListener.expectMessageType[FlexOptionsResultEvent] match { + case FlexOptionsResultEvent(flexOptionsResult) => + flexOptionsResult.getpRef should equalWithTolerance(0.asMegaWatt) + flexOptionsResult.getpMin should equalWithTolerance(-0.016.asMegaWatt) + flexOptionsResult.getpMax should equalWithTolerance(0.006.asMegaWatt) + } + + resultListener.expectMessageType[ParticipantResultEvent] match { + case ParticipantResultEvent(emResult: EmResult) => + emResult.getInputModel shouldBe updatedEmInput.getUuid + emResult.getTime shouldBe 0.toDateTime(simulationStartDate) + emResult.getP should equalWithTolerance(.006.asMegaWatt) + emResult.getQ should equalWithTolerance(.0006.asMegaVar) + } + + service.expectMessageType[WrappedFlexResponse] match { + case WrappedFlexResponse(FlexResult(modelUuid, result), receiver) => + modelUuid shouldBe updatedEmInput.getUuid + result.p should approximate(Kilowatts(6)) + result.q should approximate(Kilovars(.6)) + + receiver shouldBe Some(parentEmAgent) + } + + parentEmAgent ! FlexResult( + updatedEmInput.getUuid, + ComplexPower( + Kilowatts(6), + Kilovars(.6), + ), + ) + + service.expectMessage( + WrappedFlexResponse( + FlexCompletion( + modelUuid = updatedEmInput.getUuid, + requestAtTick = Some(300), + ), + Some(parentEmAgent), + ) + ) + + parentEmAgentFlex ! IssueNoControl(150) + + // TODO: FIX + // service.expectMessage(WrappedFlexRequest(IssueNoControl(150), emAgentFlex)) + + /* TICK 150 */ + // The mock parent EM now acts as if the situation changed before tick 300, + // so that the flex control changes before new flex option calculations are due + + // no control means reference power of the latest flex options = 0 kW + emAgentFlex ! IssueNoControl(150) + + // We already sent NoControl at last tick, so we're still at -5 kW + pvAgent.expectNoMessage() + + // We need 5 kW to compensate PV feed-in + evcsAgent.expectMessageType[IssuePowerControl] match { + case IssuePowerControl(150, setPower) => + setPower should approximate(Kilowatts(5.0)) + } + + service.expectNoMessage() + + emAgent ! FlexResult( + modelUuid = evcsInput.getUuid, + result = ComplexPower(Kilowatts(5.0), Kilovars(.5)), + ) + emAgent ! FlexCompletion( + modelUuid = evcsInput.getUuid, + requestAtTick = Some(700), + ) + + // expect correct results + resultListener.expectMessageType[ParticipantResultEvent] match { + case ParticipantResultEvent(emResult: EmResult) => + emResult.getInputModel shouldBe updatedEmInput.getUuid + emResult.getTime shouldBe 150.toDateTime(simulationStartDate) + emResult.getP should equalWithTolerance(0.asMegaWatt) + emResult.getQ should equalWithTolerance(0.asMegaVar) + } + + service.expectMessageType[WrappedFlexResponse] match { + case WrappedFlexResponse(FlexResult(modelUuid, result), receiver) => + modelUuid shouldBe updatedEmInput.getUuid + result.p should approximate(Kilowatts(0)) + result.q should approximate(Kilovars(0)) + + receiver shouldBe Some(parentEmAgent) + } + + parentEmAgent ! FlexResult( + updatedEmInput.getUuid, + ComplexPower(Kilowatts(0), Kilovars(0)), + ) + + service.expectMessage( + WrappedFlexResponse( + FlexCompletion( + modelUuid = updatedEmInput.getUuid, + requestAtTick = Some(600), + ), + Some(parentEmAgent), + ) + ) + } + } + +} diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala index 6ab5ee808e..c594dbc410 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala @@ -79,6 +79,7 @@ class DBFSAlgorithmCenGridSpec runtimeEventListener = runtimeEvents.ref, primaryServiceProxy = primaryService.ref.toClassic, weather = weatherService.ref.toClassic, + emDataService = None, evDataService = None, ) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala index d5ece47f75..fc404f3ec3 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala @@ -64,6 +64,7 @@ class DBFSAlgorithmFailedPowerFlowSpec runtimeEventListener = runtimeEvents.ref, primaryServiceProxy = primaryService.ref.toClassic, weather = weatherService.ref.toClassic, + emDataService = None, evDataService = None, ) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala index b3bf299c4b..6fb3a2a690 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala @@ -59,6 +59,7 @@ class DBFSAlgorithmParticipantSpec runtimeEventListener = runtimeEvents.ref, primaryServiceProxy = primaryService.ref.toClassic, weather = weatherService.ref.toClassic, + emDataService = None, evDataService = None, ) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala index f81b92ea51..b648c2f43c 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala @@ -63,6 +63,7 @@ class DBFSAlgorithmSupGridSpec runtimeEventListener = runtimeEvents.ref, primaryServiceProxy = primaryService.ref.toClassic, weather = weatherService.ref.toClassic, + emDataService = None, evDataService = None, ) diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala index 3691b3efde..d62caf4cb7 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala @@ -275,7 +275,7 @@ class PrimaryServiceProxySpec proxy invokePrivate prepareStateData( maliciousConfig, simulationStart, - Map.empty, + Seq.empty, ) match { case Success(_) => fail("Building state data with missing config should fail") @@ -296,7 +296,7 @@ class PrimaryServiceProxySpec proxy invokePrivate prepareStateData( maliciousConfig, simulationStart, - Map.empty, + Seq.empty, ) match { case Success(_) => fail("Building state data with missing config should fail") @@ -310,7 +310,7 @@ class PrimaryServiceProxySpec proxy invokePrivate prepareStateData( validPrimaryConfig, simulationStart, - Map.empty, + Seq.empty, ) match { case Success( PrimaryServiceStateData( @@ -362,7 +362,7 @@ class PrimaryServiceProxySpec proxy invokePrivate prepareStateData( validPrimaryConfig, simulationStart, - Map(extPrimaryDataConnection -> validExtPrimaryDataService), + Seq((extPrimaryDataConnection, validExtPrimaryDataService)), ) match { case Success( PrimaryServiceStateData( diff --git a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala index 2a4f97ed8f..7ea3c0dd0a 100644 --- a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala @@ -310,7 +310,8 @@ class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { new MockSetup() { override def resultEventListener( - context: ActorContext[_] + context: ActorContext[_], + extSimSetupData: ExtSimSetupData, ): Seq[ActorRef[ResultEventListener.Request]] = throwTestException() } @@ -418,7 +419,8 @@ object SimonaSimSpec { ) override def resultEventListener( - context: ActorContext[_] + context: ActorContext[_], + extSimSetupData: ExtSimSetupData, ): Seq[ActorRef[ResultEventListener.Request]] = Seq( context.spawn( stoppableForwardMessage(resultEventProbe), diff --git a/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala index fed6aeb0e5..3a660fe7df 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala @@ -49,7 +49,8 @@ class SimonaSetupSpec ) override def resultEventListener( - context: ActorContext[_] + context: ActorContext[_], + extSimSetupData: ExtSimSetupData, ): Seq[ActorRef[ResultEventListener.Request]] = throw new NotImplementedException("This is a dummy setup") From 95cd561fa3f16c097e50ea01d2dee5c3f19a23f5 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 10 Mar 2025 15:01:44 +0100 Subject: [PATCH 011/125] Adding test for `ExtEmDataService`. Fixing some issues. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 1 + .../messages/services/EmMessage.scala | 1 + .../simona/service/em/ExtEmDataService.scala | 73 ++--- .../agent/em/EmAgentWithServiceSpec.scala | 11 +- .../service/em/ExtEmDataServiceSpec.scala | 257 ++++++++++++++++++ 5 files changed, 308 insertions(+), 35 deletions(-) create mode 100644 src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index ce3d0b6171..24d359f667 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -138,6 +138,7 @@ object EmAgent { ExtEmDataService.emServiceResponseAdapter( service, parentOption, + ctx.self )(ctx) Right(FlexControlledData(serviceResponseAdapter, flexAdapter)) diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala index 37a8c91f97..d2381516bd 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala @@ -22,6 +22,7 @@ object EmMessage { final case class WrappedFlexResponse( flexResponse: FlexResponse, receiver: Option[ActorRef[FlexResponse]], + self: Option[ActorRef[FlexResponse]] = None, ) extends EmResponseMessage final case class WrappedFlexRequest( 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 4e21a31e80..5d3fa18308 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -6,56 +6,43 @@ package edu.ie3.simona.service.em +import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.em.model.FlexOptionValue import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.api.data.em.{ExtEmDataConnection, NoSetPointValue} import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException -import edu.ie3.simona.exceptions.{InitializationException, ServiceException} +import edu.ie3.simona.exceptions.{CriticalFailureException, InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - WrappedFlexRequest, - WrappedFlexResponse, -} +import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - DataResponseMessage, - RegisterForEmDataService, -} -import edu.ie3.simona.service.ServiceStateData.{ - InitializeServiceStateData, - ServiceBaseStateData, -} -import edu.ie3.simona.service.em.ExtEmDataService.{ - ExtEmDataStateData, - InitExtEmData, -} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{DataResponseMessage, RegisterForEmDataService} +import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} +import edu.ie3.simona.service.em.ExtEmDataService.{ExtEmDataStateData, InitExtEmData} import edu.ie3.simona.service.{ExtDataSupport, SimonaService} import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.util.quantities.PowerSystemUnits.KILOWATT import edu.ie3.util.quantities.QuantityUtils._ import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import org.apache.pekko.actor.typed.ActorRef -import org.apache.pekko.actor.typed.scaladsl.{ - Behaviors, - ActorContext => TypedContext, -} +import org.apache.pekko.actor.typed.scaladsl.{Behaviors, ActorContext => TypedContext} import org.apache.pekko.actor.{ActorContext, Props, ActorRef => ClassicRef} import squants.Power import squants.energy.Kilowatts import tech.units.indriya.ComparableQuantity +import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.{Power => PsdmPower} -import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsScala} +import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala} import scala.jdk.OptionConverters.RichOptional import scala.util.{Failure, Success, Try} object ExtEmDataService { - def props(scheduler: ClassicRef): Props = + def props(scheduler: ClassicRef)(implicit simulationStart: ZonedDateTime): Props = Props( new ExtEmDataService(scheduler: ClassicRef) ) @@ -63,6 +50,7 @@ object ExtEmDataService { def emServiceResponseAdapter( emService: ClassicRef, receiver: Option[ActorRef[FlexResponse]], + self: ActorRef[FlexResponse], )(implicit ctx: TypedContext[EmAgent.Request]): ActorRef[FlexResponse] = { val request = Behaviors.receiveMessagePartial[FlexResponse] { @@ -70,6 +58,7 @@ object ExtEmDataService { emService ! WrappedFlexResponse( response, receiver, + Some(self) ) Behaviors.same @@ -101,7 +90,7 @@ object ExtEmDataService { uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = Map.empty, flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, extEmDataMessage: Option[EmDataMessageFromExt] = None, - flexOptionResponse: ReceiveDataMap[UUID, ProvideFlexOptions] = + flexOptionResponse: ReceiveDataMap[UUID, FlexOptionsResult] = ReceiveDataMap.empty, setPointResponse: ReceiveDataMap[UUID, IssueFlexControl] = ReceiveDataMap.empty, @@ -154,6 +143,8 @@ object ExtEmDataService { final case class ExtEmDataService( override val scheduler: ClassicRef + )( + implicit val simulationStart: ZonedDateTime, ) extends SimonaService[ExtEmDataStateData](scheduler) with ExtDataSupport[ExtEmDataStateData] { @@ -275,8 +266,6 @@ final case class ExtEmDataService( serviceStateData: ExtEmDataStateData, ctx: ActorContext, ): (ExtEmDataStateData, Option[Long]) = { - println("Here!!!") - val updatedStateData = serviceStateData.extEmDataMessage.getOrElse( throw ServiceException( "ExtEMDataService was triggered without ExtEmDataMessage available" @@ -404,11 +393,29 @@ final case class ExtEmDataService( ): ExtEmDataStateData = extResponseMsg match { case WrappedFlexResponse( provideFlexOptions: ProvideFlexOptions, - Some(receiver), + receiver, + self ) => - val uuid = serviceStateData.emHierarchy.refToControlled(receiver) - val updated = - serviceStateData.flexOptionResponse.addData(uuid, provideFlexOptions) + + val ref = receiver.getOrElse(self.getOrElse( + throw new CriticalFailureException("No receiver defined!") + )) + + val uuid = serviceStateData.emHierarchy.getUuid(ref) + + val updated = provideFlexOptions match { + case ProvideMinMaxFlexOptions(modelUuid, ref, min, max) => + + serviceStateData.flexOptionResponse.addData(uuid, new FlexOptionsResult( + simulationStart, // TODO: Fix this + modelUuid, + min.toQuantity, + ref.toQuantity, + max.toQuantity, + )) + } + + if (updated.nonComplete) { // responses are still incomplete @@ -418,7 +425,11 @@ final case class ExtEmDataService( } else { // all responses received, forward them to external simulation in a bundle - // serviceStateData.extEmDataConnection.queueExtResponseMsg(new FlexOptionsResponse(updated.receivedData.asJava)) + serviceStateData.extEmDataConnection.queueExtResponseMsg( + new FlexOptionsResponse( + updated.receivedData.asJava + ) + ) serviceStateData.copy( flexOptionResponse = ReceiveDataMap.empty diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala index 30934016d0..a4606b5f19 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala @@ -211,6 +211,7 @@ class EmAgentWithServiceSpec maxPower, ), receiver, + _, ) => modelUuid shouldBe emInput.getUuid referencePower shouldBe Kilowatts(0) @@ -262,7 +263,7 @@ class EmAgentWithServiceSpec } service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse(FlexResult(modelUuid, result), receiver) => + case WrappedFlexResponse(FlexResult(modelUuid, result), receiver, _) => modelUuid shouldBe emInput.getUuid result.p should approximate(Kilowatts(6)) result.q should approximate(Kilovars(.6)) @@ -317,7 +318,7 @@ class EmAgentWithServiceSpec } service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse(FlexResult(modelUuid, result), receiver) => + case WrappedFlexResponse(FlexResult(modelUuid, result), receiver, _) => modelUuid shouldBe emInput.getUuid result.p should approximate(Kilowatts(0)) result.q should approximate(Kilovars(0)) @@ -550,6 +551,7 @@ class EmAgentWithServiceSpec maxPower, ), receiver, + _, ) => modelUuid shouldBe updatedEmInput.getUuid referencePower shouldBe Kilowatts(0) @@ -575,6 +577,7 @@ class EmAgentWithServiceSpec maxPower, ), receiver, + _, ) => modelUuid shouldBe parentEmInput.getUuid referencePower shouldBe Kilowatts(0) @@ -642,7 +645,7 @@ class EmAgentWithServiceSpec } service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse(FlexResult(modelUuid, result), receiver) => + case WrappedFlexResponse(FlexResult(modelUuid, result), receiver, _) => modelUuid shouldBe updatedEmInput.getUuid result.p should approximate(Kilowatts(6)) result.q should approximate(Kilovars(.6)) @@ -710,7 +713,7 @@ class EmAgentWithServiceSpec } service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse(FlexResult(modelUuid, result), receiver) => + case WrappedFlexResponse(FlexResult(modelUuid, result), receiver, _) => modelUuid shouldBe updatedEmInput.getUuid result.p should approximate(Kilowatts(0)) result.q should approximate(Kilovars(0)) diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala new file mode 100644 index 0000000000..0b0019c4af --- /dev/null +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -0,0 +1,257 @@ +/* + * © 2021. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.em + +import com.typesafe.config.ConfigFactory +import edu.ie3.datamodel.models.result.system.FlexOptionsResult +import edu.ie3.simona.api.data.em.ExtEmDataConnection +import edu.ie3.simona.api.data.em.ontology.{FlexOptionsResponse, RequestEmFlexResults} +import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.exceptions.ServiceException +import edu.ie3.simona.ontology.messages.Activation +import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.FlexActivation +import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions +import edu.ie3.simona.ontology.messages.services.EmMessage.WrappedFlexResponse +import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService +import edu.ie3.simona.scheduler.ScheduleLock +import edu.ie3.simona.service.SimonaService +import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData +import edu.ie3.simona.test.common.input.EmInputTestData +import edu.ie3.simona.test.common.{TestKitWithShutdown, TestSpawnerClassic} +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import edu.ie3.util.quantities.QuantityUtils._ +import org.apache.pekko.actor.ActorSystem +import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps +import org.apache.pekko.testkit.{TestActorRef, TestProbe} +import org.scalatest.wordspec.AnyWordSpecLike +import squants.energy.Kilowatts + +import java.time.ZonedDateTime +import java.util.UUID +import scala.concurrent.duration.DurationInt +import scala.jdk.CollectionConverters._ + +class ExtEmDataServiceSpec + extends TestKitWithShutdown( + ActorSystem( + "ExtEvDataServiceSpec", + ConfigFactory + .parseString(""" + |pekko.loggers = ["org.apache.pekko.testkit.TestEventListener"] + |pekko.loglevel = "INFO" + |""".stripMargin), + ) + ) + with AnyWordSpecLike + with EmInputTestData + with TestSpawnerClassic { + + implicit val simulationStart: ZonedDateTime = ZonedDateTime.now() + + private val emAgent1UUID = UUID.fromString("06a14909-366e-4e94-a593-1016e1455b30") + private val emAgent2UUID = UUID.fromString("104acdaa-5dc5-4197-aed2-2fddb3c4f237") + + "An uninitialized em service" must { + "send correct completion message after initialisation" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") + + val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) + val extEmDataConnection = new ExtEmDataConnection(Map.empty[String, UUID].asJava) + extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) + + val key = + ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) + scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + + scheduler.send( + emService, + SimonaService.Create(InitExtEmData(extEmDataConnection), key), + ) + scheduler.expectMsg( + ScheduleActivation(emService.toTyped, INIT_SIM_TICK, Some(key)) + ) + + scheduler.send(emService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(emService.toTyped)) + } + + "stash registration request and handle it correctly once initialized" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") + + val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) + val extEmDataConnection = new ExtEmDataConnection(Map.empty[String, UUID].asJava) + extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) + + val emAgent = TestProbe("emAgent") + + // this one should be stashed + emAgent.send(emService, RegisterForEmDataService( + emInput.getUuid, + emAgent.ref.toTyped, + emAgent.ref.toTyped, + None, + None + )) + + scheduler.expectNoMessage() + + val key = + ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) + scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + + scheduler.send( + emService, + SimonaService.Create(InitExtEmData(extEmDataConnection), key), + ) + scheduler.expectMsg( + ScheduleActivation(emService.toTyped, INIT_SIM_TICK, Some(key)) + ) + + scheduler.send(emService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(emService.toTyped)) + } + } + + "An idle em service" must { + + "fail when activated without having received ExtEmMessage" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") + + val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) + val extEmDataConnection = new ExtEmDataConnection(Map.empty[String, UUID].asJava) + extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) + + val key = + ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) + scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + + scheduler.send( + emService, + SimonaService.Create(InitExtEmData(extEmDataConnection), key), + ) + scheduler.expectMsg( + ScheduleActivation(emService.toTyped, INIT_SIM_TICK, Some(key)) + ) + + scheduler.send(emService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(emService.toTyped)) + + // we trigger ev service and expect an exception + assertThrows[ServiceException] { + emService.receive( + Activation(0), + scheduler.ref, + ) + } + + scheduler.expectNoMessage() + } + + "handle flex option request correctly" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") + + val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) + val extEmDataConnection = new ExtEmDataConnection(Map.empty[String, UUID].asJava) + extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) + + val key = + ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) + scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + + scheduler.send( + emService, + SimonaService.Create(InitExtEmData(extEmDataConnection), key), + ) + scheduler.expectMsgType[ScheduleActivation] + + scheduler.send(emService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(emService.toTyped)) + + val emAgent1 = TestProbe("emAgent1") + val emAgent2 = TestProbe("emAgent2") + + emAgent1.send(emService, RegisterForEmDataService(emAgent1UUID, emAgent1.ref.toTyped, emAgent1.ref.toTyped, None, None)) + emAgent1.expectNoMessage() + + emAgent2.send(emService, RegisterForEmDataService(emAgent2UUID, emAgent2.ref.toTyped, emAgent2.ref.toTyped, None, None)) + emAgent2.expectNoMessage() + + extEmDataConnection.sendExtMsg( + new RequestEmFlexResults( + INIT_SIM_TICK, + List.empty.asJava + ) + ) + + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(emService)) + scheduler.send(emService, Activation(INIT_SIM_TICK)) + + emAgent1.expectNoMessage() + emAgent2.expectNoMessage() + + scheduler.expectMsg(Completion(emService.toTyped)) + + extEmDataConnection.sendExtMsg( + new RequestEmFlexResults( + 0, + List(emAgent1UUID).asJava + ) + ) + + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(emService)) + scheduler.send(emService, Activation(0)) + + emAgent1.expectMsg(FlexActivation(0)) + emAgent2.expectNoMessage() + + scheduler.expectMsg(Completion(emService.toTyped)) + + extEmDataConnection.receiveTriggerQueue shouldBe empty + + emAgent1.send(emService, WrappedFlexResponse( + ProvideMinMaxFlexOptions( + emAgent1UUID, + Kilowatts(5), + Kilowatts(0), + Kilowatts(10) + ), + None, + Some(emAgent1.ref.toTyped) + )) + + awaitCond( + !extEmDataConnection.receiveTriggerQueue.isEmpty, + max = 3.seconds, + message = "No message received", + ) + + extEmDataConnection.receiveTriggerQueue.size() shouldBe 1 + + extEmDataConnection.receiveTriggerQueue.take() shouldBe new FlexOptionsResponse( + Map(emAgent1UUID -> new FlexOptionsResult( + simulationStart, + emAgent1UUID, + 0.asKiloWatt, + 5.asKiloWatt, + 10.asKiloWatt + )).asJava, + ) + } + + "handle set point request correctly" in {} + + "handle flex option provision correctly" in {} + + "handle set point provision correctly" in {} + + } +} From 6b84f5052cdc57ee67ef6bb437ca948cdb79ca69 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 11 Mar 2025 09:55:56 +0100 Subject: [PATCH 012/125] Adding test for `ExtEmDataService`. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 2 +- .../simona/service/em/ExtEmDataService.scala | 122 ++++-- .../service/em/ExtEmDataServiceSpec.scala | 396 ++++++++++++++++-- 3 files changed, 442 insertions(+), 78 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 24d359f667..ed93805c81 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -138,7 +138,7 @@ object EmAgent { ExtEmDataService.emServiceResponseAdapter( service, parentOption, - ctx.self + ctx.self, )(ctx) Right(FlexControlledData(serviceResponseAdapter, flexAdapter)) 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 5d3fa18308..86a6c93b2e 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -7,27 +7,48 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.result.system.FlexOptionsResult +import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.FlexOptionValue +import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexOptionValue} import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.api.data.em.{ExtEmDataConnection, NoSetPointValue} import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException -import edu.ie3.simona.exceptions.{CriticalFailureException, InitializationException, ServiceException} +import edu.ie3.simona.exceptions.{ + CriticalFailureException, + InitializationException, + ServiceException, +} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions -import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + WrappedFlexRequest, + WrappedFlexResponse, +} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{DataResponseMessage, RegisterForEmDataService} -import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} -import edu.ie3.simona.service.em.ExtEmDataService.{ExtEmDataStateData, InitExtEmData} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + DataResponseMessage, + RegisterForEmDataService, +} +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, +} +import edu.ie3.simona.service.em.ExtEmDataService.{ + ExtEmDataStateData, + InitExtEmData, +} import edu.ie3.simona.service.{ExtDataSupport, SimonaService} import edu.ie3.simona.util.ReceiveDataMap +import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.util.quantities.PowerSystemUnits.KILOWATT import edu.ie3.util.quantities.QuantityUtils._ import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import org.apache.pekko.actor.typed.ActorRef -import org.apache.pekko.actor.typed.scaladsl.{Behaviors, ActorContext => TypedContext} +import org.apache.pekko.actor.typed.scaladsl.{ + Behaviors, + ActorContext => TypedContext, +} import org.apache.pekko.actor.{ActorContext, Props, ActorRef => ClassicRef} import squants.Power import squants.energy.Kilowatts @@ -36,13 +57,19 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.{Power => PsdmPower} -import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala} -import scala.jdk.OptionConverters.RichOptional +import scala.jdk.CollectionConverters.{ + ListHasAsScala, + MapHasAsJava, + MapHasAsScala, +} +import scala.jdk.OptionConverters.{RichOption, RichOptional} import scala.util.{Failure, Success, Try} object ExtEmDataService { - def props(scheduler: ClassicRef)(implicit simulationStart: ZonedDateTime): Props = + def props(scheduler: ClassicRef)(implicit + simulationStart: ZonedDateTime + ): Props = Props( new ExtEmDataService(scheduler: ClassicRef) ) @@ -58,7 +85,7 @@ object ExtEmDataService { emService ! WrappedFlexResponse( response, receiver, - Some(self) + Some(self), ) Behaviors.same @@ -92,7 +119,7 @@ object ExtEmDataService { extEmDataMessage: Option[EmDataMessageFromExt] = None, flexOptionResponse: ReceiveDataMap[UUID, FlexOptionsResult] = ReceiveDataMap.empty, - setPointResponse: ReceiveDataMap[UUID, IssueFlexControl] = + setPointResponse: ReceiveDataMap[UUID, EmSetPointResult] = ReceiveDataMap.empty, ) extends ServiceBaseStateData @@ -143,8 +170,8 @@ object ExtEmDataService { final case class ExtEmDataService( override val scheduler: ClassicRef - )( - implicit val simulationStart: ZonedDateTime, +)(implicit + val simulationStart: ZonedDateTime ) extends SimonaService[ExtEmDataStateData](scheduler) with ExtDataSupport[ExtEmDataStateData] { @@ -394,29 +421,30 @@ final case class ExtEmDataService( case WrappedFlexResponse( provideFlexOptions: ProvideFlexOptions, receiver, - self + self, ) => - - val ref = receiver.getOrElse(self.getOrElse( - throw new CriticalFailureException("No receiver defined!") - )) + val ref = receiver.getOrElse( + self.getOrElse( + throw new CriticalFailureException("No receiver defined!") + ) + ) val uuid = serviceStateData.emHierarchy.getUuid(ref) val updated = provideFlexOptions match { case ProvideMinMaxFlexOptions(modelUuid, ref, min, max) => - - serviceStateData.flexOptionResponse.addData(uuid, new FlexOptionsResult( - simulationStart, // TODO: Fix this - modelUuid, - min.toQuantity, - ref.toQuantity, - max.toQuantity, - )) + serviceStateData.flexOptionResponse.addData( + uuid, + new FlexOptionsResult( + simulationStart, // TODO: Fix this + modelUuid, + min.toQuantity, + ref.toQuantity, + max.toQuantity, + ), + ) } - - if (updated.nonComplete) { // responses are still incomplete serviceStateData.copy( @@ -426,10 +454,10 @@ final case class ExtEmDataService( // all responses received, forward them to external simulation in a bundle serviceStateData.extEmDataConnection.queueExtResponseMsg( - new FlexOptionsResponse( - updated.receivedData.asJava - ) + new FlexOptionsResponse( + updated.receivedData.asJava ) + ) serviceStateData.copy( flexOptionResponse = ReceiveDataMap.empty @@ -439,10 +467,27 @@ final case class ExtEmDataService( case WrappedFlexRequest(issueFlexControl: IssueFlexControl, receiver) => val uuid = serviceStateData.flexAdapterToUuid(receiver) - val updated = serviceStateData.setPointResponse.addData( - uuid, - issueFlexControl, - ) + val updated = issueFlexControl match { + case IssueNoControl(tick) => + serviceStateData.setPointResponse.addData( + uuid, + new EmSetPointResult( + tick.toDateTime, + uuid, + None.toJava, + ), + ) + + case IssuePowerControl(tick, setPower) => + serviceStateData.setPointResponse.addData( + uuid, + new EmSetPointResult( + tick.toDateTime, + uuid, + Some(new PValue(setPower.toQuantity)).toJava, + ), + ) + } if (updated.nonComplete) { // responses are still incomplete @@ -451,7 +496,10 @@ final case class ExtEmDataService( ) } else { // all responses received, forward them to external simulation in a bundle - // serviceStateData.extEmDataConnection.queueExtResponseMsg(new EmSetPointDataResponse(updated.receivedData.asJava)) + + serviceStateData.extEmDataConnection.queueExtResponseMsg( + new EmSetPointDataResponse(updated.receivedData.asJava) + ) serviceStateData.copy( setPointResponse = ReceiveDataMap.empty 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 0b0019c4af..946e9c96aa 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -8,15 +8,33 @@ package edu.ie3.simona.service.em import com.typesafe.config.ConfigFactory import edu.ie3.datamodel.models.result.system.FlexOptionsResult +import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.api.data.em.ExtEmDataConnection -import edu.ie3.simona.api.data.em.ontology.{FlexOptionsResponse, RequestEmFlexResults} +import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexOptionValue} +import edu.ie3.simona.api.data.em.ontology.{ + EmSetPointDataResponse, + FlexOptionsResponse, + ProvideEmFlexOptionData, + ProvideEmSetPointData, + RequestEmFlexResults, + RequestEmSetPoints, +} import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage import edu.ie3.simona.exceptions.ServiceException import edu.ie3.simona.ontology.messages.Activation -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.FlexActivation +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ + FlexActivation, + IssuePowerControl, +} import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions -import edu.ie3.simona.ontology.messages.services.EmMessage.WrappedFlexResponse +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + WrappedFlexRequest, + WrappedFlexResponse, +} import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.SimonaService @@ -25,6 +43,7 @@ import edu.ie3.simona.test.common.input.EmInputTestData import edu.ie3.simona.test.common.{TestKitWithShutdown, TestSpawnerClassic} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.quantities.QuantityUtils._ +import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import org.apache.pekko.actor.ActorSystem import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps import org.apache.pekko.testkit.{TestActorRef, TestProbe} @@ -35,6 +54,7 @@ import java.time.ZonedDateTime import java.util.UUID import scala.concurrent.duration.DurationInt import scala.jdk.CollectionConverters._ +import scala.jdk.OptionConverters.RichOption class ExtEmDataServiceSpec extends TestKitWithShutdown( @@ -53,8 +73,10 @@ class ExtEmDataServiceSpec implicit val simulationStart: ZonedDateTime = ZonedDateTime.now() - private val emAgent1UUID = UUID.fromString("06a14909-366e-4e94-a593-1016e1455b30") - private val emAgent2UUID = UUID.fromString("104acdaa-5dc5-4197-aed2-2fddb3c4f237") + private val emAgent1UUID = + UUID.fromString("06a14909-366e-4e94-a593-1016e1455b30") + private val emAgent2UUID = + UUID.fromString("104acdaa-5dc5-4197-aed2-2fddb3c4f237") "An uninitialized em service" must { "send correct completion message after initialisation" in { @@ -62,7 +84,8 @@ class ExtEmDataServiceSpec val extSimAdapter = TestProbe("extSimAdapter") val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) - val extEmDataConnection = new ExtEmDataConnection(Map.empty[String, UUID].asJava) + val extEmDataConnection = + new ExtEmDataConnection(Map.empty[String, UUID].asJava) extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) val key = @@ -86,19 +109,23 @@ class ExtEmDataServiceSpec val extSimAdapter = TestProbe("extSimAdapter") val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) - val extEmDataConnection = new ExtEmDataConnection(Map.empty[String, UUID].asJava) + val extEmDataConnection = + new ExtEmDataConnection(Map.empty[String, UUID].asJava) extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) val emAgent = TestProbe("emAgent") // this one should be stashed - emAgent.send(emService, RegisterForEmDataService( - emInput.getUuid, - emAgent.ref.toTyped, - emAgent.ref.toTyped, - None, - None - )) + emAgent.send( + emService, + RegisterForEmDataService( + emInput.getUuid, + emAgent.ref.toTyped, + emAgent.ref.toTyped, + None, + None, + ), + ) scheduler.expectNoMessage() @@ -126,7 +153,8 @@ class ExtEmDataServiceSpec val extSimAdapter = TestProbe("extSimAdapter") val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) - val extEmDataConnection = new ExtEmDataConnection(Map.empty[String, UUID].asJava) + val extEmDataConnection = + new ExtEmDataConnection(Map.empty[String, UUID].asJava) extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) val key = @@ -160,7 +188,8 @@ class ExtEmDataServiceSpec val extSimAdapter = TestProbe("extSimAdapter") val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) - val extEmDataConnection = new ExtEmDataConnection(Map.empty[String, UUID].asJava) + val extEmDataConnection = + new ExtEmDataConnection(Map.empty[String, UUID].asJava) extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) val key = @@ -179,16 +208,34 @@ class ExtEmDataServiceSpec val emAgent1 = TestProbe("emAgent1") val emAgent2 = TestProbe("emAgent2") - emAgent1.send(emService, RegisterForEmDataService(emAgent1UUID, emAgent1.ref.toTyped, emAgent1.ref.toTyped, None, None)) + emAgent1.send( + emService, + RegisterForEmDataService( + emAgent1UUID, + emAgent1.ref.toTyped, + emAgent1.ref.toTyped, + None, + None, + ), + ) emAgent1.expectNoMessage() - emAgent2.send(emService, RegisterForEmDataService(emAgent2UUID, emAgent2.ref.toTyped, emAgent2.ref.toTyped, None, None)) + emAgent2.send( + emService, + RegisterForEmDataService( + emAgent2UUID, + emAgent2.ref.toTyped, + emAgent2.ref.toTyped, + None, + None, + ), + ) emAgent2.expectNoMessage() extEmDataConnection.sendExtMsg( new RequestEmFlexResults( INIT_SIM_TICK, - List.empty.asJava + List.empty.asJava, ) ) @@ -203,7 +250,7 @@ class ExtEmDataServiceSpec extEmDataConnection.sendExtMsg( new RequestEmFlexResults( 0, - List(emAgent1UUID).asJava + List(emAgent1UUID).asJava, ) ) @@ -217,16 +264,19 @@ class ExtEmDataServiceSpec extEmDataConnection.receiveTriggerQueue shouldBe empty - emAgent1.send(emService, WrappedFlexResponse( - ProvideMinMaxFlexOptions( - emAgent1UUID, - Kilowatts(5), - Kilowatts(0), - Kilowatts(10) + emAgent1.send( + emService, + WrappedFlexResponse( + ProvideMinMaxFlexOptions( + emAgent1UUID, + Kilowatts(5), + Kilowatts(0), + Kilowatts(10), + ), + None, + Some(emAgent1.ref.toTyped), ), - None, - Some(emAgent1.ref.toTyped) - )) + ) awaitCond( !extEmDataConnection.receiveTriggerQueue.isEmpty, @@ -236,22 +286,288 @@ class ExtEmDataServiceSpec extEmDataConnection.receiveTriggerQueue.size() shouldBe 1 - extEmDataConnection.receiveTriggerQueue.take() shouldBe new FlexOptionsResponse( - Map(emAgent1UUID -> new FlexOptionsResult( - simulationStart, + extEmDataConnection.receiveTriggerQueue + .take() shouldBe new FlexOptionsResponse( + Map( + emAgent1UUID -> new FlexOptionsResult( + simulationStart, + emAgent1UUID, + 0.asKiloWatt, + 5.asKiloWatt, + 10.asKiloWatt, + ) + ).asJava + ) + } + + "handle flex option provision correctly" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") + + val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) + val extEmDataConnection = + new ExtEmDataConnection(Map.empty[String, UUID].asJava) + extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) + + val key = + ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) + scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + + scheduler.send( + emService, + SimonaService.Create(InitExtEmData(extEmDataConnection), key), + ) + scheduler.expectMsgType[ScheduleActivation] + + scheduler.send(emService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(emService.toTyped)) + + val emAgent1 = TestProbe("emAgent1") + val emAgent2 = TestProbe("emAgent2") + + emAgent1.send( + emService, + RegisterForEmDataService( + emAgent1UUID, + emAgent1.ref.toTyped, + emAgent1.ref.toTyped, + None, + None, + ), + ) + emAgent1.expectNoMessage() + + emAgent2.send( + emService, + RegisterForEmDataService( + emAgent2UUID, + emAgent2.ref.toTyped, + emAgent2.ref.toTyped, + None, + None, + ), + ) + emAgent2.expectNoMessage() + + extEmDataConnection.sendExtMsg( + new ProvideEmFlexOptionData( + 0, + Map( + emAgent2UUID -> new FlexOptionValue( + emAgent1UUID, + -3.asKiloWatt, + -1.asKiloWatt, + 1.asKiloWatt, + ) + ).asJava, + None.toJava, + ) + ) + + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(emService)) + scheduler.send(emService, Activation(0)) + + emAgent1.expectNoMessage() + emAgent2.expectMsg( + ProvideMinMaxFlexOptions( emAgent1UUID, - 0.asKiloWatt, - 5.asKiloWatt, - 10.asKiloWatt - )).asJava, + Kilowatts(-1), + Kilowatts(-3), + Kilowatts(1), + ) ) + + scheduler.expectMsg(Completion(emService.toTyped)) } - "handle set point request correctly" in {} + "handle set point provision correctly" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - "handle flex option provision correctly" in {} + val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) + val extEmDataConnection = + new ExtEmDataConnection(Map.empty[String, UUID].asJava) + extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) - "handle set point provision correctly" in {} + val key = + ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) + scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + + scheduler.send( + emService, + SimonaService.Create(InitExtEmData(extEmDataConnection), key), + ) + scheduler.expectMsgType[ScheduleActivation] + + scheduler.send(emService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(emService.toTyped)) + + val emAgent1 = TestProbe("emAgent1") + val emAgent2 = TestProbe("emAgent2") + + emAgent1.send( + emService, + RegisterForEmDataService( + emAgent1UUID, + emAgent1.ref.toTyped, + emAgent1.ref.toTyped, + None, + None, + ), + ) + emAgent1.expectNoMessage() + + emAgent2.send( + emService, + RegisterForEmDataService( + emAgent2UUID, + emAgent2.ref.toTyped, + emAgent2.ref.toTyped, + None, + None, + ), + ) + emAgent2.expectNoMessage() + + extEmDataConnection.sendExtMsg( + new ProvideEmSetPointData( + 0, + Map( + emAgent1UUID -> new PValue(-3.asKiloWatt), + emAgent2UUID -> new PValue(0.asKiloWatt), + ).asJava, + None.toJava, + ) + ) + + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(emService)) + scheduler.send(emService, Activation(0)) + + emAgent1.expectMsg( + IssuePowerControl(0, Kilowatts(-3)) + ) + emAgent2.expectMsg( + IssuePowerControl(0, zeroKW) + ) + + scheduler.expectMsg(Completion(emService.toTyped)) + } + + "handle set point request correctly" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") + + val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) + val extEmDataConnection = + new ExtEmDataConnection(Map.empty[String, UUID].asJava) + extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) + + val key = + ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) + scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + + scheduler.send( + emService, + SimonaService.Create(InitExtEmData(extEmDataConnection), key), + ) + scheduler.expectMsgType[ScheduleActivation] + + scheduler.send(emService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(emService.toTyped)) + + val emAgent1 = TestProbe("emAgent1") + val emAgent2 = TestProbe("emAgent2") + + emAgent1.send( + emService, + RegisterForEmDataService( + emAgent1UUID, + emAgent1.ref.toTyped, + emAgent1.ref.toTyped, + None, + None, + ), + ) + emAgent1.expectNoMessage() + + emAgent2.send( + emService, + RegisterForEmDataService( + emAgent2UUID, + emAgent2.ref.toTyped, + emAgent2.ref.toTyped, + None, + None, + ), + ) + emAgent2.expectNoMessage() + + // parent em normally sets itself a set point to 0 kW + // we replace this behavior with the external data service + extEmDataConnection.sendExtMsg( + new ProvideEmSetPointData( + 0, + Map(emAgent2UUID -> new PValue(0.asKiloWatt)).asJava, + None.toJava, + ) + ) + + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(emService)) + scheduler.send(emService, Activation(0)) + + emAgent1.expectNoMessage() + emAgent2.expectMsg( + IssuePowerControl(0, zeroKW) + ) + + scheduler.expectMsg(Completion(emService.toTyped)) + + // we now request the set points of the controlled em agent + extEmDataConnection.sendExtMsg( + new RequestEmSetPoints( + 0, + List(emAgent1UUID).asJava, + ) + ) + + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(emService)) + scheduler.send(emService, Activation(0)) + + emAgent1.expectNoMessage() + emAgent2.expectNoMessage() + + scheduler.expectMsg(Completion(emService.toTyped)) + + extEmDataConnection.receiveTriggerQueue shouldBe empty + + // the parent em agent sends the controlled em agent an IssuePowerControl message through the service + emAgent2.send( + emService, + WrappedFlexRequest( + IssuePowerControl(0, Kilowatts(2)), + emAgent1.ref.toTyped, + ), + ) + + awaitCond( + !extEmDataConnection.receiveTriggerQueue.isEmpty, + max = 3.seconds, + message = "No message received", + ) + + extEmDataConnection.receiveTriggerQueue.size() shouldBe 1 + + extEmDataConnection.receiveTriggerQueue + .take() shouldBe new EmSetPointDataResponse( + Map( + emAgent1UUID -> new EmSetPointResult( + simulationStart, + emAgent1UUID, + Some(new PValue(2.asKiloWatt)).toJava, + ) + ).asJava + ) + } } } From 66485880e994a96e93a104ef3e30f8820b0fa9b2 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 11 Mar 2025 12:23:56 +0100 Subject: [PATCH 013/125] Adapting to changes in `dev`. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 2 +- .../messages/services/EmMessage.scala | 5 +- .../simona/service/em/ExtEmDataService.scala | 25 +++----- .../agent/em/EmAgentWithServiceSpec.scala | 63 +++++++++++-------- .../service/em/ExtEmDataServiceSpec.scala | 3 +- 5 files changed, 50 insertions(+), 48 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index ed93805c81..51be9aa2ac 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -138,7 +138,7 @@ object EmAgent { ExtEmDataService.emServiceResponseAdapter( service, parentOption, - ctx.self, + inputModel.getUuid, )(ctx) Right(FlexControlledData(serviceResponseAdapter, flexAdapter)) diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala index d2381516bd..db6b5dd421 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala @@ -13,6 +13,8 @@ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ import edu.ie3.simona.ontology.messages.services.ServiceMessage.DataResponseMessage import org.apache.pekko.actor.typed.ActorRef +import java.util.UUID + sealed trait EmMessage object EmMessage { @@ -21,8 +23,7 @@ object EmMessage { final case class WrappedFlexResponse( flexResponse: FlexResponse, - receiver: Option[ActorRef[FlexResponse]], - self: Option[ActorRef[FlexResponse]] = None, + receiver: Either[UUID, ActorRef[FlexResponse]], ) extends EmResponseMessage final case class WrappedFlexRequest( 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 86a6c93b2e..5a1dbe8a8c 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -14,11 +14,7 @@ import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.api.data.em.{ExtEmDataConnection, NoSetPointValue} import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException -import edu.ie3.simona.exceptions.{ - CriticalFailureException, - InitializationException, - ServiceException, -} +import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EmMessage.{ @@ -77,15 +73,14 @@ object ExtEmDataService { def emServiceResponseAdapter( emService: ClassicRef, receiver: Option[ActorRef[FlexResponse]], - self: ActorRef[FlexResponse], + self: UUID, )(implicit ctx: TypedContext[EmAgent.Request]): ActorRef[FlexResponse] = { val request = Behaviors.receiveMessagePartial[FlexResponse] { case response: FlexResponse => emService ! WrappedFlexResponse( response, - receiver, - Some(self), + receiver.map(Right(_)).getOrElse(Left(self)), ) Behaviors.same @@ -421,15 +416,13 @@ final case class ExtEmDataService( case WrappedFlexResponse( provideFlexOptions: ProvideFlexOptions, receiver, - self, ) => - val ref = receiver.getOrElse( - self.getOrElse( - throw new CriticalFailureException("No receiver defined!") - ) - ) - - val uuid = serviceStateData.emHierarchy.getUuid(ref) + val uuid = receiver match { + case Right(otherRef) => + serviceStateData.emHierarchy.getUuid(otherRef) + case Left(self: UUID) => + self + } val updated = provideFlexOptions match { case ProvideMinMaxFlexOptions(modelUuid, ref, min, max) => diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala index a4606b5f19..68e54c6988 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala @@ -124,7 +124,7 @@ class EmAgentWithServiceSpec service.expectMessage( WrappedFlexResponse( ScheduleFlexActivation(emInput.getUuid, INIT_SIM_TICK), - Some(parentEmAgent.ref), + Right(parentEmAgent.ref), ) ) @@ -164,7 +164,7 @@ class EmAgentWithServiceSpec modelUuid = emInput.getUuid, requestAtTick = Some(0), ), - Some(parentEmAgent.ref), + Right(parentEmAgent.ref), ) ) @@ -210,15 +210,14 @@ class EmAgentWithServiceSpec minPower, maxPower, ), - receiver, - _, + Right(receiver), ) => modelUuid shouldBe emInput.getUuid referencePower shouldBe Kilowatts(0) minPower shouldBe Kilowatts(-16) maxPower shouldBe Kilowatts(6) // hint: PV is not flexible - receiver shouldBe Some(parentEmAgent.ref) + receiver shouldBe parentEmAgent.ref } // issue power control and expect EmAgent to distribute it @@ -263,12 +262,15 @@ class EmAgentWithServiceSpec } service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse(FlexResult(modelUuid, result), receiver, _) => + case WrappedFlexResponse( + FlexResult(modelUuid, result), + Right(receiver), + ) => modelUuid shouldBe emInput.getUuid result.p should approximate(Kilowatts(6)) result.q should approximate(Kilovars(.6)) - receiver shouldBe Some(parentEmAgent.ref) + receiver shouldBe parentEmAgent.ref } service.expectMessage( @@ -277,7 +279,7 @@ class EmAgentWithServiceSpec modelUuid = emInput.getUuid, requestAtTick = Some(300), ), - Some(parentEmAgent.ref), + Right(parentEmAgent.ref), ) ) @@ -318,12 +320,15 @@ class EmAgentWithServiceSpec } service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse(FlexResult(modelUuid, result), receiver, _) => + case WrappedFlexResponse( + FlexResult(modelUuid, result), + Right(receiver), + ) => modelUuid shouldBe emInput.getUuid result.p should approximate(Kilowatts(0)) result.q should approximate(Kilovars(0)) - receiver shouldBe Some(parentEmAgent.ref) + receiver shouldBe parentEmAgent.ref } service.expectMessage( WrappedFlexResponse( @@ -331,7 +336,7 @@ class EmAgentWithServiceSpec modelUuid = emInput.getUuid, requestAtTick = Some(600), ), - Some(parentEmAgent.ref), + Right(parentEmAgent.ref), ) ) @@ -419,7 +424,7 @@ class EmAgentWithServiceSpec service.expectMessage( WrappedFlexResponse( ScheduleFlexActivation(updatedEmInput.getUuid, INIT_SIM_TICK), - Some(parentEmAgent), + Right(parentEmAgent), ) ) @@ -431,7 +436,7 @@ class EmAgentWithServiceSpec service.expectMessage( WrappedFlexResponse( ScheduleFlexActivation(parentEmInput.getUuid, INIT_SIM_TICK), - None, + Left(parentEmInput.getUuid), ) ) @@ -480,7 +485,7 @@ class EmAgentWithServiceSpec modelUuid = updatedEmInput.getUuid, requestAtTick = Some(0), ), - Some(parentEmAgent), + Right(parentEmAgent), ) ) @@ -495,7 +500,7 @@ class EmAgentWithServiceSpec modelUuid = parentEmInput.getUuid, requestAtTick = Some(0), ), - None, + Left(parentEmInput.getUuid), ) ) @@ -550,15 +555,14 @@ class EmAgentWithServiceSpec minPower, maxPower, ), - receiver, - _, + Right(receiver), ) => modelUuid shouldBe updatedEmInput.getUuid referencePower shouldBe Kilowatts(0) minPower shouldBe Kilowatts(-16) maxPower shouldBe Kilowatts(6) // hint: PV is not flexible - receiver shouldBe Some(parentEmAgent) + receiver shouldBe parentEmAgent } parentEmAgent ! ProvideMinMaxFlexOptions( @@ -576,15 +580,14 @@ class EmAgentWithServiceSpec minPower, maxPower, ), - receiver, - _, + Left(self), ) => modelUuid shouldBe parentEmInput.getUuid referencePower shouldBe Kilowatts(0) minPower shouldBe Kilowatts(-16) maxPower shouldBe Kilowatts(6) // hint: PV is not flexible - receiver shouldBe None + self shouldBe parentEmInput.getUuid } parentEmAgentFlex ! IssuePowerControl(0, Kilowatts(6)) @@ -645,12 +648,15 @@ class EmAgentWithServiceSpec } service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse(FlexResult(modelUuid, result), receiver, _) => + case WrappedFlexResponse( + FlexResult(modelUuid, result), + Right(receiver), + ) => modelUuid shouldBe updatedEmInput.getUuid result.p should approximate(Kilowatts(6)) result.q should approximate(Kilovars(.6)) - receiver shouldBe Some(parentEmAgent) + receiver shouldBe parentEmAgent } parentEmAgent ! FlexResult( @@ -667,7 +673,7 @@ class EmAgentWithServiceSpec modelUuid = updatedEmInput.getUuid, requestAtTick = Some(300), ), - Some(parentEmAgent), + Right(parentEmAgent), ) ) @@ -713,12 +719,15 @@ class EmAgentWithServiceSpec } service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse(FlexResult(modelUuid, result), receiver, _) => + case WrappedFlexResponse( + FlexResult(modelUuid, result), + Right(receiver), + ) => modelUuid shouldBe updatedEmInput.getUuid result.p should approximate(Kilowatts(0)) result.q should approximate(Kilovars(0)) - receiver shouldBe Some(parentEmAgent) + receiver shouldBe parentEmAgent } parentEmAgent ! FlexResult( @@ -732,7 +741,7 @@ class EmAgentWithServiceSpec modelUuid = updatedEmInput.getUuid, requestAtTick = Some(600), ), - Some(parentEmAgent), + Right(parentEmAgent), ) ) } 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 946e9c96aa..b1f819f53c 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -273,8 +273,7 @@ class ExtEmDataServiceSpec Kilowatts(0), Kilowatts(10), ), - None, - Some(emAgent1.ref.toTyped), + Left(emAgent1UUID), ), ) From 0fa60f724c2403e477e3f4b621d206a52d45cd5e Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 13 Mar 2025 12:26:17 +0100 Subject: [PATCH 014/125] Adapting to changes in SIMONA's `dev` branch. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 4 +- .../agent/grid/GridAgentController.scala | 5 +- .../messages/services/EmMessage.scala | 8 +- .../messages/services/ServiceMessage.scala | 13 +- .../simona/service/em/ExtEmDataService.scala | 106 ++-- .../primary/ExtPrimaryDataService.scala | 57 +- .../service/primary/PrimaryServiceProxy.scala | 12 +- .../ie3/simona/sim/setup/ExtSimSetup.scala | 73 +-- .../simona/sim/setup/ExtSimSetupData.scala | 28 +- .../agent/em/EmAgentWithServiceSpec.scala | 9 +- .../service/em/ExtEmDataServiceSpec.scala | 584 ++++++++++-------- .../primary/ExtPrimaryDataServiceSpec.scala | 135 ++-- .../primary/PrimaryServiceProxySpec.scala | 6 +- .../sim/setup/ExtSimSetupDataSpec.scala | 34 +- 14 files changed, 552 insertions(+), 522 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 51be9aa2ac..ac100ac862 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -25,6 +25,7 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ } import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions +import edu.ie3.simona.ontology.messages.services.EmMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.service.em.ExtEmDataService @@ -33,7 +34,6 @@ import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import edu.ie3.util.scala.quantities.DefaultQuantities._ import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.{ActorRef, Behavior} -import org.apache.pekko.actor.{ActorRef => ClassicRef} import java.time.ZonedDateTime import scala.jdk.OptionConverters.RichOptional @@ -106,7 +106,7 @@ object EmAgent { simulationStartDate: ZonedDateTime, parent: Either[ActorRef[SchedulerMessage], ActorRef[FlexResponse]], listener: Iterable[ActorRef[ResultEvent]], - emDataService: Option[ClassicRef], + emDataService: Option[ActorRef[EmMessage]], ): Behavior[Request] = Behaviors.setup[Request] { ctx => val flexAdapter = ctx.messageAdapter[FlexRequest](Flex) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala index f257ededde..3010d2bf31 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala @@ -33,6 +33,7 @@ import edu.ie3.simona.exceptions.agent.GridAgentInitializationException import edu.ie3.simona.ontology.messages.SchedulerMessage import edu.ie3.simona.ontology.messages.SchedulerMessage.ScheduleActivation import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.FlexResponse +import edu.ie3.simona.ontology.messages.services.EmMessage import edu.ie3.simona.service.ServiceType import edu.ie3.simona.util.ConfigUtil import edu.ie3.simona.util.ConfigUtil._ @@ -262,7 +263,7 @@ class GridAgentController( outputConfigUtil: OutputConfigUtil, emInputs: Map[UUID, EmInput], previousLevelEms: Map[UUID, ActorRef[FlexResponse]] = Map.empty, - emDataService: Option[ClassicRef], + emDataService: Option[ActorRef[EmMessage]], ): Map[UUID, ActorRef[FlexResponse]] = { // For the current level, split controlled and uncontrolled EMs. // Uncontrolled EMs can be built right away. @@ -571,7 +572,7 @@ class GridAgentController( modelConfiguration: EmRuntimeConfig, outputConfig: NotifierConfig, maybeControllingEm: Option[ActorRef[FlexResponse]], - emDataService: Option[ClassicRef], + emDataService: Option[ActorRef[EmMessage]], ): ActorRef[FlexResponse] = gridAgentContext.spawn( EmAgent( diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala index db6b5dd421..2ae59869e9 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala @@ -10,7 +10,7 @@ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ FlexRequest, FlexResponse, } -import edu.ie3.simona.ontology.messages.services.ServiceMessage.DataResponseMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage.ServiceResponseMessage import org.apache.pekko.actor.typed.ActorRef import java.util.UUID @@ -19,16 +19,16 @@ sealed trait EmMessage object EmMessage { - trait EmResponseMessage extends EmMessage with DataResponseMessage + private[services] trait EmInternal extends EmMessage final case class WrappedFlexResponse( flexResponse: FlexResponse, receiver: Either[UUID, ActorRef[FlexResponse]], - ) extends EmResponseMessage + ) extends ServiceResponseMessage final case class WrappedFlexRequest( flexRequest: FlexRequest, receiver: ActorRef[FlexRequest], - ) extends EmResponseMessage + ) extends ServiceResponseMessage } diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala index 3d444f9fa9..376ea12926 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala @@ -6,30 +6,27 @@ package edu.ie3.simona.ontology.messages.services +import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.participant2.ParticipantAgent import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.ontology.messages.Activation -import edu.ie3.simona.ontology.messages.services.EvMessage.EvInternal -import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ FlexRequest, FlexResponse, } +import edu.ie3.simona.ontology.messages.services.EmMessage.EmInternal +import edu.ie3.simona.ontology.messages.services.EvMessage.EvInternal import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.simona.service.ServiceStateData.InitializeServiceStateData import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.{ActorRef => ClassicRef} -import java.util.UUID -import org.apache.pekko.actor.typed.ActorRef -import org.apache.pekko.actor.{ActorRef => ClassicRef} - import java.util.UUID /** Collections of all messages, that are send to and from the different * services */ -sealed trait ServiceMessage extends EvInternal +sealed trait ServiceMessage extends EmInternal with EvInternal object ServiceMessage { @@ -63,8 +60,6 @@ object ServiceMessage { */ trait ServiceRegistrationMessage extends ServiceMessage - trait DataResponseMessage extends ServiceMessage - /** Message to register with a primary data service. * * @param requestingActor 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 5a1dbe8a8c..a5817c187e 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -17,35 +17,28 @@ import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequ import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions +import edu.ie3.simona.ontology.messages.services.EmMessage import edu.ie3.simona.ontology.messages.services.EmMessage.{ WrappedFlexRequest, WrappedFlexResponse, } -import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - DataResponseMessage, RegisterForEmDataService, + ServiceRegistrationMessage, + ServiceResponseMessage, } import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceBaseStateData, } -import edu.ie3.simona.service.em.ExtEmDataService.{ - ExtEmDataStateData, - InitExtEmData, -} -import edu.ie3.simona.service.{ExtDataSupport, SimonaService} +import edu.ie3.simona.service.{ExtDataSupport, TypedSimonaService} import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.util.quantities.PowerSystemUnits.KILOWATT import edu.ie3.util.quantities.QuantityUtils._ import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import org.apache.pekko.actor.typed.ActorRef -import org.apache.pekko.actor.typed.scaladsl.{ - Behaviors, - ActorContext => TypedContext, -} -import org.apache.pekko.actor.{ActorContext, Props, ActorRef => ClassicRef} +import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} import squants.Power import squants.energy.Kilowatts import tech.units.indriya.ComparableQuantity @@ -61,20 +54,27 @@ import scala.jdk.CollectionConverters.{ import scala.jdk.OptionConverters.{RichOption, RichOptional} import scala.util.{Failure, Success, Try} -object ExtEmDataService { +object ExtEmDataService + extends TypedSimonaService[EmMessage] + with ExtDataSupport[EmMessage] { - def props(scheduler: ClassicRef)(implicit - simulationStart: ZonedDateTime - ): Props = - Props( - new ExtEmDataService(scheduler: ClassicRef) - ) + override type S = ExtEmDataStateData + + implicit class SquantsToQuantity(private val value: Power) { + def toQuantity: ComparableQuantity[PsdmPower] = value.toKilowatts.asKiloWatt + } + + implicit class quantityToSquants( + private val value: ComparableQuantity[PsdmPower] + ) { + def toSquants: Power = Kilowatts(value.to(KILOWATT).getValue.doubleValue()) + } def emServiceResponseAdapter( - emService: ClassicRef, + emService: ActorRef[EmMessage], receiver: Option[ActorRef[FlexResponse]], self: UUID, - )(implicit ctx: TypedContext[EmAgent.Request]): ActorRef[FlexResponse] = { + )(implicit ctx: ActorContext[EmAgent.Request]): ActorRef[FlexResponse] = { val request = Behaviors.receiveMessagePartial[FlexResponse] { case response: FlexResponse => @@ -90,9 +90,9 @@ object ExtEmDataService { } def emServiceRequestAdapter( - emService: ClassicRef, + emService: ActorRef[EmMessage], receiver: ActorRef[FlexRequest], - )(implicit ctx: TypedContext[EmAgent.Request]): ActorRef[FlexRequest] = { + )(implicit ctx: ActorContext[EmAgent.Request]): ActorRef[FlexRequest] = { val response = Behaviors.receiveMessagePartial[FlexRequest] { case request: FlexRequest => emService ! WrappedFlexRequest( @@ -108,6 +108,7 @@ object ExtEmDataService { final case class ExtEmDataStateData( extEmDataConnection: ExtEmDataConnection, + startTime: ZonedDateTime, emHierarchy: EmHierarchy = EmHierarchy(), uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = Map.empty, flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, @@ -159,26 +160,9 @@ object ExtEmDataService { } case class InitExtEmData( - extEmData: ExtEmDataConnection + extEmData: ExtEmDataConnection, + startTime: ZonedDateTime, ) extends InitializeServiceStateData -} - -final case class ExtEmDataService( - override val scheduler: ClassicRef -)(implicit - val simulationStart: ZonedDateTime -) extends SimonaService[ExtEmDataStateData](scheduler) - with ExtDataSupport[ExtEmDataStateData] { - - implicit class SquantsToQuantity(private val value: Power) { - def toQuantity: ComparableQuantity[PsdmPower] = value.toKilowatts.asKiloWatt - } - - implicit class quantityToSquants( - private val value: ComparableQuantity[PsdmPower] - ) { - def toSquants: Power = Kilowatts(value.to(KILOWATT).getValue.doubleValue()) - } /** Initialize the concrete service implementation using the provided * initialization data. This method should perform all heavyweight tasks @@ -197,8 +181,9 @@ final case class ExtEmDataService( override def init( initServiceData: InitializeServiceStateData ): Try[(ExtEmDataStateData, Option[Long])] = initServiceData match { - case InitExtEmData(extEmDataConnection) => - val emDataInitializedStateData = ExtEmDataStateData(extEmDataConnection) + case InitExtEmData(extEmDataConnection, startTime) => + val emDataInitializedStateData = + ExtEmDataStateData(extEmDataConnection, startTime) Success( emDataInitializedStateData, None, @@ -223,8 +208,11 @@ final case class ExtEmDataService( * with updated values) */ override protected def handleRegistrationRequest( - registrationMessage: ServiceMessage.ServiceRegistrationMessage - )(implicit serviceStateData: ExtEmDataStateData): Try[ExtEmDataStateData] = + registrationMessage: ServiceRegistrationMessage + )(implicit + serviceStateData: ExtEmDataStateData, + ctx: ActorContext[EmMessage], + ): Try[ExtEmDataStateData] = registrationMessage match { case RegisterForEmDataService( modelUuid, @@ -286,7 +274,7 @@ final case class ExtEmDataService( */ override protected def announceInformation(tick: Long)(implicit serviceStateData: ExtEmDataStateData, - ctx: ActorContext, + ctx: ActorContext[EmMessage], ): (ExtEmDataStateData, Option[Long]) = { val updatedStateData = serviceStateData.extEmDataMessage.getOrElse( throw ServiceException( @@ -325,7 +313,8 @@ final case class ExtEmDataService( private def announceFlexOptions( provideFlexOptions: ProvideEmFlexOptionData )(implicit - serviceStateData: ExtEmDataStateData + serviceStateData: ExtEmDataStateData, + ctx: ActorContext[EmMessage], ): ExtEmDataStateData = { val hierarchy = serviceStateData.emHierarchy @@ -343,7 +332,7 @@ final case class ExtEmDataService( ) case None => - log.warning(s"No em agent with uuid '$agent' registered!") + ctx.log.warn(s"No em agent with uuid '$agent' registered!") } } @@ -353,7 +342,10 @@ final case class ExtEmDataService( private def announceEmSetPoints( tick: Long, provideEmSetPointData: ProvideEmSetPointData, - )(implicit serviceStateData: ExtEmDataStateData): ExtEmDataStateData = { + )(implicit + serviceStateData: ExtEmDataStateData, + ctx: ActorContext[EmMessage], + ): ExtEmDataStateData = { provideEmSetPointData .emData() @@ -372,7 +364,7 @@ final case class ExtEmDataService( } case None => - log.warning(s"No em agent with uuid '$agent' registered!") + ctx.log.warn(s"No em agent with uuid '$agent' registered!") } } @@ -390,7 +382,9 @@ final case class ExtEmDataService( */ override protected def handleDataMessage( extMsg: DataMessageFromExt - )(implicit serviceStateData: ExtEmDataStateData): ExtEmDataStateData = { + )(implicit + serviceStateData: ExtEmDataStateData + ): ExtEmDataStateData = { extMsg match { case extEmDataMessage: EmDataMessageFromExt => serviceStateData.copy( @@ -409,7 +403,7 @@ final case class ExtEmDataService( * the updated state data */ override protected def handleDataResponseMessage( - extResponseMsg: DataResponseMessage + extResponseMsg: ServiceResponseMessage )(implicit serviceStateData: ExtEmDataStateData ): ExtEmDataStateData = extResponseMsg match { @@ -429,7 +423,7 @@ final case class ExtEmDataService( serviceStateData.flexOptionResponse.addData( uuid, new FlexOptionsResult( - simulationStart, // TODO: Fix this + serviceStateData.startTime, // TODO: Fix this modelUuid, min.toQuantity, ref.toQuantity, @@ -465,7 +459,7 @@ final case class ExtEmDataService( serviceStateData.setPointResponse.addData( uuid, new EmSetPointResult( - tick.toDateTime, + tick.toDateTime(serviceStateData.startTime), uuid, None.toJava, ), @@ -475,7 +469,7 @@ final case class ExtEmDataService( serviceStateData.setPointResponse.addData( uuid, new EmSetPointResult( - tick.toDateTime, + tick.toDateTime(serviceStateData.startTime), uuid, Some(new PValue(setPower.toQuantity)).toJava, ), diff --git a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala index 616d7bfe6a..2008ecadb6 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala @@ -22,31 +22,32 @@ import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequ import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - DataResponseMessage, PrimaryServiceRegistrationMessage, + ServiceResponseMessage, } import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceBaseStateData, } -import edu.ie3.simona.service.primary.ExtPrimaryDataService.{ - ExtPrimaryDataStateData, - InitExtPrimaryData, +import edu.ie3.simona.service.{ + ExtDataSupport, + ServiceStateData, + TypedSimonaService, } -import edu.ie3.simona.service.{ExtDataSupport, ServiceStateData, SimonaService} -import org.apache.pekko.actor.{ActorContext, ActorRef, Props} +import org.apache.pekko.actor.ActorRef +import org.apache.pekko.actor.typed.scaladsl.ActorContext +import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import java.util.UUID import scala.jdk.CollectionConverters.MapHasAsScala import scala.jdk.OptionConverters.RichOptional import scala.util.{Failure, Success, Try} -object ExtPrimaryDataService { +object ExtPrimaryDataService + extends TypedSimonaService[ServiceMessage] + with ExtDataSupport[ServiceMessage] { - def props(scheduler: ActorRef): Props = - Props( - new ExtPrimaryDataService(scheduler: ActorRef) - ) + override type S = ExtPrimaryDataStateData final case class ExtPrimaryDataStateData( extPrimaryData: ExtPrimaryDataConnection, @@ -61,12 +62,6 @@ object ExtPrimaryDataService { extPrimaryData: ExtPrimaryDataConnection ) extends InitializeServiceStateData -} -final case class ExtPrimaryDataService( - override val scheduler: ActorRef -) extends SimonaService[ExtPrimaryDataStateData](scheduler) - with ExtDataSupport[ExtPrimaryDataStateData] { - override def init( initServiceData: ServiceStateData.InitializeServiceStateData ): Try[(ExtPrimaryDataStateData, Option[Long])] = initServiceData match { @@ -90,7 +85,8 @@ final case class ExtPrimaryDataService( override protected def handleRegistrationRequest( registrationMessage: ServiceMessage.ServiceRegistrationMessage )(implicit - serviceStateData: ExtPrimaryDataStateData + serviceStateData: ExtPrimaryDataStateData, + ctx: ActorContext[ServiceMessage], ): Try[ExtPrimaryDataStateData] = registrationMessage match { case PrimaryServiceRegistrationMessage( requestingActor, @@ -109,7 +105,8 @@ final case class ExtPrimaryDataService( agentToBeRegistered: ActorRef, agentUUID: UUID, )(implicit - serviceStateData: ExtPrimaryDataStateData + serviceStateData: ExtPrimaryDataStateData, + ctx: ActorContext[ServiceMessage], ): ExtPrimaryDataStateData = { serviceStateData.uuidToActorRef.get(agentUUID) match { case None => @@ -124,11 +121,11 @@ final case class ExtPrimaryDataService( ) agentToBeRegistered ! PrimaryRegistrationSuccessfulMessage( - self, + ctx.self.toClassic, 0L, PrimaryData.getPrimaryDataExtra(valueClass), ) - log.info(s"Successful registration for $agentUUID") + ctx.log.info(s"Successful registration for $agentUUID") serviceStateData.copy( subscribers = serviceStateData.subscribers :+ agentUUID, @@ -138,7 +135,7 @@ final case class ExtPrimaryDataService( case Some(_) => // actor is already registered, do nothing - log.warning( + ctx.log.warn( "Sending actor {} is already registered", agentToBeRegistered, ) @@ -161,7 +158,7 @@ final case class ExtPrimaryDataService( tick: Long )(implicit serviceStateData: ExtPrimaryDataStateData, - ctx: ActorContext, + ctx: ActorContext[ServiceMessage], ): (ExtPrimaryDataStateData, Option[Long]) = { // We got activated for this tick, so we expect incoming primary data serviceStateData.extPrimaryDataMessage.getOrElse( throw ServiceException( @@ -181,19 +178,21 @@ final case class ExtPrimaryDataService( primaryDataMessage: ProvidePrimaryData, )(implicit serviceStateData: ExtPrimaryDataStateData, - ctx: ActorContext, + ctx: ActorContext[ServiceMessage], ): ( ExtPrimaryDataStateData, Option[Long], ) = { - log.debug(s"Got activation to distribute primaryData = $primaryDataMessage") + ctx.log.debug( + s"Got activation to distribute primaryData = $primaryDataMessage" + ) val actorToPrimaryData = primaryDataMessage.primaryData.asScala.flatMap { case (agent, primaryDataPerAgent) => serviceStateData.uuidToActorRef .get(agent) .map((_, primaryDataPerAgent)) .orElse { - log.warning( + ctx.log.warn( "A corresponding actor ref for UUID {} could not be found", agent, ) @@ -210,13 +209,13 @@ final case class ExtPrimaryDataService( case Success(primaryData) => actor ! DataProvision( tick, - self, + ctx.self.toClassic, primaryData, maybeNextTick, ) case Failure(exception) => /* Processing of data failed */ - log.warning( + ctx.log.warn( "Unable to convert received value to primary data. Skipped that data." + "\nException: {}", exception, @@ -245,7 +244,7 @@ final case class ExtPrimaryDataService( } override protected def handleDataResponseMessage( - extResponseMsg: DataResponseMessage + extResponseMsg: ServiceResponseMessage )(implicit serviceStateData: ExtPrimaryDataStateData ): ExtPrimaryDataStateData = serviceStateData diff --git a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala index d92a5d37a2..8664d77f30 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala @@ -42,6 +42,7 @@ import edu.ie3.simona.exceptions.{ import edu.ie3.simona.logging.SimonaActorLogging import edu.ie3.simona.ontology.messages.Activation import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion +import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ PrimaryServiceRegistrationMessage, WorkerRegistrationMessage, @@ -62,6 +63,7 @@ import edu.ie3.simona.service.{ServiceStateData, SimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps import org.apache.pekko.actor.{Actor, ActorRef, PoisonPill, Props} +import org.apache.pekko.actor.typed.{ActorRef => TypedRef} import java.nio.file.Paths import java.text.SimpleDateFormat @@ -142,7 +144,9 @@ case class PrimaryServiceProxy( private def prepareStateData( primaryConfig: PrimaryConfig, simulationStart: ZonedDateTime, - extSimulationData: Seq[(ExtPrimaryDataConnection, ActorRef)], + extSimulationData: Seq[ + (ExtPrimaryDataConnection, TypedRef[ServiceMessage]) + ], ): Try[PrimaryServiceStateData] = { createSources(primaryConfig).map { case (mappingSource, metaInformationSource) => @@ -557,7 +561,9 @@ object PrimaryServiceProxy { final case class InitPrimaryServiceProxyStateData( primaryConfig: PrimaryConfig, simulationStart: ZonedDateTime, - extSimulationData: Seq[(ExtPrimaryDataConnection, ActorRef)], + extSimulationData: Seq[ + (ExtPrimaryDataConnection, TypedRef[ServiceMessage]) + ], ) extends InitializeServiceStateData /** Holding the state of an initialized proxy. @@ -579,7 +585,7 @@ object PrimaryServiceProxy { simulationStart: ZonedDateTime, primaryConfig: PrimaryConfig, mappingSource: TimeSeriesMappingSource, - extSubscribersToService: Map[UUID, ActorRef] = Map.empty, + extSubscribersToService: Map[UUID, TypedRef[ServiceMessage]] = Map.empty, ) extends ServiceStateData /** Giving reference to the target time series and source worker. diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index e8557d2f9e..b006cf93e2 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -22,7 +22,6 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.ScheduleServiceActivation import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceStateData.InitializeServiceStateData -import edu.ie3.simona.service.SimonaService import edu.ie3.simona.service.em.ExtEmDataService import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData import edu.ie3.simona.service.ev.ExtEvDataService @@ -42,15 +41,15 @@ import org.apache.pekko.actor.typed.scaladsl.adapter.{ TypedActorContextOps, TypedActorRefOps, } -import org.apache.pekko.actor.typed.{ActorRef, Behavior} +import org.apache.pekko.actor.typed.{ActorRef, Behavior, Scheduler} import org.apache.pekko.actor.{ActorRef => ClassicRef} +import org.apache.pekko.util.{Timeout => PekkoTimeout} import org.slf4j.{Logger, LoggerFactory} import java.time.ZonedDateTime import java.util.UUID import scala.concurrent.Await -import scala.concurrent.duration.DurationInt -import scala.concurrent.duration.FiniteDuration +import scala.concurrent.duration.{DurationInt, FiniteDuration} import scala.jdk.CollectionConverters.{ListHasAsScala, SetHasAsScala} import scala.jdk.DurationConverters.ScalaDurationOps import scala.util.{Failure, Success, Try} @@ -115,7 +114,7 @@ object ExtSimSetup { .start() // updating the data with newly connected external simulation - updatedSetupData.update(extSimAdapter) + updatedSetupData.updateAdapter(extSimAdapter) } match { case Failure(exception) => log.warn( @@ -167,7 +166,15 @@ object ExtSimSetup { case (setupData, connection) => connection match { case extPrimaryDataConnection: ExtPrimaryDataConnection => - extPrimaryDataSetup(setupData, extPrimaryDataConnection) + val serviceRef = setupInputService( + extPrimaryDataConnection, + ExtPrimaryDataService.apply(scheduler), + ExtPrimaryDataService.adapter, + "ExtPrimaryDataService", + InitExtPrimaryData(extPrimaryDataConnection), + ) + + extSimSetupData.update(extPrimaryDataConnection, serviceRef) case extEmDataConnection: ExtEmDataConnection => if (setupData.emDataConnection.nonEmpty) { @@ -184,9 +191,10 @@ object ExtSimSetup { } else { val serviceRef = setupInputService( extEmDataConnection, - ExtEmDataService.props, + ExtEmDataService.apply(scheduler), + ExtEmDataService.adapter, "ExtEmDataService", - InitExtEmData, + InitExtEmData(extEmDataConnection, startTime), ) extSimSetupData.update(extEmDataConnection, serviceRef) @@ -204,7 +212,7 @@ object ExtSimSetup { ExtEvDataService.apply(scheduler), ExtEvDataService.adapter, "ExtEvDataService", - InitExtEvData, + InitExtEvData(extEvDataConnection), ) extSimSetupData.update(extEvDataConnection, serviceRef) @@ -248,14 +256,14 @@ object ExtSimSetup { * The reference to the service. */ private[setup] def setupInputService[ - T <: ExtInputDataConnection, + T <: ExtInputDataConnection[_], M >: ServiceMessage, ]( extInputDataConnection: T, behavior: Behavior[M], adapterToExt: ActorRef[M] => Behavior[DataMessageFromExt], name: String, - initData: T => InitializeServiceStateData, + initData: InitializeServiceStateData, )(implicit context: ActorContext[_], scheduler: ActorRef[SchedulerMessage], @@ -267,7 +275,7 @@ object ExtSimSetup { ) extDataService ! ServiceMessage.Create( - initData(extInputDataConnection), + initData, ScheduleLock.singleKey( context, scheduler, @@ -288,47 +296,6 @@ object ExtSimSetup { extDataService } - /** Method to set up an external primary data service. - * @param extSimSetupData - * that contains information about all external simulations - * @param extPrimaryDataConnection - * the data connection - * @param context - * the actor context of this actor system - * @param scheduler - * the scheduler of simona - * @param extSimAdapter - * the adapter for the external simulation - * @return - * an updated [[ExtSimSetupData]] - */ - private[setup] def extPrimaryDataSetup( - extSimSetupData: ExtSimSetupData, - extPrimaryDataConnection: ExtPrimaryDataConnection, - )(implicit - context: ActorContext[_], - scheduler: ActorRef[SchedulerMessage], - extSimAdapter: ClassicRef, - ): ExtSimSetupData = { - val extPrimaryDataService = context.toClassic.simonaActorOf( - ExtPrimaryDataService.props(scheduler.toClassic), - "ExtPrimaryDataService", - ) - - extPrimaryDataService ! SimonaService.Create( - InitExtPrimaryData(extPrimaryDataConnection), - ScheduleLock.singleKey( - context, - scheduler, - INIT_SIM_TICK, - ), - ) - - extPrimaryDataConnection.setActorRefs(extPrimaryDataService, extSimAdapter) - - extSimSetupData update (extPrimaryDataConnection, extPrimaryDataService) - } - /** Method to set up an external result data service. * * @param extSimSetupData diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index c723c046b1..61e715e4e7 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -11,10 +11,13 @@ import edu.ie3.simona.api.data.em.ExtEmDataConnection import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.api.data.results.ExtResultDataConnection -import edu.ie3.simona.ontology.messages.services.{EvMessage, ServiceMessage} +import edu.ie3.simona.ontology.messages.services.{ + EmMessage, + EvMessage, + ServiceMessage, +} import edu.ie3.simona.service.results.ExtResultDataProvider import org.apache.pekko.actor.typed.ActorRef -import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import org.apache.pekko.actor.{ActorRef => ClassicRef} /** Case class that holds information regarding the external data connections as @@ -32,24 +35,26 @@ import org.apache.pekko.actor.{ActorRef => ClassicRef} final case class ExtSimSetupData( extSimAdapters: Iterable[ClassicRef], extPrimaryDataServices: Seq[ - (ExtPrimaryDataConnection, ActorRef[_ >: ServiceMessage]) + (ExtPrimaryDataConnection, ActorRef[ServiceMessage]) ], extDataServices: Seq[ - (_ <: ExtInputDataConnection, ActorRef[_ >: ServiceMessage]) + (_ <: ExtInputDataConnection[_], ActorRef[_ >: ServiceMessage]) + ], + extResultListeners: Seq[ + (ExtResultDataConnection, ActorRef[ExtResultDataProvider.Request]) ], - extResultListeners: Seq[(ExtResultDataConnection, ActorRef[_])], ) { private[setup] def update( connection: ExtPrimaryDataConnection, - ref: ActorRef[_ >: ServiceMessage], + ref: ActorRef[ServiceMessage], ): ExtSimSetupData = copy(extPrimaryDataServices = extPrimaryDataServices ++ Seq((connection, ref)) ) private[setup] def update( - connection: ExtInputDataConnection, + connection: ExtInputDataConnection[_], ref: ActorRef[_ >: ServiceMessage], ): ExtSimSetupData = connection match { case primaryConnection: ExtPrimaryDataConnection => @@ -64,7 +69,7 @@ final case class ExtSimSetupData( ): ExtSimSetupData = copy(extResultListeners = extResultListeners ++ Seq((connection, ref))) - private[setup] def update(extSimAdapter: ClassicRef): ExtSimSetupData = + private[setup] def updateAdapter(extSimAdapter: ClassicRef): ExtSimSetupData = copy(extSimAdapters = extSimAdapters ++ Set(extSimAdapter)) def evDataService: Option[ActorRef[EvMessage]] = @@ -72,9 +77,10 @@ final case class ExtSimSetupData( case (_: ExtEvDataConnection, ref: ActorRef[EvMessage]) => ref } - def emDataService: Option[ClassicRef] = - extDataServices.collectFirst { case (_: ExtEmDataConnection, ref) => - ref.toClassic + def emDataService: Option[ActorRef[EmMessage]] = + extDataServices.collectFirst { + case (_: ExtEmDataConnection, ref: ActorRef[EmMessage]) => + ref } def evDataConnection: Option[ExtEvDataConnection] = diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala index 68e54c6988..3b623b80fb 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala @@ -18,6 +18,7 @@ import edu.ie3.simona.event.notifier.NotifierConfig import edu.ie3.simona.ontology.messages.SchedulerMessage import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions +import edu.ie3.simona.ontology.messages.services.EmMessage import edu.ie3.simona.ontology.messages.services.EmMessage.{ WrappedFlexRequest, WrappedFlexResponse, @@ -80,8 +81,8 @@ class EmAgentWithServiceSpec val parentEmAgent = TestProbe[FlexResponse]("ParentEmAgent") - val service = TestProbe[Any]("emService") - val serviceRef = service.ref.toClassic + val service = TestProbe[EmMessage]("emService") + val serviceRef = service.ref val emAgent = spawn( EmAgent( @@ -346,8 +347,8 @@ class EmAgentWithServiceSpec val resultListener = TestProbe[ResultEvent]("ResultListener") val scheduler = TestProbe[SchedulerMessage]("Scheduler") - val service = TestProbe[Any]("emService") - val serviceRef = service.ref.toClassic + val service = TestProbe[EmMessage]("emService") + val serviceRef = service.ref val parentEmInput = emInput .copy() 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 b1f819f53c..4c25f89a0f 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -6,28 +6,20 @@ package edu.ie3.simona.service.em -import com.typesafe.config.ConfigFactory import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue +import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.em.ExtEmDataConnection import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexOptionValue} -import edu.ie3.simona.api.data.em.ontology.{ - EmSetPointDataResponse, - FlexOptionsResponse, - ProvideEmFlexOptionData, - ProvideEmSetPointData, - RequestEmFlexResults, - RequestEmSetPoints, -} +import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.exceptions.ServiceException -import edu.ie3.simona.ontology.messages.Activation import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, } import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ FlexActivation, + FlexRequest, IssuePowerControl, } import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions @@ -35,18 +27,24 @@ import edu.ie3.simona.ontology.messages.services.EmMessage.{ WrappedFlexRequest, WrappedFlexResponse, } -import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + Create, + RegisterForEmDataService, +} +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock -import edu.ie3.simona.service.SimonaService import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData +import edu.ie3.simona.test.common.TestSpawnerTyped import edu.ie3.simona.test.common.input.EmInputTestData -import edu.ie3.simona.test.common.{TestKitWithShutdown, TestSpawnerClassic} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.quantities.QuantityUtils._ import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW -import org.apache.pekko.actor.ActorSystem -import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps -import org.apache.pekko.testkit.{TestActorRef, TestProbe} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} +import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps +import org.apache.pekko.testkit.TestKit.awaitCond import org.scalatest.wordspec.AnyWordSpecLike import squants.energy.Kilowatts @@ -57,22 +55,15 @@ import scala.jdk.CollectionConverters._ import scala.jdk.OptionConverters.RichOption class ExtEmDataServiceSpec - extends TestKitWithShutdown( - ActorSystem( - "ExtEvDataServiceSpec", - ConfigFactory - .parseString(""" - |pekko.loggers = ["org.apache.pekko.testkit.TestEventListener"] - |pekko.loglevel = "INFO" - |""".stripMargin), - ) - ) + extends ScalaTestWithActorTestKit with AnyWordSpecLike with EmInputTestData - with TestSpawnerClassic { + with TestSpawnerTyped { implicit val simulationStart: ZonedDateTime = ZonedDateTime.now() + private val emptyMap = Map.empty[String, UUID].asJava + private val emAgent1UUID = UUID.fromString("06a14909-366e-4e94-a593-1016e1455b30") private val emAgent2UUID = @@ -80,155 +71,173 @@ class ExtEmDataServiceSpec "An uninitialized em service" must { "send correct completion message after initialisation" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) - val extEmDataConnection = - new ExtEmDataConnection(Map.empty[String, UUID].asJava) - extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) + val emService = spawn(ExtEmDataService.apply(scheduler.ref)) + val extEmDataConnection = new ExtEmDataConnection(emptyMap) + extEmDataConnection.setActorRefs( + emService.toClassic, + extSimAdapter.ref.toClassic, + ) val key = - ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) - scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) + scheduler + .expectMessageType[ScheduleActivation] // lock activation scheduled - scheduler.send( - emService, - SimonaService.Create(InitExtEmData(extEmDataConnection), key), - ) - scheduler.expectMsg( - ScheduleActivation(emService.toTyped, INIT_SIM_TICK, Some(key)) + emService ! Create( + InitExtEmData(extEmDataConnection, simulationStart), + key, ) - scheduler.send(emService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(emService.toTyped)) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] + activationMsg.tick shouldBe INIT_SIM_TICK + activationMsg.unlockKey shouldBe Some(key) + + val serviceActivation = activationMsg.actor + + serviceActivation ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(serviceActivation)) } "stash registration request and handle it correctly once initialized" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) - val extEmDataConnection = - new ExtEmDataConnection(Map.empty[String, UUID].asJava) - extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) + val emService = spawn(ExtEmDataService.apply(scheduler.ref)) + val extEmDataConnection = new ExtEmDataConnection(emptyMap) + extEmDataConnection.setActorRefs( + emService.toClassic, + extSimAdapter.ref.toClassic, + ) - val emAgent = TestProbe("emAgent") + val emAgent = TestProbe[EmAgent.Request]("emAgent") + val emAgentFlex = TestProbe[FlexRequest]("emAgentFlex") // this one should be stashed - emAgent.send( - emService, - RegisterForEmDataService( - emInput.getUuid, - emAgent.ref.toTyped, - emAgent.ref.toTyped, - None, - None, - ), + emService ! RegisterForEmDataService( + emInput.getUuid, + emAgent.ref, + emAgentFlex.ref, + None, + None, ) scheduler.expectNoMessage() val key = - ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) - scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) + scheduler + .expectMessageType[ScheduleActivation] // lock activation scheduled - scheduler.send( - emService, - SimonaService.Create(InitExtEmData(extEmDataConnection), key), - ) - scheduler.expectMsg( - ScheduleActivation(emService.toTyped, INIT_SIM_TICK, Some(key)) + emService ! Create( + InitExtEmData(extEmDataConnection, simulationStart), + key, ) - scheduler.send(emService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(emService.toTyped)) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] + activationMsg.tick shouldBe INIT_SIM_TICK + activationMsg.unlockKey shouldBe Some(key) + + val serviceActivation = activationMsg.actor + + serviceActivation ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(serviceActivation)) } } "An idle em service" must { "fail when activated without having received ExtEmMessage" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) - val extEmDataConnection = - new ExtEmDataConnection(Map.empty[String, UUID].asJava) - extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) + val emService = spawn(ExtEmDataService.apply(scheduler.ref)) + val extEmDataConnection = new ExtEmDataConnection(emptyMap) + extEmDataConnection.setActorRefs( + emService.toClassic, + extSimAdapter.ref.toClassic, + ) val key = - ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) - scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) + scheduler + .expectMessageType[ScheduleActivation] // lock activation scheduled - scheduler.send( - emService, - SimonaService.Create(InitExtEmData(extEmDataConnection), key), - ) - scheduler.expectMsg( - ScheduleActivation(emService.toTyped, INIT_SIM_TICK, Some(key)) + emService ! Create( + InitExtEmData(extEmDataConnection, simulationStart), + key, ) - scheduler.send(emService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(emService.toTyped)) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] + activationMsg.tick shouldBe INIT_SIM_TICK + activationMsg.unlockKey shouldBe Some(key) - // we trigger ev service and expect an exception - assertThrows[ServiceException] { - emService.receive( - Activation(0), - scheduler.ref, - ) - } + val serviceActivation = activationMsg.actor + serviceActivation ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(serviceActivation)) + + // we trigger em service and expect an exception + serviceActivation ! Activation(0) scheduler.expectNoMessage() + + val deathWatch = createTestProbe("deathWatch") + deathWatch.expectTerminated(emService.ref) } "handle flex option request correctly" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) - val extEmDataConnection = - new ExtEmDataConnection(Map.empty[String, UUID].asJava) - extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) + val emService = spawn(ExtEmDataService.apply(scheduler.ref)) + val adapter = spawn(ExtEmDataService.adapter(emService)) + val extEmDataConnection = new ExtEmDataConnection(emptyMap) + extEmDataConnection.setActorRefs( + adapter.toClassic, + extSimAdapter.ref.toClassic, + ) val key = - ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) - scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) + scheduler + .expectMessageType[ScheduleActivation] // lock activation scheduled - scheduler.send( - emService, - SimonaService.Create(InitExtEmData(extEmDataConnection), key), + emService ! Create( + InitExtEmData(extEmDataConnection, simulationStart), + key, ) - scheduler.expectMsgType[ScheduleActivation] - scheduler.send(emService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(emService.toTyped)) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] + activationMsg.tick shouldBe INIT_SIM_TICK + activationMsg.unlockKey shouldBe Some(key) - val emAgent1 = TestProbe("emAgent1") - val emAgent2 = TestProbe("emAgent2") + val serviceActivation = activationMsg.actor - emAgent1.send( - emService, - RegisterForEmDataService( - emAgent1UUID, - emAgent1.ref.toTyped, - emAgent1.ref.toTyped, - None, - None, - ), + serviceActivation ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(serviceActivation)) + + val emAgent1 = TestProbe[EmAgent.Request]("emAgent1") + val emAgentFlex1 = TestProbe[FlexRequest]("emAgentFlex1") + val emAgent2 = TestProbe[EmAgent.Request]("emAgent2") + val emAgentFlex2 = TestProbe[FlexRequest]("emAgentFlex2") + + emService ! RegisterForEmDataService( + emAgent1UUID, + emAgent1.ref, + emAgentFlex1.ref, + None, + None, ) emAgent1.expectNoMessage() - emAgent2.send( - emService, - RegisterForEmDataService( - emAgent2UUID, - emAgent2.ref.toTyped, - emAgent2.ref.toTyped, - None, - None, - ), + emService ! RegisterForEmDataService( + emAgent2UUID, + emAgent2.ref, + emAgentFlex2.ref, + None, + None, ) emAgent2.expectNoMessage() @@ -239,13 +248,18 @@ class ExtEmDataServiceSpec ) ) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(emService)) - scheduler.send(emService, Activation(INIT_SIM_TICK)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) + scheduler.expectNoMessage() + + serviceActivation ! Activation(INIT_SIM_TICK) emAgent1.expectNoMessage() - emAgent2.expectNoMessage() + emAgentFlex1.expectNoMessage() - scheduler.expectMsg(Completion(emService.toTyped)) + emAgent2.expectNoMessage() + emAgentFlex2.expectNoMessage() extEmDataConnection.sendExtMsg( new RequestEmFlexResults( @@ -254,33 +268,36 @@ class ExtEmDataServiceSpec ) ) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(emService)) - scheduler.send(emService, Activation(0)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) + scheduler.expectMessage(Completion(serviceActivation)) + + serviceActivation ! Activation(0) + + emAgent1.expectNoMessage() + emAgentFlex1.expectMessage(FlexActivation(0)) - emAgent1.expectMsg(FlexActivation(0)) emAgent2.expectNoMessage() + emAgentFlex2.expectNoMessage() - scheduler.expectMsg(Completion(emService.toTyped)) + scheduler.expectMessage(Completion(serviceActivation)) extEmDataConnection.receiveTriggerQueue shouldBe empty - emAgent1.send( - emService, - WrappedFlexResponse( - ProvideMinMaxFlexOptions( - emAgent1UUID, - Kilowatts(5), - Kilowatts(0), - Kilowatts(10), - ), - Left(emAgent1UUID), + emService ! WrappedFlexResponse( + ProvideMinMaxFlexOptions( + emAgent1UUID, + Kilowatts(5), + Kilowatts(0), + Kilowatts(10), ), + Left(emAgent1UUID), ) awaitCond( !extEmDataConnection.receiveTriggerQueue.isEmpty, max = 3.seconds, - message = "No message received", ) extEmDataConnection.receiveTriggerQueue.size() shouldBe 1 @@ -300,51 +317,56 @@ class ExtEmDataServiceSpec } "handle flex option provision correctly" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) - val extEmDataConnection = - new ExtEmDataConnection(Map.empty[String, UUID].asJava) - extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) + val emService = spawn(ExtEmDataService.apply(scheduler.ref)) + val adapter = spawn(ExtEmDataService.adapter(emService)) + val extEmDataConnection = new ExtEmDataConnection(emptyMap) + extEmDataConnection.setActorRefs( + adapter.toClassic, + extSimAdapter.ref.toClassic, + ) val key = - ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) - scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) + scheduler + .expectMessageType[ScheduleActivation] // lock activation scheduled - scheduler.send( - emService, - SimonaService.Create(InitExtEmData(extEmDataConnection), key), + emService ! Create( + InitExtEmData(extEmDataConnection, simulationStart), + key, ) - scheduler.expectMsgType[ScheduleActivation] - scheduler.send(emService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(emService.toTyped)) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] + activationMsg.tick shouldBe INIT_SIM_TICK + activationMsg.unlockKey shouldBe Some(key) - val emAgent1 = TestProbe("emAgent1") - val emAgent2 = TestProbe("emAgent2") + val serviceActivation = activationMsg.actor - emAgent1.send( - emService, - RegisterForEmDataService( - emAgent1UUID, - emAgent1.ref.toTyped, - emAgent1.ref.toTyped, - None, - None, - ), + serviceActivation ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(serviceActivation)) + + val emAgent1 = TestProbe[EmAgent.Request]("emAgent1") + val emAgentFlex1 = TestProbe[FlexRequest]("emAgentFlex1") + val emAgent2 = TestProbe[EmAgent.Request]("emAgent2") + val emAgentFlex2 = TestProbe[FlexRequest]("emAgentFlex2") + + emService ! RegisterForEmDataService( + emAgent1UUID, + emAgent1.ref, + emAgentFlex1.ref, + None, + None, ) emAgent1.expectNoMessage() - emAgent2.send( - emService, - RegisterForEmDataService( - emAgent2UUID, - emAgent2.ref.toTyped, - emAgent2.ref.toTyped, - None, - None, - ), + emService ! RegisterForEmDataService( + emAgent2UUID, + emAgent2.ref, + emAgentFlex2.ref, + None, + None, ) emAgent2.expectNoMessage() @@ -363,11 +385,13 @@ class ExtEmDataServiceSpec ) ) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(emService)) - scheduler.send(emService, Activation(0)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) + serviceActivation ! Activation(0) emAgent1.expectNoMessage() - emAgent2.expectMsg( + emAgent2.expectMessage( ProvideMinMaxFlexOptions( emAgent1UUID, Kilowatts(-1), @@ -376,55 +400,60 @@ class ExtEmDataServiceSpec ) ) - scheduler.expectMsg(Completion(emService.toTyped)) + scheduler.expectMessage(Completion(serviceActivation)) } "handle set point provision correctly" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) - val extEmDataConnection = - new ExtEmDataConnection(Map.empty[String, UUID].asJava) - extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) + val emService = spawn(ExtEmDataService.apply(scheduler.ref)) + val adapter = spawn(ExtEmDataService.adapter(emService)) + val extEmDataConnection = new ExtEmDataConnection(emptyMap) + extEmDataConnection.setActorRefs( + adapter.toClassic, + extSimAdapter.ref.toClassic, + ) val key = - ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) - scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) + scheduler + .expectMessageType[ScheduleActivation] // lock activation scheduled - scheduler.send( - emService, - SimonaService.Create(InitExtEmData(extEmDataConnection), key), + emService ! Create( + InitExtEmData(extEmDataConnection, simulationStart), + key, ) - scheduler.expectMsgType[ScheduleActivation] - scheduler.send(emService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(emService.toTyped)) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] + activationMsg.tick shouldBe INIT_SIM_TICK + activationMsg.unlockKey shouldBe Some(key) - val emAgent1 = TestProbe("emAgent1") - val emAgent2 = TestProbe("emAgent2") + val serviceActivation = activationMsg.actor - emAgent1.send( - emService, - RegisterForEmDataService( - emAgent1UUID, - emAgent1.ref.toTyped, - emAgent1.ref.toTyped, - None, - None, - ), + serviceActivation ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(serviceActivation)) + + val emAgent1 = TestProbe[EmAgent.Request]("emAgent1") + val emAgentFlex1 = TestProbe[FlexRequest]("emAgentFlex1") + val emAgent2 = TestProbe[EmAgent.Request]("emAgent2") + val emAgentFlex2 = TestProbe[FlexRequest]("emAgentFlex2") + + emService ! RegisterForEmDataService( + emAgent1UUID, + emAgent1.ref, + emAgentFlex1.ref, + None, + None, ) emAgent1.expectNoMessage() - emAgent2.send( - emService, - RegisterForEmDataService( - emAgent2UUID, - emAgent2.ref.toTyped, - emAgent2.ref.toTyped, - None, - None, - ), + emService ! RegisterForEmDataService( + emAgent2UUID, + emAgent2.ref, + emAgentFlex2.ref, + None, + None, ) emAgent2.expectNoMessage() @@ -439,65 +468,75 @@ class ExtEmDataServiceSpec ) ) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(emService)) - scheduler.send(emService, Activation(0)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) + serviceActivation ! Activation(0) - emAgent1.expectMsg( + emAgent1.expectNoMessage() + emAgentFlex1.expectMessage( IssuePowerControl(0, Kilowatts(-3)) ) - emAgent2.expectMsg( + + emAgent2.expectNoMessage() + emAgentFlex2.expectMessage( IssuePowerControl(0, zeroKW) ) - scheduler.expectMsg(Completion(emService.toTyped)) + scheduler.expectMessage(Completion(serviceActivation)) } "handle set point request correctly" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val emService = TestActorRef(new ExtEmDataService(scheduler.ref)) - val extEmDataConnection = - new ExtEmDataConnection(Map.empty[String, UUID].asJava) - extEmDataConnection.setActorRefs(emService, extSimAdapter.ref) + val emService = spawn(ExtEmDataService.apply(scheduler.ref)) + val adapter = spawn(ExtEmDataService.adapter(emService)) + val extEmDataConnection = new ExtEmDataConnection(emptyMap) + extEmDataConnection.setActorRefs( + adapter.toClassic, + extSimAdapter.ref.toClassic, + ) val key = - ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) - scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) + scheduler + .expectMessageType[ScheduleActivation] // lock activation scheduled - scheduler.send( - emService, - SimonaService.Create(InitExtEmData(extEmDataConnection), key), + emService ! Create( + InitExtEmData(extEmDataConnection, simulationStart), + key, ) - scheduler.expectMsgType[ScheduleActivation] - scheduler.send(emService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(emService.toTyped)) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] + activationMsg.tick shouldBe INIT_SIM_TICK + activationMsg.unlockKey shouldBe Some(key) - val emAgent1 = TestProbe("emAgent1") - val emAgent2 = TestProbe("emAgent2") + val serviceActivation = activationMsg.actor - emAgent1.send( - emService, - RegisterForEmDataService( - emAgent1UUID, - emAgent1.ref.toTyped, - emAgent1.ref.toTyped, - None, - None, - ), + serviceActivation ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(serviceActivation)) + + val emAgent1 = TestProbe[EmAgent.Request]("emAgent1") + val emAgentFlex1 = TestProbe[FlexRequest]("emAgentFlex1") + val emAgent2 = TestProbe[EmAgent.Request]("emAgent2") + val emAgentFlex2 = TestProbe[FlexRequest]("emAgentFlex2") + + emService ! RegisterForEmDataService( + emAgent1UUID, + emAgent1.ref, + emAgentFlex1.ref, + None, + None, ) emAgent1.expectNoMessage() - emAgent2.send( - emService, - RegisterForEmDataService( - emAgent2UUID, - emAgent2.ref.toTyped, - emAgent2.ref.toTyped, - None, - None, - ), + emService ! RegisterForEmDataService( + emAgent2UUID, + emAgent2.ref, + emAgentFlex2.ref, + None, + None, ) emAgent2.expectNoMessage() @@ -511,15 +550,20 @@ class ExtEmDataServiceSpec ) ) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(emService)) - scheduler.send(emService, Activation(0)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) + serviceActivation ! Activation(0) emAgent1.expectNoMessage() - emAgent2.expectMsg( + emAgentFlex1.expectNoMessage() + + emAgent2.expectNoMessage() + emAgentFlex2.expectMessage( IssuePowerControl(0, zeroKW) ) - scheduler.expectMsg(Completion(emService.toTyped)) + scheduler.expectMessage(Completion(serviceActivation)) // we now request the set points of the controlled em agent extEmDataConnection.sendExtMsg( @@ -529,29 +573,27 @@ class ExtEmDataServiceSpec ) ) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(emService)) - scheduler.send(emService, Activation(0)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) + serviceActivation ! Activation(0) emAgent1.expectNoMessage() emAgent2.expectNoMessage() - scheduler.expectMsg(Completion(emService.toTyped)) + scheduler.expectMessage(Completion(serviceActivation)) extEmDataConnection.receiveTriggerQueue shouldBe empty // the parent em agent sends the controlled em agent an IssuePowerControl message through the service - emAgent2.send( - emService, - WrappedFlexRequest( - IssuePowerControl(0, Kilowatts(2)), - emAgent1.ref.toTyped, - ), + emService ! WrappedFlexRequest( + IssuePowerControl(0, Kilowatts(2)), + emAgentFlex1.ref, ) awaitCond( !extEmDataConnection.receiveTriggerQueue.isEmpty, max = 3.seconds, - message = "No message received", ) extEmDataConnection.receiveTriggerQueue.size() shouldBe 1 diff --git a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala index 3b57bd2bce..1602646549 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala @@ -6,48 +6,53 @@ package edu.ie3.simona.service.primary -import com.typesafe.config.ConfigFactory +import com.typesafe.scalalogging.LazyLogging import edu.ie3.simona.agent.participant.data.Data.PrimaryData import edu.ie3.simona.agent.participant2.ParticipantAgent.{ DataProvision, RegistrationSuccessfulMessage, } +import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection -import edu.ie3.simona.ontology.messages.Activation import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, } -import edu.ie3.simona.ontology.messages.services.ServiceMessage.PrimaryServiceRegistrationMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + PrimaryServiceRegistrationMessage, + WrappedActivation, +} import edu.ie3.simona.ontology.messages.services.WeatherMessage.RegisterForWeatherMessage +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock -import edu.ie3.simona.service.SimonaService import edu.ie3.simona.service.primary.ExtPrimaryDataService.InitExtPrimaryData -import edu.ie3.simona.test.common.{AgentSpec, TestSpawnerClassic} +import edu.ie3.simona.test.common.TestSpawnerTyped import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK -import org.apache.pekko.actor.ActorSystem -import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps -import org.apache.pekko.testkit.{TestActorRef, TestProbe} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} +import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps +import org.scalatest.PrivateMethodTester +import org.scalatest.matchers.should +import org.scalatest.wordspec.AnyWordSpecLike import java.util.UUID import scala.concurrent.duration.DurationInt import scala.jdk.CollectionConverters._ class ExtPrimaryDataServiceSpec - extends AgentSpec( - ActorSystem( - "ExtPrimaryDataServiceSpec", - ConfigFactory - .parseString(""" - |pekko.loggers = ["org.apache.pekko.testkit.TestEventListener"] - |pekko.loglevel = "INFO" - |""".stripMargin), - ) - ) - with TestSpawnerClassic { + extends ScalaTestWithActorTestKit + with AnyWordSpecLike + with should.Matchers + with PrivateMethodTester + with LazyLogging + with TestSpawnerTyped { - private val scheduler = TestProbe("scheduler") - private val extSimAdapter = TestProbe("extSimAdapter") + private val scheduler = TestProbe[SchedulerMessage]("scheduler") + private val extSimAdapter = + TestProbe[ScheduleDataServiceMessage]("extSimAdapter") private val extPrimaryDataConnection = new ExtPrimaryDataConnection( Map.empty[String, UUID].asJava @@ -56,70 +61,84 @@ class ExtPrimaryDataServiceSpec "An uninitialized external primary data service" must { "send correct completion message after initialisation" in { - val primaryDataService = TestActorRef( - new ExtPrimaryDataService( - scheduler.ref - ) - ) + val primaryDataService = spawn(ExtPrimaryDataService.apply(scheduler.ref)) val key = - ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) - scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) + scheduler + .expectMessageType[ScheduleActivation] // lock activation scheduled extPrimaryDataConnection.setActorRefs( - primaryDataService, - extSimAdapter.ref, + primaryDataService.toClassic, + extSimAdapter.ref.toClassic, ) - scheduler.send( - primaryDataService, - SimonaService.Create( - InitExtPrimaryData(extPrimaryDataConnection), - key, - ), - ) - scheduler.expectMsg( - ScheduleActivation(primaryDataService.toTyped, INIT_SIM_TICK, Some(key)) + primaryDataService ! ServiceMessage.Create( + InitExtPrimaryData(extPrimaryDataConnection), + key, ) - scheduler.send(primaryDataService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(primaryDataService.toTyped)) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] + activationMsg.tick shouldBe INIT_SIM_TICK + activationMsg.unlockKey shouldBe Some(key) + + val serviceActivation = activationMsg.actor + + serviceActivation ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(serviceActivation)) } } "An external primary service actor" should { - val serviceRef = - TestActorRef( - new ExtPrimaryDataService( - scheduler.ref - ) - ) + val serviceRef = spawn(ExtPrimaryDataService.apply(scheduler.ref)) + val systemParticipant = TestProbe[Any]("dummySystemParticipant") "refuse registration for wrong registration request" in { - serviceRef ! RegisterForWeatherMessage(self, 51.4843281, 7.4116482) - expectNoMessage() + val schedulerProbe = TestProbe[SchedulerMessage]("schedulerProbe") + + // we need to create another service, since we want to continue using the other in later tests + val service = spawn(ExtPrimaryDataService(schedulerProbe.ref)) + + val key = + ScheduleLock.singleKey(TSpawner, schedulerProbe.ref, INIT_SIM_TICK) + + service ! ServiceMessage.Create( + InitExtPrimaryData(extPrimaryDataConnection), + key, + ) + + service ! WrappedActivation(Activation(INIT_SIM_TICK)) + + service ! RegisterForWeatherMessage( + systemParticipant.ref.toClassic, + 51.4843281, + 7.4116482, + ) + + val deathWatch = createTestProbe("deathWatch") + deathWatch.expectTerminated(service.ref) } - val systemParticipant: TestProbe = TestProbe("dummySystemParticipant") "correctly register a forwarded request" ignore { serviceRef ! PrimaryServiceRegistrationMessage( - systemParticipant.ref, + systemParticipant.ref.toClassic, UUID.randomUUID(), ) - println("Try to register") /* Wait for request approval */ - val msg = systemParticipant.expectMsgType[RegistrationSuccessfulMessage]( - 10.seconds - ) + val msg = + systemParticipant.expectMessageType[RegistrationSuccessfulMessage]( + 10.seconds + ) msg.serviceRef shouldBe serviceRef.ref msg.firstDataTick shouldBe 0L /* We cannot directly check, if the requesting actor is among the subscribers, therefore we ask the actor to * provide data to all subscribed actors and check, if the subscribed probe gets one */ - scheduler.send(serviceRef, Activation(0)) - scheduler.expectMsgType[Completion] - systemParticipant.expectMsgAllClassOf(classOf[DataProvision[PrimaryData]]) + serviceRef ! WrappedActivation(Activation(0)) + scheduler.expectMessageType[Completion] + + systemParticipant.expectMessageType[DataProvision[PrimaryData]] } } } diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala index d7fdda1faa..c94812feda 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala @@ -129,9 +129,9 @@ class PrimaryServiceProxySpec private val scheduler: TestProbe = TestProbe("scheduler") - private val validExtPrimaryDataService = TestActorRef( - new ExtPrimaryDataService( - scheduler.ref + private val validExtPrimaryDataService = TSpawner.spawn( + ExtPrimaryDataService.apply( + scheduler.ref.toTyped ) ) diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala index fac717ff6f..f9255fe65d 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala @@ -128,7 +128,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { updated.extResultListeners shouldBe Seq((resultConnection, resultRef)) } - "be updated with multiple different connections" in { + "be updated with multiple different connections correctly" in { val extSimSetupData = ExtSimSetupData.apply val primaryConnection = new ExtPrimaryDataConnection(emptyMapInput) @@ -168,43 +168,43 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { updated.extResultListeners shouldBe Seq((resultConnection, resultRef)) } - "return evDataService correctly" in { - val evConnection = new ExtEvDataConnection() - val evRef = TestProbe("ev_service").ref + "return emDataService correctly" in { + val emConnection = new ExtEmDataConnection(emptyMapInput) + val emRef = TestProbe("em_service").ref val cases = Table( ("extSimSetupData", "expectedConnection", "expectedService"), ( - ExtSimSetupData.apply.update(evConnection, evRef), - Some(evConnection), - Some(evRef), + ExtSimSetupData.apply.update(emConnection, emRef), + Some(emConnection), + Some(emRef), ), (ExtSimSetupData.apply, None, None), ) forAll(cases) { (extSimSetupData, expectedConnection, expectedService) => - extSimSetupData.evDataConnection shouldBe expectedConnection - extSimSetupData.evDataService shouldBe expectedService + extSimSetupData.emDataConnection shouldBe expectedConnection + extSimSetupData.emDataService shouldBe expectedService } } - "return emDataService correctly" in { - val emConnection = new ExtEmDataConnection(emptyMapInput) - val emRef = TestProbe("em_service").ref + "return evDataService correctly" in { + val evConnection = new ExtEvDataConnection() + val evRef = TestProbe("ev_service").ref val cases = Table( ("extSimSetupData", "expectedConnection", "expectedService"), ( - ExtSimSetupData.apply.update(emConnection, emRef), - Some(emConnection), - Some(emRef), + ExtSimSetupData.apply.update(evConnection, evRef), + Some(evConnection), + Some(evRef), ), (ExtSimSetupData.apply, None, None), ) forAll(cases) { (extSimSetupData, expectedConnection, expectedService) => - extSimSetupData.emDataConnection shouldBe expectedConnection - extSimSetupData.emDataService shouldBe expectedService.map(_.toClassic) + extSimSetupData.evDataConnection shouldBe expectedConnection + extSimSetupData.evDataService shouldBe expectedService } } From 74c290d39230bc480632802e95280c716565ad70 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 18 Mar 2025 12:55:35 +0100 Subject: [PATCH 015/125] Saving changes. --- input/samples/vn_simona/vn_simona.conf | 6 +- .../event/listener/ResultEventListener.scala | 43 ++---- .../messages/flex/FlexibilityMessage.scala | 10 +- .../simona/service/em/ExtEmDataService.scala | 138 ++++++++++++++---- .../results/ExtResultDataProvider.scala | 127 ++++++++++++++-- .../simona/sim/setup/ExtSimSetupData.scala | 3 + .../sim/setup/SimonaStandaloneSetup.scala | 2 +- 7 files changed, 244 insertions(+), 85 deletions(-) diff --git a/input/samples/vn_simona/vn_simona.conf b/input/samples/vn_simona/vn_simona.conf index be3fc534c1..49d8d54279 100644 --- a/input/samples/vn_simona/vn_simona.conf +++ b/input/samples/vn_simona/vn_simona.conf @@ -23,13 +23,13 @@ simona.time.schedulerReadyCheckWindow = 900 # Input Parameters ################################################################## simona.input.primary.csvParams = { - directoryPath: "input/samples/vn_simona/fullGrid" + directoryPath: "simona/input/samples/vn_simona/fullGridEm" csvSep: "," isHierarchic: false } simona.input.grid.datasource.id = "csv" simona.input.grid.datasource.csvParams = { - directoryPath: "input/samples/vn_simona/fullGrid" + directoryPath: "simona/input/samples/vn_simona/fullGridEm" csvSep: "," isHierarchic: false } @@ -44,7 +44,7 @@ simona.input.weather.datasource = { ################################################################## # Output Parameters ################################################################## -simona.output.base.dir = "output/vn_simona" +simona.output.base.dir = "simona/output/vn_simonaEM" simona.output.base.addTimestampToOutputDir = true simona.output.sink.csv { diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index 1eeffa3a0b..e60fd35083 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -6,10 +6,11 @@ package edu.ie3.simona.event.listener +import org.apache.pekko.actor.typed.scaladsl.Behaviors +import org.apache.pekko.actor.typed.{ActorRef, Behavior, PostStop} import edu.ie3.datamodel.io.processor.result.ResultEntityProcessor import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult -import edu.ie3.simona.api.data.results.ExtResultDataConnection import edu.ie3.simona.event.ResultEvent.{ FlexOptionsResultEvent, ParticipantResultEvent, @@ -24,8 +25,6 @@ import edu.ie3.simona.io.result._ import edu.ie3.simona.service.results.ExtResultDataProvider import edu.ie3.simona.service.results.ExtResultDataProvider.ResultResponseMessage import edu.ie3.simona.util.ResultFileHierarchy -import org.apache.pekko.actor.typed.scaladsl.Behaviors -import org.apache.pekko.actor.typed.{ActorRef, Behavior, PostStop} import org.slf4j.Logger import scala.concurrent.ExecutionContext.Implicits.global @@ -54,9 +53,8 @@ object ResultEventListener extends Transformer3wResultSupport { */ private final case class BaseData( classToSink: Map[Class[_], ResultEntitySink], - extResultListeners: Map[ExtResultDataConnection, ActorRef[ - ExtResultDataProvider.Request - ]], + extResultListeners: Iterable[ActorRef[ExtResultDataProvider.Request]] = + Iterable.empty, threeWindingResults: Map[ Transformer3wKey, AggregatedTransformer3wResult, @@ -168,16 +166,13 @@ object ResultEventListener extends Transformer3wResultSupport { baseData: BaseData, log: Logger, ): BaseData = { - // log.info("Got Result " + resultEntity) - handOverToSink(resultEntity, baseData.classToSink, log) - if (baseData.extResultListeners.nonEmpty) { - handOverToExternalService( - resultEntity, - baseData.extResultListeners.values, + baseData.extResultListeners.foreach( + _ ! ResultResponseMessage(resultEntity) ) } + handOverToSink(resultEntity, baseData.classToSink, log) baseData } @@ -252,25 +247,10 @@ object ResultEventListener extends Transformer3wResultSupport { log.error("Error while writing result event: ", exception) } - private def handOverToExternalService( - resultEntity: ResultEntity, - extResultListeners: Iterable[ActorRef[ExtResultDataProvider.Request]], - ): Unit = Try { - resultEntity match { - case modelResultEntity: ResultEntity => - extResultListeners.foreach( - _ ! ResultResponseMessage(modelResultEntity) - ) - case _ => - throw new Exception("Wrong data type!") - } - } - def apply( resultFileHierarchy: ResultFileHierarchy, - extResultListeners: Map[ExtResultDataConnection, ActorRef[ - ExtResultDataProvider.Request - ]] = Map.empty, + extResultListeners: Iterable[ActorRef[ExtResultDataProvider.Request]] = + Iterable.empty, ): Behavior[Request] = Behaviors.setup[Request] { ctx => ctx.log.debug("Starting initialization!") resultFileHierarchy.resultSinkType match { @@ -298,9 +278,8 @@ object ResultEventListener extends Transformer3wResultSupport { } private def init( - extResultListeners: Map[ExtResultDataConnection, ActorRef[ - ExtResultDataProvider.Request - ]] + extResultListeners: Iterable[ActorRef[ExtResultDataProvider.Request]] = + Iterable.empty ): Behavior[Request] = Behaviors.withStash(200) { buffer => Behaviors.receive[Request] { case (ctx, SinkResponse(response)) => diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala index 3f3e502c30..58b95e3e1d 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala @@ -37,15 +37,7 @@ object FlexibilityMessage { val modelUuid: UUID } - final case class SetPointFlexRequest( - tick: Long - ) extends FlexRequest - - final case class FlexOptionsRequest( - tick: Long - ) extends FlexRequest - - /** Message that registers a flex options provider with an + /** Message that registers a controlled asset model with an * [[edu.ie3.simona.agent.em.EmAgent]]. * * @param participant 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 a5817c187e..2579b3b55d 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -18,27 +18,19 @@ import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EmMessage -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - WrappedFlexRequest, - WrappedFlexResponse, -} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - RegisterForEmDataService, - ServiceRegistrationMessage, - ServiceResponseMessage, -} -import edu.ie3.simona.service.ServiceStateData.{ - InitializeServiceStateData, - ServiceBaseStateData, -} +import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{RegisterForEmDataService, ServiceRegistrationMessage, ServiceResponseMessage} +import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} import edu.ie3.simona.service.{ExtDataSupport, TypedSimonaService} import edu.ie3.simona.util.ReceiveDataMap +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.util.quantities.PowerSystemUnits.KILOWATT import edu.ie3.util.quantities.QuantityUtils._ import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} +import org.slf4j.{Logger, LoggerFactory} import squants.Power import squants.energy.Kilowatts import tech.units.indriya.ComparableQuantity @@ -46,11 +38,7 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.{Power => PsdmPower} -import scala.jdk.CollectionConverters.{ - ListHasAsScala, - MapHasAsJava, - MapHasAsScala, -} +import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala} import scala.jdk.OptionConverters.{RichOption, RichOptional} import scala.util.{Failure, Success, Try} @@ -58,6 +46,8 @@ object ExtEmDataService extends TypedSimonaService[EmMessage] with ExtDataSupport[EmMessage] { + private val log: Logger = LoggerFactory.getLogger(ExtEmDataService.getClass) + override type S = ExtEmDataStateData implicit class SquantsToQuantity(private val value: Power) { @@ -109,10 +99,12 @@ object ExtEmDataService final case class ExtEmDataStateData( extEmDataConnection: ExtEmDataConnection, startTime: ZonedDateTime, + tick: Long = INIT_SIM_TICK, emHierarchy: EmHierarchy = EmHierarchy(), uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = Map.empty, flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, extEmDataMessage: Option[EmDataMessageFromExt] = None, + toSchedule: Map[UUID, ScheduleFlexActivation] = Map.empty, flexOptionResponse: ReceiveDataMap[UUID, FlexOptionsResult] = ReceiveDataMap.empty, setPointResponse: ReceiveDataMap[UUID, EmSetPointResult] = @@ -124,7 +116,7 @@ object ExtEmDataService refToUncontrolled: Map[ActorRef[FlexResponse], UUID] = Map.empty, controlledToRef: Map[UUID, ActorRef[FlexResponse]] = Map.empty, refToControlled: Map[ActorRef[FlexResponse], UUID] = Map.empty, - parentToControlled: Map[ActorRef[FlexResponse], List[UUID]] = Map.empty, + parentRef: Map[ActorRef[FlexResponse], ActorRef[EmAgent.Request]] = Map.empty ) { def add(model: UUID, ref: ActorRef[EmAgent.Request]): EmHierarchy = copy( uncontrolledToRef = uncontrolledToRef + (model -> ref), @@ -136,13 +128,11 @@ object ExtEmDataService ref: ActorRef[EmAgent.Request], parent: ActorRef[FlexResponse], ): EmHierarchy = { - val hierarchy = parentToControlled.getOrElse(parent, List.empty) copy( controlledToRef = controlledToRef + (model -> ref), refToControlled = refToControlled + (ref -> model), - parentToControlled = - parentToControlled + (parent -> (hierarchy ++ List(model))), + parentRef = parentRef + (parent -> ref), ) } @@ -157,6 +147,7 @@ object ExtEmDataService controlledToRef.get(uuid) } + } case class InitExtEmData( @@ -407,10 +398,46 @@ object ExtEmDataService )(implicit serviceStateData: ExtEmDataStateData ): ExtEmDataStateData = extResponseMsg match { - case WrappedFlexResponse( - provideFlexOptions: ProvideFlexOptions, - receiver, - ) => + case WrappedFlexResponse(flexResponse, receiver) => + flexResponse match { + case scheduleFlexActivation: ScheduleFlexActivation => + handleScheduleFlexActivation(scheduleFlexActivation, receiver) + + case options: ProvideFlexOptions => + handleFlexProvision(options, receiver) + + case msg: FlexCompletion => + log.warn(s"$msg") + + serviceStateData + } + + case WrappedFlexRequest(issueFlexControl: IssueFlexControl, receiver) => + handleFlexControl(issueFlexControl, receiver) + + case WrappedFlexRequest(flexActivation: FlexActivation, receiver) => + handleFlexActivation(flexActivation, receiver) + } + + private def handleFlexProvision( + provideFlexOptions: ProvideFlexOptions, + receiver: Either[UUID, ActorRef[FlexResponse]], + )(implicit + serviceStateData: ExtEmDataStateData + ): ExtEmDataStateData = { + + if (serviceStateData.tick == INIT_SIM_TICK) { + receiver match { + case Right(otherRef) => + otherRef ! provideFlexOptions + + case Left(self: UUID) => + serviceStateData.uuidToFlexAdapter(self) ! IssuePowerControl(INIT_SIM_TICK, zeroKW) + } + + serviceStateData + } else { + val uuid = receiver match { case Right(otherRef) => serviceStateData.emHierarchy.getUuid(otherRef) @@ -450,8 +477,63 @@ object ExtEmDataService flexOptionResponse = ReceiveDataMap.empty ) } + } + } - case WrappedFlexRequest(issueFlexControl: IssueFlexControl, receiver) => + private def handleScheduleFlexActivation( + scheduleFlexActivation: ScheduleFlexActivation, + receiver: Either[UUID, ActorRef[FlexResponse]], + )(implicit + serviceStateData: ExtEmDataStateData + ): ExtEmDataStateData = { + log.warn(s"$scheduleFlexActivation") + + receiver match { + case Right(ref) => + + if (scheduleFlexActivation.tick == INIT_SIM_TICK) { + ref ! scheduleFlexActivation + } + + + case Left(uuid) => + + if (scheduleFlexActivation.tick == INIT_SIM_TICK) { + serviceStateData.uuidToFlexAdapter(uuid) ! FlexActivation(INIT_SIM_TICK) + } + } + + + serviceStateData + } + + private def handleFlexActivation( + flexActivation: FlexActivation, + receiver: ActorRef[FlexRequest], + )(implicit + serviceStateData: ExtEmDataStateData + ): ExtEmDataStateData = { + + if (flexActivation.tick == INIT_SIM_TICK) { + receiver ! flexActivation + } + + serviceStateData + } + + + private def handleFlexControl( + issueFlexControl: IssueFlexControl, + receiver: ActorRef[FlexRequest], + )(implicit + serviceStateData: ExtEmDataStateData + ): ExtEmDataStateData = { + if (issueFlexControl.tick == INIT_SIM_TICK) { + + receiver ! issueFlexControl + serviceStateData + + } else { val uuid = serviceStateData.flexAdapterToUuid(receiver) val updated = issueFlexControl match { @@ -492,6 +574,6 @@ object ExtEmDataService setPointResponse = ReceiveDataMap.empty ) } - + } } } diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala index d602a177a7..11f3243e06 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala @@ -6,7 +6,12 @@ package edu.ie3.simona.service.results -import edu.ie3.datamodel.models.result.ResultEntity +import edu.ie3.datamodel.models.result.connector.LineResult +import edu.ie3.datamodel.models.result.system.{ + FlexOptionsResult, + SystemParticipantResult, +} +import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} import edu.ie3.simona.api.data.results.ExtResultDataConnection import edu.ie3.simona.api.data.results.ontology.{ ProvideResultEntities, @@ -80,7 +85,7 @@ object ExtResultDataProvider { def apply( scheduler: ActorRef[SchedulerMessage], startTime: ZonedDateTime, - ): Behavior[Request] = Behaviors.withStash(5000) { buffer => + ): Behavior[Request] = Behaviors.withStash(5000000) { buffer => Behaviors.setup[Request] { ctx => val activationAdapter: ActorRef[Activation] = ctx.messageAdapter[Activation](msg => WrappedActivation(msg)) @@ -149,10 +154,12 @@ object ExtResultDataProvider { initServiceData.extResultData.getGridResultDataAssets.asScala.toList val initParticipantSubscribers = initServiceData.extResultData.getParticipantResultDataAssets.asScala.toList + val initFlexOptionSubscribers = + initServiceData.extResultData.getFlexOptionAssets.asScala.toList var initResultScheduleMap = Map.empty[Long, Set[UUID]] initResultScheduleMap = - initResultScheduleMap + (0L -> initParticipantSubscribers.toSet) // First result for system participants expected for tick 0 + initResultScheduleMap + (0L -> (initParticipantSubscribers ++ initFlexOptionSubscribers).toSet) // First result for system participants expected for tick 0 initResultScheduleMap = initResultScheduleMap + (initServiceData.powerFlowResolution -> initGridSubscribers.toSet) // First result for grid expected for tick powerflowresolution @@ -193,30 +200,51 @@ object ExtResultDataProvider { // ctx.log.info(s"[${updatedStateData.currentTick}] [requestResults] resultStorage = ${updatedStateData.resultStorage}\n extResultScheduler ${updatedStateData.extResultScheduler}") val currentTick = updatedStateData.currentTick if (msg.tick == currentTick) { // check, if we are in the right tick - val expectedKeys = - serviceStateData.extResultSchedule.getExpectedKeys( + // ctx.log.info(s"[${updatedStateData.currentTick}] RequestResultEntities with message = $msg") + // ctx.log.info(s"[${updatedStateData.currentTick}] RequestResultEntities for ${msg.requestedResults()}") + + // Search for too old schedules + val shiftedSchedule = + serviceStateData.extResultSchedule.shiftPastTicksToCurrentTick( currentTick - ) // Expected keys are for this tick scheduled and not scheduled + ) + + val requestedKeys = msg.requestedResults().asScala + val expectedKeys = shiftedSchedule + .getExpectedKeys( + currentTick + ) + .intersect(msg.requestedResults().asScala.toSet) + // ctx.log.info(s"[${updatedStateData.currentTick}] [requestResults] Expected Keys = $expectedKeys") val receiveDataMap = ReceiveDataMap[UUID, ResultEntity](expectedKeys) val updatedSchedule = - serviceStateData.extResultSchedule.handleActivation(currentTick) + shiftedSchedule.handleActivationWithRequest( + currentTick, + msg.requestedResults().asScala, + ) // ctx.log.info(s"[${updatedStateData.currentTick}] [requestResults] updatedSchedule = $updatedSchedule \n receiveDataMap = $receiveDataMap") if (receiveDataMap.isComplete) { // --- There are no expected results for this tick! Send the send right away! - // ctx.log.info(s"[requestResults] tick ${msg.tick} -> ReceiveDataMap is complete -> send it right away: ${serviceStateData.resultStorage}") + // ctx.log.info(s"[requestResults] tick ${msg.tick} -> ReceiveDataMap is complete \n requestedKeys = $requestedKeys -> send it right away: \n ${serviceStateData.resultStorage}") + val filteredStorage = + serviceStateData.resultStorage.filter(entry => + requestedKeys.toSet.contains(entry._1) + ) + // ctx.log.info(s"\u001b[0;34m[${serviceStateData.currentTick}] receiveDataMap = $receiveDataMap,\nexpectedKeys = ${receiveDataMap.receivedData.keySet},\nfilteredStorage = $filteredStorage\u001b[0;0m") serviceStateData.extResultData.queueExtResponseMsg( new ProvideResultEntities( - serviceStateData.resultStorage.asJava + filteredStorage.asJava ) ) updatedStateData = updatedStateData.copy( extResultsMessage = None, receiveDataMap = None, extResultSchedule = updatedSchedule, + extRequestedResultKeys = List.empty, ) scheduler ! Completion(activationAdapter, None) } else { @@ -227,6 +255,7 @@ object ExtResultDataProvider { extResultsMessage = None, receiveDataMap = Some(receiveDataMap), extResultSchedule = updatedSchedule, + extRequestedResultKeys = requestedKeys, ) } } else { @@ -332,16 +361,49 @@ object ExtResultDataProvider { } case ( - _, - _: ResultRequestMessage, + ctx, + msg: ResultRequestMessage, ) => // Received internal result request -> unstash messages - // ctx.log.info(s"[handleDataResponseMessage] Received ResultRequestMessage $msg -> Now unstash all buffered messages!") + ctx.self ! msg buffer.unstashAll(idle(serviceStateData)) case (ctx, msg: DelayedStopHelper.StoppingMsg) => DelayedStopHelper.handleMsg((ctx, msg)) } + private def checkResultType( + result: ResultEntity, + serviceStateData: ExtResultStateData, + ): Boolean = { + val uuid = result.getInputModel + result match { + case _: FlexOptionsResult => + if (serviceStateData.extResultData.getFlexOptionAssets.contains(uuid)) { + true + } else { + false + } + case _: SystemParticipantResult => + if ( + serviceStateData.extResultData.getParticipantResultDataAssets + .contains(uuid) + ) { + true + } else { + false + } + case _: NodeResult | _: LineResult => + if ( + serviceStateData.extResultData.getGridResultDataAssets.contains(uuid) + ) { + true + } else { + false + } + case _ => false + } + } + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- final case class ExtResultStateData( extResultData: ExtResultDataConnection, @@ -351,6 +413,7 @@ object ExtResultDataProvider { extResultsMessage: Option[ResultDataMessageFromExt] = None, resultStorage: Map[UUID, ResultEntity] = Map.empty, receiveDataMap: Option[ReceiveDataMap[UUID, ResultEntity]] = None, + extRequestedResultKeys: Iterable[UUID] = List.empty, ) { def handleActivation(activation: Activation): ExtResultStateData = { copy( @@ -367,6 +430,29 @@ object ExtResultDataProvider { scheduleMap: Map[Long, Set[UUID]] = Map.empty, unscheduledList: Set[UUID] = Set.empty, ) { + def shiftPastTicksToCurrentTick( + currentTick: Long + ): ExtResultSchedule = { + // Sammle alle Sets von Keys, deren Schlüssel kleiner als currentTick + val (toMerge, remaining) = scheduleMap.partition { case (tick, _) => + tick < currentTick + } + + // Kombiniere die Sets zu einem einzigen Set + val mergedSet = toMerge.values.flatten.toSet + + // Aktualisiere den scheduleMap mit dem neuen Set für currentTick + val updatedScheduleMap = remaining.updated( + currentTick, + scheduleMap.getOrElse(currentTick, Set.empty) ++ mergedSet, + ) + + // Rückgabe eines neuen ExtResultSchedule mit dem aktualisierten scheduleMap + copy( + scheduleMap = updatedScheduleMap + ) + } + def getExpectedKeys(tick: Long): Set[UUID] = { scheduleMap.getOrElse( tick, @@ -384,6 +470,23 @@ object ExtResultDataProvider { ) } + def handleActivationWithRequest( + tick: Long, + keys: Iterable[UUID], + ): ExtResultSchedule = { + val remainingKeys = + scheduleMap.get(tick).map(_.diff(keys.toSet)).getOrElse(Set.empty) + if (remainingKeys.isEmpty) { + copy( + scheduleMap = scheduleMap.-(tick) + ) + } else { + copy( + scheduleMap = scheduleMap.updated(tick, remainingKeys) + ) + } + } + def handleResult( msg: ResultResponseMessage, nextTick: Long, diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index 61e715e4e7..dc3adef2d0 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -83,6 +83,9 @@ final case class ExtSimSetupData( ref } + def resultDataServices: Iterable[ActorRef[ExtResultDataProvider.Request]] = + extResultListeners.map { case (_, ref) => ref } + def evDataConnection: Option[ExtEvDataConnection] = extDataServices.collectFirst { case (connection: ExtEvDataConnection, _) => connection diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index bc27ff38cd..78033666f8 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -272,7 +272,7 @@ class SimonaStandaloneSetup( .spawn( ResultEventListener( resultFileHierarchy, - extSimSetupData.extResultListeners.toMap, + extSimSetupData.resultDataServices, ), ResultEventListener.getClass.getSimpleName, ) From 7db942e37bf9a167017ef8937cf62cfaf7ae35a2 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 19 Mar 2025 16:54:28 +0100 Subject: [PATCH 016/125] Saving changes. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 8 +- .../ie3/simona/service/ExtDataSupport.scala | 4 +- .../simona/service/TypedSimonaService.scala | 9 ++ .../simona/service/em/ExtEmDataService.scala | 99 ++++++++++++++----- .../ie3/simona/sim/setup/ExtSimSetup.scala | 2 +- 5 files changed, 93 insertions(+), 29 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index ac100ac862..cf6b232db1 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -231,6 +231,8 @@ object EmAgent { inactive(emData, modelShell, newCore) case (ctx, msg: ActivationRequest) => + ctx.log.warn(s"$msg") + val flexOptionsCore = core.activate(msg.tick) msg match { @@ -266,10 +268,12 @@ object EmAgent { emData: EmData, modelShell: EmModelShell, flexOptionsCore: EmDataCore.AwaitingFlexOptions, - ): Behavior[Request] = Behaviors.receiveMessagePartial { - case flexOptions: ProvideFlexOptions => + ): Behavior[Request] = Behaviors.receivePartial { + case (ctx, flexOptions: ProvideFlexOptions) => val updatedCore = flexOptionsCore.handleFlexOptions(flexOptions) + ctx.log.warn(s"$updatedCore") + if (updatedCore.isComplete) { val allFlexOptions = updatedCore.getFlexOptions diff --git a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala index ba35c087a7..51a21526ed 100644 --- a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala +++ b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala @@ -55,7 +55,9 @@ trait ExtDataSupport[ idle(updatedStateData, constantData) - case (_, extResponseMsg: ServiceResponseMessage) => + case (ctx, extResponseMsg: ServiceResponseMessage) => + ctx.log.warn(s"Response: $extResponseMsg") + val updatedStateData = handleDataResponseMessage(extResponseMsg)(stateData) diff --git a/src/main/scala/edu/ie3/simona/service/TypedSimonaService.scala b/src/main/scala/edu/ie3/simona/service/TypedSimonaService.scala index 407ad17371..33526025be 100644 --- a/src/main/scala/edu/ie3/simona/service/TypedSimonaService.scala +++ b/src/main/scala/edu/ie3/simona/service/TypedSimonaService.scala @@ -16,6 +16,7 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ Create, ScheduleServiceActivation, ServiceRegistrationMessage, + ServiceResponseMessage, WrappedActivation, } import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} @@ -144,6 +145,10 @@ abstract class TypedSimonaService[ buffer.stash(msg) Behaviors.same + case (_, msg: ServiceResponseMessage) => + handleServiceResponse(msg) + Behaviors.same + // unhandled message case (ctx, x) => ctx.log.error(s"Received unhandled message: $x") @@ -228,6 +233,10 @@ abstract class TypedSimonaService[ constantData: ServiceConstantStateData, ): Behavior[T] = idleInternal + protected def handleServiceResponse( + serviceResponse: ServiceResponseMessage + ): Unit = {} + /** Initialize the concrete service implementation using the provided * initialization data. This method should perform all heavyweight tasks * before the actor becomes ready. The return values are a) the state data of 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 2579b3b55d..c99d42ed37 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -18,9 +18,19 @@ import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EmMessage -import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{RegisterForEmDataService, ServiceRegistrationMessage, ServiceResponseMessage} -import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + WrappedFlexRequest, + WrappedFlexResponse, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + RegisterForEmDataService, + ServiceRegistrationMessage, + ServiceResponseMessage, +} +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, +} import edu.ie3.simona.service.{ExtDataSupport, TypedSimonaService} import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK @@ -38,7 +48,11 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.{Power => PsdmPower} -import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala} +import scala.jdk.CollectionConverters.{ + ListHasAsScala, + MapHasAsJava, + MapHasAsScala, +} import scala.jdk.OptionConverters.{RichOption, RichOptional} import scala.util.{Failure, Success, Try} @@ -116,7 +130,8 @@ object ExtEmDataService refToUncontrolled: Map[ActorRef[FlexResponse], UUID] = Map.empty, controlledToRef: Map[UUID, ActorRef[FlexResponse]] = Map.empty, refToControlled: Map[ActorRef[FlexResponse], UUID] = Map.empty, - parentRef: Map[ActorRef[FlexResponse], ActorRef[EmAgent.Request]] = Map.empty + parentRef: Map[ActorRef[FlexResponse], ActorRef[EmAgent.Request]] = + Map.empty, ) { def add(model: UUID, ref: ActorRef[EmAgent.Request]): EmHierarchy = copy( uncontrolledToRef = uncontrolledToRef + (model -> ref), @@ -146,8 +161,6 @@ object ExtEmDataService case None => controlledToRef.get(uuid) } - - } case class InitExtEmData( @@ -155,6 +168,16 @@ object ExtEmDataService startTime: ZonedDateTime, ) extends InitializeServiceStateData + override protected def handleServiceResponse( + serviceResponse: ServiceResponseMessage + ): Unit = serviceResponse match { + case WrappedFlexResponse( + scheduleFlexActivation: ScheduleFlexActivation, + _, + ) => + scheduleFlexActivation.scheduleKey.foreach(_.unlock()) + } + /** Initialize the concrete service implementation using the provided * initialization data. This method should perform all heavyweight tasks * before the actor becomes ready. The return values are a) the state data of @@ -243,6 +266,8 @@ object ExtEmDataService hierarchy.add(modelUuid, modelActorRef) } + flexAdapter ! FlexActivation(INIT_SIM_TICK) + serviceStateData.copy( emHierarchy = updatedHierarchy, uuidToFlexAdapter = @@ -267,35 +292,50 @@ object ExtEmDataService serviceStateData: ExtEmDataStateData, ctx: ActorContext[EmMessage], ): (ExtEmDataStateData, Option[Long]) = { + val updatedTick = serviceStateData.copy(tick = tick) + val updatedStateData = serviceStateData.extEmDataMessage.getOrElse( throw ServiceException( "ExtEMDataService was triggered without ExtEmDataMessage available" ) ) match { case requestEmFlexResults: RequestEmFlexResults => - val uuids = requestEmFlexResults.emEntities().asScala.toSet + ctx.log.warn(s"$requestEmFlexResults") + + // TODO: Fix this + val uuids = + requestEmFlexResults.emEntities().asScala.flatMap(_._2.asScala).toSet val uuidToRef = serviceStateData.uuidToFlexAdapter - uuids.map(uuidToRef).foreach(_ ! FlexActivation(tick)) + val refs = uuids.map(uuidToRef) - serviceStateData.copy( + log.warn(s"$refs") + refs.foreach(_ ! FlexActivation(tick)) + + updatedTick.copy( extEmDataMessage = None, flexOptionResponse = ReceiveDataMap(uuids), ) case requestEmSetPoints: RequestEmSetPoints => + ctx.log.warn(s"$requestEmSetPoints") + val uuids = requestEmSetPoints.emEntities().asScala.toSet - serviceStateData.copy( + updatedTick.copy( extEmDataMessage = None, setPointResponse = ReceiveDataMap(uuids), ) case provideFlexOptions: ProvideEmFlexOptionData => - announceFlexOptions(provideFlexOptions) + ctx.log.warn(s"$provideFlexOptions") + + announceFlexOptions(provideFlexOptions)(updatedTick, ctx) case providedEmData: ProvideEmSetPointData => - announceEmSetPoints(tick, providedEmData) + ctx.log.warn(s"$providedEmData") + + announceEmSetPoints(tick, providedEmData)(updatedTick, ctx) } (updatedStateData, None) @@ -432,7 +472,10 @@ object ExtEmDataService otherRef ! provideFlexOptions case Left(self: UUID) => - serviceStateData.uuidToFlexAdapter(self) ! IssuePowerControl(INIT_SIM_TICK, zeroKW) + serviceStateData.uuidToFlexAdapter(self) ! IssuePowerControl( + INIT_SIM_TICK, + zeroKW, + ) } serviceStateData @@ -457,8 +500,13 @@ object ExtEmDataService max.toQuantity, ), ) + + case _ => + serviceStateData.flexOptionResponse } + log.warn(s"$updated") + if (updated.nonComplete) { // responses are still incomplete serviceStateData.copy( @@ -490,29 +538,31 @@ object ExtEmDataService receiver match { case Right(ref) => - if (scheduleFlexActivation.tick == INIT_SIM_TICK) { ref ! scheduleFlexActivation + } else { + log.warn(s"$scheduleFlexActivation not handled!") } - case Left(uuid) => - if (scheduleFlexActivation.tick == INIT_SIM_TICK) { - serviceStateData.uuidToFlexAdapter(uuid) ! FlexActivation(INIT_SIM_TICK) + serviceStateData.uuidToFlexAdapter(uuid) ! FlexActivation( + INIT_SIM_TICK + ) + } else { + log.warn(s"$scheduleFlexActivation not handled!") } } - serviceStateData } private def handleFlexActivation( - flexActivation: FlexActivation, - receiver: ActorRef[FlexRequest], - )(implicit - serviceStateData: ExtEmDataStateData - ): ExtEmDataStateData = { + flexActivation: FlexActivation, + receiver: ActorRef[FlexRequest], + )(implicit + serviceStateData: ExtEmDataStateData + ): ExtEmDataStateData = { if (flexActivation.tick == INIT_SIM_TICK) { receiver ! flexActivation @@ -521,7 +571,6 @@ object ExtEmDataService serviceStateData } - private def handleFlexControl( issueFlexControl: IssueFlexControl, receiver: ActorRef[FlexRequest], diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index 7713342092..0409beebd5 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -360,7 +360,7 @@ object ExtSimSetup { ScheduleLock.singleKey( context, scheduler, - INIT_SIM_TICK, + PRE_INIT_TICK, ), ) From 01beeab2cd849596e055e3a94c23b0d9cde7fc93 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 20 Mar 2025 14:50:05 +0100 Subject: [PATCH 017/125] Saving changes. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 4 +- .../simona/service/em/ExtEmDataService.scala | 73 +++++++------ .../ie3/simona/util/ReceiveMultiDataMap.scala | 103 ++++++++++++++++++ .../service/em/ExtEmDataServiceSpec.scala | 4 +- 4 files changed, 145 insertions(+), 39 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index cf6b232db1..462fdc7a5e 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -270,9 +270,11 @@ object EmAgent { flexOptionsCore: EmDataCore.AwaitingFlexOptions, ): Behavior[Request] = Behaviors.receivePartial { case (ctx, flexOptions: ProvideFlexOptions) => + ctx.log.warn(s"Core: $flexOptionsCore") + val updatedCore = flexOptionsCore.handleFlexOptions(flexOptions) - ctx.log.warn(s"$updatedCore") + ctx.log.warn(s"Updated core: $updatedCore") if (updatedCore.isComplete) { 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 c99d42ed37..b2f993a57e 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -9,7 +9,7 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexOptionValue} +import edu.ie3.simona.api.data.em.model.EmSetPointResult import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.api.data.em.{ExtEmDataConnection, NoSetPointValue} import edu.ie3.simona.api.data.ontology.DataMessageFromExt @@ -18,23 +18,13 @@ import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EmMessage -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - WrappedFlexRequest, - WrappedFlexResponse, -} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - RegisterForEmDataService, - ServiceRegistrationMessage, - ServiceResponseMessage, -} -import edu.ie3.simona.service.ServiceStateData.{ - InitializeServiceStateData, - ServiceBaseStateData, -} +import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{RegisterForEmDataService, ServiceRegistrationMessage, ServiceResponseMessage} +import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} import edu.ie3.simona.service.{ExtDataSupport, TypedSimonaService} -import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.TickLong +import edu.ie3.simona.util.{ReceiveDataMap, ReceiveMultiDataMap} import edu.ie3.util.quantities.PowerSystemUnits.KILOWATT import edu.ie3.util.quantities.QuantityUtils._ import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW @@ -48,11 +38,7 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.{Power => PsdmPower} -import scala.jdk.CollectionConverters.{ - ListHasAsScala, - MapHasAsJava, - MapHasAsScala, -} +import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala} import scala.jdk.OptionConverters.{RichOption, RichOptional} import scala.util.{Failure, Success, Try} @@ -119,8 +105,8 @@ object ExtEmDataService flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, extEmDataMessage: Option[EmDataMessageFromExt] = None, toSchedule: Map[UUID, ScheduleFlexActivation] = Map.empty, - flexOptionResponse: ReceiveDataMap[UUID, FlexOptionsResult] = - ReceiveDataMap.empty, + flexOptionResponse: ReceiveMultiDataMap[UUID, FlexOptionsResult] = + ReceiveMultiDataMap.empty, setPointResponse: ReceiveDataMap[UUID, EmSetPointResult] = ReceiveDataMap.empty, ) extends ServiceBaseStateData @@ -302,19 +288,25 @@ object ExtEmDataService case requestEmFlexResults: RequestEmFlexResults => ctx.log.warn(s"$requestEmFlexResults") - // TODO: Fix this - val uuids = - requestEmFlexResults.emEntities().asScala.flatMap(_._2.asScala).toSet + val (flexOptionResponse, allInferior) = requestEmFlexResults + .emEntities() + .asScala + .foldLeft(Map.empty[UUID, Set[UUID]], Set.empty[UUID]) { + case ((dataMap, allInferior), (key, inferior)) => + val inferiorKeys = inferior.asScala.toSet + + (dataMap.updated(key, inferiorKeys), allInferior ++ inferiorKeys) + } val uuidToRef = serviceStateData.uuidToFlexAdapter - val refs = uuids.map(uuidToRef) + val refs = allInferior.map(uuidToRef) log.warn(s"$refs") refs.foreach(_ ! FlexActivation(tick)) updatedTick.copy( extEmDataMessage = None, - flexOptionResponse = ReceiveDataMap(uuids), + flexOptionResponse = ReceiveMultiDataMap(flexOptionResponse), ) case requestEmSetPoints: RequestEmSetPoints => @@ -352,15 +344,21 @@ object ExtEmDataService provideFlexOptions .flexOptions() .asScala - .foreach { case (agent, flexOption: FlexOptionValue) => + .map { case (agent, value) => + agent -> value.flexOptions.asScala + } + .foreach { case (agent, flexOptions) => hierarchy.getResponseRef(agent) match { case Some(receiver) => - receiver ! ProvideMinMaxFlexOptions( - flexOption.sender, - flexOption.pRef.toSquants, - flexOption.pMin.toSquants, - flexOption.pMax.toSquants, - ) + + flexOptions.foreach { flexOption => + receiver ! ProvideMinMaxFlexOptions( + flexOption.sender, + flexOption.pRef.toSquants, + flexOption.pMin.toSquants, + flexOption.pMax.toSquants, + ) + } case None => ctx.log.warn(s"No em agent with uuid '$agent' registered!") @@ -492,6 +490,7 @@ object ExtEmDataService case ProvideMinMaxFlexOptions(modelUuid, ref, min, max) => serviceStateData.flexOptionResponse.addData( uuid, + modelUuid, new FlexOptionsResult( serviceStateData.startTime, // TODO: Fix this modelUuid, @@ -515,14 +514,16 @@ object ExtEmDataService } else { // all responses received, forward them to external simulation in a bundle + val (data, updatedFlexOptionResponse) = updated.removeLastFinished() + serviceStateData.extEmDataConnection.queueExtResponseMsg( new FlexOptionsResponse( - updated.receivedData.asJava + data.map { case (k, v) => k -> v.asJava }.asJava ) ) serviceStateData.copy( - flexOptionResponse = ReceiveDataMap.empty + flexOptionResponse = updatedFlexOptionResponse ) } } diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala new file mode 100644 index 0000000000..9fba2eb3a8 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala @@ -0,0 +1,103 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.util + +/** Map that holds data that an actor is expected to receive over time and + * efficiently determines if all expected data has been received. + * + * @param keyToInferior + * The mapping of a key and a [[ReceiveDataMap]]. + * @tparam K + * The type of the key + * @tparam V + * The type of the value + */ +final case class ReceiveMultiDataMap[K, V]( + private val maxTime: Int, + private val lastFinished: Option[Int], + private val unfinishedTimes: Set[Int], + private val finished: Set[Int], + private val keyToTime: Map[K, Int], + private val keyToInferior: Map[K, ReceiveDataMap[K, V]], +) { + def isComplete: Boolean = lastFinished.isDefined + + def nonComplete: Boolean = lastFinished.isEmpty + + def addKeys(keyMap: Map[K, Set[K]]): ReceiveMultiDataMap[K, V] = { + + keyMap.foreach { case (key, _) => + if (keyToInferior.contains(key)) { + throw new RuntimeException(s"Received key $key is already registered!") + } + } + + val time = maxTime + 1 + val updatedKeyToTime = keyToTime ++ keyMap.keySet.map(_ -> time) + + val updated: Map[K, ReceiveDataMap[K, V]] = keyMap.map { case (key, inferiorKeys) => + key -> ReceiveDataMap(inferiorKeys) + } + + copy( + maxTime = time, + unfinishedTimes = unfinishedTimes + time, + keyToTime = updatedKeyToTime, + keyToInferior = keyToInferior ++ updated, + ) + } + + def addData( + key: K, + inferiorKey: K, + value: V, + ): ReceiveMultiDataMap[K, V] = { + + if (!keyToInferior.contains(key)) + throw new RuntimeException( + s"Received value $value for key $key, but no data has been expected for this key." + ) + + val updated = keyToInferior(key).addData(inferiorKey, value) + + val updatedKeyToTime = if (updated.isComplete) { + keyToTime.removed(key) + } else keyToTime + + val updatedUnfinished = updatedKeyToTime.values.toSet + val updatedFinished = unfinishedTimes.diff(updatedUnfinished) + + copy( + lastFinished = updatedFinished.maxOption, + unfinishedTimes = updatedUnfinished, + finished = updatedFinished, + keyToTime = updatedKeyToTime, + keyToInferior = keyToInferior.updated(key, updated), + ) + } + + def removeLastFinished(): (Map[K, Map[K, V]], ReceiveMultiDataMap[K, V]) = { + val map = keyToInferior.filter { case (_, map) => map.isComplete }.map { case (k, v) => k -> v.receivedData } + + (map, copy(keyToInferior = keyToInferior.removedAll(map.keySet))) + } + + + def getExpectedKeys(key: K): Set[K] = + keyToInferior.get(key).map(_.getExpectedKeys).getOrElse(Set.empty) + +} + +object ReceiveMultiDataMap { + + def apply[K, V](keyMap: Map[K, Set[K]]): ReceiveMultiDataMap[K, V] = + empty.addKeys(keyMap) + + def empty[K, V]: ReceiveMultiDataMap[K, V] = + ReceiveMultiDataMap(-1, None, Set.empty, Set.empty, Map.empty, Map.empty) + +} 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 4c25f89a0f..c55ab4b09c 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -10,7 +10,7 @@ import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.em.ExtEmDataConnection -import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexOptionValue} +import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexOptions} import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage import edu.ie3.simona.ontology.messages.SchedulerMessage.{ @@ -374,7 +374,7 @@ class ExtEmDataServiceSpec new ProvideEmFlexOptionData( 0, Map( - emAgent2UUID -> new FlexOptionValue( + emAgent2UUID -> new FlexOptions( emAgent1UUID, -3.asKiloWatt, -1.asKiloWatt, From 02ae5afaa530a8a2c07a6f9cc1b71bf6a5921b7f Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 21 Mar 2025 11:36:12 +0100 Subject: [PATCH 018/125] Saving changes. --- .../scala/edu/ie3/simona/service/em/ExtEmDataService.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 b2f993a57e..ee7f6e4d45 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -344,14 +344,11 @@ object ExtEmDataService provideFlexOptions .flexOptions() .asScala - .map { case (agent, value) => - agent -> value.flexOptions.asScala - } .foreach { case (agent, flexOptions) => hierarchy.getResponseRef(agent) match { case Some(receiver) => - flexOptions.foreach { flexOption => + flexOptions.asScala.foreach { flexOption => receiver ! ProvideMinMaxFlexOptions( flexOption.sender, flexOption.pRef.toSquants, From 7aea7c6bf4b129d4426e4073ccb91fc30691a8e6 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 21 Mar 2025 14:20:06 +0100 Subject: [PATCH 019/125] Saving changes. --- .../simona/service/em/ExtEmDataService.scala | 25 ++++++++++++++----- .../ie3/simona/util/ReceiveMultiDataMap.scala | 22 ++++++++-------- 2 files changed, 31 insertions(+), 16 deletions(-) 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 ee7f6e4d45..abc992de06 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -18,9 +18,19 @@ import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EmMessage -import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{RegisterForEmDataService, ServiceRegistrationMessage, ServiceResponseMessage} -import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + WrappedFlexRequest, + WrappedFlexResponse, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + RegisterForEmDataService, + ServiceRegistrationMessage, + ServiceResponseMessage, +} +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, +} import edu.ie3.simona.service.{ExtDataSupport, TypedSimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.TickLong @@ -38,7 +48,11 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.{Power => PsdmPower} -import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala} +import scala.jdk.CollectionConverters.{ + ListHasAsScala, + MapHasAsJava, + MapHasAsScala, +} import scala.jdk.OptionConverters.{RichOption, RichOptional} import scala.util.{Failure, Success, Try} @@ -347,7 +361,6 @@ object ExtEmDataService .foreach { case (agent, flexOptions) => hierarchy.getResponseRef(agent) match { case Some(receiver) => - flexOptions.asScala.foreach { flexOption => receiver ! ProvideMinMaxFlexOptions( flexOption.sender, @@ -515,7 +528,7 @@ object ExtEmDataService serviceStateData.extEmDataConnection.queueExtResponseMsg( new FlexOptionsResponse( - data.map { case (k, v) => k -> v.asJava }.asJava + data.values.flatten.toMap.asJava ) ) diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala index 9fba2eb3a8..739e26c0bb 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala @@ -17,12 +17,12 @@ package edu.ie3.simona.util * The type of the value */ final case class ReceiveMultiDataMap[K, V]( - private val maxTime: Int, - private val lastFinished: Option[Int], - private val unfinishedTimes: Set[Int], - private val finished: Set[Int], - private val keyToTime: Map[K, Int], - private val keyToInferior: Map[K, ReceiveDataMap[K, V]], + private val maxTime: Int, + private val lastFinished: Option[Int], + private val unfinishedTimes: Set[Int], + private val finished: Set[Int], + private val keyToTime: Map[K, Int], + private val keyToInferior: Map[K, ReceiveDataMap[K, V]], ) { def isComplete: Boolean = lastFinished.isDefined @@ -39,8 +39,9 @@ final case class ReceiveMultiDataMap[K, V]( val time = maxTime + 1 val updatedKeyToTime = keyToTime ++ keyMap.keySet.map(_ -> time) - val updated: Map[K, ReceiveDataMap[K, V]] = keyMap.map { case (key, inferiorKeys) => - key -> ReceiveDataMap(inferiorKeys) + val updated: Map[K, ReceiveDataMap[K, V]] = keyMap.map { + case (key, inferiorKeys) => + key -> ReceiveDataMap(inferiorKeys) } copy( @@ -81,12 +82,13 @@ final case class ReceiveMultiDataMap[K, V]( } def removeLastFinished(): (Map[K, Map[K, V]], ReceiveMultiDataMap[K, V]) = { - val map = keyToInferior.filter { case (_, map) => map.isComplete }.map { case (k, v) => k -> v.receivedData } + val map = keyToInferior.filter { case (_, map) => map.isComplete }.map { + case (k, v) => k -> v.receivedData + } (map, copy(keyToInferior = keyToInferior.removedAll(map.keySet))) } - def getExpectedKeys(key: K): Set[K] = keyToInferior.get(key).map(_.getExpectedKeys).getOrElse(Set.empty) From d026dbcea95189b118ad334cd1a872627cb249f5 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 21 Mar 2025 17:54:31 +0100 Subject: [PATCH 020/125] Saving changes. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 9 +- .../simona/service/em/ExtEmDataService.scala | 82 ++++++++----------- .../ie3/simona/util/ReceiveMultiDataMap.scala | 76 ++++------------- 3 files changed, 53 insertions(+), 114 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 462fdc7a5e..afd753488b 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -231,13 +231,16 @@ object EmAgent { inactive(emData, modelShell, newCore) case (ctx, msg: ActivationRequest) => - ctx.log.warn(s"$msg") + ctx.log.warn(s"${ctx.self}: $msg") val flexOptionsCore = core.activate(msg.tick) msg match { case Flex(_: FlexActivation) | EmActivation(_) => val (toActivate, newCore) = flexOptionsCore.takeNewFlexRequests() + + ctx.log.warn(s"${ctx.self} -> $toActivate") + toActivate.foreach { _ ! FlexActivation(msg.tick) } @@ -270,12 +273,10 @@ object EmAgent { flexOptionsCore: EmDataCore.AwaitingFlexOptions, ): Behavior[Request] = Behaviors.receivePartial { case (ctx, flexOptions: ProvideFlexOptions) => - ctx.log.warn(s"Core: $flexOptionsCore") + ctx.log.warn(s"${ctx.self} from: ${flexOptions.modelUuid}") val updatedCore = flexOptionsCore.handleFlexOptions(flexOptions) - ctx.log.warn(s"Updated core: $updatedCore") - if (updatedCore.isComplete) { val allFlexOptions = updatedCore.getFlexOptions 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 abc992de06..3a4d89287d 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -18,19 +18,9 @@ import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EmMessage -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - WrappedFlexRequest, - WrappedFlexResponse, -} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - RegisterForEmDataService, - ServiceRegistrationMessage, - ServiceResponseMessage, -} -import edu.ie3.simona.service.ServiceStateData.{ - InitializeServiceStateData, - ServiceBaseStateData, -} +import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{RegisterForEmDataService, ServiceRegistrationMessage, ServiceResponseMessage} +import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} import edu.ie3.simona.service.{ExtDataSupport, TypedSimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.TickLong @@ -48,11 +38,7 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.{Power => PsdmPower} -import scala.jdk.CollectionConverters.{ - ListHasAsScala, - MapHasAsJava, - MapHasAsScala, -} +import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala} import scala.jdk.OptionConverters.{RichOption, RichOptional} import scala.util.{Failure, Success, Try} @@ -119,23 +105,23 @@ object ExtEmDataService flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, extEmDataMessage: Option[EmDataMessageFromExt] = None, toSchedule: Map[UUID, ScheduleFlexActivation] = Map.empty, - flexOptionResponse: ReceiveMultiDataMap[UUID, FlexOptionsResult] = + flexOptionResponse: ReceiveMultiDataMap[UUID, (UUID, FlexOptionsResult)] = ReceiveMultiDataMap.empty, setPointResponse: ReceiveDataMap[UUID, EmSetPointResult] = ReceiveDataMap.empty, ) extends ServiceBaseStateData final case class EmHierarchy( - uncontrolledToRef: Map[UUID, ActorRef[EmAgent.Request]] = Map.empty, - refToUncontrolled: Map[ActorRef[FlexResponse], UUID] = Map.empty, - controlledToRef: Map[UUID, ActorRef[FlexResponse]] = Map.empty, - refToControlled: Map[ActorRef[FlexResponse], UUID] = Map.empty, - parentRef: Map[ActorRef[FlexResponse], ActorRef[EmAgent.Request]] = + refToUuid: Map[ActorRef[EmAgent.Request], UUID] = Map.empty, + uuidToRef: Map[UUID, ActorRef[EmAgent.Request]] = Map.empty, + uuidToFlexResponse: Map[UUID, ActorRef[FlexResponse]] = Map.empty, + flexResponseToUuid: Map[ActorRef[FlexResponse], UUID] = Map.empty, + parentRefToRef: Map[ActorRef[FlexResponse], ActorRef[EmAgent.Request]] = Map.empty, ) { def add(model: UUID, ref: ActorRef[EmAgent.Request]): EmHierarchy = copy( - uncontrolledToRef = uncontrolledToRef + (model -> ref), - refToUncontrolled = refToUncontrolled + (ref -> model), + uuidToRef = uuidToRef + (model -> ref), + refToUuid = refToUuid + (ref -> model), ) def add( @@ -145,22 +131,24 @@ object ExtEmDataService ): EmHierarchy = { copy( - controlledToRef = controlledToRef + (model -> ref), - refToControlled = refToControlled + (ref -> model), - parentRef = parentRef + (parent -> ref), + uuidToRef = uuidToRef + (model -> ref), + refToUuid = refToUuid + (ref -> model), + uuidToFlexResponse = uuidToFlexResponse + (model -> parent), + flexResponseToUuid = flexResponseToUuid + (parent -> model), + parentRefToRef = parentRefToRef + (parent -> ref), ) } + def getInferiors(uuids: Set[UUID]): Set[UUID] = + uuids.flatMap(uuidToFlexResponse.get) + .flatMap(parentRefToRef.get) + .map(refToUuid) + def getUuid(ref: ActorRef[FlexResponse]): UUID = - refToUncontrolled.getOrElse(ref, refToControlled(ref)) + flexResponseToUuid(ref) def getResponseRef(uuid: UUID): Option[ActorRef[FlexResponse]] = - uncontrolledToRef.get(uuid) match { - case Some(value) => - Some(value) - case None => - controlledToRef.get(uuid) - } + uuidToFlexResponse.get(uuid) } case class InitExtEmData( @@ -302,25 +290,20 @@ object ExtEmDataService case requestEmFlexResults: RequestEmFlexResults => ctx.log.warn(s"$requestEmFlexResults") - val (flexOptionResponse, allInferior) = requestEmFlexResults - .emEntities() - .asScala - .foldLeft(Map.empty[UUID, Set[UUID]], Set.empty[UUID]) { - case ((dataMap, allInferior), (key, inferior)) => - val inferiorKeys = inferior.asScala.toSet + // entities for which flex options are requested + val emEntities: Set[UUID] = requestEmFlexResults.emEntities().asScala.keys.toSet - (dataMap.updated(key, inferiorKeys), allInferior ++ inferiorKeys) - } + val inferior = serviceStateData.emHierarchy.getInferiors(emEntities) val uuidToRef = serviceStateData.uuidToFlexAdapter - val refs = allInferior.map(uuidToRef) + val refs = emEntities.map(uuidToRef) log.warn(s"$refs") refs.foreach(_ ! FlexActivation(tick)) updatedTick.copy( extEmDataMessage = None, - flexOptionResponse = ReceiveMultiDataMap(flexOptionResponse), + flexOptionResponse = ReceiveMultiDataMap(emEntities), ) case requestEmSetPoints: RequestEmSetPoints => @@ -499,15 +482,14 @@ object ExtEmDataService val updated = provideFlexOptions match { case ProvideMinMaxFlexOptions(modelUuid, ref, min, max) => serviceStateData.flexOptionResponse.addData( - uuid, modelUuid, - new FlexOptionsResult( + (uuid, new FlexOptionsResult( serviceStateData.startTime, // TODO: Fix this modelUuid, min.toQuantity, ref.toQuantity, max.toQuantity, - ), + )), ) case _ => @@ -528,7 +510,7 @@ object ExtEmDataService serviceStateData.extEmDataConnection.queueExtResponseMsg( new FlexOptionsResponse( - data.values.flatten.toMap.asJava + data.map { case (entity, (_, value)) => entity -> value }.asJava ) ) diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala index 739e26c0bb..7029457cf0 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala @@ -9,8 +9,6 @@ package edu.ie3.simona.util /** Map that holds data that an actor is expected to receive over time and * efficiently determines if all expected data has been received. * - * @param keyToInferior - * The mapping of a key and a [[ReceiveDataMap]]. * @tparam K * The type of the key * @tparam V @@ -18,88 +16,46 @@ package edu.ie3.simona.util */ final case class ReceiveMultiDataMap[K, V]( private val maxTime: Int, - private val lastFinished: Option[Int], - private val unfinishedTimes: Set[Int], - private val finished: Set[Int], private val keyToTime: Map[K, Int], - private val keyToInferior: Map[K, ReceiveDataMap[K, V]], + private val values: Map[Int, ReceiveDataMap[K, V]], ) { - def isComplete: Boolean = lastFinished.isDefined + def isComplete: Boolean = values(maxTime).isComplete - def nonComplete: Boolean = lastFinished.isEmpty - - def addKeys(keyMap: Map[K, Set[K]]): ReceiveMultiDataMap[K, V] = { - - keyMap.foreach { case (key, _) => - if (keyToInferior.contains(key)) { - throw new RuntimeException(s"Received key $key is already registered!") - } - } + def nonComplete: Boolean = values(maxTime).nonComplete + def addKeys(keys: Set[K]): ReceiveMultiDataMap[K, V] = { val time = maxTime + 1 - val updatedKeyToTime = keyToTime ++ keyMap.keySet.map(_ -> time) - - val updated: Map[K, ReceiveDataMap[K, V]] = keyMap.map { - case (key, inferiorKeys) => - key -> ReceiveDataMap(inferiorKeys) - } copy( maxTime = time, - unfinishedTimes = unfinishedTimes + time, - keyToTime = updatedKeyToTime, - keyToInferior = keyToInferior ++ updated, + keyToTime = keyToTime ++ keys.map(_ -> time), + values = values + (time -> ReceiveDataMap(keys)), ) } def addData( key: K, - inferiorKey: K, value: V, ): ReceiveMultiDataMap[K, V] = { + val time = keyToTime(key) + val updated = values(time).addData(key, value) - if (!keyToInferior.contains(key)) - throw new RuntimeException( - s"Received value $value for key $key, but no data has been expected for this key." - ) - - val updated = keyToInferior(key).addData(inferiorKey, value) - - val updatedKeyToTime = if (updated.isComplete) { - keyToTime.removed(key) - } else keyToTime - - val updatedUnfinished = updatedKeyToTime.values.toSet - val updatedFinished = unfinishedTimes.diff(updatedUnfinished) - - copy( - lastFinished = updatedFinished.maxOption, - unfinishedTimes = updatedUnfinished, - finished = updatedFinished, - keyToTime = updatedKeyToTime, - keyToInferior = keyToInferior.updated(key, updated), - ) + copy(values = values + (time -> updated)) } - def removeLastFinished(): (Map[K, Map[K, V]], ReceiveMultiDataMap[K, V]) = { - val map = keyToInferior.filter { case (_, map) => map.isComplete }.map { - case (k, v) => k -> v.receivedData - } + def removeLastFinished(): (Map[K, V], ReceiveMultiDataMap[K, V]) = { + val time = maxTime - 1 + val map = values(maxTime).receivedData - (map, copy(keyToInferior = keyToInferior.removedAll(map.keySet))) + (map, copy(maxTime = time, values = values.removed(maxTime))) } - - def getExpectedKeys(key: K): Set[K] = - keyToInferior.get(key).map(_.getExpectedKeys).getOrElse(Set.empty) - } object ReceiveMultiDataMap { - - def apply[K, V](keyMap: Map[K, Set[K]]): ReceiveMultiDataMap[K, V] = - empty.addKeys(keyMap) + def apply[K, V](keys: Set[K]): ReceiveMultiDataMap[K, V] = + empty.addKeys(keys) def empty[K, V]: ReceiveMultiDataMap[K, V] = - ReceiveMultiDataMap(-1, None, Set.empty, Set.empty, Map.empty, Map.empty) + ReceiveMultiDataMap(-1, Map.empty, Map.empty) } From cca2dd6593019f6a4ba05aaa21f92e1f62cd6924 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 24 Mar 2025 12:40:55 +0100 Subject: [PATCH 021/125] Saving changes. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 3 - .../simona/service/TypedSimonaService.scala | 6 +- .../simona/service/em/ExtEmDataService.scala | 79 +++++++++------- .../util/ReceiveHierarchicalDataMap.scala | 92 +++++++++++++++++++ 4 files changed, 142 insertions(+), 38 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index afd753488b..5dcd91f9d7 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -238,9 +238,6 @@ object EmAgent { msg match { case Flex(_: FlexActivation) | EmActivation(_) => val (toActivate, newCore) = flexOptionsCore.takeNewFlexRequests() - - ctx.log.warn(s"${ctx.self} -> $toActivate") - toActivate.foreach { _ ! FlexActivation(msg.tick) } diff --git a/src/main/scala/edu/ie3/simona/service/TypedSimonaService.scala b/src/main/scala/edu/ie3/simona/service/TypedSimonaService.scala index 6e5b195fb8..d0e6a5d606 100644 --- a/src/main/scala/edu/ie3/simona/service/TypedSimonaService.scala +++ b/src/main/scala/edu/ie3/simona/service/TypedSimonaService.scala @@ -145,8 +145,8 @@ abstract class TypedSimonaService[ buffer.stash(msg) Behaviors.same - case (_, msg: ServiceResponseMessage) => - handleServiceResponse(msg) + case (ctx, msg: ServiceResponseMessage) => + handleServiceResponse(msg)(ctx) Behaviors.same // unhandled message @@ -240,7 +240,7 @@ abstract class TypedSimonaService[ protected def handleServiceResponse( serviceResponse: ServiceResponseMessage - ): Unit = {} + )(implicit ctx: ActorContext[T]): Unit = {} /** Initialize the concrete service implementation using the provided * initialization data. This method should perform all heavyweight tasks 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 3a4d89287d..a7836cb94a 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -24,7 +24,7 @@ import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, Serv import edu.ie3.simona.service.{ExtDataSupport, TypedSimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.TickLong -import edu.ie3.simona.util.{ReceiveDataMap, ReceiveMultiDataMap} +import edu.ie3.simona.util.{ReceiveDataMap, ReceiveHierarchicalDataMap} import edu.ie3.util.quantities.PowerSystemUnits.KILOWATT import edu.ie3.util.quantities.QuantityUtils._ import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW @@ -105,8 +105,8 @@ object ExtEmDataService flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, extEmDataMessage: Option[EmDataMessageFromExt] = None, toSchedule: Map[UUID, ScheduleFlexActivation] = Map.empty, - flexOptionResponse: ReceiveMultiDataMap[UUID, (UUID, FlexOptionsResult)] = - ReceiveMultiDataMap.empty, + flexOptionResponse: ReceiveHierarchicalDataMap[UUID, (UUID, FlexOptionsResult)] = + ReceiveHierarchicalDataMap.empty, setPointResponse: ReceiveDataMap[UUID, EmSetPointResult] = ReceiveDataMap.empty, ) extends ServiceBaseStateData @@ -139,11 +139,6 @@ object ExtEmDataService ) } - def getInferiors(uuids: Set[UUID]): Set[UUID] = - uuids.flatMap(uuidToFlexResponse.get) - .flatMap(parentRefToRef.get) - .map(refToUuid) - def getUuid(ref: ActorRef[FlexResponse]): UUID = flexResponseToUuid(ref) @@ -158,12 +153,24 @@ object ExtEmDataService override protected def handleServiceResponse( serviceResponse: ServiceResponseMessage - ): Unit = serviceResponse match { + )(implicit ctx: ActorContext[EmMessage]): Unit = serviceResponse match { case WrappedFlexResponse( scheduleFlexActivation: ScheduleFlexActivation, - _, + receiver, ) => - scheduleFlexActivation.scheduleKey.foreach(_.unlock()) + ctx.log.info(s"Received response message: $scheduleFlexActivation") + + receiver match { + case Right(ref) => + log.info(s"Forwarding the message to: $ref") + ref ! scheduleFlexActivation + case Left(_) => + log.info(s"Unlocking msg: $scheduleFlexActivation") + + scheduleFlexActivation.scheduleKey.foreach(_.unlock()) + } + + } /** Initialize the concrete service implementation using the provided @@ -221,7 +228,7 @@ object ExtEmDataService requestingActor, flexAdapter, parentEm, - _, + parentUuid, ) => Success( handleEmRegistrationRequest( @@ -229,6 +236,7 @@ object ExtEmDataService requestingActor, flexAdapter, parentEm, + parentUuid, ) ) case invalidMessage => @@ -244,9 +252,15 @@ object ExtEmDataService modelActorRef: ActorRef[EmAgent.Request], flexAdapter: ActorRef[FlexRequest], parentEm: Option[ActorRef[FlexResponse]], + parentUuid: Option[UUID], )(implicit serviceStateData: ExtEmDataStateData): ExtEmDataStateData = { val hierarchy = serviceStateData.emHierarchy + val updatedDataMap = serviceStateData.flexOptionResponse.updateStructure( + parentUuid, + modelUuid + ) + val updatedHierarchy = parentEm match { case Some(parent) => hierarchy.add(modelUuid, modelActorRef, parent) @@ -254,7 +268,9 @@ object ExtEmDataService hierarchy.add(modelUuid, modelActorRef) } - flexAdapter ! FlexActivation(INIT_SIM_TICK) + if (parentEm.isEmpty) { + flexAdapter ! FlexActivation(INIT_SIM_TICK) + } serviceStateData.copy( emHierarchy = updatedHierarchy, @@ -262,6 +278,7 @@ object ExtEmDataService serviceStateData.uuidToFlexAdapter + (modelUuid -> flexAdapter), flexAdapterToUuid = serviceStateData.flexAdapterToUuid + (flexAdapter -> modelUuid), + flexOptionResponse = updatedDataMap ) } @@ -292,19 +309,13 @@ object ExtEmDataService // entities for which flex options are requested val emEntities: Set[UUID] = requestEmFlexResults.emEntities().asScala.keys.toSet - - val inferior = serviceStateData.emHierarchy.getInferiors(emEntities) - val uuidToRef = serviceStateData.uuidToFlexAdapter val refs = emEntities.map(uuidToRef) - log.warn(s"$refs") + log.warn(s"Em refs: $refs") refs.foreach(_ ! FlexActivation(tick)) - updatedTick.copy( - extEmDataMessage = None, - flexOptionResponse = ReceiveMultiDataMap(emEntities), - ) + updatedTick.copy(extEmDataMessage = None) case requestEmSetPoints: RequestEmSetPoints => ctx.log.warn(s"$requestEmSetPoints") @@ -437,8 +448,9 @@ object ExtEmDataService case options: ProvideFlexOptions => handleFlexProvision(options, receiver) - case msg: FlexCompletion => - log.warn(s"$msg") + case completion: FlexCompletion => + receiver.map(_ ! completion) + log.warn(s"Completion: $completion") serviceStateData } @@ -496,17 +508,12 @@ object ExtEmDataService serviceStateData.flexOptionResponse } - log.warn(s"$updated") + log.warn(s"Updated data map: $updated") - if (updated.nonComplete) { - // responses are still incomplete - serviceStateData.copy( - flexOptionResponse = updated - ) - } else { + if (updated.hasCompletedKeys) { // all responses received, forward them to external simulation in a bundle - val (data, updatedFlexOptionResponse) = updated.removeLastFinished() + val (data, updatedFlexOptionResponse) = updated.getFinishedData serviceStateData.extEmDataConnection.queueExtResponseMsg( new FlexOptionsResponse( @@ -517,6 +524,11 @@ object ExtEmDataService serviceStateData.copy( flexOptionResponse = updatedFlexOptionResponse ) + } else { + // responses are still incomplete + serviceStateData.copy( + flexOptionResponse = updated + ) } } } @@ -527,11 +539,12 @@ object ExtEmDataService )(implicit serviceStateData: ExtEmDataStateData ): ExtEmDataStateData = { - log.warn(s"$scheduleFlexActivation") + log.warn(s"Flex activation: $scheduleFlexActivation") receiver match { case Right(ref) => if (scheduleFlexActivation.tick == INIT_SIM_TICK) { + log.warn(s"$ref: $scheduleFlexActivation") ref ! scheduleFlexActivation } else { log.warn(s"$scheduleFlexActivation not handled!") @@ -559,6 +572,8 @@ object ExtEmDataService if (flexActivation.tick == INIT_SIM_TICK) { receiver ! flexActivation + } else { + log.warn(s"Unhandled: $flexActivation") } serviceStateData diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala new file mode 100644 index 0000000000..ba37bbbfe2 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala @@ -0,0 +1,92 @@ +package edu.ie3.simona.util + +import org.slf4j.{Logger, LoggerFactory} + +final case class ReceiveHierarchicalDataMap[K, V]( + structure: Map[K, Set[K]], + allKeys: Set[K], + receivedData: Map[K, V] + ) { + private val log: Logger = LoggerFactory.getLogger(ReceiveHierarchicalDataMap.getClass) + + + def hasCompletedKeys: Boolean = structure.keySet.exists(isComplete) + + def isComplete(key: K): Boolean = + structure.get(key).forall(_.forall(receivedData.contains)) + + + def updateStructure( + key: Option[K], + subKey: K + ): ReceiveHierarchicalDataMap[K, V] = { + log.warn(s"Parent '$key' with sub '$subKey'.") + + val (updatedStructure, updatedKeys): (Map[K, Set[K]], Set[K]) = key match { + case Some(parent) => + + structure.get(parent) match { + case Some(subKeys) => + val allSubKeys = subKeys + subKey + + ( + structure ++ Map(parent -> allSubKeys), + allKeys + subKey + ) + case None => + + ( + structure ++ Map(parent -> Set(subKey)), + allKeys ++ List(parent, subKey) + ) + } + + case None => + ( + structure ++ Map(subKey -> Set.empty), + allKeys + subKey + ) + } + + copy( + structure = updatedStructure, + allKeys = updatedKeys + ) + } + + + def addData( + key: K, + value: V, + ): ReceiveHierarchicalDataMap[K, V] = { + + if (!allKeys.contains(key)) + throw new RuntimeException( + s"Received value $value for key $key, but no data has been expected for this key." + ) + + copy(receivedData = receivedData.updated(key, value)) + } + + + def getFinishedData: (Map[K, V], ReceiveHierarchicalDataMap[K, V]) = { + val dataMap = structure.keySet.filter(isComplete).flatMap(key => structure(key)) + .map(key => key -> receivedData(key)) + .toMap + + val updated = receivedData.removedAll(dataMap.keys) + + (dataMap, copy(receivedData = updated)) + } + +} + +object ReceiveHierarchicalDataMap { + + def empty[K, V]: ReceiveHierarchicalDataMap[K, V] = ReceiveHierarchicalDataMap( + Map.empty, + Set.empty, + Map.empty + ) + +} \ No newline at end of file From 0fc91889bdf01efa0a447fa98d8a48e41ef56327 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 25 Mar 2025 13:50:51 +0100 Subject: [PATCH 022/125] Saving changes. --- .../ie3/simona/agent/EnvironmentRefs.scala | 6 +- .../messages/services/ServiceMessage.scala | 5 +- .../simona/service/em/ExtEmDataService.scala | 168 +++++++++++++----- .../util/ReceiveHierarchicalDataMap.scala | 118 ++++++++---- 4 files changed, 210 insertions(+), 87 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala b/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala index b295b77106..36654cc936 100644 --- a/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala +++ b/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala @@ -8,7 +8,11 @@ package edu.ie3.simona.agent import edu.ie3.simona.event.RuntimeEvent import edu.ie3.simona.ontology.messages.SchedulerMessage -import edu.ie3.simona.ontology.messages.services.{EmMessage, EvMessage, WeatherMessage} +import edu.ie3.simona.ontology.messages.services.{ + EmMessage, + EvMessage, + WeatherMessage, +} import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.{ActorRef => ClassicRef} diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala index ac68c0b11b..87d263e7eb 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala @@ -27,7 +27,10 @@ import java.util.UUID /** Collections of all messages, that are send to and from the different * services */ -sealed trait ServiceMessage extends EmInternal with EvInternal with WeatherInternal +sealed trait ServiceMessage + extends EmInternal + with EvInternal + with WeatherInternal object ServiceMessage { 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 a7836cb94a..c6723ac310 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -9,7 +9,7 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.EmSetPointResult +import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexRequestResult} import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.api.data.em.{ExtEmDataConnection, NoSetPointValue} import edu.ie3.simona.api.data.ontology.DataMessageFromExt @@ -18,9 +18,19 @@ import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EmMessage -import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{RegisterForEmDataService, ServiceRegistrationMessage, ServiceResponseMessage} -import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + WrappedFlexRequest, + WrappedFlexResponse, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + RegisterForEmDataService, + ServiceRegistrationMessage, + ServiceResponseMessage, +} +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, +} import edu.ie3.simona.service.{ExtDataSupport, TypedSimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.TickLong @@ -38,7 +48,11 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.{Power => PsdmPower} -import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala} +import scala.jdk.CollectionConverters.{ + ListHasAsScala, + MapHasAsJava, + MapHasAsScala, +} import scala.jdk.OptionConverters.{RichOption, RichOptional} import scala.util.{Failure, Success, Try} @@ -105,8 +119,12 @@ object ExtEmDataService flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, extEmDataMessage: Option[EmDataMessageFromExt] = None, toSchedule: Map[UUID, ScheduleFlexActivation] = Map.empty, - flexOptionResponse: ReceiveHierarchicalDataMap[UUID, (UUID, FlexOptionsResult)] = - ReceiveHierarchicalDataMap.empty, + flexRequest: ReceiveHierarchicalDataMap[UUID, Boolean] = + ReceiveHierarchicalDataMap.empty(false), + flexOptionResponse: ReceiveHierarchicalDataMap[ + UUID, + (UUID, FlexOptionsResult), + ] = ReceiveHierarchicalDataMap.empty, setPointResponse: ReceiveDataMap[UUID, EmSetPointResult] = ReceiveDataMap.empty, ) extends ServiceBaseStateData @@ -116,8 +134,6 @@ object ExtEmDataService uuidToRef: Map[UUID, ActorRef[EmAgent.Request]] = Map.empty, uuidToFlexResponse: Map[UUID, ActorRef[FlexResponse]] = Map.empty, flexResponseToUuid: Map[ActorRef[FlexResponse], UUID] = Map.empty, - parentRefToRef: Map[ActorRef[FlexResponse], ActorRef[EmAgent.Request]] = - Map.empty, ) { def add(model: UUID, ref: ActorRef[EmAgent.Request]): EmHierarchy = copy( uuidToRef = uuidToRef + (model -> ref), @@ -135,7 +151,6 @@ object ExtEmDataService refToUuid = refToUuid + (ref -> model), uuidToFlexResponse = uuidToFlexResponse + (model -> parent), flexResponseToUuid = flexResponseToUuid + (parent -> model), - parentRefToRef = parentRefToRef + (parent -> ref), ) } @@ -170,7 +185,6 @@ object ExtEmDataService scheduleFlexActivation.scheduleKey.foreach(_.unlock()) } - } /** Initialize the concrete service implementation using the provided @@ -256,16 +270,16 @@ object ExtEmDataService )(implicit serviceStateData: ExtEmDataStateData): ExtEmDataStateData = { val hierarchy = serviceStateData.emHierarchy - val updatedDataMap = serviceStateData.flexOptionResponse.updateStructure( - parentUuid, - modelUuid - ) + val updatedFlexRequest = + serviceStateData.flexRequest.updateStructure(parentUuid, modelUuid) + val updatedFlexResponse = + serviceStateData.flexOptionResponse.updateStructure(parentUuid, modelUuid) val updatedHierarchy = parentEm match { case Some(parent) => hierarchy.add(modelUuid, modelActorRef, parent) case None => - hierarchy.add(modelUuid, modelActorRef) + hierarchy.add(modelUuid, modelActorRef, modelActorRef) } if (parentEm.isEmpty) { @@ -278,7 +292,8 @@ object ExtEmDataService serviceStateData.uuidToFlexAdapter + (modelUuid -> flexAdapter), flexAdapterToUuid = serviceStateData.flexAdapterToUuid + (flexAdapter -> modelUuid), - flexOptionResponse = updatedDataMap + flexRequest = updatedFlexRequest, + flexOptionResponse = updatedFlexResponse, ) } @@ -301,30 +316,33 @@ object ExtEmDataService val updatedStateData = serviceStateData.extEmDataMessage.getOrElse( throw ServiceException( - "ExtEMDataService was triggered without ExtEmDataMessage available" + "ExtEmDataService was triggered without ExtEmDataMessage available" ) ) match { - case requestEmFlexResults: RequestEmFlexResults => - ctx.log.warn(s"$requestEmFlexResults") + case provideFlexRequests: ProvideFlexRequestData => + ctx.log.warn(s"$provideFlexRequests") // entities for which flex options are requested - val emEntities: Set[UUID] = requestEmFlexResults.emEntities().asScala.keys.toSet + val emEntities: Set[UUID] = provideFlexRequests + .flexRequests() + .asScala + .flatMap { case (_, v) => v.asScala } + .toSet val uuidToRef = serviceStateData.uuidToFlexAdapter val refs = emEntities.map(uuidToRef) log.warn(s"Em refs: $refs") refs.foreach(_ ! FlexActivation(tick)) - updatedTick.copy(extEmDataMessage = None) - - case requestEmSetPoints: RequestEmSetPoints => - ctx.log.warn(s"$requestEmSetPoints") - - val uuids = requestEmSetPoints.emEntities().asScala.toSet + val updatedFlexRequest = + serviceStateData.flexRequest.addSubKeysToExpectedKeys(emEntities) + val updatedFlexOptionResponse = + serviceStateData.flexOptionResponse.addExpectedKeys(emEntities) updatedTick.copy( extEmDataMessage = None, - setPointResponse = ReceiveDataMap(uuids), + flexRequest = updatedFlexRequest, + flexOptionResponse = updatedFlexOptionResponse, ) case provideFlexOptions: ProvideEmFlexOptionData => @@ -336,6 +354,16 @@ object ExtEmDataService ctx.log.warn(s"$providedEmData") announceEmSetPoints(tick, providedEmData)(updatedTick, ctx) + + case requestEmSetPoints: RequestEmSetPoints => + ctx.log.warn(s"$requestEmSetPoints") + + val uuids = requestEmSetPoints.emEntities().asScala.toSet + + updatedTick.copy( + extEmDataMessage = None, + setPointResponse = ReceiveDataMap(uuids), + ) } (updatedStateData, None) @@ -355,12 +383,12 @@ object ExtEmDataService .foreach { case (agent, flexOptions) => hierarchy.getResponseRef(agent) match { case Some(receiver) => - flexOptions.asScala.foreach { flexOption => + flexOptions.asScala.foreach { case (sender, options) => receiver ! ProvideMinMaxFlexOptions( - flexOption.sender, - flexOption.pRef.toSquants, - flexOption.pMin.toSquants, - flexOption.pMax.toSquants, + sender, + options.pRef.toSquants, + options.pMin.toSquants, + options.pMax.toSquants, ) } @@ -495,13 +523,16 @@ object ExtEmDataService case ProvideMinMaxFlexOptions(modelUuid, ref, min, max) => serviceStateData.flexOptionResponse.addData( modelUuid, - (uuid, new FlexOptionsResult( - serviceStateData.startTime, // TODO: Fix this - modelUuid, - min.toQuantity, - ref.toQuantity, - max.toQuantity, - )), + ( + uuid, + new FlexOptionsResult( + serviceStateData.startTime, // TODO: Fix this + modelUuid, + min.toQuantity, + ref.toQuantity, + max.toQuantity, + ), + ), ) case _ => @@ -515,7 +546,7 @@ object ExtEmDataService val (data, updatedFlexOptionResponse) = updated.getFinishedData - serviceStateData.extEmDataConnection.queueExtResponseMsg( + queueExtResponseMsg( new FlexOptionsResponse( data.map { case (entity, (_, value)) => entity -> value }.asJava ) @@ -525,7 +556,7 @@ object ExtEmDataService flexOptionResponse = updatedFlexOptionResponse ) } else { - // responses are still incomplete + // responses are still incomplete serviceStateData.copy( flexOptionResponse = updated ) @@ -572,11 +603,41 @@ object ExtEmDataService if (flexActivation.tick == INIT_SIM_TICK) { receiver ! flexActivation + + serviceStateData } else { - log.warn(s"Unhandled: $flexActivation") - } + val uuid = serviceStateData.flexAdapterToUuid(receiver) - serviceStateData + log.warn(s"Receiver: $uuid") + + val updated = serviceStateData.flexRequest.addData( + uuid, + true, + ) + + log.warn(s"$updated") + + if (updated.hasCompletedKeys) { + + val (dataMap, updatedFlexRequest) = updated.getFinishedData + + log.warn(s"Data to be send: $dataMap") + + val map = dataMap.map { case (key, _) => + key -> + new FlexRequestResult( + flexActivation.tick.toDateTime(serviceStateData.startTime), + key, + ) + } + + queueExtResponseMsg(new FlexRequestResponse(map.asJava)) + + serviceStateData.copy(flexRequest = updatedFlexRequest) + } else { + serviceStateData.copy(flexRequest = updated) + } + } } private def handleFlexControl( @@ -623,7 +684,7 @@ object ExtEmDataService } else { // all responses received, forward them to external simulation in a bundle - serviceStateData.extEmDataConnection.queueExtResponseMsg( + queueExtResponseMsg( new EmSetPointDataResponse(updated.receivedData.asJava) ) @@ -633,4 +694,19 @@ object ExtEmDataService } } } + + private def queueExtResponseMsg( + msg: EmDataResponseMessageToExt + )(implicit serviceStateData: ExtEmDataStateData): Unit = { + val flexRequests = serviceStateData.flexRequest.allCompleted + val flexOptions = serviceStateData.flexOptionResponse.allCompleted + val setPoints = serviceStateData.setPointResponse.isComplete + + val isFinished = flexRequests && flexOptions && setPoints + + val connection = serviceStateData.extEmDataConnection + + connection.queueExtResponseMsg(msg, isFinished) + } + } diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala index ba37bbbfe2..dae0437564 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala @@ -1,78 +1,105 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + package edu.ie3.simona.util import org.slf4j.{Logger, LoggerFactory} final case class ReceiveHierarchicalDataMap[K, V]( - structure: Map[K, Set[K]], - allKeys: Set[K], - receivedData: Map[K, V] - ) { - private val log: Logger = LoggerFactory.getLogger(ReceiveHierarchicalDataMap.getClass) + withExpected: Boolean, + structure: Map[K, Set[K]], + allKeys: Set[K], + expectedKeys: Set[K], + receivedData: Map[K, V], +) { + private val log: Logger = + LoggerFactory.getLogger(ReceiveHierarchicalDataMap.getClass) + def allCompleted: Boolean = structure.keySet.forall(isComplete) def hasCompletedKeys: Boolean = structure.keySet.exists(isComplete) - def isComplete(key: K): Boolean = + def isComplete(key: K): Boolean = if (withExpected) { + structure + .get(key) + .map(_.intersect(expectedKeys)) + .forall(_.forall(receivedData.contains)) + } else { structure.get(key).forall(_.forall(receivedData.contains)) - + } def updateStructure( - key: Option[K], - subKey: K - ): ReceiveHierarchicalDataMap[K, V] = { + key: Option[K], + subKey: K, + ): ReceiveHierarchicalDataMap[K, V] = { log.warn(s"Parent '$key' with sub '$subKey'.") val (updatedStructure, updatedKeys): (Map[K, Set[K]], Set[K]) = key match { case Some(parent) => - structure.get(parent) match { - case Some(subKeys) => - val allSubKeys = subKeys + subKey - - ( - structure ++ Map(parent -> allSubKeys), - allKeys + subKey - ) - case None => - - ( - structure ++ Map(parent -> Set(subKey)), - allKeys ++ List(parent, subKey) - ) - } + case Some(subKeys) => + val allSubKeys = subKeys + subKey + + ( + structure ++ Map(parent -> allSubKeys), + allKeys + subKey, + ) + case None => + ( + structure ++ Map(parent -> Set(subKey)), + allKeys ++ List(parent, subKey), + ) + } case None => ( structure ++ Map(subKey -> Set.empty), - allKeys + subKey - ) + allKeys + subKey, + ) } copy( structure = updatedStructure, - allKeys = updatedKeys + allKeys = updatedKeys, ) } + def addExpectedKey(key: K): ReceiveHierarchicalDataMap[K, V] = + copy(expectedKeys = expectedKeys + key) + + def addExpectedKeys(keys: Set[K]): ReceiveHierarchicalDataMap[K, V] = + copy(expectedKeys = expectedKeys ++ keys) + + def addSubKeysToExpectedKeys(keys: Set[K]): ReceiveHierarchicalDataMap[K, V] = + copy(expectedKeys = expectedKeys ++ keys.flatMap(structure.get).flatten) def addData( - key: K, - value: V, - ): ReceiveHierarchicalDataMap[K, V] = { + key: K, + value: V, + ): ReceiveHierarchicalDataMap[K, V] = { if (!allKeys.contains(key)) throw new RuntimeException( s"Received value $value for key $key, but no data has been expected for this key." ) - copy(receivedData = receivedData.updated(key, value)) + copy( + expectedKeys = expectedKeys.excl(key), + receivedData = receivedData.updated(key, value), + ) } - def getFinishedData: (Map[K, V], ReceiveHierarchicalDataMap[K, V]) = { - val dataMap = structure.keySet.filter(isComplete).flatMap(key => structure(key)) - .map(key => key -> receivedData(key)) - .toMap + val dataMap = if (expectedKeys.nonEmpty) { + structure.keySet + .filter(isComplete) + .flatMap(key => structure(key)) + .map(key => key -> receivedData(key)) + .toMap + } else receivedData val updated = receivedData.removedAll(dataMap.keys) @@ -83,10 +110,23 @@ final case class ReceiveHierarchicalDataMap[K, V]( object ReceiveHierarchicalDataMap { - def empty[K, V]: ReceiveHierarchicalDataMap[K, V] = ReceiveHierarchicalDataMap( + def empty[K, V]: ReceiveHierarchicalDataMap[K, V] = + ReceiveHierarchicalDataMap( + withExpected = true, + Map.empty, + Set.empty, + Set.empty, + Map.empty, + ) + + def empty[K, V]( + withExpected: Boolean = true + ): ReceiveHierarchicalDataMap[K, V] = ReceiveHierarchicalDataMap( + withExpected, Map.empty, Set.empty, - Map.empty + Set.empty, + Map.empty, ) -} \ No newline at end of file +} From 5d5109ead7b08c152fef594e9d9148e28e5bf58e Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 26 Mar 2025 12:57:30 +0100 Subject: [PATCH 023/125] Adapted to typed actor in simonaAPI --- CHANGELOG.md | 1 + .../edu/ie3/simona/api/ExtSimAdapter.scala | 9 +- .../messages/services/ServiceMessage.scala | 4 +- .../ie3/simona/service/ExtDataSupport.scala | 13 +- .../simona/service/ev/ExtEvDataService.scala | 12 +- .../ie3/simona/sim/setup/ExtSimSetup.scala | 17 +-- .../simona/sim/setup/ExtSimSetupData.scala | 24 ++-- .../ie3/simona/api/ExtSimAdapterSpec.scala | 10 +- .../service/ev/ExtEvDataServiceSpec.scala | 117 +++++++++--------- 9 files changed, 103 insertions(+), 104 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cbcfabdfa..6570676cdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -167,6 +167,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Converting `PrimaryServiceProxy` to pekko typed [#1230](https://github.com/ie3-institute/simona/issues/1230) - Made some methods of `PvModel` static [#1217](https://github.com/ie3-institute/simona/issues/1217) - FlexOptions types in `FlexibilityMessage` [#1306](https://github.com/ie3-institute/simona/issues/1306) +- Adapted to typed actor in simonaAPI [#1311](https://github.com/ie3-institute/simona/issues/1311) ### Fixed - Fix rendering of references in documentation [#505](https://github.com/ie3-institute/simona/issues/505) diff --git a/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala b/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala index 1dfb6a79c5..251e92366c 100644 --- a/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala +++ b/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala @@ -7,7 +7,10 @@ package edu.ie3.simona.api import edu.ie3.simona.api.ExtSimAdapter.{Create, ExtSimAdapterStateData, Stop} -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.api.data.ontology.{ + DataMessageFromExt, + ScheduleDataServiceMessage, +} import edu.ie3.simona.api.simulation.ExtSimAdapterData import edu.ie3.simona.api.simulation.ontology.{ ActivationMessage, @@ -98,13 +101,13 @@ final case class ExtSimAdapter(scheduler: ActorRef) context become receiveIdle(stateData.copy(currentTick = None)) - case scheduleDataService: ScheduleDataServiceMessage => + case scheduleDataService: ScheduleDataServiceMessage[DataMessageFromExt] => val tick = stateData.currentTick.getOrElse( throw new RuntimeException("No tick has been triggered") ) val key = ScheduleLock.singleKey(context, scheduler.toTyped, tick) - scheduleDataService.getDataService ! ScheduleServiceActivation( + scheduleDataService.dataService ! ScheduleServiceActivation( tick, key, ) diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala index 6f8608e25f..7bcf789a49 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala @@ -27,8 +27,8 @@ object ServiceMessage { final case class WrappedActivation(activation: Activation) extends ServiceMessage - final case class WrappedExternalMessage( - extMsg: DataMessageFromExt + final case class WrappedExternalMessage[D <: DataMessageFromExt]( + extMsg: D ) extends ServiceMessage /** Service initialization data can sometimes only be constructed once the diff --git a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala index 6ec4c5b6b5..6ecaeff9fa 100644 --- a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala +++ b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala @@ -22,7 +22,8 @@ import org.apache.pekko.actor.typed.{ActorRef, Behavior} * the type of messages this service accepts */ trait ExtDataSupport[ - T >: ServiceMessage + T >: ServiceMessage, + D <: DataMessageFromExt, ] { this: SimonaService[T] => @@ -34,14 +35,14 @@ trait ExtDataSupport[ * @return * The behavior of the adapter. */ - def adapter(service: ActorRef[T]): Behavior[DataMessageFromExt] = - Behaviors.receiveMessagePartial[DataMessageFromExt] { + def adapter(service: ActorRef[T]): Behavior[D] = + Behaviors.receiveMessagePartial[D] { case scheduleServiceActivation: ScheduleServiceActivation => // TODO: Refactor this with scala3 service ! scheduleServiceActivation Behaviors.same - case extMsg => + case extMsg: D => service ! WrappedExternalMessage(extMsg) Behaviors.same } @@ -50,7 +51,7 @@ trait ExtDataSupport[ stateData: S, constantData: ServiceConstantStateData, ): PartialFunction[(ActorContext[T], T), Behavior[T]] = { - case (_, WrappedExternalMessage(extMsg)) => + case (_, WrappedExternalMessage(extMsg: D)) => val updatedStateData = handleDataMessage(extMsg)(stateData) idle(updatedStateData, constantData) @@ -72,7 +73,7 @@ trait ExtDataSupport[ * the updated state data */ protected def handleDataMessage( - extMsg: DataMessageFromExt + extMsg: D )(implicit serviceStateData: S): S /** Handle a message from inside SIMONA sent to external 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 3ba6a3f683..35a4f93775 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -14,7 +14,6 @@ import edu.ie3.simona.agent.participant2.ParticipantAgent.{ import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.data.ev.model.EvModel import edu.ie3.simona.api.data.ev.ontology._ -import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{ CriticalFailureException, @@ -47,7 +46,7 @@ import scala.util.{Failure, Success, Try} object ExtEvDataService extends SimonaService[EvMessage] - with ExtDataSupport[EvMessage] { + with ExtDataSupport[EvMessage, EvDataMessageFromExt] { override type S = ExtEvStateData @@ -334,14 +333,9 @@ object ExtEvDataService } override protected def handleDataMessage( - extMsg: DataMessageFromExt + extMsg: EvDataMessageFromExt )(implicit serviceStateData: S): S = - extMsg match { - case extEvMessage: EvDataMessageFromExt => - serviceStateData.copy( - extEvMessage = Some(extEvMessage) - ) - } + serviceStateData.copy(extEvMessage = Some(extMsg)) override protected def handleDataResponseMessage( extResponseMsg: ServiceResponseMessage diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index 29e70f5c04..a5dca1a24d 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -11,6 +11,7 @@ import edu.ie3.simona.api.data.ExtInputDataConnection import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection +import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt import edu.ie3.simona.api.simulation.{ExtSimAdapterData, ExtSimulation} import edu.ie3.simona.api.{ExtLinkInterface, ExtSimAdapter} import edu.ie3.simona.exceptions.ServiceException @@ -23,11 +24,11 @@ import edu.ie3.simona.service.ev.ExtEvDataService.InitExtEvData import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK import org.apache.pekko.actor.typed.scaladsl.ActorContext import org.apache.pekko.actor.typed.scaladsl.adapter.{ + ClassicActorRefOps, TypedActorContextOps, TypedActorRefOps, } import org.apache.pekko.actor.typed.{ActorRef, Behavior} -import org.apache.pekko.actor.{ActorRef => ClassicRef} import org.slf4j.{Logger, LoggerFactory} import java.util.UUID @@ -73,7 +74,7 @@ object ExtSimSetup { // creating the adapter data implicit val extSimAdapterData: ExtSimAdapterData = - new ExtSimAdapterData(extSimAdapter, args) + new ExtSimAdapterData(extSimAdapter.toTyped, args) Try { // sets up the external simulation @@ -132,7 +133,8 @@ object ExtSimSetup { extSimAdapterData: ExtSimAdapterData, resolution: FiniteDuration, ): ExtSimSetupData = { - implicit val extSimAdapter: ClassicRef = extSimAdapterData.getAdapter + implicit val extSimAdapter: ActorRef[ControlResponseMessageFromExt] = + extSimAdapterData.getAdapter // the data connections this external simulation provides val connections = extSimulation.getDataConnections.asScala @@ -197,18 +199,19 @@ object ExtSimSetup { * The reference to the service. */ private[setup] def setupInputService[ - T <: ExtInputDataConnection, + T <: ExtInputDataConnection[D], M >: ServiceMessage, + D <: DataMessageFromExt, ]( extInputDataConnection: T, behavior: Behavior[M], - adapterToExt: ActorRef[M] => Behavior[DataMessageFromExt], + adapterToExt: ActorRef[M] => Behavior[D], name: String, initData: T => InitializeServiceStateData, )(implicit context: ActorContext[_], scheduler: ActorRef[SchedulerMessage], - extSimAdapter: ClassicRef, + extSimAdapter: ActorRef[ControlResponseMessageFromExt], ): ActorRef[M] = { val extDataService = context.spawn( behavior, @@ -230,7 +233,7 @@ object ExtSimSetup { ) extInputDataConnection.setActorRefs( - adapter.toClassic, + adapter, extSimAdapter, ) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index 437acd24e6..00518cabb8 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -9,9 +9,12 @@ package edu.ie3.simona.sim.setup import edu.ie3.simona.api.data.ExtInputDataConnection import edu.ie3.simona.api.data.em.ExtEmDataConnection import edu.ie3.simona.api.data.ev.ExtEvDataConnection +import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection +import edu.ie3.simona.api.data.primarydata.ontology.PrimaryDataMessageFromExt import edu.ie3.simona.api.data.results.ExtResultDataConnection import edu.ie3.simona.ontology.messages.services.{EvMessage, ServiceMessage} +import edu.ie3.simona.sim.setup.ExtSimSetupData.Input import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.{ActorRef => ClassicRef} @@ -21,20 +24,16 @@ import org.apache.pekko.actor.{ActorRef => ClassicRef} * @param extSimAdapters * All adapters to external simulations. * @param extPrimaryDataServices - * Map: external primary data connections to service references. + * Seq: external primary data connections to service references. * @param extDataServices - * Map: external input data connection to service references. + * Seq: external input data connection to service references. * @param extResultListeners * Map: external result data connections to result data providers. */ final case class ExtSimSetupData( extSimAdapters: Iterable[ClassicRef], - extPrimaryDataServices: Seq[ - (ExtPrimaryDataConnection, ActorRef[_ >: ServiceMessage]) - ], - extDataServices: Seq[ - (_ <: ExtInputDataConnection, ActorRef[_ >: ServiceMessage]) - ], + extPrimaryDataServices: Seq[Input[PrimaryDataMessageFromExt]], + extDataServices: Seq[Input[_ <: DataMessageFromExt]], extResultListeners: Seq[(ExtResultDataConnection, ActorRef[_])], ) { @@ -47,7 +46,7 @@ final case class ExtSimSetupData( ) private[setup] def update( - connection: ExtInputDataConnection, + connection: ExtInputDataConnection[_], ref: ActorRef[_ >: ServiceMessage], ): ExtSimSetupData = connection match { case primaryConnection: ExtPrimaryDataConnection => @@ -71,8 +70,8 @@ final case class ExtSimSetupData( } def emDataService: Option[ActorRef[ServiceMessage]] = - extDataServices.collectFirst { case (_: ExtEmDataConnection, ref) => - ref + extDataServices.collectFirst { + case (_: ExtEmDataConnection, ref: ActorRef[ServiceMessage]) => ref } def evDataConnection: Option[ExtEvDataConnection] = @@ -98,6 +97,9 @@ final case class ExtSimSetupData( object ExtSimSetupData { + type Input[T <: DataMessageFromExt] = + (ExtInputDataConnection[T], ActorRef[_ >: ServiceMessage]) + /** Returns an empty [[ExtSimSetupData]]. */ def apply: ExtSimSetupData = ExtSimSetupData( diff --git a/src/test/scala/edu/ie3/simona/api/ExtSimAdapterSpec.scala b/src/test/scala/edu/ie3/simona/api/ExtSimAdapterSpec.scala index b1d5d34028..f2ba9119cb 100644 --- a/src/test/scala/edu/ie3/simona/api/ExtSimAdapterSpec.scala +++ b/src/test/scala/edu/ie3/simona/api/ExtSimAdapterSpec.scala @@ -59,7 +59,7 @@ class ExtSimAdapterSpec new ExtSimAdapter(scheduler.ref) ) - val extData = new ExtSimAdapterData(extSimAdapter, mainArgs) + val extData = new ExtSimAdapterData(extSimAdapter.toTyped, mainArgs) val key1 = ScheduleKey(lock.ref.toTyped, UUID.randomUUID()) scheduler.send(extSimAdapter, ExtSimAdapter.Create(extData, key1)) @@ -77,7 +77,7 @@ class ExtSimAdapterSpec new ExtSimAdapter(scheduler.ref) ) - val extData = new ExtSimAdapterData(extSimAdapter, mainArgs) + val extData = new ExtSimAdapterData(extSimAdapter.toTyped, mainArgs) val key1 = ScheduleKey(lock.ref.toTyped, UUID.randomUUID()) scheduler.send(extSimAdapter, ExtSimAdapter.Create(extData, key1)) @@ -116,7 +116,7 @@ class ExtSimAdapterSpec new ExtSimAdapter(scheduler.ref) ) - val extData = new ExtSimAdapterData(extSimAdapter, mainArgs) + val extData = new ExtSimAdapterData(extSimAdapter.toTyped, mainArgs) val dataService = TestProbe("dataService") val key1 = ScheduleKey(lock.ref.toTyped, UUID.randomUUID()) @@ -136,7 +136,7 @@ class ExtSimAdapterSpec extData.receiveMessageQueue.take() extSimAdapter ! new ScheduleDataServiceMessage( - dataService.ref + dataService.ref.toTyped ) scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled @@ -154,7 +154,7 @@ class ExtSimAdapterSpec new ExtSimAdapter(scheduler.ref) ) - val extData = new ExtSimAdapterData(extSimAdapter, mainArgs) + val extData = new ExtSimAdapterData(extSimAdapter.toTyped, mainArgs) val key1 = ScheduleKey(lock.ref.toTyped, UUID.randomUUID()) scheduler.send(extSimAdapter, ExtSimAdapter.Create(extData, key1)) diff --git a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala index 416c5869b2..b3195c7589 100644 --- a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala @@ -15,6 +15,7 @@ import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.data.ev.model.EvModel import edu.ie3.simona.api.data.ev.ontology._ import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt import edu.ie3.simona.model.participant2.evcs.EvModelWrapper import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, @@ -37,7 +38,6 @@ import org.apache.pekko.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe, } -import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import org.apache.pekko.testkit.TestKit.awaitCond import org.scalatest.wordspec.AnyWordSpecLike import tech.units.indriya.quantity.Quantities @@ -57,7 +57,9 @@ class ExtEvDataServiceSpec implicit def wrap(msg: Activation): WrappedActivation = WrappedActivation(msg) - implicit def wrap(msg: EvDataMessageFromExt): WrappedExternalMessage = + implicit def wrap( + msg: EvDataMessageFromExt + ): WrappedExternalMessage[EvDataMessageFromExt] = WrappedExternalMessage(msg) private val evcs1UUID = @@ -68,12 +70,13 @@ class ExtEvDataServiceSpec "An uninitialized ev movement service" must { "send correct completion message after initialisation" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val evService = spawn(ExtEvDataService(scheduler.ref)) val adapter = spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(adapter.toClassic, extSimAdapter.ref.toClassic) + extEvData.setActorRefs(adapter, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) // lock activation scheduled @@ -91,12 +94,13 @@ class ExtEvDataServiceSpec "stash registration request and handle it correctly once initialized" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val evService = spawn(ExtEvDataService(scheduler.ref)) val adapter = spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(adapter.toClassic, extSimAdapter.ref.toClassic) + extEvData.setActorRefs(adapter, extSimAdapter.ref) val evcs1 = TestProbe[ParticipantAgent.Request]("evcs1") @@ -125,12 +129,13 @@ class ExtEvDataServiceSpec "handle duplicate registrations correctly" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val evService = spawn(ExtEvDataService(scheduler.ref)) val adapter = spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(adapter.toClassic, extSimAdapter.ref.toClassic) + extEvData.setActorRefs(adapter, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) // lock activation scheduled @@ -161,9 +166,8 @@ class ExtEvDataServiceSpec Some(long2Long(0L)).toJava, ) ) - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + scheduler.expectNoMessage() evService ! Activation(INIT_SIM_TICK) @@ -176,12 +180,13 @@ class ExtEvDataServiceSpec "fail when activated without having received ExtEvMessage" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val evService = spawn(ExtEvDataService(scheduler.ref)) val adapter = spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(adapter.toClassic, extSimAdapter.ref.toClassic) + extEvData.setActorRefs(adapter, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) // lock activation scheduled @@ -206,12 +211,13 @@ class ExtEvDataServiceSpec "handle free lots requests correctly and forward them to the correct evcs" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val evService = spawn(ExtEvDataService(scheduler.ref)) val adapter = spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(adapter.toClassic, extSimAdapter.ref.toClassic) + extEvData.setActorRefs(adapter, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) // lock activation scheduled @@ -238,9 +244,7 @@ class ExtEvDataServiceSpec Some(long2Long(0L)).toJava, ) ) - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) scheduler.expectNoMessage() @@ -257,9 +261,7 @@ class ExtEvDataServiceSpec // ev service should receive request at this moment // scheduler should receive schedule msg - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) val tick = 0L @@ -297,12 +299,13 @@ class ExtEvDataServiceSpec "handle price requests correctly by returning dummy values" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val evService = spawn(ExtEvDataService(scheduler.ref)) val adapter = spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(adapter.toClassic, extSimAdapter.ref.toClassic) + extEvData.setActorRefs(adapter, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) // lock activation scheduled @@ -329,9 +332,8 @@ class ExtEvDataServiceSpec Some(long2Long(0L)).toJava, ) ) - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + evService ! Activation(INIT_SIM_TICK) scheduler.expectMessage(Completion(activationMsg.actor)) @@ -343,9 +345,7 @@ class ExtEvDataServiceSpec // ev service should receive request at this moment // scheduler should receive schedule msg - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) val tick = 0L @@ -375,12 +375,13 @@ class ExtEvDataServiceSpec "return free lots requests right away if there are no evcs registered" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val evService = spawn(ExtEvDataService(scheduler.ref)) val adapter = spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(adapter.toClassic, extSimAdapter.ref.toClassic) + extEvData.setActorRefs(adapter, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) // lock activation scheduled @@ -397,9 +398,7 @@ class ExtEvDataServiceSpec // ev service should receive movements msg at this moment // scheduler receives schedule msg - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) val tick = 0L @@ -421,12 +420,13 @@ class ExtEvDataServiceSpec "handle ev departure requests correctly and return departed evs" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val evService = spawn(ExtEvDataService(scheduler.ref)) val adapter = spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(adapter.toClassic, extSimAdapter.ref.toClassic) + extEvData.setActorRefs(adapter, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) // lock activation scheduled @@ -453,9 +453,8 @@ class ExtEvDataServiceSpec Some(long2Long(0L)).toJava, ) ) - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + evService ! Activation(INIT_SIM_TICK) scheduler.expectMessage(Completion(activationMsg.actor)) @@ -474,9 +473,7 @@ class ExtEvDataServiceSpec // ev service should receive departure msg at this moment // scheduler should receive schedule msg - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) val tick = 0L @@ -528,12 +525,13 @@ class ExtEvDataServiceSpec "return ev departure requests right away if request list is empty" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val evService = spawn(ExtEvDataService(scheduler.ref)) val adapter = spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(adapter.toClassic, extSimAdapter.ref.toClassic) + extEvData.setActorRefs(adapter, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) // lock activation scheduled @@ -551,9 +549,7 @@ class ExtEvDataServiceSpec // ev service should receive departure msg at this moment // scheduler should receive schedule msg - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) val tick = 0L @@ -575,12 +571,13 @@ class ExtEvDataServiceSpec "handle ev arrivals correctly and forward them to the correct evcs" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val evService = spawn(ExtEvDataService(scheduler.ref)) val adapter = spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(adapter.toClassic, extSimAdapter.ref.toClassic) + extEvData.setActorRefs(adapter, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) // lock activation scheduled @@ -607,9 +604,8 @@ class ExtEvDataServiceSpec Some(long2Long(0L)).toJava, ) ) - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + evService ! Activation(INIT_SIM_TICK) scheduler.expectMessage(Completion(activationMsg.actor)) @@ -628,9 +624,7 @@ class ExtEvDataServiceSpec // ev service should receive movements msg at this moment // scheduler receive schedule msg - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) val tick = 0L @@ -659,12 +653,13 @@ class ExtEvDataServiceSpec "skip a movements provision from an evcs that is not registered" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val evService = spawn(ExtEvDataService(scheduler.ref)) val adapter = spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(adapter.toClassic, extSimAdapter.ref.toClassic) + extEvData.setActorRefs(adapter, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) // lock activation scheduled @@ -687,9 +682,8 @@ class ExtEvDataServiceSpec Some(long2Long(0L)).toJava, ) ) - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + evService ! Activation(INIT_SIM_TICK) scheduler.expectMessage(Completion(activationMsg.actor)) @@ -706,7 +700,8 @@ class ExtEvDataServiceSpec // ev service should receive movements msg at this moment // scheduler should receive schedule msg - extSimAdapter.expectMessageType[ScheduleDataServiceMessage] + extSimAdapter + .expectMessageType[ScheduleDataServiceMessage[EvDataMessageFromExt]] val tick = 0L From 42fb9c3acba375b7c54f9c120c8b24b5e2eb994d Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 26 Mar 2025 16:32:46 +0100 Subject: [PATCH 024/125] Adapt to changes in `dev`. --- .../ie3/simona/agent/EnvironmentRefs.scala | 2 +- .../edu/ie3/simona/agent/em/EmAgent.scala | 10 ++++++-- .../ie3/simona/service/SimonaService.scala | 9 ++++++++ .../simona/service/em/ExtEmDataService.scala | 18 ++++++++------- .../service/primary/PrimaryServiceProxy.scala | 23 ++++++++----------- .../util/ReceiveHierarchicalDataMap.scala | 8 +++---- 6 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala b/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala index b6afebc8d8..194a38646a 100644 --- a/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala +++ b/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala @@ -12,7 +12,7 @@ import edu.ie3.simona.ontology.messages.services.{ EmMessage, EvMessage, ServiceMessage, - WeatherMessage + WeatherMessage, } import org.apache.pekko.actor.typed.ActorRef diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index a412dde134..bae754e9da 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -12,11 +12,17 @@ import edu.ie3.simona.agent.participant.data.Data.PrimaryData.ComplexPower import edu.ie3.simona.agent.participant.statedata.BaseStateData.FlexControlledData import edu.ie3.simona.config.RuntimeConfig.EmRuntimeConfig import edu.ie3.simona.event.ResultEvent -import edu.ie3.simona.event.ResultEvent.{FlexOptionsResultEvent, ParticipantResultEvent} +import edu.ie3.simona.event.ResultEvent.{ + FlexOptionsResultEvent, + ParticipantResultEvent, +} import edu.ie3.simona.event.notifier.NotifierConfig import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.model.em.{EmModelShell, EmTools} -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.services.EmMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService diff --git a/src/main/scala/edu/ie3/simona/service/SimonaService.scala b/src/main/scala/edu/ie3/simona/service/SimonaService.scala index 454b145cad..76eb610914 100644 --- a/src/main/scala/edu/ie3/simona/service/SimonaService.scala +++ b/src/main/scala/edu/ie3/simona/service/SimonaService.scala @@ -16,6 +16,7 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ Create, ScheduleServiceActivation, ServiceRegistrationMessage, + ServiceResponseMessage, WrappedActivation, } import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} @@ -144,6 +145,10 @@ abstract class SimonaService[ buffer.stash(msg) Behaviors.same + case (ctx, msg: ServiceResponseMessage) => + handleServiceResponse(msg)(ctx) + Behaviors.same + // unhandled message case (ctx, x) => ctx.log.error(s"Received unhandled message: $x") @@ -251,6 +256,10 @@ abstract class SimonaService[ initServiceData: InitializeServiceStateData ): Try[(S, Option[Long])] + protected def handleServiceResponse( + serviceResponse: ServiceResponseMessage + )(implicit ctx: ActorContext[T]): Unit = {} + /** Handle a request to register for information from this service * * @param registrationMessage 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 c6723ac310..0281ab57f4 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -16,7 +16,7 @@ import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ -import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions +import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EmMessage import edu.ie3.simona.ontology.messages.services.EmMessage.{ WrappedFlexRequest, @@ -27,11 +27,11 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ ServiceRegistrationMessage, ServiceResponseMessage, } +import edu.ie3.simona.service.{ExtDataSupport, SimonaService} import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceBaseStateData, } -import edu.ie3.simona.service.{ExtDataSupport, TypedSimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.simona.util.{ReceiveDataMap, ReceiveHierarchicalDataMap} @@ -57,7 +57,7 @@ import scala.jdk.OptionConverters.{RichOption, RichOptional} import scala.util.{Failure, Success, Try} object ExtEmDataService - extends TypedSimonaService[EmMessage] + extends SimonaService[EmMessage] with ExtDataSupport[EmMessage] { private val log: Logger = LoggerFactory.getLogger(ExtEmDataService.getClass) @@ -384,11 +384,13 @@ object ExtEmDataService hierarchy.getResponseRef(agent) match { case Some(receiver) => flexOptions.asScala.foreach { case (sender, options) => - receiver ! ProvideMinMaxFlexOptions( + receiver ! ProvideFlexOptions( sender, - options.pRef.toSquants, - options.pMin.toSquants, - options.pMax.toSquants, + MinMaxFlexOptions( + options.pRef.toSquants, + options.pMin.toSquants, + options.pMax.toSquants, + ), ) } @@ -520,7 +522,7 @@ object ExtEmDataService } val updated = provideFlexOptions match { - case ProvideMinMaxFlexOptions(modelUuid, ref, min, max) => + case ProvideFlexOptions(modelUuid, MinMaxFlexOptions(ref, min, max)) => serviceStateData.flexOptionResponse.addData( modelUuid, ( diff --git a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala index 6a83c43f6f..5c6d36db1e 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala @@ -40,10 +40,7 @@ import edu.ie3.simona.exceptions.{ InitializationException, InvalidConfigParameterException, } -import edu.ie3.simona.logging.SimonaActorLogging -import edu.ie3.simona.ontology.messages.Activation -import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion -import edu.ie3.simona.ontology.messages.services.ServiceMessage +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, @@ -55,7 +52,6 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ WorkerRegistrationMessage, WrappedActivation, } -import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceStateData import edu.ie3.simona.service.ServiceStateData.{ @@ -75,11 +71,6 @@ import org.apache.pekko.actor.typed.scaladsl.{ } import org.apache.pekko.actor.typed.{ActorRef, Behavior} import org.slf4j.Logger -import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps -import org.apache.pekko.actor.{Actor, ActorRef, PoisonPill, Props} -import org.apache.pekko.actor.typed.{ActorRef => TypedRef} -import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps -import org.apache.pekko.actor.{Actor, ActorRef, PoisonPill, Props, Stash} import java.nio.file.Paths import java.text.SimpleDateFormat @@ -110,6 +101,9 @@ object PrimaryServiceProxy { final case class InitPrimaryServiceProxyStateData( primaryConfig: PrimaryConfig, simulationStart: ZonedDateTime, + extSimulationData: Seq[ + (ExtPrimaryDataConnection, ActorRef[ServiceMessage]) + ], ) extends InitializeServiceStateData /** Holding the state of an initialized proxy. @@ -131,6 +125,7 @@ object PrimaryServiceProxy { simulationStart: ZonedDateTime, primaryConfig: PrimaryConfig, mappingSource: TimeSeriesMappingSource, + extSubscribersToService: Map[UUID, ActorRef[ServiceMessage]] = Map.empty, ) extends ServiceStateData /** Giving reference to the target time series and source worker. @@ -283,7 +278,7 @@ object PrimaryServiceProxy { primaryConfig: PrimaryConfig, simulationStart: ZonedDateTime, extSimulationData: Seq[ - (ExtPrimaryDataConnection, TypedRef[ServiceMessage]) + (ExtPrimaryDataConnection, ActorRef[ServiceMessage]) ], )(implicit log: Logger): Try[PrimaryServiceStateData] = { createSources(primaryConfig).map { @@ -417,9 +412,9 @@ object PrimaryServiceProxy { stateData.extSubscribersToService.get(modelUuid) match { case Some(_) => /* There is external data apparent for this model */ - val updatedStateData = handleExternalModel(modelUuid, stateData, requestingActor) + handleExternalModel(modelUuid, stateData, requestingActor) - onMessage(updatedStateData) + Behaviors.same case None => ctx.log.debug( @@ -518,7 +513,7 @@ object PrimaryServiceProxy { protected def handleExternalModel( modelUuid: UUID, stateData: PrimaryServiceStateData, - requestingActor: ActorRef, + requestingActor: ActorRef[ParticipantAgent.Request], ): Unit = { stateData.extSubscribersToService.foreach { case (_, ref) => ref ! PrimaryServiceRegistrationMessage( diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala index dae0437564..7b5835646a 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala @@ -95,10 +95,10 @@ final case class ReceiveHierarchicalDataMap[K, V]( def getFinishedData: (Map[K, V], ReceiveHierarchicalDataMap[K, V]) = { val dataMap = if (expectedKeys.nonEmpty) { structure.keySet - .filter(isComplete) - .flatMap(key => structure(key)) - .map(key => key -> receivedData(key)) - .toMap + .filter(isComplete) + .flatMap(key => structure(key)) + .map(key => key -> receivedData(key)) + .toMap } else receivedData val updated = receivedData.removedAll(dataMap.keys) From 6c59247f925a77dafaea48ad2c65760b70d6c3c5 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 27 Mar 2025 16:09:51 +0100 Subject: [PATCH 025/125] Saving changes. --- .../flex/MinMaxFixFlexibilityMessage.scala | 30 ++++----- .../simona/service/em/ExtEmDataService.scala | 65 ++++++++----------- .../primary/ExtPrimaryDataService.scala | 41 ++++-------- 3 files changed, 54 insertions(+), 82 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala index 6d42c70d0a..89b7f4a12a 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala @@ -7,7 +7,6 @@ package edu.ie3.simona.ontology.messages.flex import edu.ie3.simona.exceptions.CriticalFailureException -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.ProvideFlexOptions import edu.ie3.util.scala.quantities.DefaultQuantities._ import squants.Power @@ -22,8 +21,6 @@ object MinMaxFixFlexibilityMessage { * maximum power. It is possible that the power values are either all * negative or all positive, meaning that feed-in or load is mandatory. * - * @param modelUuid - * The UUID of the flex options provider asset model * @param ref * The reference active power that the flex options provider would * produce/consume regularly at the current tick, i.e. if it was not @@ -35,24 +32,23 @@ object MinMaxFixFlexibilityMessage { * The maximum active power that the flex options provider allows at the * current tick */ - final case class ProvideMinMaxFixFlexOptions private ( - override val modelUuid: UUID, + final case class MinMaxFixFlexOptions private( ref: Power, min: Power, max: Power, fix: Power, - ) extends ProvideFlexOptions + ) extends FlexOptions - object ProvideMinMaxFixFlexOptions { + object MinMaxFixFlexOptions { implicit class RichIterable( - private val flexOptions: Iterable[ProvideMinMaxFixFlexOptions] + private val flexOptions: Iterable[MinMaxFixFlexOptions] ) extends AnyVal { def flexSum: (Power, Power, Power, Power) = flexOptions.foldLeft((zeroKW, zeroKW, zeroKW, zeroKW)) { case ( (sumRef, sumMin, sumMax, sumFix), - ProvideMinMaxFixFlexOptions(_, addRef, addMin, addMax, addFix), + MinMaxFixFlexOptions(addRef, addMin, addMax, addFix), ) => ( sumRef + addRef, @@ -64,7 +60,7 @@ object MinMaxFixFlexibilityMessage { } /** Creates a - * [[edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions]] + * [[edu.ie3.simona.ontology.messages.flex.MinMaxFixFlexibilityMessage.MinMaxFixFlexOptions]] * message with sanity checks regarding the power values * * @param modelUuid @@ -81,7 +77,7 @@ object MinMaxFixFlexibilityMessage { * current tick * @return * The - * [[edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions]] + * [[edu.ie3.simona.ontology.messages.flex.MinMaxFixFlexibilityMessage.MinMaxFixFlexOptions]] * message */ def apply( @@ -90,7 +86,7 @@ object MinMaxFixFlexibilityMessage { min: Power, max: Power, fix: Power, - ): ProvideMinMaxFixFlexOptions = { + ): MinMaxFixFlexOptions = { if (min > ref) throw new CriticalFailureException( s"Minimum power $min is greater than reference power $ref" @@ -101,11 +97,11 @@ object MinMaxFixFlexibilityMessage { s"Reference power $ref is greater than maximum power $max" ) - new ProvideMinMaxFixFlexOptions(modelUuid, ref, min, max, fix) + new MinMaxFixFlexOptions(ref, min, max, fix) } /** Creates a - * [[edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions]] + * [[edu.ie3.simona.ontology.messages.flex.MinMaxFixFlexibilityMessage.MinMaxFixFlexOptions]] * message that does not allow any flexibility, meaning that min = ref = * max power. * @@ -115,13 +111,13 @@ object MinMaxFixFlexibilityMessage { * The active power that the flex provider requires * @return * The corresponding - * [[edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions]] + * [[edu.ie3.simona.ontology.messages.flex.MinMaxFixFlexibilityMessage.MinMaxFixFlexOptions]] * message */ def noFlexOption( modelUuid: UUID, power: Power, - ): ProvideMinMaxFixFlexOptions = - ProvideMinMaxFixFlexOptions(modelUuid, power, power, power, zeroKW) + ): MinMaxFixFlexOptions = + MinMaxFixFlexOptions(modelUuid, power, power, power, zeroKW) } } 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 0281ab57f4..f0874242ba 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -10,7 +10,7 @@ import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexRequestResult} -import edu.ie3.simona.api.data.em.ontology._ +import edu.ie3.simona.api.data.em.ontology.{RequestEmCompletion, _} import edu.ie3.simona.api.data.em.{ExtEmDataConnection, NoSetPointValue} import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException @@ -18,20 +18,10 @@ import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EmMessage -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - WrappedFlexRequest, - WrappedFlexResponse, -} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - RegisterForEmDataService, - ServiceRegistrationMessage, - ServiceResponseMessage, -} +import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{RegisterForEmDataService, ServiceRegistrationMessage, ServiceResponseMessage} import edu.ie3.simona.service.{ExtDataSupport, SimonaService} -import edu.ie3.simona.service.ServiceStateData.{ - InitializeServiceStateData, - ServiceBaseStateData, -} +import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.simona.util.{ReceiveDataMap, ReceiveHierarchicalDataMap} @@ -48,11 +38,7 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.{Power => PsdmPower} -import scala.jdk.CollectionConverters.{ - ListHasAsScala, - MapHasAsJava, - MapHasAsScala, -} +import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala} import scala.jdk.OptionConverters.{RichOption, RichOptional} import scala.util.{Failure, Success, Try} @@ -319,6 +305,26 @@ object ExtEmDataService "ExtEmDataService was triggered without ExtEmDataMessage available" ) ) match { + case requestEmCompletion : RequestEmCompletion => + + if (requestEmCompletion.tick != tick) { + log.warn(s"Received completion request for tick '${requestEmCompletion.tick}' in tick '$tick'.") + serviceStateData + + } else { + ctx.log.info(s"Receive a request for completion for tick '$tick'.") + + val setEms = serviceStateData.setPointResponse.getExpectedKeys + + serviceStateData.uuidToFlexAdapter.foreach { case (uuid, adapter) if !setEms.contains(uuid) => + adapter ! IssueNoControl(tick) + } + + serviceStateData.extEmDataConnection.queueExtResponseMsg(new EmCompletion()) + + serviceStateData.copy(setPointResponse = ReceiveDataMap.empty) + } + case provideFlexRequests: ProvideFlexRequestData => ctx.log.warn(s"$provideFlexRequests") @@ -548,7 +554,7 @@ object ExtEmDataService val (data, updatedFlexOptionResponse) = updated.getFinishedData - queueExtResponseMsg( + serviceStateData.extEmDataConnection.queueExtResponseMsg( new FlexOptionsResponse( data.map { case (entity, (_, value)) => entity -> value }.asJava ) @@ -633,7 +639,7 @@ object ExtEmDataService ) } - queueExtResponseMsg(new FlexRequestResponse(map.asJava)) + serviceStateData.extEmDataConnection.queueExtResponseMsg(new FlexRequestResponse(map.asJava)) serviceStateData.copy(flexRequest = updatedFlexRequest) } else { @@ -686,7 +692,7 @@ object ExtEmDataService } else { // all responses received, forward them to external simulation in a bundle - queueExtResponseMsg( + serviceStateData.extEmDataConnection.queueExtResponseMsg( new EmSetPointDataResponse(updated.receivedData.asJava) ) @@ -696,19 +702,4 @@ object ExtEmDataService } } } - - private def queueExtResponseMsg( - msg: EmDataResponseMessageToExt - )(implicit serviceStateData: ExtEmDataStateData): Unit = { - val flexRequests = serviceStateData.flexRequest.allCompleted - val flexOptions = serviceStateData.flexOptionResponse.allCompleted - val setPoints = serviceStateData.setPointResponse.isComplete - - val isFinished = flexRequests && flexOptions && setPoints - - val connection = serviceStateData.extEmDataConnection - - connection.queueExtResponseMsg(msg, isFinished) - } - } diff --git a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala index 2008ecadb6..56cc70ffb0 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala @@ -8,33 +8,18 @@ package edu.ie3.simona.service.primary import edu.ie3.simona.agent.participant.data.Data.PrimaryData import edu.ie3.simona.agent.participant.data.Data.PrimaryData.RichValue -import edu.ie3.simona.agent.participant2.ParticipantAgent.{ - DataProvision, - PrimaryRegistrationSuccessfulMessage, -} +import edu.ie3.simona.agent.participant2.ParticipantAgent +import edu.ie3.simona.agent.participant2.ParticipantAgent.{DataProvision, PrimaryRegistrationSuccessfulMessage} import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection -import edu.ie3.simona.api.data.primarydata.ontology.{ - PrimaryDataMessageFromExt, - ProvidePrimaryData, -} +import edu.ie3.simona.api.data.primarydata.ontology.{PrimaryDataMessageFromExt, ProvidePrimaryData} import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - PrimaryServiceRegistrationMessage, - ServiceResponseMessage, -} -import edu.ie3.simona.service.ServiceStateData.{ - InitializeServiceStateData, - ServiceBaseStateData, -} -import edu.ie3.simona.service.{ - ExtDataSupport, - ServiceStateData, - TypedSimonaService, -} -import org.apache.pekko.actor.ActorRef +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{PrimaryServiceRegistrationMessage, ServiceResponseMessage} +import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} +import edu.ie3.simona.service.{ExtDataSupport, ServiceStateData, SimonaService} +import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps @@ -44,7 +29,7 @@ import scala.jdk.OptionConverters.RichOptional import scala.util.{Failure, Success, Try} object ExtPrimaryDataService - extends TypedSimonaService[ServiceMessage] + extends SimonaService[ServiceMessage] with ExtDataSupport[ServiceMessage] { override type S = ExtPrimaryDataStateData @@ -52,8 +37,8 @@ object ExtPrimaryDataService final case class ExtPrimaryDataStateData( extPrimaryData: ExtPrimaryDataConnection, subscribers: List[UUID] = List.empty, - uuidToActorRef: Map[UUID, ActorRef] = - Map.empty[UUID, ActorRef], // subscribers in SIMONA + uuidToActorRef: Map[UUID, ActorRef[ParticipantAgent.Request]] = + Map.empty, // subscribers in SIMONA extPrimaryDataMessage: Option[PrimaryDataMessageFromExt] = None, maybeNextTick: Option[Long] = None, ) extends ServiceBaseStateData @@ -102,7 +87,7 @@ object ExtPrimaryDataService } private def handleRegistrationRequest( - agentToBeRegistered: ActorRef, + agentToBeRegistered: ActorRef[ParticipantAgent.Request], agentUUID: UUID, )(implicit serviceStateData: ExtPrimaryDataStateData, @@ -121,7 +106,7 @@ object ExtPrimaryDataService ) agentToBeRegistered ! PrimaryRegistrationSuccessfulMessage( - ctx.self.toClassic, + ctx.self, 0L, PrimaryData.getPrimaryDataExtra(valueClass), ) @@ -209,7 +194,7 @@ object ExtPrimaryDataService case Success(primaryData) => actor ! DataProvision( tick, - ctx.self.toClassic, + ctx.self, primaryData, maybeNextTick, ) From ef0c08933f45bf32944aba0c8494db191b9d1a64 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 28 Mar 2025 20:43:54 +0100 Subject: [PATCH 026/125] Saving changes. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 2 + .../flex/MinMaxFixFlexibilityMessage.scala | 2 +- .../ie3/simona/service/SimonaService.scala | 4 +- .../service/em/EmCommunicationCore.scala | 422 +++++++++++++ .../simona/service/em/EmServiceBaseCore.scala | 59 ++ .../ie3/simona/service/em/EmServiceCore.scala | 125 ++++ .../simona/service/em/ExtEmDataService.scala | 592 ++---------------- .../primary/ExtPrimaryDataService.scala | 20 +- .../edu/ie3/simona/util/ReceiveDataMap.scala | 3 + 9 files changed, 684 insertions(+), 545 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala create mode 100644 src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala create mode 100644 src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index bae754e9da..9c12a7f572 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -238,6 +238,8 @@ object EmAgent { msg match { case Flex(_: FlexActivation) | EmActivation(_) => val (toActivate, newCore) = flexOptionsCore.takeNewFlexRequests() + ctx.log.info(s"${ctx.self}, to activate: $toActivate") + toActivate.foreach { _ ! FlexActivation(msg.tick) } diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala index 89b7f4a12a..e208f91206 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala @@ -32,7 +32,7 @@ object MinMaxFixFlexibilityMessage { * The maximum active power that the flex options provider allows at the * current tick */ - final case class MinMaxFixFlexOptions private( + final case class MinMaxFixFlexOptions private ( ref: Power, min: Power, max: Power, diff --git a/src/main/scala/edu/ie3/simona/service/SimonaService.scala b/src/main/scala/edu/ie3/simona/service/SimonaService.scala index 76eb610914..41100f6046 100644 --- a/src/main/scala/edu/ie3/simona/service/SimonaService.scala +++ b/src/main/scala/edu/ie3/simona/service/SimonaService.scala @@ -258,7 +258,9 @@ abstract class SimonaService[ protected def handleServiceResponse( serviceResponse: ServiceResponseMessage - )(implicit ctx: ActorContext[T]): Unit = {} + )(implicit + ctx: ActorContext[T] + ): Unit = {} /** Handle a request to register for information from this service * diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala new file mode 100644 index 0000000000..4ffaab6648 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -0,0 +1,422 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.em + +import edu.ie3.datamodel.models.result.system.FlexOptionsResult +import edu.ie3.datamodel.models.value.PValue +import edu.ie3.simona.agent.em.EmAgent +import edu.ie3.simona.api.data.em.NoSetPointValue +import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexRequestResult} +import edu.ie3.simona.api.data.em.ontology._ +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ +import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions +import edu.ie3.simona.ontology.messages.services.EmMessage.WrappedFlexResponse +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + RegisterForEmDataService, + ServiceResponseMessage, +} +import edu.ie3.simona.service.em.EmCommunicationCore.{DataMap, EmHierarchy} +import edu.ie3.simona.util.ReceiveHierarchicalDataMap +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import edu.ie3.simona.util.TickUtil.TickLong +import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW +import org.apache.pekko.actor.typed.ActorRef +import org.slf4j.Logger +import tech.units.indriya.ComparableQuantity + +import java.time.ZonedDateTime +import java.util.UUID +import javax.measure.quantity.Power +import scala.jdk.CollectionConverters.{ + IterableHasAsScala, + MapHasAsJava, + MapHasAsScala, +} + +final case class EmCommunicationCore( + hierarchy: EmHierarchy = EmHierarchy(), + override val uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = + Map.empty, + flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, + uuidToPRef: Map[UUID, ComparableQuantity[Power]] = Map.empty, + toSchedule: Map[UUID, ScheduleFlexActivation] = Map.empty, + flexRequestReceived: DataMap[UUID, Boolean] = + ReceiveHierarchicalDataMap.empty(false), + flexOptionResponse: DataMap[UUID, (UUID, FlexOptionsResult)] = + ReceiveHierarchicalDataMap.empty, + setPointResponse: DataMap[UUID, EmSetPointResult] = + ReceiveHierarchicalDataMap.empty(false), + completions: DataMap[UUID, FlexCompletion] = + ReceiveHierarchicalDataMap.empty(false), +) extends EmServiceCore { + + override def handleRegistration( + registerMsg: RegisterForEmDataService + ): EmServiceCore = { + val uuid = registerMsg.modelUuid + val ref = registerMsg.requestingActor + val flexAdapter = registerMsg.flexAdapter + val parentEm = registerMsg.parentEm + val parentUuid = registerMsg.parentUuid + + val updatedHierarchy = parentEm match { + case Some(parent) => + hierarchy.add(uuid, ref, parent) + case None => + hierarchy.add(uuid, ref, ref) + } + + copy( + hierarchy = updatedHierarchy, + uuidToFlexAdapter = uuidToFlexAdapter + (uuid -> flexAdapter), + flexAdapterToUuid = flexAdapterToUuid + (flexAdapter -> uuid), + flexRequestReceived = + flexRequestReceived.updateStructure(parentUuid, uuid), + flexOptionResponse = flexOptionResponse.updateStructure(parentUuid, uuid), + setPointResponse = setPointResponse.updateStructure(parentUuid, uuid), + completions = completions.updateStructure(parentUuid, uuid), + ) + } + + override def handleExtMessage( + tick: Long, + extMSg: EmDataMessageFromExt, + )(implicit + log: Logger + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMSg match { + case requestEmCompletion: RequestEmCompletion => + if (requestEmCompletion.tick != tick) { + log.warn( + s"Received completion request for tick '${requestEmCompletion.tick}' in tick '$tick'." + ) + (this, None) + + } else { + log.info(s"Receive a request for completion for tick '$tick'.") + + uuidToFlexAdapter.foreach { case (_, adapter) => + adapter ! IssueNoControl(tick) + } + + (EmCommunicationCore.empty, Some(new EmCompletion())) + } + + case provideFlexRequests: ProvideFlexRequestData => + log.info(s"Handling of: $provideFlexRequests") + + // entities for which flex options are requested + val emEntities: Set[UUID] = provideFlexRequests + .flexRequests() + .asScala + .flatMap { case (_, v) => v.asScala } + .toSet + + val refs = emEntities.map(uuidToFlexAdapter) + + log.warn(s"Em refs: $refs") + refs.foreach(_ ! FlexActivation(tick)) + + (addSubKeysToExpectedKeys(emEntities), None) + + case provideFlexOptions: ProvideEmFlexOptionData => + log.info(s"Handling of: $provideFlexOptions") + + provideFlexOptions + .flexOptions() + .asScala + .foreach { case (agent, flexOptions) => + hierarchy.getResponseRef(agent) match { + case Some(receiver) => + flexOptions.asScala.foreach { case (sender, options) => + receiver ! ProvideFlexOptions( + sender, + MinMaxFlexOptions( + options.pRef.toSquants, + options.pMin.toSquants, + options.pMax.toSquants, + ), + ) + } + + case None => + log.warn(s"No em agent with uuid '$agent' registered!") + } + } + + (this, None) + + case providedSetPoints: ProvideEmSetPointData => + handleSetPoint(tick, providedSetPoints, log) + + (this, None) + } + + override def handleFlexResponse( + tick: Long, + flexResponse: FlexResponse, + receiver: Either[UUID, ActorRef[FlexResponse]], + )(implicit + startTime: ZonedDateTime, + log: Logger, + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = flexResponse match { + case scheduleFlexActivation: ScheduleFlexActivation => + log.warn(s"Flex activation: $scheduleFlexActivation") + + if (scheduleFlexActivation.tick == INIT_SIM_TICK) { + receiver match { + case Right(ref) => + log.warn(s"$ref: $scheduleFlexActivation") + ref ! scheduleFlexActivation + + case Left(uuid) => + uuidToFlexAdapter(uuid) ! FlexActivation(INIT_SIM_TICK) + } + } else { + log.warn(s"$scheduleFlexActivation not handled!") + } + + (this, None) + + case provideFlexOptions: ProvideFlexOptions => + if (tick == INIT_SIM_TICK) { + receiver match { + case Right(otherRef) => + otherRef ! provideFlexOptions + + case Left(self: UUID) => + uuidToFlexAdapter(self) ! IssuePowerControl( + INIT_SIM_TICK, + zeroKW, + ) + } + + (this, None) + } else { + + val uuid = receiver match { + case Right(otherRef) => + hierarchy.getUuid(otherRef) + case Left(self: UUID) => + self + } + + val updated = provideFlexOptions match { + case ProvideFlexOptions( + modelUuid, + MinMaxFlexOptions(ref, min, max), + ) => + flexOptionResponse.addData( + modelUuid, + ( + uuid, + new FlexOptionsResult( + tick.toDateTime(startTime), + modelUuid, + min.toQuantity, + ref.toQuantity, + max.toQuantity, + ), + ), + ) + + case _ => + flexOptionResponse + } + + log.warn(s"Updated data map: $updated") + + if (updated.hasCompletedKeys) { + // all responses received, forward them to external simulation in a bundle + + val (data, updatedFlexOptionResponse) = updated.getFinishedData + + val pRefs = data.map { case (uuid, (_, options)) => + uuid -> options.getpRef + } + + val msgToExt = new FlexOptionsResponse(data.map { + case (entity, (_, value)) => entity -> value + }.asJava) + + ( + copy( + flexOptionResponse = updatedFlexOptionResponse, + uuidToPRef = uuidToPRef ++ pRefs, + ), + Some(msgToExt), + ) + + } else { + // responses are still incomplete + (copy(flexOptionResponse = updated), None) + } + } + + case FlexResult(modelUuid, result) => + log.info(s"Flex result '$result' for model '$modelUuid'.") + (this, None) + + case completion: FlexCompletion => + receiver.map(_ ! completion) + log.warn(s"Completion: $completion") + + // TODO: Check if necessary + if (tick != INIT_SIM_TICK) { + + val model = completion.modelUuid + + val updated = completions.addData(model, completion) + + if (updated.allCompleted) { + + val (_, updatedCompletions) = updated.getFinishedData + + // every em agent has sent a completion message + // send completion message to external simulation + (copy(completions = updatedCompletions), Some(new EmCompletion())) + + } else { + (copy(completions = updated), None) + } + } else { + (this, None) + } + } + + override def handleFlexRequest( + flexRequest: FlexRequest, + receiver: ActorRef[FlexRequest], + )(implicit + startTime: ZonedDateTime, + log: Logger, + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = flexRequest match { + case flexActivation @ FlexActivation(tick) => + if (tick == INIT_SIM_TICK) { + receiver ! flexActivation + + (this, None) + } else { + val uuid = flexAdapterToUuid(receiver) + + log.warn(s"Receiver: $uuid") + + val updated = flexRequestReceived.addData( + uuid, + true, + ) + + log.warn(s"$updated") + + if (updated.hasCompletedKeys) { + + val (dataMap, updatedFlexRequest) = updated.getFinishedData + + log.warn(s"Data to be send: $dataMap") + + val map = dataMap.map { case (key, _) => + key -> + new FlexRequestResult(flexActivation.tick.toDateTime, key) + } + + ( + copy(flexRequestReceived = updatedFlexRequest), + Some(new FlexRequestResponse(map.asJava)), + ) + + } else { + (copy(flexRequestReceived = updated), None) + } + } + + case issueFlexControl: IssueFlexControl => + if (issueFlexControl.tick == INIT_SIM_TICK) { + + receiver ! issueFlexControl + + (this, None) + + } else { + val uuid = flexAdapterToUuid(receiver) + + val (time, power) = issueFlexControl match { + case IssueNoControl(tick) => + (tick.toDateTime, new NoSetPointValue(uuidToPRef(uuid))) + + case IssuePowerControl(tick, setPower) => + (tick.toDateTime, new PValue(setPower.toQuantity)) + } + + val updated = setPointResponse.addData( + uuid, + new EmSetPointResult(time, uuid, power), + ) + + log.warn(s"Updated set point response: $updated") + + if (updated.hasCompletedKeys) { + + val (dataMap, updatedSetPointResponse) = updated.getFinishedData + + ( + copy(setPointResponse = updatedSetPointResponse), + Some(new EmSetPointDataResponse(dataMap.asJava)), + ) + + } else { + // responses are still incomplete + (copy(setPointResponse = updated), None) + } + } + } + + private def addSubKeysToExpectedKeys(keys: Set[UUID]): EmCommunicationCore = + copy( + flexRequestReceived = flexRequestReceived.addSubKeysToExpectedKeys(keys), + flexOptionResponse = flexOptionResponse.addSubKeysToExpectedKeys(keys), + setPointResponse = setPointResponse.addSubKeysToExpectedKeys(keys), + completions = completions.addSubKeysToExpectedKeys(keys), + ) +} + +object EmCommunicationCore { + + type DataMap[K, V] = ReceiveHierarchicalDataMap[K, V] + + final case class EmHierarchy( + refToUuid: Map[ActorRef[EmAgent.Request], UUID] = Map.empty, + uuidToRef: Map[UUID, ActorRef[EmAgent.Request]] = Map.empty, + uuidToFlexResponse: Map[UUID, ActorRef[FlexResponse]] = Map.empty, + flexResponseToUuid: Map[ActorRef[FlexResponse], UUID] = Map.empty, + ) { + def add(model: UUID, ref: ActorRef[EmAgent.Request]): EmHierarchy = copy( + uuidToRef = uuidToRef + (model -> ref), + refToUuid = refToUuid + (ref -> model), + ) + + def add( + model: UUID, + ref: ActorRef[EmAgent.Request], + parent: ActorRef[FlexResponse], + ): EmHierarchy = { + + copy( + uuidToRef = uuidToRef + (model -> ref), + refToUuid = refToUuid + (ref -> model), + uuidToFlexResponse = uuidToFlexResponse + (model -> parent), + flexResponseToUuid = flexResponseToUuid + (parent -> model), + ) + } + + def getUuid(ref: ActorRef[FlexResponse]): UUID = + flexResponseToUuid(ref) + + def getResponseRef(uuid: UUID): Option[ActorRef[FlexResponse]] = + uuidToFlexResponse.get(uuid) + } + + def empty: EmCommunicationCore = EmCommunicationCore() +} diff --git a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala new file mode 100644 index 0000000000..c54feab69b --- /dev/null +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -0,0 +1,59 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.em + +import edu.ie3.simona.api.data.em.ontology._ +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ + FlexRequest, + FlexResponse, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService +import org.apache.pekko.actor.typed.ActorRef +import org.slf4j.Logger + +import java.time.ZonedDateTime +import java.util.UUID + +case class EmServiceBaseCore( + override val uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = Map.empty +) extends EmServiceCore { + + override def handleRegistration( + registrationMsg: RegisterForEmDataService + ): EmServiceBaseCore = + copy(uuidToFlexAdapter = + uuidToFlexAdapter ++ Map( + registrationMsg.modelUuid -> registrationMsg.flexAdapter + ) + ) + + override def handleExtMessage(tick: Long, extMSg: EmDataMessageFromExt)( + implicit log: Logger + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = ??? + + override def handleFlexResponse( + tick: Long, + flexResponse: FlexResponse, + receiver: Either[UUID, ActorRef[FlexResponse]], + )(implicit + startTime: ZonedDateTime, + log: Logger, + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = ??? + + override def handleFlexRequest( + flexRequest: FlexRequest, + receiver: ActorRef[FlexRequest], + )(implicit + startTime: ZonedDateTime, + log: Logger, + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = ??? +} + +object EmServiceBaseCore { + + def empty: EmServiceBaseCore = EmServiceBaseCore() +} diff --git a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala new file mode 100644 index 0000000000..4ed92383f0 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -0,0 +1,125 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.em + +import edu.ie3.simona.api.data.em.NoSetPointValue +import edu.ie3.simona.api.data.em.ontology._ +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ + FlexRequest, + FlexResponse, + IssueNoControl, + IssuePowerControl, +} +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + WrappedFlexRequest, + WrappedFlexResponse, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + RegisterForEmDataService, + ServiceResponseMessage, +} +import edu.ie3.util.quantities.PowerSystemUnits +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW +import org.apache.pekko.actor.typed.ActorRef +import org.slf4j.Logger +import squants.Power +import squants.energy.Kilowatts +import tech.units.indriya.ComparableQuantity + +import java.time.ZonedDateTime +import java.util.UUID +import javax.measure.quantity.{Power => PsdmPower} +import scala.jdk.CollectionConverters.MapHasAsScala +import scala.jdk.OptionConverters.RichOptional + +trait EmServiceCore { + def uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] + + implicit class SquantsToQuantity(private val value: Power) { + def toQuantity: ComparableQuantity[PsdmPower] = value.toMegawatts.asMegaWatt + } + implicit class QuantityToSquants( + private val value: ComparableQuantity[PsdmPower] + ) { + def toSquants: Power = Kilowatts( + value.to(PowerSystemUnits.KILOWATT).getValue.doubleValue() + ) + } + + def handleRegistration( + registerForEmDataService: RegisterForEmDataService + ): EmServiceCore + + def handleExtMessage( + tick: Long, + extMSg: EmDataMessageFromExt, + )(implicit + log: Logger + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) + + final def handleDataResponseMessage( + tick: Long, + responseMsg: ServiceResponseMessage, + )(implicit + startTime: ZonedDateTime, + log: Logger, + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = responseMsg match { + case WrappedFlexRequest(flexRequest, receiver) => + handleFlexRequest(flexRequest, receiver) + + case WrappedFlexResponse(flexResponse, receiver) => + handleFlexResponse(tick, flexResponse, receiver) + } + + final def handleSetPoint( + tick: Long, + provideEmSetPoints: ProvideEmSetPointData, + log: Logger, + ): Unit = { + log.info(s"Handling of: $provideEmSetPoints") + + provideEmSetPoints + .emData() + .asScala + .foreach { case (agent, setPoint) => + uuidToFlexAdapter.get(agent) match { + case Some(receiver) => + setPoint match { + case _: NoSetPointValue => + receiver ! IssueNoControl(tick) + case _ => + val power = + setPoint.getP.toScala.map(_.toSquants).getOrElse(zeroKW) + + receiver ! IssuePowerControl(tick, power) + } + + case None => + log.warn(s"No em agent with uuid '$agent' registered!") + } + } + + } + + def handleFlexResponse( + tick: Long, + flexResponse: FlexResponse, + receiver: Either[UUID, ActorRef[FlexResponse]], + )(implicit + startTime: ZonedDateTime, + log: Logger, + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) + + def handleFlexRequest( + flexRequest: FlexRequest, + receiver: ActorRef[FlexRequest], + )(implicit + startTime: ZonedDateTime, + log: Logger, + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) +} 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 f0874242ba..6834cb9073 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -6,40 +6,35 @@ package edu.ie3.simona.service.em -import edu.ie3.datamodel.models.result.system.FlexOptionsResult -import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexRequestResult} -import edu.ie3.simona.api.data.em.ontology.{RequestEmCompletion, _} -import edu.ie3.simona.api.data.em.{ExtEmDataConnection, NoSetPointValue} +import edu.ie3.simona.api.data.em.ExtEmDataConnection +import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ -import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EmMessage -import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{RegisterForEmDataService, ServiceRegistrationMessage, ServiceResponseMessage} +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + WrappedFlexRequest, + WrappedFlexResponse, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + RegisterForEmDataService, + ServiceRegistrationMessage, + ServiceResponseMessage, +} +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, +} import edu.ie3.simona.service.{ExtDataSupport, SimonaService} -import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK -import edu.ie3.simona.util.TickUtil.TickLong -import edu.ie3.simona.util.{ReceiveDataMap, ReceiveHierarchicalDataMap} -import edu.ie3.util.quantities.PowerSystemUnits.KILOWATT -import edu.ie3.util.quantities.QuantityUtils._ -import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} import org.slf4j.{Logger, LoggerFactory} -import squants.Power -import squants.energy.Kilowatts -import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID -import javax.measure.quantity.{Power => PsdmPower} -import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala} -import scala.jdk.OptionConverters.{RichOption, RichOptional} import scala.util.{Failure, Success, Try} object ExtEmDataService @@ -50,16 +45,6 @@ object ExtEmDataService override type S = ExtEmDataStateData - implicit class SquantsToQuantity(private val value: Power) { - def toQuantity: ComparableQuantity[PsdmPower] = value.toKilowatts.asKiloWatt - } - - implicit class quantityToSquants( - private val value: ComparableQuantity[PsdmPower] - ) { - def toSquants: Power = Kilowatts(value.to(KILOWATT).getValue.doubleValue()) - } - def emServiceResponseAdapter( emService: ActorRef[EmMessage], receiver: Option[ActorRef[FlexResponse]], @@ -99,54 +84,11 @@ object ExtEmDataService final case class ExtEmDataStateData( extEmDataConnection: ExtEmDataConnection, startTime: ZonedDateTime, + serviceCore: EmServiceCore, tick: Long = INIT_SIM_TICK, - emHierarchy: EmHierarchy = EmHierarchy(), - uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = Map.empty, - flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, extEmDataMessage: Option[EmDataMessageFromExt] = None, - toSchedule: Map[UUID, ScheduleFlexActivation] = Map.empty, - flexRequest: ReceiveHierarchicalDataMap[UUID, Boolean] = - ReceiveHierarchicalDataMap.empty(false), - flexOptionResponse: ReceiveHierarchicalDataMap[ - UUID, - (UUID, FlexOptionsResult), - ] = ReceiveHierarchicalDataMap.empty, - setPointResponse: ReceiveDataMap[UUID, EmSetPointResult] = - ReceiveDataMap.empty, ) extends ServiceBaseStateData - final case class EmHierarchy( - refToUuid: Map[ActorRef[EmAgent.Request], UUID] = Map.empty, - uuidToRef: Map[UUID, ActorRef[EmAgent.Request]] = Map.empty, - uuidToFlexResponse: Map[UUID, ActorRef[FlexResponse]] = Map.empty, - flexResponseToUuid: Map[ActorRef[FlexResponse], UUID] = Map.empty, - ) { - def add(model: UUID, ref: ActorRef[EmAgent.Request]): EmHierarchy = copy( - uuidToRef = uuidToRef + (model -> ref), - refToUuid = refToUuid + (ref -> model), - ) - - def add( - model: UUID, - ref: ActorRef[EmAgent.Request], - parent: ActorRef[FlexResponse], - ): EmHierarchy = { - - copy( - uuidToRef = uuidToRef + (model -> ref), - refToUuid = refToUuid + (ref -> model), - uuidToFlexResponse = uuidToFlexResponse + (model -> parent), - flexResponseToUuid = flexResponseToUuid + (parent -> model), - ) - } - - def getUuid(ref: ActorRef[FlexResponse]): UUID = - flexResponseToUuid(ref) - - def getResponseRef(uuid: UUID): Option[ActorRef[FlexResponse]] = - uuidToFlexResponse.get(uuid) - } - case class InitExtEmData( extEmData: ExtEmDataConnection, startTime: ZonedDateTime, @@ -154,12 +96,14 @@ object ExtEmDataService override protected def handleServiceResponse( serviceResponse: ServiceResponseMessage - )(implicit ctx: ActorContext[EmMessage]): Unit = serviceResponse match { + )(implicit + ctx: ActorContext[EmMessage] + ): Unit = serviceResponse match { case WrappedFlexResponse( scheduleFlexActivation: ScheduleFlexActivation, receiver, ) => - ctx.log.info(s"Received response message: $scheduleFlexActivation") + log.info(s"Received response message: $scheduleFlexActivation") receiver match { case Right(ref) => @@ -170,29 +114,18 @@ object ExtEmDataService scheduleFlexActivation.scheduleKey.foreach(_.unlock()) } - } - /** Initialize the concrete service implementation using the provided - * initialization data. This method should perform all heavyweight tasks - * before the actor becomes ready. The return values are a) the state data of - * the initialized service and b) optional triggers that should be send to - * the [[edu.ie3.simona.scheduler.Scheduler]] together with the completion - * message that is send in response to the trigger that is send to start the - * initialization process - * - * @param initServiceData - * the data that should be used for initialization - * @return - * the state data of this service and optional tick that should be included - * in the completion message - */ override def init( initServiceData: InitializeServiceStateData ): Try[(ExtEmDataStateData, Option[Long])] = initServiceData match { case InitExtEmData(extEmDataConnection, startTime) => + val serviceCore = if (extEmDataConnection.useCommunication) { + EmCommunicationCore.empty + } else EmServiceBaseCore.empty + val emDataInitializedStateData = - ExtEmDataStateData(extEmDataConnection, startTime) + ExtEmDataStateData(extEmDataConnection, startTime, serviceCore) Success( emDataInitializedStateData, None, @@ -206,16 +139,6 @@ object ExtEmDataService ) } - /** Handle a request to register for information from this service - * - * @param registrationMessage - * registration message to handle - * @param serviceStateData - * current state data of the actor - * @return - * the service stata data that should be used in the next state (normally - * with updated values) - */ override protected def handleRegistrationRequest( registrationMessage: ServiceRegistrationMessage )(implicit @@ -223,22 +146,15 @@ object ExtEmDataService ctx: ActorContext[EmMessage], ): Try[ExtEmDataStateData] = registrationMessage match { - case RegisterForEmDataService( - modelUuid, - requestingActor, - flexAdapter, - parentEm, - parentUuid, - ) => - Success( - handleEmRegistrationRequest( - modelUuid, - requestingActor, - flexAdapter, - parentEm, - parentUuid, - ) - ) + case registrationMsg: RegisterForEmDataService => + val updatedCore = + serviceStateData.serviceCore.handleRegistration(registrationMsg) + + if (registrationMsg.parentEm.isEmpty) { + registrationMsg.flexAdapter ! FlexActivation(INIT_SIM_TICK) + } + + Success(serviceStateData.copy(serviceCore = updatedCore)) case invalidMessage => Failure( InvalidRegistrationRequestException( @@ -247,208 +163,30 @@ object ExtEmDataService ) } - private def handleEmRegistrationRequest( - modelUuid: UUID, - modelActorRef: ActorRef[EmAgent.Request], - flexAdapter: ActorRef[FlexRequest], - parentEm: Option[ActorRef[FlexResponse]], - parentUuid: Option[UUID], - )(implicit serviceStateData: ExtEmDataStateData): ExtEmDataStateData = { - val hierarchy = serviceStateData.emHierarchy - - val updatedFlexRequest = - serviceStateData.flexRequest.updateStructure(parentUuid, modelUuid) - val updatedFlexResponse = - serviceStateData.flexOptionResponse.updateStructure(parentUuid, modelUuid) - - val updatedHierarchy = parentEm match { - case Some(parent) => - hierarchy.add(modelUuid, modelActorRef, parent) - case None => - hierarchy.add(modelUuid, modelActorRef, modelActorRef) - } - - if (parentEm.isEmpty) { - flexAdapter ! FlexActivation(INIT_SIM_TICK) - } - - serviceStateData.copy( - emHierarchy = updatedHierarchy, - uuidToFlexAdapter = - serviceStateData.uuidToFlexAdapter + (modelUuid -> flexAdapter), - flexAdapterToUuid = - serviceStateData.flexAdapterToUuid + (flexAdapter -> modelUuid), - flexRequest = updatedFlexRequest, - flexOptionResponse = updatedFlexResponse, - ) - } - - /** Send out the information to all registered recipients - * - * @param tick - * current tick data should be announced for - * @param serviceStateData - * the current state data of this service - * @return - * the service stata data that should be used in the next state (normally - * with updated values) together with the completion message that is send - * in response to the trigger that was sent to start this announcement - */ override protected def announceInformation(tick: Long)(implicit serviceStateData: ExtEmDataStateData, ctx: ActorContext[EmMessage], ): (ExtEmDataStateData, Option[Long]) = { - val updatedTick = serviceStateData.copy(tick = tick) - - val updatedStateData = serviceStateData.extEmDataMessage.getOrElse( + val extMsg = serviceStateData.extEmDataMessage.getOrElse( throw ServiceException( "ExtEmDataService was triggered without ExtEmDataMessage available" ) - ) match { - case requestEmCompletion : RequestEmCompletion => - - if (requestEmCompletion.tick != tick) { - log.warn(s"Received completion request for tick '${requestEmCompletion.tick}' in tick '$tick'.") - serviceStateData - - } else { - ctx.log.info(s"Receive a request for completion for tick '$tick'.") - - val setEms = serviceStateData.setPointResponse.getExpectedKeys - - serviceStateData.uuidToFlexAdapter.foreach { case (uuid, adapter) if !setEms.contains(uuid) => - adapter ! IssueNoControl(tick) - } - - serviceStateData.extEmDataConnection.queueExtResponseMsg(new EmCompletion()) - - serviceStateData.copy(setPointResponse = ReceiveDataMap.empty) - } - - case provideFlexRequests: ProvideFlexRequestData => - ctx.log.warn(s"$provideFlexRequests") - - // entities for which flex options are requested - val emEntities: Set[UUID] = provideFlexRequests - .flexRequests() - .asScala - .flatMap { case (_, v) => v.asScala } - .toSet - val uuidToRef = serviceStateData.uuidToFlexAdapter - val refs = emEntities.map(uuidToRef) - - log.warn(s"Em refs: $refs") - refs.foreach(_ ! FlexActivation(tick)) - - val updatedFlexRequest = - serviceStateData.flexRequest.addSubKeysToExpectedKeys(emEntities) - val updatedFlexOptionResponse = - serviceStateData.flexOptionResponse.addExpectedKeys(emEntities) - - updatedTick.copy( - extEmDataMessage = None, - flexRequest = updatedFlexRequest, - flexOptionResponse = updatedFlexOptionResponse, - ) - - case provideFlexOptions: ProvideEmFlexOptionData => - ctx.log.warn(s"$provideFlexOptions") - - announceFlexOptions(provideFlexOptions)(updatedTick, ctx) - - case providedEmData: ProvideEmSetPointData => - ctx.log.warn(s"$providedEmData") - - announceEmSetPoints(tick, providedEmData)(updatedTick, ctx) - - case requestEmSetPoints: RequestEmSetPoints => - ctx.log.warn(s"$requestEmSetPoints") - - val uuids = requestEmSetPoints.emEntities().asScala.toSet - - updatedTick.copy( - extEmDataMessage = None, - setPointResponse = ReceiveDataMap(uuids), - ) - } - - (updatedStateData, None) - } - - private def announceFlexOptions( - provideFlexOptions: ProvideEmFlexOptionData - )(implicit - serviceStateData: ExtEmDataStateData, - ctx: ActorContext[EmMessage], - ): ExtEmDataStateData = { - val hierarchy = serviceStateData.emHierarchy - - provideFlexOptions - .flexOptions() - .asScala - .foreach { case (agent, flexOptions) => - hierarchy.getResponseRef(agent) match { - case Some(receiver) => - flexOptions.asScala.foreach { case (sender, options) => - receiver ! ProvideFlexOptions( - sender, - MinMaxFlexOptions( - options.pRef.toSquants, - options.pMin.toSquants, - options.pMax.toSquants, - ), - ) - } - - case None => - ctx.log.warn(s"No em agent with uuid '$agent' registered!") - } - } - - serviceStateData.copy(extEmDataMessage = None) - } + ) - private def announceEmSetPoints( - tick: Long, - provideEmSetPointData: ProvideEmSetPointData, - )(implicit - serviceStateData: ExtEmDataStateData, - ctx: ActorContext[EmMessage], - ): ExtEmDataStateData = { + val (updatedCore, msgToExt) = + serviceStateData.serviceCore.handleExtMessage(tick, extMsg)(ctx.log) - provideEmSetPointData - .emData() - .asScala - .foreach { case (agent, emSetPoint) => - serviceStateData.uuidToFlexAdapter.get(agent) match { - case Some(receiver) => - emSetPoint match { - case _: NoSetPointValue => - receiver ! IssueNoControl(tick) - case _ => - val power = - emSetPoint.getP.toScala.map(_.toSquants).getOrElse(zeroKW) - - receiver ! IssuePowerControl(tick, power) - } - - case None => - ctx.log.warn(s"No em agent with uuid '$agent' registered!") - } - } + msgToExt.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) - serviceStateData.copy(extEmDataMessage = None) + ( + serviceStateData.copy( + tick = tick, + serviceCore = updatedCore, + ), + None, + ) } - /** Handle a message from outside the simulation - * - * @param extMsg - * the external incoming message - * @param serviceStateData - * the current state data of this service - * @return - * the updated state data - */ override protected def handleDataMessage( extMsg: DataMessageFromExt )(implicit @@ -462,244 +200,20 @@ object ExtEmDataService } } - /** Handle a message from inside SIMONA sent to external - * - * @param extResponseMsg - * the external incoming message - * @param serviceStateData - * the current state data of this service - * @return - * the updated state data - */ override protected def handleDataResponseMessage( extResponseMsg: ServiceResponseMessage )(implicit serviceStateData: ExtEmDataStateData - ): ExtEmDataStateData = extResponseMsg match { - case WrappedFlexResponse(flexResponse, receiver) => - flexResponse match { - case scheduleFlexActivation: ScheduleFlexActivation => - handleScheduleFlexActivation(scheduleFlexActivation, receiver) - - case options: ProvideFlexOptions => - handleFlexProvision(options, receiver) - - case completion: FlexCompletion => - receiver.map(_ ! completion) - log.warn(s"Completion: $completion") - - serviceStateData - } - - case WrappedFlexRequest(issueFlexControl: IssueFlexControl, receiver) => - handleFlexControl(issueFlexControl, receiver) - - case WrappedFlexRequest(flexActivation: FlexActivation, receiver) => - handleFlexActivation(flexActivation, receiver) - } - - private def handleFlexProvision( - provideFlexOptions: ProvideFlexOptions, - receiver: Either[UUID, ActorRef[FlexResponse]], - )(implicit - serviceStateData: ExtEmDataStateData ): ExtEmDataStateData = { - if (serviceStateData.tick == INIT_SIM_TICK) { - receiver match { - case Right(otherRef) => - otherRef ! provideFlexOptions - - case Left(self: UUID) => - serviceStateData.uuidToFlexAdapter(self) ! IssuePowerControl( - INIT_SIM_TICK, - zeroKW, - ) - } - - serviceStateData - } else { - - val uuid = receiver match { - case Right(otherRef) => - serviceStateData.emHierarchy.getUuid(otherRef) - case Left(self: UUID) => - self - } - - val updated = provideFlexOptions match { - case ProvideFlexOptions(modelUuid, MinMaxFlexOptions(ref, min, max)) => - serviceStateData.flexOptionResponse.addData( - modelUuid, - ( - uuid, - new FlexOptionsResult( - serviceStateData.startTime, // TODO: Fix this - modelUuid, - min.toQuantity, - ref.toQuantity, - max.toQuantity, - ), - ), - ) - - case _ => - serviceStateData.flexOptionResponse - } - - log.warn(s"Updated data map: $updated") - - if (updated.hasCompletedKeys) { - // all responses received, forward them to external simulation in a bundle - - val (data, updatedFlexOptionResponse) = updated.getFinishedData + val (updatedCore, extMsg) = + serviceStateData.serviceCore.handleDataResponseMessage( + serviceStateData.tick, + extResponseMsg, + )(serviceStateData.startTime, log) - serviceStateData.extEmDataConnection.queueExtResponseMsg( - new FlexOptionsResponse( - data.map { case (entity, (_, value)) => entity -> value }.asJava - ) - ) + extMsg.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) - serviceStateData.copy( - flexOptionResponse = updatedFlexOptionResponse - ) - } else { - // responses are still incomplete - serviceStateData.copy( - flexOptionResponse = updated - ) - } - } - } - - private def handleScheduleFlexActivation( - scheduleFlexActivation: ScheduleFlexActivation, - receiver: Either[UUID, ActorRef[FlexResponse]], - )(implicit - serviceStateData: ExtEmDataStateData - ): ExtEmDataStateData = { - log.warn(s"Flex activation: $scheduleFlexActivation") - - receiver match { - case Right(ref) => - if (scheduleFlexActivation.tick == INIT_SIM_TICK) { - log.warn(s"$ref: $scheduleFlexActivation") - ref ! scheduleFlexActivation - } else { - log.warn(s"$scheduleFlexActivation not handled!") - } - - case Left(uuid) => - if (scheduleFlexActivation.tick == INIT_SIM_TICK) { - serviceStateData.uuidToFlexAdapter(uuid) ! FlexActivation( - INIT_SIM_TICK - ) - } else { - log.warn(s"$scheduleFlexActivation not handled!") - } - } - - serviceStateData - } - - private def handleFlexActivation( - flexActivation: FlexActivation, - receiver: ActorRef[FlexRequest], - )(implicit - serviceStateData: ExtEmDataStateData - ): ExtEmDataStateData = { - - if (flexActivation.tick == INIT_SIM_TICK) { - receiver ! flexActivation - - serviceStateData - } else { - val uuid = serviceStateData.flexAdapterToUuid(receiver) - - log.warn(s"Receiver: $uuid") - - val updated = serviceStateData.flexRequest.addData( - uuid, - true, - ) - - log.warn(s"$updated") - - if (updated.hasCompletedKeys) { - - val (dataMap, updatedFlexRequest) = updated.getFinishedData - - log.warn(s"Data to be send: $dataMap") - - val map = dataMap.map { case (key, _) => - key -> - new FlexRequestResult( - flexActivation.tick.toDateTime(serviceStateData.startTime), - key, - ) - } - - serviceStateData.extEmDataConnection.queueExtResponseMsg(new FlexRequestResponse(map.asJava)) - - serviceStateData.copy(flexRequest = updatedFlexRequest) - } else { - serviceStateData.copy(flexRequest = updated) - } - } - } - - private def handleFlexControl( - issueFlexControl: IssueFlexControl, - receiver: ActorRef[FlexRequest], - )(implicit - serviceStateData: ExtEmDataStateData - ): ExtEmDataStateData = { - if (issueFlexControl.tick == INIT_SIM_TICK) { - - receiver ! issueFlexControl - serviceStateData - - } else { - val uuid = serviceStateData.flexAdapterToUuid(receiver) - - val updated = issueFlexControl match { - case IssueNoControl(tick) => - serviceStateData.setPointResponse.addData( - uuid, - new EmSetPointResult( - tick.toDateTime(serviceStateData.startTime), - uuid, - None.toJava, - ), - ) - - case IssuePowerControl(tick, setPower) => - serviceStateData.setPointResponse.addData( - uuid, - new EmSetPointResult( - tick.toDateTime(serviceStateData.startTime), - uuid, - Some(new PValue(setPower.toQuantity)).toJava, - ), - ) - } - - if (updated.nonComplete) { - // responses are still incomplete - serviceStateData.copy( - setPointResponse = updated - ) - } else { - // all responses received, forward them to external simulation in a bundle - - serviceStateData.extEmDataConnection.queueExtResponseMsg( - new EmSetPointDataResponse(updated.receivedData.asJava) - ) - - serviceStateData.copy( - setPointResponse = ReceiveDataMap.empty - ) - } - } + serviceStateData.copy(serviceCore = updatedCore) } } diff --git a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala index 56cc70ffb0..17f64e5507 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala @@ -9,15 +9,27 @@ package edu.ie3.simona.service.primary import edu.ie3.simona.agent.participant.data.Data.PrimaryData import edu.ie3.simona.agent.participant.data.Data.PrimaryData.RichValue import edu.ie3.simona.agent.participant2.ParticipantAgent -import edu.ie3.simona.agent.participant2.ParticipantAgent.{DataProvision, PrimaryRegistrationSuccessfulMessage} +import edu.ie3.simona.agent.participant2.ParticipantAgent.{ + DataProvision, + PrimaryRegistrationSuccessfulMessage, +} import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection -import edu.ie3.simona.api.data.primarydata.ontology.{PrimaryDataMessageFromExt, ProvidePrimaryData} +import edu.ie3.simona.api.data.primarydata.ontology.{ + PrimaryDataMessageFromExt, + ProvidePrimaryData, +} import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{PrimaryServiceRegistrationMessage, ServiceResponseMessage} -import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + PrimaryServiceRegistrationMessage, + ServiceResponseMessage, +} +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, +} import edu.ie3.simona.service.{ExtDataSupport, ServiceStateData, SimonaService} import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala index 4550f9e1de..2aa5855d01 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala @@ -41,6 +41,9 @@ final case class ReceiveDataMap[K, V]( ) } + def addExpectedKeys(keys: Set[K]): ReceiveDataMap[K, V] = + copy(expectedKeys = expectedKeys ++ keys) + def getExpectedKeys: Set[K] = expectedKeys } From d874ba7097d27cca480390d258d418769856a135 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 31 Mar 2025 15:21:01 +0200 Subject: [PATCH 027/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 4ffaab6648..d595aa27e3 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -14,11 +14,7 @@ import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexRequestResult} import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions -import edu.ie3.simona.ontology.messages.services.EmMessage.WrappedFlexResponse -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - RegisterForEmDataService, - ServiceResponseMessage, -} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.service.em.EmCommunicationCore.{DataMap, EmHierarchy} import edu.ie3.simona.util.ReceiveHierarchicalDataMap import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK @@ -31,11 +27,7 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.Power -import scala.jdk.CollectionConverters.{ - IterableHasAsScala, - MapHasAsJava, - MapHasAsScala, -} +import scala.jdk.CollectionConverters.{IterableHasAsScala, MapHasAsJava, MapHasAsScala} final case class EmCommunicationCore( hierarchy: EmHierarchy = EmHierarchy(), @@ -131,13 +123,14 @@ final case class EmCommunicationCore( .foreach { case (agent, flexOptions) => hierarchy.getResponseRef(agent) match { case Some(receiver) => - flexOptions.asScala.foreach { case (sender, options) => + flexOptions.asScala.foreach { option => + receiver ! ProvideFlexOptions( - sender, + option.sender, MinMaxFlexOptions( - options.pRef.toSquants, - options.pMin.toSquants, - options.pMax.toSquants, + option.pRef.toSquants, + option.pMin.toSquants, + option.pMax.toSquants, ), ) } From 911b6713efb41722ca3608502c1154de3965c5dc Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 31 Mar 2025 15:35:56 +0200 Subject: [PATCH 028/125] Saving changes. --- .../simona/service/em/EmCommunicationCore.scala | 14 ++++++++++---- .../edu/ie3/simona/service/em/EmServiceCore.scala | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index d595aa27e3..16a3c0452f 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -9,8 +9,11 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.NoSetPointValue -import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexRequestResult} +import edu.ie3.simona.api.data.em.model.{ + EmSetPointResult, + FlexRequestResult, + NoSetPointValue, +} import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions @@ -27,7 +30,11 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.Power -import scala.jdk.CollectionConverters.{IterableHasAsScala, MapHasAsJava, MapHasAsScala} +import scala.jdk.CollectionConverters.{ + IterableHasAsScala, + MapHasAsJava, + MapHasAsScala, +} final case class EmCommunicationCore( hierarchy: EmHierarchy = EmHierarchy(), @@ -124,7 +131,6 @@ final case class EmCommunicationCore( hierarchy.getResponseRef(agent) match { case Some(receiver) => flexOptions.asScala.foreach { option => - receiver ! ProvideFlexOptions( option.sender, MinMaxFlexOptions( 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 4ed92383f0..e8d6bf7ed1 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -6,7 +6,7 @@ package edu.ie3.simona.service.em -import edu.ie3.simona.api.data.em.NoSetPointValue +import edu.ie3.simona.api.data.em.model.NoSetPointValue import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ FlexRequest, From 137e31dcea5cd57ce15ffa8989574e4a8c547da7 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 1 Apr 2025 08:14:27 +0200 Subject: [PATCH 029/125] Adapting to API changes. --- .../ontology/messages/services/ServiceMessage.scala | 4 ++-- .../edu/ie3/simona/service/ev/ExtEvDataService.scala | 12 +++++++++--- .../scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala | 5 ++--- .../edu/ie3/simona/sim/setup/ExtSimSetupData.scala | 12 +++++------- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala index 7bcf789a49..6f8608e25f 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala @@ -27,8 +27,8 @@ object ServiceMessage { final case class WrappedActivation(activation: Activation) extends ServiceMessage - final case class WrappedExternalMessage[D <: DataMessageFromExt]( - extMsg: D + final case class WrappedExternalMessage( + extMsg: DataMessageFromExt ) extends ServiceMessage /** Service initialization data can sometimes only be constructed once the 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 35a4f93775..3ba6a3f683 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -14,6 +14,7 @@ import edu.ie3.simona.agent.participant2.ParticipantAgent.{ import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.data.ev.model.EvModel import edu.ie3.simona.api.data.ev.ontology._ +import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{ CriticalFailureException, @@ -46,7 +47,7 @@ import scala.util.{Failure, Success, Try} object ExtEvDataService extends SimonaService[EvMessage] - with ExtDataSupport[EvMessage, EvDataMessageFromExt] { + with ExtDataSupport[EvMessage] { override type S = ExtEvStateData @@ -333,9 +334,14 @@ object ExtEvDataService } override protected def handleDataMessage( - extMsg: EvDataMessageFromExt + extMsg: DataMessageFromExt )(implicit serviceStateData: S): S = - serviceStateData.copy(extEvMessage = Some(extMsg)) + extMsg match { + case extEvMessage: EvDataMessageFromExt => + serviceStateData.copy( + extEvMessage = Some(extEvMessage) + ) + } override protected def handleDataResponseMessage( extResponseMsg: ServiceResponseMessage diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index a5dca1a24d..e0502c0e5e 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -199,13 +199,12 @@ object ExtSimSetup { * The reference to the service. */ private[setup] def setupInputService[ - T <: ExtInputDataConnection[D], + T <: ExtInputDataConnection, M >: ServiceMessage, - D <: DataMessageFromExt, ]( extInputDataConnection: T, behavior: Behavior[M], - adapterToExt: ActorRef[M] => Behavior[D], + adapterToExt: ActorRef[M] => Behavior[DataMessageFromExt], name: String, initData: T => InitializeServiceStateData, )(implicit diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index 00518cabb8..25e1910e8b 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -9,9 +9,7 @@ package edu.ie3.simona.sim.setup import edu.ie3.simona.api.data.ExtInputDataConnection import edu.ie3.simona.api.data.em.ExtEmDataConnection import edu.ie3.simona.api.data.ev.ExtEvDataConnection -import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection -import edu.ie3.simona.api.data.primarydata.ontology.PrimaryDataMessageFromExt import edu.ie3.simona.api.data.results.ExtResultDataConnection import edu.ie3.simona.ontology.messages.services.{EvMessage, ServiceMessage} import edu.ie3.simona.sim.setup.ExtSimSetupData.Input @@ -32,8 +30,8 @@ import org.apache.pekko.actor.{ActorRef => ClassicRef} */ final case class ExtSimSetupData( extSimAdapters: Iterable[ClassicRef], - extPrimaryDataServices: Seq[Input[PrimaryDataMessageFromExt]], - extDataServices: Seq[Input[_ <: DataMessageFromExt]], + extPrimaryDataServices: Seq[Input], + extDataServices: Seq[Input], extResultListeners: Seq[(ExtResultDataConnection, ActorRef[_])], ) { @@ -46,7 +44,7 @@ final case class ExtSimSetupData( ) private[setup] def update( - connection: ExtInputDataConnection[_], + connection: ExtInputDataConnection, ref: ActorRef[_ >: ServiceMessage], ): ExtSimSetupData = connection match { case primaryConnection: ExtPrimaryDataConnection => @@ -97,8 +95,8 @@ final case class ExtSimSetupData( object ExtSimSetupData { - type Input[T <: DataMessageFromExt] = - (ExtInputDataConnection[T], ActorRef[_ >: ServiceMessage]) + type Input = + (ExtInputDataConnection, ActorRef[_ >: ServiceMessage]) /** Returns an empty [[ExtSimSetupData]]. */ From 7ec841474b4332042fd140a6f0cd568c60cf8314 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 1 Apr 2025 15:09:50 +0200 Subject: [PATCH 030/125] Saving changes. --- .../edu/ie3/simona/api/ExtSimAdapter.scala | 2 +- .../event/listener/DelayedStopHelper.scala | 2 - .../event/listener/ResultEventListener.scala | 28 +- .../results/ExtResultDataProvider.scala | 503 ------------------ .../service/results/ExtResultProvider.scala | 257 +++++++++ .../service/results/ExtResultSchedule.scala | 85 +++ .../ie3/simona/sim/setup/ExtSimSetup.scala | 61 +-- .../simona/sim/setup/ExtSimSetupData.scala | 15 +- 8 files changed, 383 insertions(+), 570 deletions(-) delete mode 100644 src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala create mode 100644 src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala create mode 100644 src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala diff --git a/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala b/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala index 251e92366c..41b2966144 100644 --- a/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala +++ b/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala @@ -101,7 +101,7 @@ final case class ExtSimAdapter(scheduler: ActorRef) context become receiveIdle(stateData.copy(currentTick = None)) - case scheduleDataService: ScheduleDataServiceMessage[DataMessageFromExt] => + case scheduleDataService: ScheduleDataServiceMessage => val tick = stateData.currentTick.getOrElse( throw new RuntimeException("No tick has been triggered") ) diff --git a/src/main/scala/edu/ie3/simona/event/listener/DelayedStopHelper.scala b/src/main/scala/edu/ie3/simona/event/listener/DelayedStopHelper.scala index 3c3a84f3ca..c6d8cea4d5 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/DelayedStopHelper.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/DelayedStopHelper.scala @@ -6,7 +6,6 @@ package edu.ie3.simona.event.listener -import edu.ie3.simona.service.results.ExtResultDataProvider import org.apache.pekko.actor.typed.Behavior import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} @@ -24,7 +23,6 @@ object DelayedStopHelper { sealed trait StoppingMsg extends ResultEventListener.Request with RuntimeEventListener.Request - with ExtResultDataProvider.Request /** Message indicating that [[RuntimeEventListener]] should stop. Instead of * using [[org.apache.pekko.actor.typed.scaladsl.ActorContext.stop]], this diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index e60fd35083..5aae5ec17a 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -22,8 +22,8 @@ import edu.ie3.simona.exceptions.{ ProcessResultEventException, } import edu.ie3.simona.io.result._ -import edu.ie3.simona.service.results.ExtResultDataProvider -import edu.ie3.simona.service.results.ExtResultDataProvider.ResultResponseMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage +import edu.ie3.simona.service.results.ExtResultProvider.ResultResponseMessage import edu.ie3.simona.util.ResultFileHierarchy import org.slf4j.Logger @@ -48,13 +48,12 @@ object ResultEventListener extends Transformer3wResultSupport { * @param classToSink * a map containing the sink for each class that should be processed by the * listener - * @param extResultListeners + * @param extSink * actors for external result data services */ private final case class BaseData( classToSink: Map[Class[_], ResultEntitySink], - extResultListeners: Iterable[ActorRef[ExtResultDataProvider.Request]] = - Iterable.empty, + extSink: Iterable[ActorRef[ServiceMessage]] = Iterable.empty, threeWindingResults: Map[ Transformer3wKey, AggregatedTransformer3wResult, @@ -166,13 +165,7 @@ object ResultEventListener extends Transformer3wResultSupport { baseData: BaseData, log: Logger, ): BaseData = { - if (baseData.extResultListeners.nonEmpty) { - baseData.extResultListeners.foreach( - _ ! ResultResponseMessage(resultEntity) - ) - } - - handOverToSink(resultEntity, baseData.classToSink, log) + handOverToSink(resultEntity, baseData.classToSink, baseData.extSink, log) baseData } @@ -204,7 +197,7 @@ object ResultEventListener extends Transformer3wResultSupport { if (updatedResult.ready) { // if result is complete, we can write it out updatedResult.consolidate.foreach { - handOverToSink(_, baseData.classToSink, log) + handOverToSink(_, baseData.classToSink, baseData.extSink, log) } // also remove partial result from map baseData.threeWindingResults.removed(key) @@ -237,9 +230,12 @@ object ResultEventListener extends Transformer3wResultSupport { private def handOverToSink( resultEntity: ResultEntity, classToSink: Map[Class[_], ResultEntitySink], + extSink: Iterable[ActorRef[ServiceMessage]], log: Logger, ): Unit = Try { + extSink.foreach(_ ! ResultResponseMessage(resultEntity)) + classToSink .get(resultEntity.getClass) .foreach(_.handleResultEntity(resultEntity)) @@ -249,8 +245,7 @@ object ResultEventListener extends Transformer3wResultSupport { def apply( resultFileHierarchy: ResultFileHierarchy, - extResultListeners: Iterable[ActorRef[ExtResultDataProvider.Request]] = - Iterable.empty, + extResultListeners: Iterable[ActorRef[ServiceMessage]] = Iterable.empty, ): Behavior[Request] = Behaviors.setup[Request] { ctx => ctx.log.debug("Starting initialization!") resultFileHierarchy.resultSinkType match { @@ -278,8 +273,7 @@ object ResultEventListener extends Transformer3wResultSupport { } private def init( - extResultListeners: Iterable[ActorRef[ExtResultDataProvider.Request]] = - Iterable.empty + extResultListeners: Iterable[ActorRef[ServiceMessage]] = Iterable.empty ): Behavior[Request] = Behaviors.withStash(200) { buffer => Behaviors.receive[Request] { case (ctx, SinkResponse(response)) => diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala deleted file mode 100644 index 11f3243e06..0000000000 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultDataProvider.scala +++ /dev/null @@ -1,503 +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.service.results - -import edu.ie3.datamodel.models.result.connector.LineResult -import edu.ie3.datamodel.models.result.system.{ - FlexOptionsResult, - SystemParticipantResult, -} -import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} -import edu.ie3.simona.api.data.results.ExtResultDataConnection -import edu.ie3.simona.api.data.results.ontology.{ - ProvideResultEntities, - RequestResultEntities, - ResultDataMessageFromExt, -} -import edu.ie3.simona.event.listener.DelayedStopHelper -import edu.ie3.simona.exceptions.ServiceException -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.ScheduleServiceActivation -import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} -import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey -import edu.ie3.simona.util.ReceiveDataMap -import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK -import edu.ie3.util.TimeUtil -import org.apache.pekko.actor.typed.scaladsl.{Behaviors, StashBuffer} -import org.apache.pekko.actor.typed.{ActorRef, Behavior} - -import java.time.ZonedDateTime -import java.util.UUID -import scala.jdk.CollectionConverters._ - -object ExtResultDataProvider { - - trait Request - - final case class WrappedActivation(activation: Activation) extends Request - - /** ExtSimulation -> ExtResultDataProvider */ - final case class WrappedResultDataMessageFromExt( - extResultDataMessageFromExt: ResultDataMessageFromExt - ) extends Request - final case class WrappedScheduleServiceActivationAdapter( - scheduleServiceActivationMsg: ScheduleServiceActivation - ) extends Request - - final case class RequestDataMessageAdapter( - sender: ActorRef[ActorRef[ResultDataMessageFromExt]] - ) extends Request - - final case class RequestScheduleActivationAdapter( - sender: ActorRef[ActorRef[ScheduleServiceActivation]] - ) extends Request - - /** ResultEventListener -> ExtResultDataProvider */ - final case class ResultResponseMessage( - result: ResultEntity - ) extends Request { - def tick(implicit startTime: ZonedDateTime): Long = - TimeUtil.withDefaults.zonedDateTimeDifferenceInSeconds( - startTime, - result.getTime, - ) - } - - /** ExtResultDataProvider -> ExtResultDataProvider */ - final case class ResultRequestMessage( - currentTick: Long - ) extends Request - - final case class Create( - initializeStateData: InitExtResultData, - unlockKey: ScheduleKey, - ) extends Request - - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - - def apply( - scheduler: ActorRef[SchedulerMessage], - startTime: ZonedDateTime, - ): Behavior[Request] = Behaviors.withStash(5000000) { buffer => - Behaviors.setup[Request] { ctx => - val activationAdapter: ActorRef[Activation] = - ctx.messageAdapter[Activation](msg => WrappedActivation(msg)) - val resultDataMessageFromExtAdapter: ActorRef[ResultDataMessageFromExt] = - ctx.messageAdapter[ResultDataMessageFromExt](msg => - WrappedResultDataMessageFromExt(msg) - ) - val scheduleServiceActivationAdapter - : ActorRef[ScheduleServiceActivation] = - ctx.messageAdapter[ScheduleServiceActivation](msg => - WrappedScheduleServiceActivationAdapter(msg) - ) - - uninitialized( - scheduler, - activationAdapter, - resultDataMessageFromExtAdapter, - scheduleServiceActivationAdapter, - buffer, - startTime, - ) - } - } - - private def uninitialized(implicit - scheduler: ActorRef[SchedulerMessage], - activationAdapter: ActorRef[Activation], - resultDataMessageFromExtAdapter: ActorRef[ResultDataMessageFromExt], - scheduleServiceActivationAdapter: ActorRef[ScheduleServiceActivation], - buffer: StashBuffer[Request], - startTime: ZonedDateTime, - ): Behavior[Request] = Behaviors.receiveMessagePartial { - case RequestDataMessageAdapter(sender) => - sender ! resultDataMessageFromExtAdapter - Behaviors.same - - case RequestScheduleActivationAdapter(sender) => - sender ! scheduleServiceActivationAdapter - Behaviors.same - - case Create( - initializeStateData: InitExtResultData, - unlockKey: ScheduleKey, - ) => - scheduler ! ScheduleActivation( - activationAdapter, - INIT_SIM_TICK, - Some(unlockKey), - ) - - initializing(initializeStateData) - } - - private def initializing( - initServiceData: InitExtResultData - )(implicit - scheduler: ActorRef[SchedulerMessage], - activationAdapter: ActorRef[Activation], - resultDataMessageFromExtAdapter: ActorRef[ResultDataMessageFromExt], - buffer: StashBuffer[Request], - startTime: ZonedDateTime, - ): Behavior[Request] = { - Behaviors.receivePartial { - case (_, WrappedActivation(Activation(INIT_SIM_TICK))) => - val initGridSubscribers = - initServiceData.extResultData.getGridResultDataAssets.asScala.toList - val initParticipantSubscribers = - initServiceData.extResultData.getParticipantResultDataAssets.asScala.toList - val initFlexOptionSubscribers = - initServiceData.extResultData.getFlexOptionAssets.asScala.toList - - var initResultScheduleMap = Map.empty[Long, Set[UUID]] - initResultScheduleMap = - initResultScheduleMap + (0L -> (initParticipantSubscribers ++ initFlexOptionSubscribers).toSet) // First result for system participants expected for tick 0 - initResultScheduleMap = - initResultScheduleMap + (initServiceData.powerFlowResolution -> initGridSubscribers.toSet) // First result for grid expected for tick powerflowresolution - - val resultInitializedStateData = ExtResultStateData( - extResultData = initServiceData.extResultData, - powerFlowResolution = initServiceData.powerFlowResolution, - currentTick = INIT_SIM_TICK, - extResultSchedule = ExtResultSchedule( - scheduleMap = initResultScheduleMap - ), - ) - scheduler ! Completion( - activationAdapter, - None, - ) - idle(resultInitializedStateData) - } - } - - private def idle(serviceStateData: ExtResultStateData)(implicit - scheduler: ActorRef[SchedulerMessage], - activationAdapter: ActorRef[Activation], - resultDataMessageFromExtAdapter: ActorRef[ResultDataMessageFromExt], - buffer: StashBuffer[Request], - startTime: ZonedDateTime, - ): Behavior[Request] = Behaviors - .receivePartial[Request] { - case (ctx, WrappedActivation(activation: Activation)) => - var updatedStateData = serviceStateData.handleActivation(activation) - // ctx.log.info(s"+++++++ Received Activation for tick ${updatedStateData.currentTick} +++++++") - - serviceStateData.extResultsMessage.getOrElse( - throw ServiceException( - "ExtResultDataService was triggered without ResultDataMessageFromExt available" - ) // this should not be possible because the external simulation schedules this service - ) match { - case msg: RequestResultEntities => // ExtResultDataProvider wurde aktiviert und es wurden Nachrichten von ExtSimulation angefragt - // ctx.log.info(s"[${updatedStateData.currentTick}] [requestResults] resultStorage = ${updatedStateData.resultStorage}\n extResultScheduler ${updatedStateData.extResultScheduler}") - val currentTick = updatedStateData.currentTick - if (msg.tick == currentTick) { // check, if we are in the right tick - // ctx.log.info(s"[${updatedStateData.currentTick}] RequestResultEntities with message = $msg") - // ctx.log.info(s"[${updatedStateData.currentTick}] RequestResultEntities for ${msg.requestedResults()}") - - // Search for too old schedules - val shiftedSchedule = - serviceStateData.extResultSchedule.shiftPastTicksToCurrentTick( - currentTick - ) - - val requestedKeys = msg.requestedResults().asScala - val expectedKeys = shiftedSchedule - .getExpectedKeys( - currentTick - ) - .intersect(msg.requestedResults().asScala.toSet) - // ctx.log.info(s"[${updatedStateData.currentTick}] [requestResults] Expected Keys = $expectedKeys") - val receiveDataMap = - ReceiveDataMap[UUID, ResultEntity](expectedKeys) - val updatedSchedule = - shiftedSchedule.handleActivationWithRequest( - currentTick, - msg.requestedResults().asScala, - ) - - // ctx.log.info(s"[${updatedStateData.currentTick}] [requestResults] updatedSchedule = $updatedSchedule \n receiveDataMap = $receiveDataMap") - - if (receiveDataMap.isComplete) { - // --- There are no expected results for this tick! Send the send right away! - // ctx.log.info(s"[requestResults] tick ${msg.tick} -> ReceiveDataMap is complete \n requestedKeys = $requestedKeys -> send it right away: \n ${serviceStateData.resultStorage}") - val filteredStorage = - serviceStateData.resultStorage.filter(entry => - requestedKeys.toSet.contains(entry._1) - ) - // ctx.log.info(s"\u001b[0;34m[${serviceStateData.currentTick}] receiveDataMap = $receiveDataMap,\nexpectedKeys = ${receiveDataMap.receivedData.keySet},\nfilteredStorage = $filteredStorage\u001b[0;0m") - - serviceStateData.extResultData.queueExtResponseMsg( - new ProvideResultEntities( - filteredStorage.asJava - ) - ) - updatedStateData = updatedStateData.copy( - extResultsMessage = None, - receiveDataMap = None, - extResultSchedule = updatedSchedule, - extRequestedResultKeys = List.empty, - ) - scheduler ! Completion(activationAdapter, None) - } else { - // We got an activation and we are waiting for some results -> trigger ourself to process - // ctx.log.info(s"[requestResults] receiveDataMap was built -> now sending ResultRequestMessage") - ctx.self ! ResultRequestMessage(msg.tick) - updatedStateData = updatedStateData.copy( - extResultsMessage = None, - receiveDataMap = Some(receiveDataMap), - extResultSchedule = updatedSchedule, - extRequestedResultKeys = requestedKeys, - ) - } - } else { - throw ServiceException( - s"Results for the wrong tick ${msg.tick} requested! We are currently in tick ${updatedStateData.currentTick}" - ) - } - } - // scheduler ! Completion(activationAdapter, None) - idle(updatedStateData) - - case ( - _, - scheduleServiceActivationMsg: WrappedScheduleServiceActivationAdapter, - ) => - scheduler ! ScheduleActivation( - activationAdapter, - scheduleServiceActivationMsg.scheduleServiceActivationMsg.tick, - Some( - scheduleServiceActivationMsg.scheduleServiceActivationMsg.unlockKey - ), - ) - Behaviors.same - - case ( - _, - resultDataMessageFromExt: WrappedResultDataMessageFromExt, - ) => // Received a request for results before activation -> save it and answer later - idle( - serviceStateData.copy( - extResultsMessage = - Some(resultDataMessageFromExt.extResultDataMessageFromExt) - ) - ) - - case ( - _, - extResultResponseMsg: ResultResponseMessage, - ) => // Received result from ResultEventListener - serviceStateData.receiveDataMap.fold { - // result arrived before activation -> stash them away - buffer.stash(extResultResponseMsg) - idle(serviceStateData) - } { dataMap => - if ( - dataMap.getExpectedKeys.contains( - extResultResponseMsg.result.getInputModel - ) - ) { // Received a result for external entity - // ctx.log.info(s"[${serviceStateData.currentTick}] Process ResultsResponseMsg = ${extResultResponseMsg.result.getInputModel}\n receiveDataMap ${serviceStateData.receiveDataMap}\n MsgTick=${extResultResponseMsg.tick}, ServiceStateDataTick=${serviceStateData.currentTick}, nextTick = ${extResultResponseMsg.nextTick}") - - if ( - extResultResponseMsg.tick == serviceStateData.currentTick | extResultResponseMsg.tick == -1L - ) { // Received a result for the current tick -> process it - // FIXME Not expected results are unconsidered - val updatedReceiveDataMap = dataMap.addData( - extResultResponseMsg.result.getInputModel, - extResultResponseMsg.result, - ) - - // ctx.log.info("[hDRM] AddData to RecentResults -> updatedReceivedResults = " + updatedReceiveDataMap) - - val updatedResultStorage = - serviceStateData.resultStorage + (extResultResponseMsg.result.getInputModel -> extResultResponseMsg.result) - val updatedResultSchedule = - serviceStateData.extResultSchedule.handleResult( - extResultResponseMsg, - extResultResponseMsg.tick + serviceStateData.powerFlowResolution, - ) - // ctx.log.info(s"[hDRM] updatedResultSchedule = $updatedResultSchedule") - // ctx.log.info(s"[hDRM] updatedResultStorage = $updatedResultStorage") - - if (updatedReceiveDataMap.nonComplete) { // There are still results missing... - // ctx.log.info(s"[${serviceStateData.currentTick}] There are still results missing...") - idle( - serviceStateData.copy( - receiveDataMap = Some(updatedReceiveDataMap), - resultStorage = updatedResultStorage, - extResultSchedule = updatedResultSchedule, - ) - ) - } else { // all responses received, forward them to external simulation in a bundle - // ctx.log.info(s"\u001b[0;34m[${serviceStateData.currentTick}] Got all ResultResponseMessage -> Now forward to external simulation in a bundle: $updatedResultStorage\u001b[0;0m") - serviceStateData.extResultData.queueExtResponseMsg( - new ProvideResultEntities(updatedResultStorage.asJava) - ) - // ctx.log.info("++++++++++++++++++ sended ExtResultData +++++++++++++++++++++++") - scheduler ! Completion(activationAdapter, None) - idle( - serviceStateData.copy( - receiveDataMap = None, - resultStorage = updatedResultStorage, - extResultSchedule = updatedResultSchedule, - ) - ) - } - } else { // Received a result for another tick -> ignore it - idle(serviceStateData) - } - } else { // Received a result for internal entity -> ignore it - idle(serviceStateData) - } - } - - case ( - ctx, - msg: ResultRequestMessage, - ) => // Received internal result request -> unstash messages - ctx.self ! msg - buffer.unstashAll(idle(serviceStateData)) - - case (ctx, msg: DelayedStopHelper.StoppingMsg) => - DelayedStopHelper.handleMsg((ctx, msg)) - } - - private def checkResultType( - result: ResultEntity, - serviceStateData: ExtResultStateData, - ): Boolean = { - val uuid = result.getInputModel - result match { - case _: FlexOptionsResult => - if (serviceStateData.extResultData.getFlexOptionAssets.contains(uuid)) { - true - } else { - false - } - case _: SystemParticipantResult => - if ( - serviceStateData.extResultData.getParticipantResultDataAssets - .contains(uuid) - ) { - true - } else { - false - } - case _: NodeResult | _: LineResult => - if ( - serviceStateData.extResultData.getGridResultDataAssets.contains(uuid) - ) { - true - } else { - false - } - case _ => false - } - } - - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - final case class ExtResultStateData( - extResultData: ExtResultDataConnection, - powerFlowResolution: Long, - currentTick: Long, - extResultSchedule: ExtResultSchedule, - extResultsMessage: Option[ResultDataMessageFromExt] = None, - resultStorage: Map[UUID, ResultEntity] = Map.empty, - receiveDataMap: Option[ReceiveDataMap[UUID, ResultEntity]] = None, - extRequestedResultKeys: Iterable[UUID] = List.empty, - ) { - def handleActivation(activation: Activation): ExtResultStateData = { - copy( - currentTick = activation.tick - ) - } - } - final case class InitExtResultData( - extResultData: ExtResultDataConnection, - powerFlowResolution: Long, - ) - - final case class ExtResultSchedule( - scheduleMap: Map[Long, Set[UUID]] = Map.empty, - unscheduledList: Set[UUID] = Set.empty, - ) { - def shiftPastTicksToCurrentTick( - currentTick: Long - ): ExtResultSchedule = { - // Sammle alle Sets von Keys, deren Schlüssel kleiner als currentTick - val (toMerge, remaining) = scheduleMap.partition { case (tick, _) => - tick < currentTick - } - - // Kombiniere die Sets zu einem einzigen Set - val mergedSet = toMerge.values.flatten.toSet - - // Aktualisiere den scheduleMap mit dem neuen Set für currentTick - val updatedScheduleMap = remaining.updated( - currentTick, - scheduleMap.getOrElse(currentTick, Set.empty) ++ mergedSet, - ) - - // Rückgabe eines neuen ExtResultSchedule mit dem aktualisierten scheduleMap - copy( - scheduleMap = updatedScheduleMap - ) - } - - def getExpectedKeys(tick: Long): Set[UUID] = { - scheduleMap.getOrElse( - tick, - Set(), - ) ++ unscheduledList - } - - private def getScheduledKeys(tick: Long): Set[UUID] = { - scheduleMap.getOrElse(tick, Set[UUID]()) - } - - def handleActivation(tick: Long): ExtResultSchedule = { - copy( - scheduleMap = scheduleMap.-(tick) - ) - } - - def handleActivationWithRequest( - tick: Long, - keys: Iterable[UUID], - ): ExtResultSchedule = { - val remainingKeys = - scheduleMap.get(tick).map(_.diff(keys.toSet)).getOrElse(Set.empty) - if (remainingKeys.isEmpty) { - copy( - scheduleMap = scheduleMap.-(tick) - ) - } else { - copy( - scheduleMap = scheduleMap.updated(tick, remainingKeys) - ) - } - } - - def handleResult( - msg: ResultResponseMessage, - nextTick: Long, - ): ExtResultSchedule = { - copy( - scheduleMap = scheduleMap.updated( - nextTick, - getScheduledKeys(nextTick) + msg.result.getInputModel, - ) - ) - } - } - -} diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala new file mode 100644 index 0000000000..699e5ef256 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala @@ -0,0 +1,257 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.results + +import edu.ie3.datamodel.models.result.ResultEntity +import edu.ie3.simona.api.data.ontology.DataMessageFromExt +import edu.ie3.simona.api.data.results.ExtResultDataConnection +import edu.ie3.simona.api.data.results.ontology.{ProvideResultEntities, RequestResultEntities, ResultDataMessageFromExt} +import edu.ie3.simona.exceptions.{InitializationException, ServiceException} +import edu.ie3.simona.ontology.messages.services.ServiceMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ServiceRegistrationMessage, ServiceResponseMessage} +import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} +import edu.ie3.simona.service.{ExtDataSupport, SimonaService} +import edu.ie3.simona.util.ReceiveDataMap +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import edu.ie3.util.TimeUtil +import org.apache.pekko.actor.typed.scaladsl.ActorContext + +import java.time.ZonedDateTime +import java.util.UUID +import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava} +import scala.util.{Failure, Success, Try} + +object ExtResultProvider + extends SimonaService[ServiceMessage] + with ExtDataSupport[ServiceMessage] { + + override type S = ExtResultStateData + + final case class ResultResponseMessage(result: ResultEntity) + extends ServiceResponseMessage { + def tick(implicit startTime: ZonedDateTime): Long = + TimeUtil.withDefaults.zonedDateTimeDifferenceInSeconds( + startTime, + result.getTime, + ) + } + + final case class ExtResultStateData( + extResultDataConnection: ExtResultDataConnection, + powerFlowResolution: Long, + currentTick: Long = INIT_SIM_TICK, + extResultSchedule: ExtResultSchedule, + extResultsMessage: Option[ResultDataMessageFromExt] = None, + receiveDataMap: ReceiveDataMap[UUID, ResultEntity] = ReceiveDataMap.empty, + resultStorage: Map[UUID, ResultEntity] = Map.empty, + implicit val startTime: ZonedDateTime, + ) extends ServiceBaseStateData + + final case class InitExtResultData( + extResultDataConnection: ExtResultDataConnection, + powerFlowResolution: Long, + startTime: ZonedDateTime, + ) extends InitializeServiceStateData + + override def init( + initServiceData: InitializeServiceStateData + ): Try[(ExtResultStateData, Option[Long])] = initServiceData match { + case InitExtResultData( + extResultDataConnection, + powerFlowResolution, + startTime, + ) => + val initGridSubscribers = + extResultDataConnection.getGridResultDataAssets.asScala + val initParticipantSubscribers = + extResultDataConnection.getParticipantResultDataAssets.asScala + val initFlexOptionSubscribers = + extResultDataConnection.getFlexOptionAssets.asScala + + // First result for system participants expected for tick 0 + // First result for grid expected for tick power flow resolution + val initResultScheduleMap = Map( + 0L -> (initParticipantSubscribers ++ initFlexOptionSubscribers).toSet + ) ++ Map(powerFlowResolution -> initGridSubscribers.toSet) + + Success( + ExtResultStateData( + extResultDataConnection, + powerFlowResolution, + extResultSchedule = ExtResultSchedule(initResultScheduleMap), + startTime = startTime, + ), + None, + ) + + case invalidData => + Failure( + new InitializationException( + s"Provided init data '${invalidData.getClass.getSimpleName}' for ExtResultProvider are invalid!" + ) + ) + } + + override protected def handleRegistrationRequest( + registrationMessage: ServiceRegistrationMessage + )(implicit + serviceStateData: ExtResultStateData, + ctx: ActorContext[ServiceMessage], + ): Try[ExtResultStateData] = { + // this should not happen + ctx.log.warn( + s"Received registration request '$registrationMessage', but no registration is required for ExtResultProvider!" + ) + Success(serviceStateData) + } + + override protected def announceInformation(tick: Long)(implicit + serviceStateData: ExtResultStateData, + ctx: ActorContext[ServiceMessage], + ): (ExtResultStateData, Option[Long]) = { + + val extMsg = serviceStateData.extResultsMessage.getOrElse( + // this should not be possible because the external simulation schedules this service + throw ServiceException( + "ExtResultDataService was triggered without ResultDataMessageFromExt available" + ) + ) + + val updatedStateData = extMsg match { + case request: RequestResultEntities => + if (request.tick != tick) { + ctx.log.warn( + s"Received result request for tick '${request.tick}', but current simulation tick is '$tick'!" + ) + + serviceStateData + } else { + // handle result request + + val currentTick = tick + val requestedKeys = request.requestedResults.asScala.toSet + + // Search for too old schedules + val shiftedSchedule = + serviceStateData.extResultSchedule.shiftPastTicksToCurrentTick( + currentTick + ) + + val expectedKeys = shiftedSchedule + .getExpectedKeys(currentTick) + .intersect(requestedKeys) + + val receiveDataMap = + serviceStateData.receiveDataMap.addExpectedKeys(expectedKeys) + + val updatedSchedule = shiftedSchedule.handleActivationWithRequest( + currentTick, + requestedKeys, + ) + + val currentStorage = serviceStateData.resultStorage + val storageKeys = expectedKeys.filter(currentStorage.contains) + + val updated = storageKeys.foldLeft(receiveDataMap) { + case (dataMap, key) => + dataMap.addData(key, currentStorage(key)) + } + + if (updated.isComplete) { + + serviceStateData.extResultDataConnection.queueExtResponseMsg( + new ProvideResultEntities(updated.receivedData.asJava) + ) + + serviceStateData.copy( + currentTick = tick, + extResultSchedule = updatedSchedule, + extResultsMessage = None, + receiveDataMap = ReceiveDataMap.empty, + resultStorage = currentStorage.removedAll(storageKeys), + ) + } else { + + serviceStateData.copy( + currentTick = tick, + extResultSchedule = updatedSchedule, + extResultsMessage = None, + receiveDataMap = updated, + resultStorage = currentStorage.removedAll(storageKeys), + ) + } + } + + case unsupported => + ctx.log.warn(s"Received unsupported external message: $unsupported") + + serviceStateData.copy(extResultsMessage = None) + } + + (updatedStateData, None) + } + + override protected def handleDataMessage( + extMsg: DataMessageFromExt + )(implicit serviceStateData: ExtResultStateData): ExtResultStateData = + extMsg match { + case extMsg: ResultDataMessageFromExt => + serviceStateData.copy( + extResultsMessage = Some(extMsg) + ) + } + + override protected def handleDataResponseMessage( + extResponseMsg: ServiceResponseMessage + )(implicit serviceStateData: ExtResultStateData): ExtResultStateData = + extResponseMsg match { + case ResultResponseMessage(result) => + val receiveDataMap = serviceStateData.receiveDataMap + + if (receiveDataMap.getExpectedKeys.isEmpty) { + + // we currently expect no result, + // save received result in storage + serviceStateData.copy( + resultStorage = + serviceStateData.resultStorage + (result.getInputModel -> result) + ) + + } else { + // we expect results, + val model = result.getInputModel + + if (receiveDataMap.getExpectedKeys.contains(model)) { + // the received model is expected + // save in data map + val updated = receiveDataMap.addData(result.getInputModel, result) + + if (updated.isComplete) { + + serviceStateData.extResultDataConnection.queueExtResponseMsg( + new ProvideResultEntities(updated.receivedData.asJava) + ) + + serviceStateData.copy(receiveDataMap = ReceiveDataMap.empty) + } else { + + serviceStateData.copy( + receiveDataMap = updated + ) + } + } else { + + // the received result is not expected + // save in storage + serviceStateData.copy( + resultStorage = serviceStateData.resultStorage + (model -> result) + ) + } + } + } + +} diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala new file mode 100644 index 0000000000..0e4f3b1388 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala @@ -0,0 +1,85 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.results + +import edu.ie3.simona.service.results.ExtResultProvider.ResultResponseMessage + +import java.util.UUID + +final case class ExtResultSchedule( + scheduleMap: Map[Long, Set[UUID]] = Map.empty, + unscheduledList: Set[UUID] = Set.empty, +) { + def shiftPastTicksToCurrentTick( + currentTick: Long + ): ExtResultSchedule = { + // Sammle alle Sets von Keys, deren Schlüssel kleiner als currentTick + val (toMerge, remaining) = scheduleMap.partition { case (tick, _) => + tick < currentTick + } + + // Kombiniere die Sets zu einem einzigen Set + val mergedSet = toMerge.values.flatten.toSet + + // Aktualisiere den scheduleMap mit dem neuen Set für currentTick + val updatedScheduleMap = remaining.updated( + currentTick, + scheduleMap.getOrElse(currentTick, Set.empty) ++ mergedSet, + ) + + // Rückgabe eines neuen ExtResultSchedule mit dem aktualisierten scheduleMap + copy( + scheduleMap = updatedScheduleMap + ) + } + + def getExpectedKeys(tick: Long): Set[UUID] = { + scheduleMap.getOrElse( + tick, + Set(), + ) ++ unscheduledList + } + + private def getScheduledKeys(tick: Long): Set[UUID] = { + scheduleMap.getOrElse(tick, Set[UUID]()) + } + + def handleActivation(tick: Long): ExtResultSchedule = { + copy( + scheduleMap = scheduleMap.-(tick) + ) + } + + def handleActivationWithRequest( + tick: Long, + keys: Iterable[UUID], + ): ExtResultSchedule = { + val remainingKeys = + scheduleMap.get(tick).map(_.diff(keys.toSet)).getOrElse(Set.empty) + if (remainingKeys.isEmpty) { + copy( + scheduleMap = scheduleMap.-(tick) + ) + } else { + copy( + scheduleMap = scheduleMap.updated(tick, remainingKeys) + ) + } + } + + def handleResult( + msg: ResultResponseMessage, + nextTick: Long, + ): ExtResultSchedule = { + copy( + scheduleMap = scheduleMap.updated( + nextTick, + getScheduledKeys(nextTick) + msg.result.getInputModel, + ) + ) + } +} diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index 08c040fd33..00e2cfb758 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -13,14 +13,13 @@ import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.api.data.results.ExtResultDataConnection -import edu.ie3.simona.api.data.results.ontology.ResultDataMessageFromExt import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt import edu.ie3.simona.api.simulation.{ExtSimAdapterData, ExtSimulation} import edu.ie3.simona.api.{ExtLinkInterface, ExtSimAdapter} import edu.ie3.simona.exceptions.ServiceException import edu.ie3.simona.ontology.messages.SchedulerMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.ScheduleServiceActivation +import edu.ie3.simona.ontology.messages.services.ServiceMessage.Create import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceStateData.InitializeServiceStateData import edu.ie3.simona.service.em.ExtEmDataService @@ -29,31 +28,22 @@ import edu.ie3.simona.service.ev.ExtEvDataService import edu.ie3.simona.service.ev.ExtEvDataService.InitExtEvData import edu.ie3.simona.service.primary.ExtPrimaryDataService import edu.ie3.simona.service.primary.ExtPrimaryDataService.InitExtPrimaryData -import edu.ie3.simona.service.results.ExtResultDataProvider -import edu.ie3.simona.service.results.ExtResultDataProvider.{ - InitExtResultData, - RequestDataMessageAdapter, - RequestScheduleActivationAdapter, -} +import edu.ie3.simona.service.results.ExtResultProvider +import edu.ie3.simona.service.results.ExtResultProvider.InitExtResultData import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK import org.apache.pekko.actor.typed.scaladsl.ActorContext -import org.apache.pekko.actor.typed.scaladsl.AskPattern.Askable import org.apache.pekko.actor.typed.scaladsl.adapter.{ ClassicActorRefOps, TypedActorContextOps, TypedActorRefOps, } -import org.apache.pekko.actor.typed.{ActorRef, Behavior, Scheduler} -import org.apache.pekko.actor.{ActorRef => ClassicRef} -import org.apache.pekko.util.{Timeout => PekkoTimeout} +import org.apache.pekko.actor.typed.{ActorRef, Behavior} import org.slf4j.{Logger, LoggerFactory} import java.time.ZonedDateTime import java.util.UUID -import scala.concurrent.Await -import scala.concurrent.duration.{DurationInt, FiniteDuration} +import scala.concurrent.duration.FiniteDuration import scala.jdk.CollectionConverters.{ListHasAsScala, SetHasAsScala} -import scala.jdk.DurationConverters.ScalaDurationOps import scala.util.{Failure, Success, Try} object ExtSimSetup { @@ -324,42 +314,33 @@ object ExtSimSetup { )(implicit context: ActorContext[_], scheduler: ActorRef[SchedulerMessage], - extSimAdapter: ClassicRef, + extSimAdapter: ActorRef[ControlResponseMessageFromExt], startTime: ZonedDateTime, resolution: FiniteDuration, ): ExtSimSetupData = { - val extResultDataProvider = - context.spawn( - ExtResultDataProvider(scheduler, startTime), - s"ExtResultDataProvider", - ) - - val timeout: PekkoTimeout = PekkoTimeout.create(5.seconds.toJava) - val scheduler2: Scheduler = context.system.scheduler - - val adapterRef = Await.result( - extResultDataProvider.ask[ActorRef[ResultDataMessageFromExt]](ref => - RequestDataMessageAdapter(ref) - )(timeout, scheduler2), - timeout.duration, + val extResultProvider = context.spawn( + ExtResultProvider(scheduler), + s"ExtResultDataProvider", ) - val adapterScheduleRef = Await.result( - extResultDataProvider.ask[ActorRef[ScheduleServiceActivation]](ref => - RequestScheduleActivationAdapter(ref) - )(timeout, scheduler2), - timeout.duration, + + val adapter = context.spawn( + ExtResultProvider.adapter(extResultProvider), + s"ExtResultDataProvider-adapter-to-external", ) extResultDataConnection.setActorRefs( - adapterRef.toClassic, - adapterScheduleRef.toClassic, + adapter, extSimAdapter, ) val powerFlowResolution = resolution.toSeconds - extResultDataProvider ! ExtResultDataProvider.Create( - InitExtResultData(extResultDataConnection, powerFlowResolution), + extResultProvider ! Create( + InitExtResultData( + extResultDataConnection, + powerFlowResolution, + startTime, + ), ScheduleLock.singleKey( context, scheduler, @@ -367,7 +348,7 @@ object ExtSimSetup { ), ) - extSimSetupData update (extResultDataConnection, extResultDataProvider) + extSimSetupData.update(extResultDataConnection, extResultProvider) } /** Method for validating the external primary data connections. diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index cb2c8730ae..a91aab3c9a 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -9,6 +9,7 @@ package edu.ie3.simona.sim.setup import edu.ie3.simona.api.data.ExtInputDataConnection import edu.ie3.simona.api.data.em.ExtEmDataConnection import edu.ie3.simona.api.data.ev.ExtEvDataConnection +import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.api.data.results.ExtResultDataConnection import edu.ie3.simona.ontology.messages.services.{ @@ -16,8 +17,6 @@ import edu.ie3.simona.ontology.messages.services.{ EvMessage, ServiceMessage, } -import edu.ie3.simona.service.results.ExtResultDataProvider -import edu.ie3.simona.ontology.messages.services.{EvMessage, ServiceMessage} import edu.ie3.simona.sim.setup.ExtSimSetupData.Input import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.{ActorRef => ClassicRef} @@ -36,9 +35,11 @@ import org.apache.pekko.actor.{ActorRef => ClassicRef} */ final case class ExtSimSetupData( extSimAdapters: Iterable[ClassicRef], - extPrimaryDataServices: Seq[Input], - extDataServices: Seq[Input], - extResultListeners: Seq[(ExtResultDataConnection, ActorRef[_])], + extPrimaryDataServices: Seq[ + (ExtPrimaryDataConnection, ActorRef[ServiceMessage]) + ], + extDataServices: Seq[Input[_ <: DataMessageFromExt]], + extResultListeners: Seq[(ExtResultDataConnection, ActorRef[ServiceMessage])], ) { private[setup] def update( @@ -61,7 +62,7 @@ final case class ExtSimSetupData( private[setup] def update( connection: ExtResultDataConnection, - ref: ActorRef[ExtResultDataProvider.Request], + ref: ActorRef[ServiceMessage], ): ExtSimSetupData = copy(extResultListeners = extResultListeners ++ Seq((connection, ref))) @@ -78,7 +79,7 @@ final case class ExtSimSetupData( case (_: ExtEmDataConnection, ref: ActorRef[EmMessage]) => ref } - def resultDataServices: Iterable[ActorRef[ExtResultDataProvider.Request]] = + def resultDataServices: Iterable[ActorRef[ServiceMessage]] = extResultListeners.map { case (_, ref) => ref } def evDataConnection: Option[ExtEvDataConnection] = From f7c69218a7b4dd3cefc59bc12cdda5400cbd59b8 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 10 Apr 2025 11:38:53 +0200 Subject: [PATCH 031/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 21 ++++++------------- .../util/ReceiveHierarchicalDataMap.scala | 13 ++++++++++++ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 16a3c0452f..a974bb3bf6 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -9,11 +9,7 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.{ - EmSetPointResult, - FlexRequestResult, - NoSetPointValue, -} +import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexRequestResult, NoSetPointValue} import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions @@ -30,11 +26,7 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.Power -import scala.jdk.CollectionConverters.{ - IterableHasAsScala, - MapHasAsJava, - MapHasAsScala, -} +import scala.jdk.CollectionConverters.{IterableHasAsScala, MapHasAsJava, MapHasAsScala, SeqHasAsJava} final case class EmCommunicationCore( hierarchy: EmHierarchy = EmHierarchy(), @@ -111,7 +103,7 @@ final case class EmCommunicationCore( val emEntities: Set[UUID] = provideFlexRequests .flexRequests() .asScala - .flatMap { case (_, v) => v.asScala } + .map { case (agent, _) => agent } .toSet val refs = emEntities.map(uuidToFlexAdapter) @@ -312,13 +304,12 @@ final case class EmCommunicationCore( if (updated.hasCompletedKeys) { - val (dataMap, updatedFlexRequest) = updated.getFinishedData + val (dataMap, updatedFlexRequest) = updated.getFinishedDataStructured log.warn(s"Data to be send: $dataMap") - val map = dataMap.map { case (key, _) => - key -> - new FlexRequestResult(flexActivation.tick.toDateTime, key) + val map = dataMap.map { case (sender, receivers) => + sender -> new FlexRequestResult(flexActivation.tick.toDateTime, sender, receivers.keySet.toList.asJava) } ( diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala index 7b5835646a..189bbbcd74 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala @@ -106,6 +106,19 @@ final case class ReceiveHierarchicalDataMap[K, V]( (dataMap, copy(receivedData = updated)) } + def getFinishedDataStructured: (Map[K, Map[K, V]], ReceiveHierarchicalDataMap[K, V]) = { + val finished = structure.keySet.filter(isComplete) + + val dataMap = finished.map(key => key -> structure(key)) + .map { case (parent, inferior) => + parent -> inferior.map(key => key -> receivedData(key)).toMap + }.toMap + + val updated = receivedData.removedAll(dataMap.values.flatMap(_.keySet)) + + (dataMap, copy(receivedData = updated)) + } + } object ReceiveHierarchicalDataMap { From af367ecbf4139fdee69c7040dd6f37e318a99d66 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 10 Apr 2025 19:05:30 +0200 Subject: [PATCH 032/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 36 +++++++++---------- .../util/ReceiveHierarchicalDataMap.scala | 16 ++++----- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index a974bb3bf6..0654e2c575 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.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 import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexRequestResult, NoSetPointValue} +import edu.ie3.simona.api.data.em.model.{EmSetPointResult, ExtendedFlexOptionsResult, FlexRequestResult, NoSetPointValue} import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions @@ -26,7 +25,7 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.Power -import scala.jdk.CollectionConverters.{IterableHasAsScala, MapHasAsJava, MapHasAsScala, SeqHasAsJava} +import scala.jdk.CollectionConverters.{IterableHasAsScala, MapHasAsJava, MapHasAsScala, SetHasAsJava} final case class EmCommunicationCore( hierarchy: EmHierarchy = EmHierarchy(), @@ -37,7 +36,7 @@ final case class EmCommunicationCore( toSchedule: Map[UUID, ScheduleFlexActivation] = Map.empty, flexRequestReceived: DataMap[UUID, Boolean] = ReceiveHierarchicalDataMap.empty(false), - flexOptionResponse: DataMap[UUID, (UUID, FlexOptionsResult)] = + flexOptionResponse: DataMap[UUID, ExtendedFlexOptionsResult] = ReceiveHierarchicalDataMap.empty, setPointResponse: DataMap[UUID, EmSetPointResult] = ReceiveHierarchicalDataMap.empty(false), @@ -54,11 +53,11 @@ final case class EmCommunicationCore( val parentEm = registerMsg.parentEm val parentUuid = registerMsg.parentUuid - val updatedHierarchy = parentEm match { - case Some(parent) => - hierarchy.add(uuid, ref, parent) + val updatedHierarchy = parentEm.zip(parentUuid) match { + case Some((parent, parentUuid)) => + hierarchy.add(uuid, ref, parent, parentUuid) case None => - hierarchy.add(uuid, ref, ref) + hierarchy.add(uuid, ref, ref, uuid) } copy( @@ -188,7 +187,7 @@ final case class EmCommunicationCore( (this, None) } else { - val uuid = receiver match { + val receiverUuid = receiver match { case Right(otherRef) => hierarchy.getUuid(otherRef) case Left(self: UUID) => @@ -202,16 +201,14 @@ final case class EmCommunicationCore( ) => flexOptionResponse.addData( modelUuid, - ( - uuid, - new FlexOptionsResult( + new ExtendedFlexOptionsResult( tick.toDateTime(startTime), modelUuid, + receiverUuid, min.toQuantity, ref.toQuantity, max.toQuantity, ), - ), ) case _ => @@ -225,12 +222,12 @@ final case class EmCommunicationCore( val (data, updatedFlexOptionResponse) = updated.getFinishedData - val pRefs = data.map { case (uuid, (_, options)) => + val pRefs = data.map { case (uuid, options) => uuid -> options.getpRef } val msgToExt = new FlexOptionsResponse(data.map { - case (entity, (_, value)) => entity -> value + case (entity, value) => entity -> value }.asJava) ( @@ -304,12 +301,12 @@ final case class EmCommunicationCore( if (updated.hasCompletedKeys) { - val (dataMap, updatedFlexRequest) = updated.getFinishedDataStructured + val (dataMap, _, updatedFlexRequest) = updated.getFinishedDataHierarchical log.warn(s"Data to be send: $dataMap") val map = dataMap.map { case (sender, receivers) => - sender -> new FlexRequestResult(flexActivation.tick.toDateTime, sender, receivers.keySet.toList.asJava) + sender -> new FlexRequestResult(flexActivation.tick.toDateTime, sender, receivers.asJava) } ( @@ -391,13 +388,14 @@ object EmCommunicationCore { model: UUID, ref: ActorRef[EmAgent.Request], parent: ActorRef[FlexResponse], + parentUuid: UUID, ): EmHierarchy = { copy( uuidToRef = uuidToRef + (model -> ref), refToUuid = refToUuid + (ref -> model), - uuidToFlexResponse = uuidToFlexResponse + (model -> parent), - flexResponseToUuid = flexResponseToUuid + (parent -> model), + uuidToFlexResponse = uuidToFlexResponse + (parentUuid -> parent), + flexResponseToUuid = flexResponseToUuid + (parent -> parentUuid), ) } diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala index 189bbbcd74..28e518cc09 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala @@ -35,7 +35,7 @@ final case class ReceiveHierarchicalDataMap[K, V]( key: Option[K], subKey: K, ): ReceiveHierarchicalDataMap[K, V] = { - log.warn(s"Parent '$key' with sub '$subKey'.") + log.warn(s"Added agent '$subKey' with parent '$key'.") val (updatedStructure, updatedKeys): (Map[K, Set[K]], Set[K]) = key match { case Some(parent) => @@ -106,17 +106,13 @@ final case class ReceiveHierarchicalDataMap[K, V]( (dataMap, copy(receivedData = updated)) } - def getFinishedDataStructured: (Map[K, Map[K, V]], ReceiveHierarchicalDataMap[K, V]) = { - val finished = structure.keySet.filter(isComplete) + def getFinishedDataHierarchical: (Map[K, Set[K]], Map[K, V], ReceiveHierarchicalDataMap[K, V]) = { + val (dataMap, updated) = getFinishedData - val dataMap = finished.map(key => key -> structure(key)) - .map { case (parent, inferior) => - parent -> inferior.map(key => key -> receivedData(key)).toMap - }.toMap + val hierarchicalDataMap = structure.keySet.filter(isComplete) + .map(parent =>parent -> structure(parent)).toMap - val updated = receivedData.removedAll(dataMap.values.flatMap(_.keySet)) - - (dataMap, copy(receivedData = updated)) + (hierarchicalDataMap, dataMap, updated) } } From c8cb3331b2d6c5c36bb55d7a1147c27bdc2b8289 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 11 Apr 2025 17:45:02 +0200 Subject: [PATCH 033/125] Refactoring em messages. --- .../service/em/EmCommunicationCore.scala | 60 +++++++++++++------ .../service/results/ExtResultProvider.scala | 16 ++++- .../util/ReceiveHierarchicalDataMap.scala | 11 ++-- 3 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 0654e2c575..3383396115 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -8,7 +8,12 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.{EmSetPointResult, ExtendedFlexOptionsResult, FlexRequestResult, NoSetPointValue} +import edu.ie3.simona.api.data.em.model.{ + EmSetPointResult, + ExtendedFlexOptionsResult, + FlexRequestResult, + NoSetPointValue, +} import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions @@ -25,7 +30,12 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.Power -import scala.jdk.CollectionConverters.{IterableHasAsScala, MapHasAsJava, MapHasAsScala, SetHasAsJava} +import scala.jdk.CollectionConverters.{ + IterableHasAsScala, + MapHasAsJava, + MapHasAsScala, + SetHasAsJava, +} final case class EmCommunicationCore( hierarchy: EmHierarchy = EmHierarchy(), @@ -38,7 +48,7 @@ final case class EmCommunicationCore( ReceiveHierarchicalDataMap.empty(false), flexOptionResponse: DataMap[UUID, ExtendedFlexOptionsResult] = ReceiveHierarchicalDataMap.empty, - setPointResponse: DataMap[UUID, EmSetPointResult] = + setPointResponse: DataMap[UUID, PValue] = ReceiveHierarchicalDataMap.empty(false), completions: DataMap[UUID, FlexCompletion] = ReceiveHierarchicalDataMap.empty(false), @@ -202,13 +212,13 @@ final case class EmCommunicationCore( flexOptionResponse.addData( modelUuid, new ExtendedFlexOptionsResult( - tick.toDateTime(startTime), - modelUuid, - receiverUuid, - min.toQuantity, - ref.toQuantity, - max.toQuantity, - ), + tick.toDateTime(startTime), + modelUuid, + receiverUuid, + min.toQuantity, + ref.toQuantity, + max.toQuantity, + ), ) case _ => @@ -301,12 +311,17 @@ final case class EmCommunicationCore( if (updated.hasCompletedKeys) { - val (dataMap, _, updatedFlexRequest) = updated.getFinishedDataHierarchical + val (dataMap, _, updatedFlexRequest) = + updated.getFinishedDataHierarchical log.warn(s"Data to be send: $dataMap") val map = dataMap.map { case (sender, receivers) => - sender -> new FlexRequestResult(flexActivation.tick.toDateTime, sender, receivers.asJava) + sender -> new FlexRequestResult( + flexActivation.tick.toDateTime, + sender, + receivers.asJava, + ) } ( @@ -337,20 +352,29 @@ final case class EmCommunicationCore( (tick.toDateTime, new PValue(setPower.toQuantity)) } - val updated = setPointResponse.addData( - uuid, - new EmSetPointResult(time, uuid, power), - ) + val updated = setPointResponse.addData(uuid, power) log.warn(s"Updated set point response: $updated") if (updated.hasCompletedKeys) { - val (dataMap, updatedSetPointResponse) = updated.getFinishedData + val (structureMap, dataMap, updatedSetPointResponse) = + updated.getFinishedDataHierarchical + + val setPointResults = structureMap.map { case (sender, receivers) => + sender -> new EmSetPointResult( + time, + sender, + receivers + .map(receiver => receiver -> dataMap(receiver)) + .toMap + .asJava, + ) + } ( copy(setPointResponse = updatedSetPointResponse), - Some(new EmSetPointDataResponse(dataMap.asJava)), + Some(new EmSetPointDataResponse(setPointResults.asJava)), ) } else { diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala index 699e5ef256..378cd2646f 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala @@ -9,11 +9,21 @@ package edu.ie3.simona.service.results import edu.ie3.datamodel.models.result.ResultEntity import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.results.ExtResultDataConnection -import edu.ie3.simona.api.data.results.ontology.{ProvideResultEntities, RequestResultEntities, ResultDataMessageFromExt} +import edu.ie3.simona.api.data.results.ontology.{ + ProvideResultEntities, + RequestResultEntities, + ResultDataMessageFromExt, +} import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ServiceRegistrationMessage, ServiceResponseMessage} -import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + ServiceRegistrationMessage, + ServiceResponseMessage, +} +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, +} import edu.ie3.simona.service.{ExtDataSupport, SimonaService} import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala index 28e518cc09..ecbfba5a54 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala @@ -106,13 +106,16 @@ final case class ReceiveHierarchicalDataMap[K, V]( (dataMap, copy(receivedData = updated)) } - def getFinishedDataHierarchical: (Map[K, Set[K]], Map[K, V], ReceiveHierarchicalDataMap[K, V]) = { + def getFinishedDataHierarchical + : (Map[K, Set[K]], Map[K, V], ReceiveHierarchicalDataMap[K, V]) = { val (dataMap, updated) = getFinishedData - val hierarchicalDataMap = structure.keySet.filter(isComplete) - .map(parent =>parent -> structure(parent)).toMap + val structureMap = structure.keySet + .filter(isComplete) + .map(parent => parent -> structure(parent)) + .toMap - (hierarchicalDataMap, dataMap, updated) + (structureMap, dataMap, updated) } } From f20bc8a96530264eb032a020c24cb004aa11157d Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 15 Apr 2025 09:09:39 +0200 Subject: [PATCH 034/125] Saving changes. --- .../scala/edu/ie3/simona/main/EmBuilder.scala | 68 +++++ .../service/em/EmCommunicationCore.scala | 15 +- src/test/resources/logback-test.xml | 2 +- .../edu/ie3/simona/agent/em/EmAgentIT.scala | 1 + .../agent/em/EmAgentWithServiceSpec.scala | 106 +++---- .../ie3/simona/agent/grid/ThermalGridIT.scala | 1 + .../service/em/ExtEmCommunicationIT.scala | 272 ++++++++++++++++++ .../service/em/ExtEmDataServiceSpec.scala | 186 ++++++------ .../service/ev/ExtEvDataServiceSpec.scala | 28 +- .../primary/ExtPrimaryDataServiceSpec.scala | 36 +-- .../primary/PrimaryServiceProxySpec.scala | 62 +--- .../sim/setup/ExtSimSetupDataSpec.scala | 35 ++- .../simona/sim/setup/ExtSimSetupSpec.scala | 5 +- .../input/EmCommunicationTestData.scala | 160 +++++++++++ 14 files changed, 708 insertions(+), 269 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/main/EmBuilder.scala create mode 100644 src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala create mode 100644 src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala diff --git a/src/main/scala/edu/ie3/simona/main/EmBuilder.scala b/src/main/scala/edu/ie3/simona/main/EmBuilder.scala new file mode 100644 index 0000000000..598bf32398 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/main/EmBuilder.scala @@ -0,0 +1,68 @@ +package edu.ie3.simona.main + +import edu.ie3.datamodel.io.naming.FileNamingStrategy +import edu.ie3.datamodel.io.sink.CsvFileSink +import edu.ie3.datamodel.io.source.csv.CsvDataSource +import edu.ie3.datamodel.io.source._ +import edu.ie3.datamodel.models.OperationTime +import edu.ie3.datamodel.models.input.{EmInput, OperatorInput} + +import java.nio.file.Path +import java.util.UUID +import scala.jdk.CollectionConverters.{MapHasAsScala, SetHasAsJava, SetHasAsScala} + +object EmBuilder { + + def main(args: Array[String]): Unit = { + val path = Path.of("simona", "input", "fullGrid") + + val csvSource = new CsvDataSource(";", path, new FileNamingStrategy()) + + val typeSource = new TypeSource(csvSource) + val gridSource = new RawGridSource(typeSource, csvSource) + val emSource = new EnergyManagementSource(typeSource, csvSource) + val thermalSource = new ThermalSource(typeSource, csvSource) + val participantSource = new SystemParticipantSource(typeSource, thermalSource, gridSource, emSource, csvSource) + + val (_, otherNodes) = gridSource.getNodes.asScala.toMap.partition { case (_, node) => node.isSlack} + + val emSup = new EmInput( + UUID.randomUUID(), + "EM_Scada", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + "PROPORTIONAL", + null + ) + + + val ems = otherNodes.map { case (_, node) => + node -> new EmInput( + UUID.randomUUID(), + s"Em_${node.getId}", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + "PROPORTIONAL", + emSup + ) + } + + val allEms = ems.values.toSet ++ Set(emSup) + + val fixedFeedIns = participantSource.getFixedFeedIns.asScala.map { ffi => + val node = ffi.getNode + ffi.copy().em(ems(node)).build() + } + + val loads = participantSource.getLoads.asScala.map { load => + val node = load.getNode + load.copy().em(ems(node)).build() + } + + val sink = new CsvFileSink(path.resolve("withEm"), new FileNamingStrategy(), ";") + + sink.persistAllIgnoreNested(allEms.asJava) + sink.persistAllIgnoreNested(fixedFeedIns.asJava) + sink.persistAllIgnoreNested(loads.asJava) + } +} diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 3383396115..62086f2e95 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -8,17 +8,13 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.{ - EmSetPointResult, - ExtendedFlexOptionsResult, - FlexRequestResult, - NoSetPointValue, -} +import edu.ie3.simona.api.data.em.model.{EmSetPointResult, ExtendedFlexOptionsResult, FlexRequestResult, NoSetPointValue} import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.service.em.EmCommunicationCore.{DataMap, EmHierarchy} +import edu.ie3.simona.service.em.ExtEmDataService.log import edu.ie3.simona.util.ReceiveHierarchicalDataMap import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.TickLong @@ -30,12 +26,7 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.Power -import scala.jdk.CollectionConverters.{ - IterableHasAsScala, - MapHasAsJava, - MapHasAsScala, - SetHasAsJava, -} +import scala.jdk.CollectionConverters.{IterableHasAsScala, MapHasAsJava, MapHasAsScala, SetHasAsJava} final case class EmCommunicationCore( hierarchy: EmHierarchy = EmHierarchy(), diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index 79b160e703..a153d47f5b 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -52,7 +52,7 @@ - + diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala index 230238e8a4..4fa8160478 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -748,6 +748,7 @@ class EmAgentIT simulationStartDate, parent = Left(scheduler.ref), listener = Iterable(resultListener.ref), + None, ), "EmAgentReactivePower", ) diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala index 3b623b80fb..e5de59b498 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala @@ -10,19 +10,13 @@ import edu.ie3.datamodel.models.result.system.EmResult import edu.ie3.simona.agent.participant.data.Data.PrimaryData.ComplexPower import edu.ie3.simona.config.RuntimeConfig.EmRuntimeConfig import edu.ie3.simona.event.ResultEvent -import edu.ie3.simona.event.ResultEvent.{ - FlexOptionsResultEvent, - ParticipantResultEvent, -} +import edu.ie3.simona.event.ResultEvent.{FlexOptionsResultEvent, ParticipantResultEvent} import edu.ie3.simona.event.notifier.NotifierConfig import edu.ie3.simona.ontology.messages.SchedulerMessage import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ -import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions +import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EmMessage -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - WrappedFlexRequest, - WrappedFlexResponse, -} +import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.test.common.input.EmInputTestData import edu.ie3.simona.test.matchers.SquantsMatchers @@ -32,11 +26,7 @@ import edu.ie3.util.TimeUtil import edu.ie3.util.quantities.QuantityMatchers.equalWithTolerance import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import edu.ie3.util.scala.quantities.{Kilovars, ReactivePower} -import org.apache.pekko.actor.testkit.typed.scaladsl.{ - ScalaTestWithActorTestKit, - TestProbe, -} -import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps +import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} import org.scalatest.matchers.should import org.scalatest.wordspec.AnyWordSpecLike import org.scalatestplus.mockito.MockitoSugar @@ -177,21 +167,25 @@ class EmAgentWithServiceSpec evcsAgent.expectMessage(FlexActivation(0)) // send flex options - emAgent ! ProvideMinMaxFlexOptions( + emAgent ! ProvideFlexOptions( pvInput.getUuid, - Kilowatts(-5), - Kilowatts(-5), - Kilowatts(0), + MinMaxFlexOptions( + Kilowatts(-5), + Kilowatts(-5), + Kilowatts(0), + ) ) pvAgent.expectNoMessage() evcsAgent.expectNoMessage() - emAgent ! ProvideMinMaxFlexOptions( + emAgent ! ProvideFlexOptions( evcsInput.getUuid, - Kilowatts(2), - Kilowatts(-11), - Kilowatts(11), + MinMaxFlexOptions( + Kilowatts(2), + Kilowatts(-11), + Kilowatts(11), + ) ) resultListener.expectMessageType[FlexOptionsResultEvent] match { @@ -205,11 +199,13 @@ class EmAgentWithServiceSpec service.expectMessageType[WrappedFlexResponse] match { case WrappedFlexResponse( - ProvideMinMaxFlexOptions( + ProvideFlexOptions( modelUuid, - referencePower, - minPower, - maxPower, + MinMaxFlexOptions( + referencePower, + minPower, + maxPower, + ) ), Right(receiver), ) => @@ -522,21 +518,25 @@ class EmAgentWithServiceSpec evcsAgent.expectMessage(FlexActivation(0)) // send flex options - emAgent ! ProvideMinMaxFlexOptions( + emAgent ! ProvideFlexOptions( pvInput.getUuid, - Kilowatts(-5), - Kilowatts(-5), - Kilowatts(0), + MinMaxFlexOptions( + Kilowatts(-5), + Kilowatts(-5), + Kilowatts(0), + ) ) pvAgent.expectNoMessage() evcsAgent.expectNoMessage() - emAgent ! ProvideMinMaxFlexOptions( + emAgent ! ProvideFlexOptions( evcsInput.getUuid, - Kilowatts(2), - Kilowatts(-11), - Kilowatts(11), + MinMaxFlexOptions( + Kilowatts(2), + Kilowatts(-11), + Kilowatts(11), + ) ) resultListener.expectMessageType[FlexOptionsResultEvent] match { @@ -550,12 +550,14 @@ class EmAgentWithServiceSpec service.expectMessageType[WrappedFlexResponse] match { case WrappedFlexResponse( - ProvideMinMaxFlexOptions( - modelUuid, - referencePower, - minPower, - maxPower, - ), + ProvideFlexOptions( + modelUuid, + MinMaxFlexOptions( + referencePower, + minPower, + maxPower, + ) + ), Right(receiver), ) => modelUuid shouldBe updatedEmInput.getUuid @@ -566,21 +568,25 @@ class EmAgentWithServiceSpec receiver shouldBe parentEmAgent } - parentEmAgent ! ProvideMinMaxFlexOptions( + parentEmAgent ! ProvideFlexOptions( updatedEmInput.getUuid, - Kilowatts(0), - Kilowatts(-16), - Kilowatts(6), + MinMaxFlexOptions( + Kilowatts(0), + Kilowatts(-16), + Kilowatts(6), + ) ) service.expectMessageType[WrappedFlexResponse] match { case WrappedFlexResponse( - ProvideMinMaxFlexOptions( - modelUuid, - referencePower, - minPower, - maxPower, - ), + ProvideFlexOptions( + modelUuid, + MinMaxFlexOptions( + referencePower, + minPower, + maxPower, + ) + ), Left(self), ) => modelUuid shouldBe parentEmInput.getUuid diff --git a/src/test/scala/edu/ie3/simona/agent/grid/ThermalGridIT.scala b/src/test/scala/edu/ie3/simona/agent/grid/ThermalGridIT.scala index 6208f494e2..c44736b3ef 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/ThermalGridIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/ThermalGridIT.scala @@ -943,6 +943,7 @@ class ThermalGridIT simulationStartWithPv, parent = Left(scheduler.ref), listener = Iterable(resultListener.ref), + None, ), "EmAgent", ) diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala new file mode 100644 index 0000000000..551bb694cf --- /dev/null +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -0,0 +1,272 @@ +package edu.ie3.simona.service.em + +import edu.ie3.datamodel.models.value.PValue +import edu.ie3.simona.agent.em.EmAgent +import edu.ie3.simona.agent.grid.GridAgent +import edu.ie3.simona.agent.participant2.ParticipantAgentInit.ParticipantRefs +import edu.ie3.simona.api.data.em.model.{ExtendedFlexOptionsResult, FlexOptionRequest, FlexOptions} +import edu.ie3.simona.api.data.em.ontology.{EmCompletion, EmSetPointDataResponse, FlexOptionsResponse, FlexRequestResponse} +import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} +import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt +import edu.ie3.simona.event.ResultEvent +import edu.ie3.simona.ontology.messages.SchedulerMessage.ScheduleActivation +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.RegisterControlledAsset +import edu.ie3.simona.ontology.messages.services.ServiceMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage.Create +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.scheduler.ScheduleLock +import edu.ie3.simona.service.ServiceType +import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData +import edu.ie3.simona.test.common.TestSpawnerTyped +import edu.ie3.simona.test.common.input.EmCommunicationTestData +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import edu.ie3.util.quantities.QuantityUtils._ +import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import org.scalatest.wordspec.AnyWordSpecLike +import org.slf4j.{Logger, LoggerFactory} + +import java.util.{Optional, UUID} +import scala.jdk.CollectionConverters.{MapHasAsJava, MapHasAsScala, SeqHasAsJava} + +class ExtEmCommunicationIT + extends ScalaTestWithActorTestKit + with AnyWordSpecLike + with EmCommunicationTestData + with TestSpawnerTyped { + + protected val log: Logger = LoggerFactory.getLogger("ExtEmCommunicationIT") + + private val emSupUuid = UUID.fromString("858f3d3d-4189-49cd-9fe5-3cd49b88dc70") + private val emNode3Uuid = UUID.fromString("fd1a8de9-722a-4304-8799-e1e976d9979c") + private val emNode4Uuid = UUID.fromString("ff0b995a-86ff-4f4d-987e-e475a64f2180") + + private val connection = new ExtEmDataConnection( + List(emSupUuid, emNode3Uuid, emNode4Uuid).asJava, + EmMode.EM_COMMUNICATION, + ) + + private val scheduler = TestProbe[SchedulerMessage]("scheduler") + private val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") + private val resultListener = TestProbe[ResultEvent]("ResultListener") + + private val gridAgent = TestProbe[GridAgent.Request]("GridAgent") + private val primaryServiceProxy = + TestProbe[ServiceMessage]("PrimaryServiceProxy") + private val weatherService = TestProbe[ServiceMessage]("WeatherService") + + private val participantRefs = ParticipantRefs( + gridAgent = gridAgent.ref, + primaryServiceProxy = primaryServiceProxy.ref, + services = Map(ServiceType.WeatherService -> weatherService.ref), + resultListener = Iterable(resultListener.ref), + ) + + "An ExtEmDataService im communication mode" should { + + "communicate correctly" in { + val service = spawn(ExtEmDataService(scheduler.ref)) + val serviceRef = service.ref + val adapter = spawn(ExtEmDataService.adapter(service)) + connection.setActorRefs(adapter, extSimAdapter.ref) + + + val emAgentSup = spawn( + EmAgent( + emSup, + modelConfig, + outputConfig, + "PROPORTIONAL", + simulationStart, + parent = Left(scheduler.ref), + listener = Iterable(resultListener.ref), + Some(serviceRef), + ) + ) + + val emAgentNode3 = spawn( + EmAgent( + emNode3, + modelConfig, + outputConfig, + "PRIORITIZED", + simulationStart, + parent = Right(emAgentSup), + listener = Iterable(resultListener.ref), + Some(serviceRef), + ) + ) + + val emAgentNode4 = spawn( + EmAgent( + emNode4, + modelConfig, + outputConfig, + "PRIORITIZED", + simulationStart, + parent = Right(emAgentSup), + listener = Iterable(resultListener.ref), + Some(serviceRef), + ) + ) + + val pvAgentNode3 = TestProbe[FlexibilityMessage.FlexRequest]("PvAgentNode3") + val pvAgentNode4 = TestProbe[FlexibilityMessage.FlexRequest]("PvAgentNode4") + val storageAgentNode3 = TestProbe[FlexibilityMessage.FlexRequest]("storageAgentNode3") + val loadAgentNode4 = TestProbe[FlexibilityMessage.FlexRequest]("LoadAgentNode4") + + emAgentNode3 ! RegisterControlledAsset(pvAgentNode3.ref, pvNode3) + emAgentNode3 ! RegisterControlledAsset(storageAgentNode3.ref, storageInput) + + emAgentNode4 ! RegisterControlledAsset(pvAgentNode4.ref, pvNode4) + emAgentNode4 ! RegisterControlledAsset(loadAgentNode4.ref, loadInput) + + /* INIT */ + + val key = + ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) + scheduler + .expectMessageType[ScheduleActivation] // lock activation scheduled + + service ! Create( + InitExtEmData(connection, simulationStart), + key, + ) + + val activationMsg = scheduler.expectMessageType[ScheduleActivation] + activationMsg.tick shouldBe INIT_SIM_TICK + activationMsg.unlockKey shouldBe Some(key) + val serviceActivation = activationMsg.actor + + serviceActivation ! Activation(INIT_SIM_TICK) + + + /* start communication */ + + // we first send a flex option request to the superior em agent + connection.sendFlexRequests( + 0, + Map(emSupUuid -> new FlexOptionRequest(emSupUuid, Optional.empty())).asJava, + Optional.of(900), + log + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0) + + // we expect to receive a request per inferior em agent + val requestsToInferior = connection.receiveWithType(classOf[FlexRequestResponse]) + requestsToInferior.flexRequests().asScala shouldBe Map(emSupUuid -> List(emNode3Uuid, emNode4Uuid).asJava) + + // we send a request to each inferior em agent + connection.sendFlexRequests( + 0, + Map( + emNode3Uuid -> new FlexOptionRequest(emNode3Uuid, Optional.of(emSupUuid)), + emNode4Uuid -> new FlexOptionRequest(emNode4Uuid, Optional.of(emSupUuid)), + ).asJava, + Optional.of(900), + log, + ) + + // we expect to receive flex options from the inferior em agents + val flexOptionResponseInferior = connection.receiveWithType(classOf[FlexOptionsResponse]) + .receiverToFlexOptions() + .asScala + + flexOptionResponseInferior shouldBe Map( + emNode3Uuid -> new ExtendedFlexOptionsResult( + simulationStart, + emNode3Uuid, + emSupUuid, + 0.0.asKiloWatt, + 0.0.asKiloWatt, + 4.asKiloWatt + ), + emNode4Uuid -> new ExtendedFlexOptionsResult( + simulationStart, + emNode4Uuid, + emSupUuid, + 2.200000413468004.asKiloWatt, + 2.200000413468004.asKiloWatt, + 2.200000413468004.asKiloWatt + ) + ) + + // we send the flex options to the superior em agent + connection.sendFlexOptions( + 0, + Map( + emSupUuid -> List( + new FlexOptions( + emSupUuid, + emNode3Uuid, + 0.0.asKiloWatt, + 0.0.asKiloWatt, + 4.asKiloWatt + ), + new FlexOptions( + emSupUuid, + emNode4Uuid, + 2.200000413468004.asKiloWatt, + 2.200000413468004.asKiloWatt, + 2.200000413468004.asKiloWatt + ), + ).asJava + ).asJava, + Optional.of(900), + log + ) + + // we expect the total flex options of the grid from the superior em agent + val totalFlexOptions = connection.receiveWithType(classOf[FlexOptionsResponse]) + .receiverToFlexOptions() + .asScala + + totalFlexOptions shouldBe Map( + emSup -> new ExtendedFlexOptionsResult( + simulationStart, + emSupUuid, + emSupUuid, + 2.200000413468004.asKiloWatt, + 2.200000413468004.asKiloWatt, + 6.200000413468004.asKiloWatt + ) + ) + + // after we received all options we will send a message, to keep the current set point + connection.sendSetPoints( + 0, + Map(emSupUuid -> new PValue(2.200000413468004.asKiloWatt)).asJava, + Optional.of(900), + log + ) + + // we expect a new set point for each inferior em agent + val inferiorSetPoints = connection.receiveWithType(classOf[EmSetPointDataResponse]) + .emData().asScala + + inferiorSetPoints shouldBe Map( + emNode3Uuid -> new PValue(0.0.asKiloWatt), + emNode4Uuid -> new PValue(2.200000413468004.asKiloWatt) + ) + + // we send the new set points to the inferior em agents + connection.sendSetPoints( + 0, + Map( + emNode3Uuid -> new PValue(0.0.asKiloWatt), + emNode4Uuid -> new PValue(2.200000413468004.asKiloWatt), + ).asJava, + Optional.of(900), + log + ) + + // we expect a finish message + connection.receiveWithType(classOf[EmCompletion]) + + + } + } +} 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 c55ab4b09c..1bec70dc2a 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -6,31 +6,18 @@ package edu.ie3.simona.service.em -import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.ExtEmDataConnection -import edu.ie3.simona.api.data.em.model.{EmSetPointResult, FlexOptions} +import edu.ie3.simona.api.data.em.model.{EmSetPointResult, ExtendedFlexOptionsResult, FlexOptions} import edu.ie3.simona.api.data.em.ontology._ +import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ - FlexActivation, - FlexRequest, - IssuePowerControl, -} -import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - WrappedFlexRequest, - WrappedFlexResponse, -} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - Create, - RegisterForEmDataService, -} +import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt +import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{FlexActivation, FlexRequest, IssuePowerControl, ProvideFlexOptions} +import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions +import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{Create, RegisterForEmDataService} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData @@ -39,16 +26,14 @@ import edu.ie3.simona.test.common.input.EmInputTestData import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.quantities.QuantityUtils._ import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW -import org.apache.pekko.actor.testkit.typed.scaladsl.{ - ScalaTestWithActorTestKit, - TestProbe, -} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import org.apache.pekko.testkit.TestKit.awaitCond import org.scalatest.wordspec.AnyWordSpecLike import squants.energy.Kilowatts import java.time.ZonedDateTime +import java._ import java.util.UUID import scala.concurrent.duration.DurationInt import scala.jdk.CollectionConverters._ @@ -62,8 +47,9 @@ class ExtEmDataServiceSpec implicit val simulationStart: ZonedDateTime = ZonedDateTime.now() - private val emptyMap = Map.empty[String, UUID].asJava + private val emptyControlled = List.empty[UUID].asJava + private val emAgentSupUUID = UUID.fromString("d797fe9c-e4af-49a3-947d-44f81933887e") private val emAgent1UUID = UUID.fromString("06a14909-366e-4e94-a593-1016e1455b30") private val emAgent2UUID = @@ -72,13 +58,14 @@ class ExtEmDataServiceSpec "An uninitialized em service" must { "send correct completion message after initialisation" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val emService = spawn(ExtEmDataService.apply(scheduler.ref)) - val extEmDataConnection = new ExtEmDataConnection(emptyMap) + val adapter = spawn(ExtEmDataService.adapter(emService)) + val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) extEmDataConnection.setActorRefs( - emService.toClassic, - extSimAdapter.ref.toClassic, + adapter, + extSimAdapter.ref, ) val key = @@ -103,13 +90,14 @@ class ExtEmDataServiceSpec "stash registration request and handle it correctly once initialized" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val emService = spawn(ExtEmDataService.apply(scheduler.ref)) - val extEmDataConnection = new ExtEmDataConnection(emptyMap) + val adapter = spawn(ExtEmDataService.adapter(emService)) + val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) extEmDataConnection.setActorRefs( - emService.toClassic, - extSimAdapter.ref.toClassic, + adapter, + extSimAdapter.ref, ) val emAgent = TestProbe[EmAgent.Request]("emAgent") @@ -151,13 +139,14 @@ class ExtEmDataServiceSpec "fail when activated without having received ExtEmMessage" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val emService = spawn(ExtEmDataService.apply(scheduler.ref)) - val extEmDataConnection = new ExtEmDataConnection(emptyMap) + val adapter = spawn(ExtEmDataService.adapter(emService)) + val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) extEmDataConnection.setActorRefs( - emService.toClassic, - extSimAdapter.ref.toClassic, + adapter, + extSimAdapter.ref, ) val key = @@ -189,14 +178,14 @@ class ExtEmDataServiceSpec "handle flex option request correctly" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val emService = spawn(ExtEmDataService.apply(scheduler.ref)) val adapter = spawn(ExtEmDataService.adapter(emService)) - val extEmDataConnection = new ExtEmDataConnection(emptyMap) + val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) extEmDataConnection.setActorRefs( - adapter.toClassic, - extSimAdapter.ref.toClassic, + adapter, + extSimAdapter.ref, ) val key = @@ -244,34 +233,32 @@ class ExtEmDataServiceSpec extEmDataConnection.sendExtMsg( new RequestEmFlexResults( INIT_SIM_TICK, - List.empty.asJava, + Map.empty[UUID, util.List[UUID]].asJava, ) ) - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + scheduler.expectNoMessage() serviceActivation ! Activation(INIT_SIM_TICK) emAgent1.expectNoMessage() - emAgentFlex1.expectNoMessage() + emAgentFlex1.expectMessage(FlexActivation(-1)) emAgent2.expectNoMessage() - emAgentFlex2.expectNoMessage() + emAgentFlex2.expectMessage(FlexActivation(-1)) extEmDataConnection.sendExtMsg( new RequestEmFlexResults( 0, - List(emAgent1UUID).asJava, + Map(emAgentSupUUID -> List(emAgent1UUID).asJava).asJava, ) ) - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) - scheduler.expectMessage(Completion(serviceActivation)) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + + //scheduler.expectMessage(Completion(serviceActivation)) serviceActivation ! Activation(0) @@ -286,11 +273,13 @@ class ExtEmDataServiceSpec extEmDataConnection.receiveTriggerQueue shouldBe empty emService ! WrappedFlexResponse( - ProvideMinMaxFlexOptions( + ProvideFlexOptions( emAgent1UUID, - Kilowatts(5), - Kilowatts(0), - Kilowatts(10), + MinMaxFlexOptions( + Kilowatts(5), + Kilowatts(0), + Kilowatts(10), + ) ), Left(emAgent1UUID), ) @@ -305,9 +294,10 @@ class ExtEmDataServiceSpec extEmDataConnection.receiveTriggerQueue .take() shouldBe new FlexOptionsResponse( Map( - emAgent1UUID -> new FlexOptionsResult( + emAgentSupUUID -> new ExtendedFlexOptionsResult( simulationStart, emAgent1UUID, + emAgentSupUUID, 0.asKiloWatt, 5.asKiloWatt, 10.asKiloWatt, @@ -318,14 +308,14 @@ class ExtEmDataServiceSpec "handle flex option provision correctly" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val emService = spawn(ExtEmDataService.apply(scheduler.ref)) val adapter = spawn(ExtEmDataService.adapter(emService)) - val extEmDataConnection = new ExtEmDataConnection(emptyMap) + val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) extEmDataConnection.setActorRefs( - adapter.toClassic, - extSimAdapter.ref.toClassic, + adapter, + extSimAdapter.ref, ) val key = @@ -374,29 +364,34 @@ class ExtEmDataServiceSpec new ProvideEmFlexOptionData( 0, Map( - emAgent2UUID -> new FlexOptions( - emAgent1UUID, - -3.asKiloWatt, - -1.asKiloWatt, - 1.asKiloWatt, - ) + emAgent2UUID -> + List( + new FlexOptions( + emAgentSupUUID, + emAgent1UUID, + -3.asKiloWatt, + -1.asKiloWatt, + 1.asKiloWatt, + ) + ).asJava ).asJava, None.toJava, ) ) - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0) emAgent1.expectNoMessage() emAgent2.expectMessage( - ProvideMinMaxFlexOptions( + ProvideFlexOptions( emAgent1UUID, - Kilowatts(-1), - Kilowatts(-3), - Kilowatts(1), + MinMaxFlexOptions( + Kilowatts(-1), + Kilowatts(-3), + Kilowatts(1), + ) ) ) @@ -405,14 +400,14 @@ class ExtEmDataServiceSpec "handle set point provision correctly" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val emService = spawn(ExtEmDataService.apply(scheduler.ref)) val adapter = spawn(ExtEmDataService.adapter(emService)) - val extEmDataConnection = new ExtEmDataConnection(emptyMap) + val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) extEmDataConnection.setActorRefs( - adapter.toClassic, - extSimAdapter.ref.toClassic, + adapter, + extSimAdapter.ref, ) val key = @@ -447,6 +442,7 @@ class ExtEmDataServiceSpec None, ) emAgent1.expectNoMessage() + emAgentFlex1.expectMessage(FlexActivation(-1)) emService ! RegisterForEmDataService( emAgent2UUID, @@ -456,6 +452,7 @@ class ExtEmDataServiceSpec None, ) emAgent2.expectNoMessage() + emAgentFlex2.expectMessage(FlexActivation(-1)) extEmDataConnection.sendExtMsg( new ProvideEmSetPointData( @@ -468,9 +465,8 @@ class ExtEmDataServiceSpec ) ) - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0) emAgent1.expectNoMessage() @@ -488,14 +484,14 @@ class ExtEmDataServiceSpec "handle set point request correctly" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val emService = spawn(ExtEmDataService.apply(scheduler.ref)) val adapter = spawn(ExtEmDataService.adapter(emService)) - val extEmDataConnection = new ExtEmDataConnection(emptyMap) + val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) extEmDataConnection.setActorRefs( - adapter.toClassic, - extSimAdapter.ref.toClassic, + adapter, + extSimAdapter.ref, ) val key = @@ -530,6 +526,7 @@ class ExtEmDataServiceSpec None, ) emAgent1.expectNoMessage() + emAgentFlex1.expectMessage(FlexActivation(-1)) emService ! RegisterForEmDataService( emAgent2UUID, @@ -539,6 +536,7 @@ class ExtEmDataServiceSpec None, ) emAgent2.expectNoMessage() + emAgentFlex2.expectMessage(FlexActivation(-1)) // parent em normally sets itself a set point to 0 kW // we replace this behavior with the external data service @@ -550,9 +548,8 @@ class ExtEmDataServiceSpec ) ) - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0) emAgent1.expectNoMessage() @@ -573,15 +570,14 @@ class ExtEmDataServiceSpec ) ) - extSimAdapter.expectMessage( - new ScheduleDataServiceMessage(adapter.toClassic) - ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0) emAgent1.expectNoMessage() emAgent2.expectNoMessage() - scheduler.expectMessage(Completion(serviceActivation)) + //scheduler.expectMessage(Completion(serviceActivation)) extEmDataConnection.receiveTriggerQueue shouldBe empty @@ -603,8 +599,8 @@ class ExtEmDataServiceSpec Map( emAgent1UUID -> new EmSetPointResult( simulationStart, - emAgent1UUID, - Some(new PValue(2.asKiloWatt)).toJava, + emAgentSupUUID, + Map(emAgent1UUID -> new PValue(2.asKiloWatt)).asJava, ) ).asJava ) diff --git a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala index b513054150..bc31168426 100644 --- a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala @@ -7,37 +7,23 @@ package edu.ie3.simona.service.ev import edu.ie3.simona.agent.participant2.ParticipantAgent -import edu.ie3.simona.agent.participant2.ParticipantAgent.{ - DataProvision, - RegistrationSuccessfulMessage, -} +import edu.ie3.simona.agent.participant2.ParticipantAgent.{DataProvision, RegistrationSuccessfulMessage} import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.data.ev.model.EvModel import edu.ie3.simona.api.data.ev.ontology._ -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.api.data.ontology.{DataMessageFromExt, ScheduleDataServiceMessage} import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt import edu.ie3.simona.model.participant2.evcs.EvModelWrapper -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} import edu.ie3.simona.ontology.messages.services.EvMessage._ -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - Create, - RegisterForEvDataMessage, - WrappedActivation, - WrappedExternalMessage, -} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{Create, RegisterForEvDataMessage, WrappedActivation, WrappedExternalMessage} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ev.ExtEvDataService.InitExtEvData import edu.ie3.simona.test.common.{EvTestData, TestSpawnerTyped, UnitSpec} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.quantities.PowerSystemUnits -import org.apache.pekko.actor.testkit.typed.scaladsl.{ - ScalaTestWithActorTestKit, - TestProbe, -} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} import org.apache.pekko.testkit.TestKit.awaitCond import tech.units.indriya.quantity.Quantities @@ -58,7 +44,7 @@ class ExtEvDataServiceSpec implicit def wrap( msg: EvDataMessageFromExt - ): WrappedExternalMessage[EvDataMessageFromExt] = + ): WrappedExternalMessage = WrappedExternalMessage(msg) private val evcs1UUID = @@ -724,7 +710,7 @@ class ExtEvDataServiceSpec // ev service should receive movements msg at this moment // scheduler should receive schedule msg extSimAdapter - .expectMessageType[ScheduleDataServiceMessage[EvDataMessageFromExt]] + .expectMessageType[ScheduleDataServiceMessage] // we trigger ev service evService ! Activation(0L) diff --git a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala index 1602646549..1afc331353 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala @@ -7,32 +7,21 @@ package edu.ie3.simona.service.primary import com.typesafe.scalalogging.LazyLogging +import edu.ie3.datamodel.models.value.Value import edu.ie3.simona.agent.participant.data.Data.PrimaryData -import edu.ie3.simona.agent.participant2.ParticipantAgent.{ - DataProvision, - RegistrationSuccessfulMessage, -} -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.agent.participant2.ParticipantAgent.{DataProvision, RegistrationSuccessfulMessage} import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} +import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt +import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - PrimaryServiceRegistrationMessage, - WrappedActivation, -} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{PrimaryServiceRegistrationMessage, WrappedActivation} import edu.ie3.simona.ontology.messages.services.WeatherMessage.RegisterForWeatherMessage import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.primary.ExtPrimaryDataService.InitExtPrimaryData import edu.ie3.simona.test.common.TestSpawnerTyped import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK -import org.apache.pekko.actor.testkit.typed.scaladsl.{ - ScalaTestWithActorTestKit, - TestProbe, -} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import org.scalatest.PrivateMethodTester import org.scalatest.matchers.should @@ -52,16 +41,17 @@ class ExtPrimaryDataServiceSpec private val scheduler = TestProbe[SchedulerMessage]("scheduler") private val extSimAdapter = - TestProbe[ScheduleDataServiceMessage]("extSimAdapter") + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") private val extPrimaryDataConnection = new ExtPrimaryDataConnection( - Map.empty[String, UUID].asJava + Map.empty[UUID, Class[_ <: Value]].asJava ) "An uninitialized external primary data service" must { "send correct completion message after initialisation" in { val primaryDataService = spawn(ExtPrimaryDataService.apply(scheduler.ref)) + val adapter = spawn(ExtPrimaryDataService.adapter(primaryDataService)) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) @@ -69,8 +59,8 @@ class ExtPrimaryDataServiceSpec .expectMessageType[ScheduleActivation] // lock activation scheduled extPrimaryDataConnection.setActorRefs( - primaryDataService.toClassic, - extSimAdapter.ref.toClassic, + adapter, + extSimAdapter.ref, ) primaryDataService ! ServiceMessage.Create( @@ -110,7 +100,7 @@ class ExtPrimaryDataServiceSpec service ! WrappedActivation(Activation(INIT_SIM_TICK)) service ! RegisterForWeatherMessage( - systemParticipant.ref.toClassic, + systemParticipant.ref, 51.4843281, 7.4116482, ) @@ -121,7 +111,7 @@ class ExtPrimaryDataServiceSpec "correctly register a forwarded request" ignore { serviceRef ! PrimaryServiceRegistrationMessage( - systemParticipant.ref.toClassic, + systemParticipant.ref, UUID.randomUUID(), ) diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala index f985039963..19799f3059 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala @@ -11,54 +11,27 @@ import edu.ie3.datamodel.io.naming.FileNamingStrategy import edu.ie3.datamodel.io.naming.timeseries.ColumnScheme import edu.ie3.datamodel.io.source.TimeSeriesMappingSource import edu.ie3.datamodel.io.source.csv.CsvTimeSeriesMappingSource -import edu.ie3.datamodel.models.value.SValue +import edu.ie3.datamodel.models.value.{PValue, SValue, Value} import edu.ie3.simona.agent.participant2.ParticipantAgent import edu.ie3.simona.agent.participant2.ParticipantAgent.RegistrationFailedMessage import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection -import edu.ie3.simona.config.ConfigParams.{ - CouchbaseParams, - TimeStampedCsvParams, - TimeStampedInfluxDb1xParams, -} +import edu.ie3.simona.config.ConfigParams.{CouchbaseParams, TimeStampedCsvParams, TimeStampedInfluxDb1xParams} import edu.ie3.simona.config.InputConfig.{Primary => PrimaryConfig} -import edu.ie3.simona.exceptions.{ - InitializationException, - InvalidConfigParameterException, -} -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} +import edu.ie3.simona.exceptions.{InitializationException, InvalidConfigParameterException} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - Create, - PrimaryServiceRegistrationMessage, - WorkerRegistrationMessage, - WrappedActivation, -} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{Create, PrimaryServiceRegistrationMessage, WorkerRegistrationMessage, WrappedActivation} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock.LockMsg import edu.ie3.simona.service.ServiceStateData.ServiceConstantStateData -import edu.ie3.simona.service.primary.PrimaryServiceProxy.{ - InitPrimaryServiceProxyStateData, - PrimaryServiceStateData, - SourceRef, -} +import edu.ie3.simona.service.primary.PrimaryServiceProxy.{InitPrimaryServiceProxyStateData, PrimaryServiceStateData, SourceRef} import edu.ie3.simona.service.primary.PrimaryServiceWorker.CsvInitPrimaryServiceStateData import edu.ie3.simona.test.common.TestSpawnerTyped import edu.ie3.simona.test.common.input.TimeSeriesTestData import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.TimeUtil -import org.apache.pekko.actor.testkit.typed.Effect.{ - NoEffects, - Spawned, - SpawnedAnonymous, -} -import org.apache.pekko.actor.testkit.typed.scaladsl.{ - BehaviorTestKit, - ScalaTestWithActorTestKit, - TestProbe, -} +import org.apache.pekko.actor.testkit.typed.Effect.{NoEffects, Spawned, SpawnedAnonymous} +import org.apache.pekko.actor.testkit.typed.scaladsl.{BehaviorTestKit, ScalaTestWithActorTestKit, TestProbe} import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} import org.apache.pekko.actor.typed.{ActorRef, Behavior} import org.mockito.ArgumentMatchers.any @@ -73,13 +46,9 @@ import org.slf4j.{Logger, LoggerFactory} import java.nio.file.{Path, Paths} import java.time.ZonedDateTime import java.util.UUID +import scala.jdk.CollectionConverters._ import scala.language.implicitConversions import scala.util.{Failure, Success} -import java.util.concurrent.TimeUnit -import java.util.{Objects, UUID} -import scala.concurrent.ExecutionContext.Implicits.global -import scala.jdk.CollectionConverters._ -import scala.util.{Failure, Success, Try} class PrimaryServiceProxySpec extends ScalaTestWithActorTestKit @@ -159,16 +128,15 @@ class PrimaryServiceProxySpec m } - private val validExtPrimaryDataService = TSpawner.spawn( - ExtPrimaryDataService.apply( - scheduler.ref.toTyped - ) - ) + private val validExtPrimaryDataService = spawn(ExtPrimaryDataService(scheduler.ref)) private val extEntityId = UUID.fromString("07bbe1aa-1f39-4dfb-b41b-339dec816ec4") + + private val valueMap: Map[UUID, Class[_ <: Value]] = Map(extEntityId -> classOf[PValue]) + private val extPrimaryDataConnection = new ExtPrimaryDataConnection( - Map(extEntityId.toString -> extEntityId).asJava + valueMap.asJava ) "Testing a primary service config" should { @@ -383,7 +351,7 @@ class PrimaryServiceProxySpec } "build proxy correctly when there is an external simulation" in { - proxy invokePrivate prepareStateData( + PrimaryServiceProxy.prepareStateData( validPrimaryConfig, simulationStart, Seq((extPrimaryDataConnection, validExtPrimaryDataService)), diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala index 3580878b34..3109035cdd 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala @@ -6,26 +6,25 @@ package edu.ie3.simona.sim.setup -import edu.ie3.simona.api.data.em.ExtEmDataConnection +import edu.ie3.datamodel.models.value.Value +import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.api.data.results.ExtResultDataConnection import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.test.common.UnitSpec -import org.apache.pekko.actor.testkit.typed.scaladsl.{ - ScalaTestWithActorTestKit, - TestProbe, -} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} import java.util.UUID -import scala.jdk.CollectionConverters.MapHasAsJava +import scala.jdk.CollectionConverters.{MapHasAsJava, SeqHasAsJava} class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { "An ExtSimSetupData" should { - val emptyMapInput = Map.empty[String, UUID].asJava - val emptyMapResult = Map.empty[UUID, String].asJava + val emptyMapInput = Map.empty[UUID, Class[_ <: Value]].asJava + val emptyListInput = List.empty[UUID].asJava + val emptyResultList = List.empty[UUID].asJava "be updated with an ExtPrimaryDataConnection correctly" in { val extSimSetupData = ExtSimSetupData.apply @@ -75,7 +74,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val evConnection = new ExtEvDataConnection() val evRef = TestProbe[ServiceMessage]("ev_service").ref - val emConnection = new ExtEmDataConnection(emptyMapInput) + val emConnection = new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) val emRef = TestProbe[ServiceMessage]("em_service").ref val cases = Table( @@ -114,9 +113,9 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val resultConnection = new ExtResultDataConnection( - emptyMapResult, - emptyMapResult, - emptyMapResult, + emptyResultList, + emptyResultList, + emptyResultList, ) val resultRef = TestProbe("result_service").ref @@ -137,14 +136,14 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val evConnection = new ExtEvDataConnection() val evRef = TestProbe[ServiceMessage]("ev_service").ref - val emConnection = new ExtEmDataConnection(emptyMapInput) + val emConnection = new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) val emRef = TestProbe[ServiceMessage]("em_service").ref val resultConnection = new ExtResultDataConnection( - emptyMapResult, - emptyMapResult, - emptyMapResult, + emptyResultList, + emptyResultList, + emptyResultList, ) val resultRef = TestProbe[ServiceMessage]("result_service").ref @@ -169,7 +168,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { } "return emDataService correctly" in { - val emConnection = new ExtEmDataConnection(emptyMapInput) + val emConnection = new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) val emRef = TestProbe("em_service").ref val cases = Table( @@ -209,7 +208,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { } "return emDataService correctly" in { - val emConnection = new ExtEmDataConnection(emptyMapInput) + val emConnection = new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) val emRef = TestProbe[ServiceMessage]("em_service").ref val cases = Table( diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala index 4b760ce656..c4925e6ea6 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.sim.setup +import edu.ie3.datamodel.models.value.{PValue, Value} import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.exceptions.ServiceException import edu.ie3.simona.test.common.UnitSpec @@ -24,8 +25,8 @@ class ExtSimSetupSpec extends UnitSpec { val uuid5 = UUID.fromString("ebcefed4-a3e6-4a2a-b4a5-74226d548546") val uuid6 = UUID.fromString("4a9c8e14-c0ee-425b-af40-9552b9075414") - def toMap(uuids: Set[UUID]): Map[String, UUID] = uuids - .map(uuid => uuid.toString -> uuid) + def toMap(uuids: Set[UUID]): Map[UUID, Class[_ <: Value]] = uuids + .map(uuid => uuid -> classOf[PValue]) .toMap "validate primary data connections without duplicates correctly" in { diff --git a/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala b/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala new file mode 100644 index 0000000000..93ffe567a4 --- /dev/null +++ b/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala @@ -0,0 +1,160 @@ +package edu.ie3.simona.test.common.input + +import edu.ie3.datamodel.models.input.system.`type`.StorageTypeInput +import edu.ie3.datamodel.models.input.system.characteristic.ReactivePowerCharacteristic +import edu.ie3.datamodel.models.input.system.{LoadInput, PvInput, StorageInput} +import edu.ie3.datamodel.models.input.{EmInput, NodeInput} +import edu.ie3.datamodel.models.profile.BdewStandardLoadProfile +import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils +import edu.ie3.simona.agent.participant2.ParticipantAgentInit.SimulationParameters +import edu.ie3.simona.config.RuntimeConfig.EmRuntimeConfig +import edu.ie3.simona.config.SimonaConfig +import edu.ie3.simona.event.notifier.NotifierConfig +import edu.ie3.simona.test.common.DefaultTestData +import edu.ie3.util.TimeUtil +import edu.ie3.util.geo.GeoUtils +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import squants.Each + +import java.time.ZonedDateTime +import java.util.UUID + +trait EmCommunicationTestData + extends DefaultTestData { + + protected implicit val simulationStart: ZonedDateTime = TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:00:00Z") + protected implicit val simulationEnd: ZonedDateTime = simulationStart.plusHours(2) + + protected val simonaConfig: SimonaConfig = createSimonaConfig() + + protected val outputConfig: NotifierConfig = NotifierConfig( + simulationResultInfo = true, + powerRequestReply = false, + flexResult = true, // also test FlexOptionsResult if EM-controlled + ) + + protected val simulationParams: SimulationParameters = SimulationParameters( + expectedPowerRequestTick = Long.MaxValue, + requestVoltageDeviationTolerance = Each(1e-14d), + simulationStart = simulationStart, + simulationEnd = simulationEnd, + ) + + protected val modelConfig: EmRuntimeConfig = EmRuntimeConfig( + calculateMissingReactivePowerWithModel = false, + scaling = 1, + uuids = List.empty, + aggregateFlex = "SELF_OPT_EXCL_REG", + curtailRegenerative = false, + ) + + + val node3 = new NodeInput( + UUID.fromString("33f29587-f63e-45b7-960b-037bda37a3cb"), + "Node_3", + 1.0.asPu, + false, + GeoUtils.buildPoint(51.4843281, 7.4116482), + GermanVoltageLevelUtils.LV, + 2 + ) + + val node4 = new NodeInput( + UUID.fromString("401f37f8-6f2c-4564-bc78-6736cb9cbf8d"), + "Node_4", + 1.0.asPu, + false, + GeoUtils.buildPoint(51.4843281, 7.4116482), + GermanVoltageLevelUtils.LV, + 2 + ) + + val emSup = new EmInput( + UUID.fromString("858f3d3d-4189-49cd-9fe5-3cd49b88dc70"), + "EM_SUP", + "PROPORTIONAL", + null + ) + + val emNode3 = new EmInput( + UUID.fromString("fd1a8de9-722a-4304-8799-e1e976d9979c"), + "emNode3", + "PRIORITIZED", + emSup + ) + + val emNode4 = new EmInput( + UUID.fromString("ff0b995a-86ff-4f4d-987e-e475a64f2180"), + "emNode4", + "PRIORITIZED", + emSup + ) + + val pvNode3 = new PvInput( + UUID.fromString("9d7cd8e2-d859-4f4f-9c01-abba06ef2e2c"), + "PV_Node_3", + node3, + ReactivePowerCharacteristic.parse("cosPhiFixed:{(0.0,0.9)}"), + emNode3, + 0.20000000298023224, + -14.803051948547363.asDegreeGeom, + 96.0.asPercent, + 42.391395568847656.asDegreeGeom, + 0.8999999761581421, + 1.0, + false, + 10.0.asKiloVoltAmpere, + 0.8999999761581421 + ) + + val pvNode4 = new PvInput( + UUID.fromString("a1eb7fc1-3bee-4b65-a387-ef3046644bf0"), + "PV_Node_4", + node4, + ReactivePowerCharacteristic.parse("cosPhiFixed:{(0.0,0.9)}"), + emNode4, + 0.20000000298023224, + -8.999500274658203.asDegreeGeom, + 98.0.asPercent, + 37.14517593383789.asDegreeGeom, + 0.8999999761581421, + 1.0, + false, + 10.0.asKiloVoltAmpere, + 0.8999999761581421 + ) + + val storageType = new StorageTypeInput( + UUID.fromString("95d4c980-d9e1-4813-9f2a-b0942488a570"), + "Typ_1", + 0.0.asEuro, + 0.65.asEuroPerKiloWattHour, + 16.0.asKiloWattHour, + 4.166666666666667.asKiloVoltAmpere, + 0.96, + 4.0.asKiloWatt, + 1.0.asPercentPerHour, + 93.0.asPercent + ) + + val storageInput: StorageInput = new StorageInput( + UUID.fromString("a2a92cfd-3492-465f-9587-e789f4620af8"), + "Storage_Node_3", + node3, + ReactivePowerCharacteristic.parse("cosPhiFixed:{(0.0,0.98)}"), + emNode3, + storageType + ) + + val loadInput: LoadInput = new LoadInput( + UUID.fromString("283a1252-a774-4b04-bfcf-fe8879065982"), + "Load_Node_4", + node4, + ReactivePowerCharacteristic.parse("cosPhiFixed:{(0.0,1.0)}"), + emNode4, + BdewStandardLoadProfile.H0, + 4000.0.asKiloWattHour, + 2.3157899379730225.asKiloVoltAmpere, + 0.949999988079071 + ) +} From 08df66fe38f80743f765ce5d36358cef3d3522a4 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 15 Apr 2025 13:30:39 +0200 Subject: [PATCH 035/125] Enhancing `ExtEmCommunicationIT`. --- .../service/em/EmCommunicationCore.scala | 1 - .../simona/service/em/ExtEmDataService.scala | 2 +- .../service/em/ExtEmCommunicationIT.scala | 479 +++++++++++++++--- 3 files changed, 416 insertions(+), 66 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 62086f2e95..fdf556023f 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -14,7 +14,6 @@ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.service.em.EmCommunicationCore.{DataMap, EmHierarchy} -import edu.ie3.simona.service.em.ExtEmDataService.log import edu.ie3.simona.util.ReceiveHierarchicalDataMap import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.TickLong 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 6834cb9073..e2e7272634 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -151,7 +151,7 @@ object ExtEmDataService serviceStateData.serviceCore.handleRegistration(registrationMsg) if (registrationMsg.parentEm.isEmpty) { - registrationMsg.flexAdapter ! FlexActivation(INIT_SIM_TICK) + //registrationMsg.flexAdapter ! FlexActivation(INIT_SIM_TICK) } Success(serviceStateData.copy(serviceCore = updatedCore)) diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index 551bb694cf..b8a5213ea9 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -3,16 +3,19 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.grid.GridAgent -import edu.ie3.simona.agent.participant2.ParticipantAgentInit.ParticipantRefs -import edu.ie3.simona.api.data.em.model.{ExtendedFlexOptionsResult, FlexOptionRequest, FlexOptions} +import edu.ie3.simona.agent.participant.statedata.ParticipantStateData.SimpleInputContainer +import edu.ie3.simona.agent.participant2.ParticipantAgentInit +import edu.ie3.simona.agent.participant2.ParticipantAgentInit.{ParticipantRefs, SimulationParameters} +import edu.ie3.simona.api.data.em.model.{ExtendedFlexOptionsResult, FlexOptionRequest, FlexOptions, FlexRequestResult} import edu.ie3.simona.api.data.em.ontology.{EmCompletion, EmSetPointDataResponse, FlexOptionsResponse, FlexRequestResponse} import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt +import edu.ie3.simona.config.RuntimeConfig.{LoadRuntimeConfig, PvRuntimeConfig, StorageRuntimeConfig} import edu.ie3.simona.event.ResultEvent -import edu.ie3.simona.ontology.messages.SchedulerMessage.ScheduleActivation -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.RegisterControlledAsset +import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ +import edu.ie3.simona.ontology.messages.flex.{FlexibilityMessage, MinMaxFlexOptions} import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.Create import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} @@ -21,21 +24,31 @@ import edu.ie3.simona.service.ServiceType import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData import edu.ie3.simona.test.common.TestSpawnerTyped import edu.ie3.simona.test.common.input.EmCommunicationTestData -import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import edu.ie3.simona.test.matchers.QuantityMatchers +import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.util.quantities.QuantityUtils._ +import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import org.scalatest.OptionValues.convertOptionToValuable import org.scalatest.wordspec.AnyWordSpecLike import org.slf4j.{Logger, LoggerFactory} +import squants.Each +import squants.energy.Kilowatts import java.util.{Optional, UUID} +import scala.concurrent.duration.{DurationInt, FiniteDuration} import scala.jdk.CollectionConverters.{MapHasAsJava, MapHasAsScala, SeqHasAsJava} +import scala.jdk.OptionConverters.RichOptional class ExtEmCommunicationIT extends ScalaTestWithActorTestKit with AnyWordSpecLike with EmCommunicationTestData + with QuantityMatchers with TestSpawnerTyped { + protected val messageTimeout: FiniteDuration = 30.seconds + protected val log: Logger = LoggerFactory.getLogger("ExtEmCommunicationIT") private val emSupUuid = UUID.fromString("858f3d3d-4189-49cd-9fe5-3cd49b88dc70") @@ -51,28 +64,13 @@ class ExtEmCommunicationIT private val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") private val resultListener = TestProbe[ResultEvent]("ResultListener") - private val gridAgent = TestProbe[GridAgent.Request]("GridAgent") - private val primaryServiceProxy = - TestProbe[ServiceMessage]("PrimaryServiceProxy") - private val weatherService = TestProbe[ServiceMessage]("WeatherService") - - private val participantRefs = ParticipantRefs( - gridAgent = gridAgent.ref, - primaryServiceProxy = primaryServiceProxy.ref, - services = Map(ServiceType.WeatherService -> weatherService.ref), - resultListener = Iterable(resultListener.ref), - ) - "An ExtEmDataService im communication mode" should { + val service = spawn(ExtEmDataService(scheduler.ref)) + val serviceRef = service.ref + val adapter = spawn(ExtEmDataService.adapter(service)) + connection.setActorRefs(adapter, extSimAdapter.ref) - "communicate correctly" in { - val service = spawn(ExtEmDataService(scheduler.ref)) - val serviceRef = service.ref - val adapter = spawn(ExtEmDataService.adapter(service)) - connection.setActorRefs(adapter, extSimAdapter.ref) - - - val emAgentSup = spawn( + val emAgentSup = spawn( EmAgent( emSup, modelConfig, @@ -85,7 +83,7 @@ class ExtEmCommunicationIT ) ) - val emAgentNode3 = spawn( + val emAgentNode3 = spawn( EmAgent( emNode3, modelConfig, @@ -98,7 +96,7 @@ class ExtEmCommunicationIT ) ) - val emAgentNode4 = spawn( + val emAgentNode4 = spawn( EmAgent( emNode4, modelConfig, @@ -111,23 +109,331 @@ class ExtEmCommunicationIT ) ) - val pvAgentNode3 = TestProbe[FlexibilityMessage.FlexRequest]("PvAgentNode3") - val pvAgentNode4 = TestProbe[FlexibilityMessage.FlexRequest]("PvAgentNode4") - val storageAgentNode3 = TestProbe[FlexibilityMessage.FlexRequest]("storageAgentNode3") - val loadAgentNode4 = TestProbe[FlexibilityMessage.FlexRequest]("LoadAgentNode4") + "with participant probes work correctly" in { + val pvAgentNode3 = TestProbe[FlexibilityMessage.FlexRequest]("PvAgentNode3") + val pvAgentNode4 = TestProbe[FlexibilityMessage.FlexRequest]("PvAgentNode4") + val storageAgentNode3 = TestProbe[FlexibilityMessage.FlexRequest]("storageAgentNode3") + val loadAgentNode4 = TestProbe[FlexibilityMessage.FlexRequest]("LoadAgentNode4") + + /* PRE_INIT */ + val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, PRE_INIT_TICK) + scheduler.expectMessageType[ScheduleActivation] // lock activation scheduled + + service ! Create( + InitExtEmData(connection, simulationStart), + key, + ) + + val activationMsg = scheduler.expectMessageType[ScheduleActivation] + activationMsg.tick shouldBe INIT_SIM_TICK + activationMsg.unlockKey shouldBe Some(key) + val serviceActivation = activationMsg.actor + + /* INIT */ + + //register all system participant agents emAgentNode3 ! RegisterControlledAsset(pvAgentNode3.ref, pvNode3) + emAgentNode3 ! ScheduleFlexActivation(pvNode3.getUuid, INIT_SIM_TICK) + emAgentNode3 ! RegisterControlledAsset(storageAgentNode3.ref, storageInput) + emAgentNode3 ! ScheduleFlexActivation(storageInput.getUuid, INIT_SIM_TICK) emAgentNode4 ! RegisterControlledAsset(pvAgentNode4.ref, pvNode4) + emAgentNode4 ! ScheduleFlexActivation(pvNode4.getUuid, INIT_SIM_TICK) + emAgentNode4 ! RegisterControlledAsset(loadAgentNode4.ref, loadInput) + emAgentNode4 ! ScheduleFlexActivation(loadInput.getUuid, INIT_SIM_TICK) - /* INIT */ + // activate the service for init tick + serviceActivation ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(serviceActivation)) + + // the agents will receive a flex activation + pvAgentNode3.expectMessage(FlexActivation(INIT_SIM_TICK)) + storageAgentNode3.expectMessage(FlexActivation(INIT_SIM_TICK)) + + pvAgentNode4.expectMessage(FlexActivation(INIT_SIM_TICK)) + loadAgentNode4.expectMessage(FlexActivation(INIT_SIM_TICK)) + + // all agents should answer with a flex completion for the init tick + emAgentNode3 ! FlexCompletion(pvNode3.getUuid, requestAtTick = Some(0)) + emAgentNode3 ! FlexCompletion(storageInput.getUuid, requestAtTick = Some(0)) - val key = - ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) - scheduler - .expectMessageType[ScheduleActivation] // lock activation scheduled + emAgentNode4 ! FlexCompletion(pvNode4.getUuid, requestAtTick = Some(0)) + emAgentNode4 ! FlexCompletion(loadInput.getUuid, requestAtTick = Some(0)) + + + /* TICK: 0 */ + + /* start communication */ + + // we first send a flex option request to the superior em agent + connection.sendFlexRequests( + 0, + Map(emSupUuid -> new FlexOptionRequest(emSupUuid, Optional.empty())).asJava, + Optional.of(900), + log + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0) + + // we expect to receive a request per inferior em agent + val requestsToInferior = connection.receiveWithType(classOf[FlexRequestResponse]) + .flexRequests() + .asScala + + requestsToInferior shouldBe Map(emSupUuid -> new FlexRequestResult(simulationStart, emSupUuid, List(emNode3Uuid, emNode4Uuid).asJava)) + + // we send a request to each inferior em agent + connection.sendFlexRequests( + 0, + Map( + emNode3Uuid -> new FlexOptionRequest(emNode3Uuid, Optional.of(emSupUuid)), + emNode4Uuid -> new FlexOptionRequest(emNode4Uuid, Optional.of(emSupUuid)), + ).asJava, + Optional.of(900), + log, + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0) + + // we expect flex request from inferior em agents + pvAgentNode3.expectMessage(messageTimeout, FlexActivation(0)) + emAgentNode3 ! ProvideFlexOptions(pvNode3.getUuid, MinMaxFlexOptions(zeroKW, zeroKW, zeroKW)) + + storageAgentNode3.expectMessage(messageTimeout, FlexActivation(0)) + emAgentNode3 ! ProvideFlexOptions(storageInput.getUuid, MinMaxFlexOptions(zeroKW, zeroKW, Kilowatts(4))) + + pvAgentNode4.expectMessage(messageTimeout, FlexActivation(0)) + emAgentNode4 ! ProvideFlexOptions(pvNode4.getUuid, MinMaxFlexOptions(zeroKW, zeroKW, zeroKW)) + + loadAgentNode4.expectMessage(messageTimeout, FlexActivation(0)) + emAgentNode4 ! ProvideFlexOptions(loadInput.getUuid, MinMaxFlexOptions(Kilowatts(2.200000413468004), Kilowatts(2.200000413468004), Kilowatts(2.200000413468004))) + + // we expect to receive flex options from the inferior em agents + val flexOptionResponseInferior = connection.receiveWithType(classOf[FlexOptionsResponse]) + .receiverToFlexOptions() + .asScala + + if (flexOptionResponseInferior.size == 1) { + flexOptionResponseInferior.addAll( + connection.receiveWithType(classOf[FlexOptionsResponse]) + .receiverToFlexOptions() + .asScala + ) + } + + flexOptionResponseInferior(emNode3Uuid) shouldBe new ExtendedFlexOptionsResult( + simulationStart, + emNode3Uuid, + emSupUuid, + 0.0.asMegaWatt, + 0.0.asMegaWatt, + 0.004.asMegaWatt + ) + + flexOptionResponseInferior(emNode4Uuid) shouldBe new ExtendedFlexOptionsResult( + simulationStart, + emNode4Uuid, + emSupUuid, + 0.002200000413468004.asMegaWatt, + 0.002200000413468004.asMegaWatt, + 0.002200000413468004.asMegaWatt + ) + + // we send the flex options to the superior em agent + connection.sendFlexOptions( + 0, + Map( + emSupUuid -> List( + new FlexOptions( + emSupUuid, + emNode3Uuid, + 0.0.asKiloWatt, + 0.0.asKiloWatt, + 4.asKiloWatt + ), + new FlexOptions( + emSupUuid, + emNode4Uuid, + 2.200000413468004.asKiloWatt, + 2.200000413468004.asKiloWatt, + 2.200000413468004.asKiloWatt + ), + ).asJava + ).asJava, + Optional.of(900), + log + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0) + + // we expect the total flex options of the grid from the superior em agent + val totalFlexOptions = connection.receiveWithType(classOf[FlexOptionsResponse]) + .receiverToFlexOptions() + .asScala + + totalFlexOptions shouldBe Map( + emSupUuid -> new ExtendedFlexOptionsResult( + simulationStart, + emSupUuid, + emSupUuid, + 0.002200000413468004.asMegaWatt, + 0.002200000413468004.asMegaWatt, + 0.0062000004134680035.asMegaWatt + ) + ) + + // after we received all options we will send a message, to keep the current set point + connection.sendSetPoints( + 0, + Map(emSupUuid -> new PValue(2.200000413468004.asKiloWatt)).asJava, + Optional.of(900), + log + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0) + + // we expect a new set point for each inferior em agent + val inferiorSetPoints = connection.receiveWithType(classOf[EmSetPointDataResponse]) + .emData().asScala + .flatMap(_._2.getReceiverToSetPoint.asScala) + + if (inferiorSetPoints.size == 1) { + inferiorSetPoints.addAll( + connection.receiveWithType(classOf[EmSetPointDataResponse]) + .emData().asScala + .flatMap(_._2.getReceiverToSetPoint.asScala) + ) + } + + inferiorSetPoints(emNode3Uuid).getP.toScala.value should equalWithTolerance(0.asKiloWatt) + inferiorSetPoints(emNode4Uuid).getP.toScala.value should equalWithTolerance(2.200000413468004.asKiloWatt) + + // we send the new set points to the inferior em agents + connection.sendSetPoints( + 0, + Map( + emNode3Uuid -> new PValue(0.0.asMegaWatt), + emNode4Uuid -> new PValue(0.002200000413468004.asMegaWatt), + ).asJava, + Optional.of(900), + log + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0) + + // we expect new flex control message for the participant agents + pvAgentNode3.expectMessage(IssueNoControl(0)) + emAgentNode3 ! FlexCompletion(pvNode3.getUuid) + + storageAgentNode3.expectMessage(IssueNoControl(0)) + emAgentNode3 ! FlexCompletion(storageInput.getUuid) + + + pvAgentNode4.expectMessage(IssueNoControl(0)) + emAgentNode4 ! FlexCompletion(pvNode4.getUuid) + + loadAgentNode4.expectMessage(IssueNoControl(0)) + emAgentNode4 ! FlexCompletion(loadInput.getUuid) + + + // we expect a finish message + connection.receiveWithType(classOf[EmCompletion]) + + + } + + "with participant agents work correctly" in { + val gridAgent = TestProbe[GridAgent.Request]("GridAgent") + val resultListener = TestProbe[ResultEvent]("ResultListener") + val primaryServiceProxy = + TestProbe[ServiceMessage]("PrimaryServiceProxy") + val weatherService = TestProbe[ServiceMessage]("WeatherService") + + val participantRefs = ParticipantRefs( + gridAgent = gridAgent.ref, + primaryServiceProxy = primaryServiceProxy.ref, + services = Map(ServiceType.WeatherService -> weatherService.ref), + resultListener = Iterable(resultListener.ref), + ) + + val simulationParams = SimulationParameters( + expectedPowerRequestTick = Long.MaxValue, + requestVoltageDeviationTolerance = Each(1e-14d), + simulationStart = simulationStart, + simulationEnd = simulationEnd, + ) + + val keys = ScheduleLock + .multiKey(TSpawner, scheduler.ref, PRE_INIT_TICK, 4) + .iterator + val lockActivation = + scheduler.expectMessageType[ScheduleActivation].actor + lockActivation ! Activation(PRE_INIT_TICK) + + val pvAgentNode3 = spawn( + ParticipantAgentInit( + SimpleInputContainer(pvNode3), + PvRuntimeConfig(), + outputConfig, + participantRefs, + simulationParams, + Right(emAgentNode3), + keys.next() + ), + "PvAgentNode3" + ) + + val storageAgentNode3 = spawn( + ParticipantAgentInit( + SimpleInputContainer(storageInput), + StorageRuntimeConfig(), + outputConfig, + participantRefs, + simulationParams, + Right(emAgentNode3), + keys.next() + ), + "storageAgentNode3" + ) + + val pvAgentNode4 = spawn( + ParticipantAgentInit( + SimpleInputContainer(pvNode4), + PvRuntimeConfig(), + outputConfig, + participantRefs, + simulationParams, + Right(emAgentNode4), + keys.next() + ), + "PvAgentNode4" + ) + + val loadAgentNode4 = spawn( + ParticipantAgentInit( + SimpleInputContainer(loadInput), + LoadRuntimeConfig(), + outputConfig, + participantRefs, + simulationParams, + Right(emAgentNode4), + keys.next() + ), + "LoadAgentNode4" + ) + + /* PRE_INIT */ + + val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, PRE_INIT_TICK) + scheduler.expectMessageType[ScheduleActivation] // lock activation scheduled service ! Create( InitExtEmData(connection, simulationStart), @@ -139,9 +445,24 @@ class ExtEmCommunicationIT activationMsg.unlockKey shouldBe Some(key) val serviceActivation = activationMsg.actor + // we expect a completion for the participant locks + scheduler.expectMessage(Completion(lockActivation)) + + + /* INIT */ + + // activate the service for init tick serviceActivation ! Activation(INIT_SIM_TICK) + // TODO: Init participants + + + + scheduler.expectMessage(Completion(serviceActivation)) + + /* TICK: 0 */ + /* start communication */ // we first send a flex option request to the superior em agent @@ -157,7 +478,10 @@ class ExtEmCommunicationIT // we expect to receive a request per inferior em agent val requestsToInferior = connection.receiveWithType(classOf[FlexRequestResponse]) - requestsToInferior.flexRequests().asScala shouldBe Map(emSupUuid -> List(emNode3Uuid, emNode4Uuid).asJava) + .flexRequests() + .asScala + + requestsToInferior shouldBe Map(emSupUuid -> new FlexRequestResult(simulationStart, emSupUuid, List(emNode3Uuid, emNode4Uuid).asJava)) // we send a request to each inferior em agent connection.sendFlexRequests( @@ -170,28 +494,38 @@ class ExtEmCommunicationIT log, ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0) + // we expect to receive flex options from the inferior em agents val flexOptionResponseInferior = connection.receiveWithType(classOf[FlexOptionsResponse]) .receiverToFlexOptions() .asScala - flexOptionResponseInferior shouldBe Map( - emNode3Uuid -> new ExtendedFlexOptionsResult( - simulationStart, - emNode3Uuid, - emSupUuid, - 0.0.asKiloWatt, - 0.0.asKiloWatt, - 4.asKiloWatt - ), - emNode4Uuid -> new ExtendedFlexOptionsResult( - simulationStart, - emNode4Uuid, - emSupUuid, - 2.200000413468004.asKiloWatt, - 2.200000413468004.asKiloWatt, - 2.200000413468004.asKiloWatt + if (flexOptionResponseInferior.size == 1) { + flexOptionResponseInferior.addAll( + connection.receiveWithType(classOf[FlexOptionsResponse]) + .receiverToFlexOptions() + .asScala ) + } + + flexOptionResponseInferior(emNode3Uuid) shouldBe new ExtendedFlexOptionsResult( + simulationStart, + emNode3Uuid, + emSupUuid, + 0.0.asMegaWatt, + 0.0.asMegaWatt, + 0.004.asMegaWatt + ) + + flexOptionResponseInferior(emNode4Uuid) shouldBe new ExtendedFlexOptionsResult( + simulationStart, + emNode4Uuid, + emSupUuid, + 0.002200000413468004.asMegaWatt, + 0.002200000413468004.asMegaWatt, + 0.002200000413468004.asMegaWatt ) // we send the flex options to the superior em agent @@ -219,19 +553,22 @@ class ExtEmCommunicationIT log ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0) + // we expect the total flex options of the grid from the superior em agent val totalFlexOptions = connection.receiveWithType(classOf[FlexOptionsResponse]) .receiverToFlexOptions() .asScala totalFlexOptions shouldBe Map( - emSup -> new ExtendedFlexOptionsResult( + emSupUuid -> new ExtendedFlexOptionsResult( simulationStart, emSupUuid, emSupUuid, - 2.200000413468004.asKiloWatt, - 2.200000413468004.asKiloWatt, - 6.200000413468004.asKiloWatt + 0.002200000413468004.asMegaWatt, + 0.002200000413468004.asMegaWatt, + 0.0062000004134680035.asMegaWatt ) ) @@ -243,30 +580,44 @@ class ExtEmCommunicationIT log ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0) + // we expect a new set point for each inferior em agent val inferiorSetPoints = connection.receiveWithType(classOf[EmSetPointDataResponse]) .emData().asScala + .flatMap(_._2.getReceiverToSetPoint.asScala) - inferiorSetPoints shouldBe Map( - emNode3Uuid -> new PValue(0.0.asKiloWatt), - emNode4Uuid -> new PValue(2.200000413468004.asKiloWatt) - ) + if (inferiorSetPoints.size == 1) { + inferiorSetPoints.addAll( + connection.receiveWithType(classOf[EmSetPointDataResponse]) + .emData().asScala + .flatMap(_._2.getReceiverToSetPoint.asScala) + ) + } + + inferiorSetPoints(emNode3Uuid).getP.toScala.value should equalWithTolerance(0.asKiloWatt) + inferiorSetPoints(emNode4Uuid).getP.toScala.value should equalWithTolerance(2.200000413468004.asKiloWatt) // we send the new set points to the inferior em agents connection.sendSetPoints( 0, Map( - emNode3Uuid -> new PValue(0.0.asKiloWatt), - emNode4Uuid -> new PValue(2.200000413468004.asKiloWatt), + emNode3Uuid -> new PValue(0.0.asMegaWatt), + emNode4Uuid -> new PValue(0.002200000413468004.asMegaWatt), ).asJava, Optional.of(900), log ) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0) + // we expect a finish message connection.receiveWithType(classOf[EmCompletion]) } + } } From d42baee99a0774d1372b93744e0b7cbae4d6eb08 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 15 Apr 2025 16:01:08 +0200 Subject: [PATCH 036/125] Saving changes. --- .../ie3/simona/service/ExtDataSupport.scala | 5 +- .../service/em/EmCommunicationCore.scala | 48 +++++++------- .../simona/service/em/EmServiceBaseCore.scala | 7 +- .../ie3/simona/service/em/EmServiceCore.scala | 2 + .../simona/service/em/ExtEmDataService.scala | 66 +++++++++++-------- .../util/ReceiveHierarchicalDataMap.scala | 8 +-- .../service/em/ExtEmCommunicationIT.scala | 53 ++++++++++++++- 7 files changed, 118 insertions(+), 71 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala index 120e804feb..18410dc2d2 100644 --- a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala +++ b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala @@ -50,15 +50,14 @@ trait ExtDataSupport[T >: ServiceMessage] { constantData: ServiceConstantStateData, ): PartialFunction[(ActorContext[T], T), Behavior[T]] = { case (_, WrappedExternalMessage(extMsg)) => - val updatedStateData = handleDataMessage(extMsg)(stateData) + val updatedStateData = handleDataMessage(extMsg) idle(updatedStateData, constantData) case (ctx, extResponseMsg: ServiceResponseMessage) => ctx.log.warn(s"Response: $extResponseMsg") - val updatedStateData = - handleDataResponseMessage(extResponseMsg)(stateData) + val updatedStateData = handleDataResponseMessage(extResponseMsg) idle(updatedStateData, constantData) } diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index fdf556023f..d80ea2d192 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -14,8 +14,8 @@ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.service.em.EmCommunicationCore.{DataMap, EmHierarchy} -import edu.ie3.simona.util.ReceiveHierarchicalDataMap -import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import edu.ie3.simona.util.{ReceiveDataMap, ReceiveHierarchicalDataMap} +import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import org.apache.pekko.actor.typed.ActorRef @@ -28,9 +28,10 @@ import javax.measure.quantity.Power import scala.jdk.CollectionConverters.{IterableHasAsScala, MapHasAsJava, MapHasAsScala, SetHasAsJava} final case class EmCommunicationCore( - hierarchy: EmHierarchy = EmHierarchy(), + override val lastFinishedTick: Long = PRE_INIT_TICK, override val uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = Map.empty, + hierarchy: EmHierarchy = EmHierarchy(), flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, uuidToPRef: Map[UUID, ComparableQuantity[Power]] = Map.empty, toSchedule: Map[UUID, ScheduleFlexActivation] = Map.empty, @@ -40,8 +41,7 @@ final case class EmCommunicationCore( ReceiveHierarchicalDataMap.empty, setPointResponse: DataMap[UUID, PValue] = ReceiveHierarchicalDataMap.empty(false), - completions: DataMap[UUID, FlexCompletion] = - ReceiveHierarchicalDataMap.empty(false), + completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, ) extends EmServiceCore { override def handleRegistration( @@ -68,7 +68,7 @@ final case class EmCommunicationCore( flexRequestReceived.updateStructure(parentUuid, uuid), flexOptionResponse = flexOptionResponse.updateStructure(parentUuid, uuid), setPointResponse = setPointResponse.updateStructure(parentUuid, uuid), - completions = completions.updateStructure(parentUuid, uuid), + completions = completions.addExpectedKeys(Set(uuid)), ) } @@ -92,7 +92,7 @@ final case class EmCommunicationCore( adapter ! IssueNoControl(tick) } - (EmCommunicationCore.empty, Some(new EmCompletion())) + (this, Some(new EmCompletion())) } case provideFlexRequests: ProvideFlexRequestData => @@ -196,9 +196,9 @@ final case class EmCommunicationCore( val updated = provideFlexOptions match { case ProvideFlexOptions( - modelUuid, - MinMaxFlexOptions(ref, min, max), - ) => + modelUuid, + MinMaxFlexOptions(ref, min, max), + ) => flexOptionResponse.addData( modelUuid, new ExtendedFlexOptionsResult( @@ -252,27 +252,23 @@ final case class EmCommunicationCore( receiver.map(_ ! completion) log.warn(s"Completion: $completion") - // TODO: Check if necessary - if (tick != INIT_SIM_TICK) { + val model = completion.modelUuid + val updated = completions.addData(model, completion) - val model = completion.modelUuid - val updated = completions.addData(model, completion) + if (updated.isComplete) { + val allKeys = updated.receivedData.keySet - if (updated.allCompleted) { + val extMsgOption = if (tick != INIT_SIM_TICK) { + // send completion message to external simulation, if we aren't in the INIT_SIM_TICK + Some(new EmCompletion()) + } else None - val (_, updatedCompletions) = updated.getFinishedData + // every em agent has sent a completion message + (copy(lastFinishedTick = tick, completions = ReceiveDataMap(allKeys)), extMsgOption) - // every em agent has sent a completion message - // send completion message to external simulation - (copy(completions = updatedCompletions), Some(new EmCompletion())) + } else (copy(completions = updated), None) - } else { - (copy(completions = updated), None) - } - } else { - (this, None) - } } override def handleFlexRequest( @@ -379,7 +375,7 @@ final case class EmCommunicationCore( flexRequestReceived = flexRequestReceived.addSubKeysToExpectedKeys(keys), flexOptionResponse = flexOptionResponse.addSubKeysToExpectedKeys(keys), setPointResponse = setPointResponse.addSubKeysToExpectedKeys(keys), - completions = completions.addSubKeysToExpectedKeys(keys), + completions = completions.addExpectedKeys(keys), ) } 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 c54feab69b..5da9274f81 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -7,11 +7,9 @@ package edu.ie3.simona.service.em import edu.ie3.simona.api.data.em.ontology._ -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ - FlexRequest, - FlexResponse, -} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{FlexRequest, FlexResponse} import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService +import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger @@ -19,6 +17,7 @@ import java.time.ZonedDateTime import java.util.UUID case class EmServiceBaseCore( + override val lastFinishedTick: Long = PRE_INIT_TICK, override val uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = Map.empty ) extends EmServiceCore { 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 e8d6bf7ed1..599377c8f5 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -38,6 +38,8 @@ import scala.jdk.CollectionConverters.MapHasAsScala import scala.jdk.OptionConverters.RichOptional trait EmServiceCore { + def lastFinishedTick: Long + def uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] implicit class SquantsToQuantity(private val value: Power) { 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 e2e7272634..f82def1c70 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -12,21 +12,12 @@ import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{InitializationException, ServiceException} +import edu.ie3.simona.ontology.messages.Activation import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ -import edu.ie3.simona.ontology.messages.services.EmMessage -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - WrappedFlexRequest, - WrappedFlexResponse, -} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - RegisterForEmDataService, - ServiceRegistrationMessage, - ServiceResponseMessage, -} -import edu.ie3.simona.service.ServiceStateData.{ - InitializeServiceStateData, - ServiceBaseStateData, -} +import edu.ie3.simona.ontology.messages.services.{EmMessage, ServiceMessage} +import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{RegisterForEmDataService, ServiceRegistrationMessage, ServiceResponseMessage} +import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData, ServiceConstantStateData} import edu.ie3.simona.service.{ExtDataSupport, SimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.typed.ActorRef @@ -151,7 +142,7 @@ object ExtEmDataService serviceStateData.serviceCore.handleRegistration(registrationMsg) if (registrationMsg.parentEm.isEmpty) { - //registrationMsg.flexAdapter ! FlexActivation(INIT_SIM_TICK) + registrationMsg.flexAdapter ! FlexActivation(INIT_SIM_TICK) } Success(serviceStateData.copy(serviceCore = updatedCore)) @@ -167,24 +158,41 @@ object ExtEmDataService serviceStateData: ExtEmDataStateData, ctx: ActorContext[EmMessage], ): (ExtEmDataStateData, Option[Long]) = { - val extMsg = serviceStateData.extEmDataMessage.getOrElse( - throw ServiceException( - "ExtEmDataService was triggered without ExtEmDataMessage available" + val stateTick = serviceStateData.tick + + if (tick != stateTick) { + + val lastFinishedTick = serviceStateData.serviceCore.lastFinishedTick + + if (lastFinishedTick == stateTick) { + announceInformation(tick)(serviceStateData.copy(tick = tick), ctx) + } + + ctx.self ! ServiceMessage.WrappedActivation(Activation(tick)) + + (serviceStateData, None) + + } else { + val extMsg = serviceStateData.extEmDataMessage.getOrElse( + throw ServiceException( + "ExtEmDataService was triggered without ExtEmDataMessage available" + ) ) - ) - val (updatedCore, msgToExt) = - serviceStateData.serviceCore.handleExtMessage(tick, extMsg)(ctx.log) + val (updatedCore, msgToExt) = + serviceStateData.serviceCore.handleExtMessage(tick, extMsg)(ctx.log) - msgToExt.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) + msgToExt.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) - ( - serviceStateData.copy( - tick = tick, - serviceCore = updatedCore, - ), - None, - ) + ( + serviceStateData.copy( + tick = tick, + serviceCore = updatedCore, + extEmDataMessage = None, + ), + None, + ) + } } override protected def handleDataMessage( diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala index ecbfba5a54..603798375a 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala @@ -18,18 +18,14 @@ final case class ReceiveHierarchicalDataMap[K, V]( private val log: Logger = LoggerFactory.getLogger(ReceiveHierarchicalDataMap.getClass) - def allCompleted: Boolean = structure.keySet.forall(isComplete) + def allCompleted: Boolean = allKeys.forall(isComplete) def hasCompletedKeys: Boolean = structure.keySet.exists(isComplete) - def isComplete(key: K): Boolean = if (withExpected) { - structure + def isComplete(key: K): Boolean = structure .get(key) .map(_.intersect(expectedKeys)) .forall(_.forall(receivedData.contains)) - } else { - structure.get(key).forall(_.forall(receivedData.contains)) - } def updateStructure( key: Option[K], diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index b8a5213ea9..3608a52ef3 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -4,6 +4,7 @@ import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.agent.participant.statedata.ParticipantStateData.SimpleInputContainer +import edu.ie3.simona.agent.participant2.ParticipantAgent.{RegistrationFailedMessage, RegistrationSuccessfulMessage} import edu.ie3.simona.agent.participant2.ParticipantAgentInit import edu.ie3.simona.agent.participant2.ParticipantAgentInit.{ParticipantRefs, SimulationParameters} import edu.ie3.simona.api.data.em.model.{ExtendedFlexOptionsResult, FlexOptionRequest, FlexOptions, FlexRequestResult} @@ -17,7 +18,8 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleAc import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.{FlexibilityMessage, MinMaxFlexOptions} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.Create +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{Create, PrimaryServiceRegistrationMessage} +import edu.ie3.simona.ontology.messages.services.WeatherMessage.RegisterForWeatherMessage import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceType @@ -453,13 +455,58 @@ class ExtEmCommunicationIT // activate the service for init tick serviceActivation ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(serviceActivation)) + + primaryServiceProxy.receiveMessages(4) should contain allOf ( + PrimaryServiceRegistrationMessage( + pvAgentNode3, + pvNode3.getUuid, + ), + PrimaryServiceRegistrationMessage( + storageAgentNode3, + storageInput.getUuid, + ), + PrimaryServiceRegistrationMessage( + pvAgentNode4, + pvNode4.getUuid, + ), + PrimaryServiceRegistrationMessage( + loadAgentNode4, + loadInput.getUuid, + ), + ) + // pv agent 3 + pvAgentNode3 ! RegistrationFailedMessage(primaryServiceProxy.ref) - // TODO: Init participants + // deal with weather service registration + weatherService.expectMessage( + RegisterForWeatherMessage( + pvAgentNode3, + pvNode3.getNode.getGeoPosition.getY, + pvNode3.getNode.getGeoPosition.getX, + ) + ) + pvAgentNode3 ! RegistrationSuccessfulMessage(weatherService.ref, 0L) + // pv agent 4 + pvAgentNode4 ! RegistrationFailedMessage(primaryServiceProxy.ref) - scheduler.expectMessage(Completion(serviceActivation)) + weatherService.expectMessage( + RegisterForWeatherMessage( + pvAgentNode4, + pvNode4.getNode.getGeoPosition.getY, + pvNode4.getNode.getGeoPosition.getX, + ) + ) + pvAgentNode4 ! RegistrationSuccessfulMessage(weatherService.ref, 0L) + + // storage + storageAgentNode3 ! RegistrationFailedMessage(primaryServiceProxy.ref) + + // load + loadAgentNode4 ! RegistrationFailedMessage(primaryServiceProxy.ref) /* TICK: 0 */ From 336fcd9d0dc13c65a7111e6915ad720ba9c6961c Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 16 Apr 2025 13:28:51 +0200 Subject: [PATCH 037/125] Saving changes. --- .../scala/edu/ie3/simona/main/EmBuilder.scala | 32 +- .../service/em/EmCommunicationCore.scala | 26 +- .../simona/service/em/EmServiceBaseCore.scala | 7 +- .../simona/service/em/ExtEmDataService.scala | 33 +- .../util/ReceiveHierarchicalDataMap.scala | 19 +- .../agent/em/EmAgentWithServiceSpec.scala | 59 +- .../service/em/ExtEmCommunicationIT.scala | 700 ++++++++---------- .../service/em/ExtEmDataServiceSpec.scala | 86 ++- .../service/ev/ExtEvDataServiceSpec.scala | 27 +- .../primary/ExtPrimaryDataServiceSpec.scala | 20 +- .../primary/PrimaryServiceProxySpec.scala | 51 +- .../sim/setup/ExtSimSetupDataSpec.scala | 17 +- .../input/EmCommunicationTestData.scala | 38 +- 13 files changed, 600 insertions(+), 515 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/main/EmBuilder.scala b/src/main/scala/edu/ie3/simona/main/EmBuilder.scala index 598bf32398..9eab6f090e 100644 --- a/src/main/scala/edu/ie3/simona/main/EmBuilder.scala +++ b/src/main/scala/edu/ie3/simona/main/EmBuilder.scala @@ -1,3 +1,9 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + package edu.ie3.simona.main import edu.ie3.datamodel.io.naming.FileNamingStrategy @@ -9,7 +15,11 @@ import edu.ie3.datamodel.models.input.{EmInput, OperatorInput} import java.nio.file.Path import java.util.UUID -import scala.jdk.CollectionConverters.{MapHasAsScala, SetHasAsJava, SetHasAsScala} +import scala.jdk.CollectionConverters.{ + MapHasAsScala, + SetHasAsJava, + SetHasAsScala, +} object EmBuilder { @@ -22,9 +32,17 @@ object EmBuilder { val gridSource = new RawGridSource(typeSource, csvSource) val emSource = new EnergyManagementSource(typeSource, csvSource) val thermalSource = new ThermalSource(typeSource, csvSource) - val participantSource = new SystemParticipantSource(typeSource, thermalSource, gridSource, emSource, csvSource) + val participantSource = new SystemParticipantSource( + typeSource, + thermalSource, + gridSource, + emSource, + csvSource, + ) - val (_, otherNodes) = gridSource.getNodes.asScala.toMap.partition { case (_, node) => node.isSlack} + val (_, otherNodes) = gridSource.getNodes.asScala.toMap.partition { + case (_, node) => node.isSlack + } val emSup = new EmInput( UUID.randomUUID(), @@ -32,10 +50,9 @@ object EmBuilder { OperatorInput.NO_OPERATOR_ASSIGNED, OperationTime.notLimited(), "PROPORTIONAL", - null + null, ) - val ems = otherNodes.map { case (_, node) => node -> new EmInput( UUID.randomUUID(), @@ -43,7 +60,7 @@ object EmBuilder { OperatorInput.NO_OPERATOR_ASSIGNED, OperationTime.notLimited(), "PROPORTIONAL", - emSup + emSup, ) } @@ -59,7 +76,8 @@ object EmBuilder { load.copy().em(ems(node)).build() } - val sink = new CsvFileSink(path.resolve("withEm"), new FileNamingStrategy(), ";") + val sink = + new CsvFileSink(path.resolve("withEm"), new FileNamingStrategy(), ";") sink.persistAllIgnoreNested(allEms.asJava) sink.persistAllIgnoreNested(fixedFeedIns.asJava) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index d80ea2d192..453ae1e421 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -8,7 +8,12 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.{EmSetPointResult, ExtendedFlexOptionsResult, FlexRequestResult, NoSetPointValue} +import edu.ie3.simona.api.data.em.model.{ + EmSetPointResult, + ExtendedFlexOptionsResult, + FlexRequestResult, + NoSetPointValue, +} import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions @@ -25,7 +30,12 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.Power -import scala.jdk.CollectionConverters.{IterableHasAsScala, MapHasAsJava, MapHasAsScala, SetHasAsJava} +import scala.jdk.CollectionConverters.{ + IterableHasAsScala, + MapHasAsJava, + MapHasAsScala, + SetHasAsJava, +} final case class EmCommunicationCore( override val lastFinishedTick: Long = PRE_INIT_TICK, @@ -196,9 +206,9 @@ final case class EmCommunicationCore( val updated = provideFlexOptions match { case ProvideFlexOptions( - modelUuid, - MinMaxFlexOptions(ref, min, max), - ) => + modelUuid, + MinMaxFlexOptions(ref, min, max), + ) => flexOptionResponse.addData( modelUuid, new ExtendedFlexOptionsResult( @@ -255,7 +265,6 @@ final case class EmCommunicationCore( val model = completion.modelUuid val updated = completions.addData(model, completion) - if (updated.isComplete) { val allKeys = updated.receivedData.keySet @@ -265,7 +274,10 @@ final case class EmCommunicationCore( } else None // every em agent has sent a completion message - (copy(lastFinishedTick = tick, completions = ReceiveDataMap(allKeys)), extMsgOption) + ( + copy(lastFinishedTick = tick, completions = ReceiveDataMap(allKeys)), + extMsgOption, + ) } else (copy(completions = updated), None) 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 5da9274f81..d040979551 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -7,7 +7,10 @@ package edu.ie3.simona.service.em import edu.ie3.simona.api.data.em.ontology._ -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{FlexRequest, FlexResponse} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ + FlexRequest, + FlexResponse, +} import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK import org.apache.pekko.actor.typed.ActorRef @@ -18,7 +21,7 @@ import java.util.UUID case class EmServiceBaseCore( override val lastFinishedTick: Long = PRE_INIT_TICK, - override val uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = Map.empty + override val uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = Map.empty, ) extends EmServiceCore { override def handleRegistration( 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 f82def1c70..c24561bd03 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -14,10 +14,20 @@ import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequ import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.Activation import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + WrappedFlexRequest, + WrappedFlexResponse, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + RegisterForEmDataService, + ServiceRegistrationMessage, + ServiceResponseMessage, +} import edu.ie3.simona.ontology.messages.services.{EmMessage, ServiceMessage} -import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{RegisterForEmDataService, ServiceRegistrationMessage, ServiceResponseMessage} -import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData, ServiceConstantStateData} +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, +} import edu.ie3.simona.service.{ExtDataSupport, SimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.typed.ActorRef @@ -26,6 +36,7 @@ import org.slf4j.{Logger, LoggerFactory} import java.time.ZonedDateTime import java.util.UUID +import scala.annotation.tailrec import scala.util.{Failure, Success, Try} object ExtEmDataService @@ -161,16 +172,22 @@ object ExtEmDataService val stateTick = serviceStateData.tick if (tick != stateTick) { + // we received an activation for the next tick + // check the last finished tick of the core val lastFinishedTick = serviceStateData.serviceCore.lastFinishedTick - if (lastFinishedTick == stateTick) { - announceInformation(tick)(serviceStateData.copy(tick = tick), ctx) - } + // we request a new activation for the same tick + ctx.self ! ServiceMessage.WrappedActivation(Activation(tick)) - ctx.self ! ServiceMessage.WrappedActivation(Activation(tick)) + if (lastFinishedTick == stateTick) { + // we finished the last tick and update the core with the requested tick + (serviceStateData.copy(tick = tick), None) - (serviceStateData, None) + } else { + // we are still waiting for data for the state data tick + (serviceStateData, None) + } } else { val extMsg = serviceStateData.extEmDataMessage.getOrElse( diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala index 603798375a..9c29a7c8fd 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala @@ -18,20 +18,18 @@ final case class ReceiveHierarchicalDataMap[K, V]( private val log: Logger = LoggerFactory.getLogger(ReceiveHierarchicalDataMap.getClass) - def allCompleted: Boolean = allKeys.forall(isComplete) - def hasCompletedKeys: Boolean = structure.keySet.exists(isComplete) def isComplete(key: K): Boolean = structure - .get(key) - .map(_.intersect(expectedKeys)) - .forall(_.forall(receivedData.contains)) + .get(key) + .map(_.intersect(expectedKeys)) + .forall(_.forall(receivedData.contains)) def updateStructure( key: Option[K], subKey: K, ): ReceiveHierarchicalDataMap[K, V] = { - log.warn(s"Added agent '$subKey' with parent '$key'.") + log.debug(s"Added agent '$subKey' with parent '$key' to structure.") val (updatedStructure, updatedKeys): (Map[K, Set[K]], Set[K]) = key match { case Some(parent) => @@ -50,11 +48,15 @@ final case class ReceiveHierarchicalDataMap[K, V]( ) } - case None => + case None if !structure.contains(subKey) => ( structure ++ Map(subKey -> Set.empty), allKeys + subKey, ) + case _ => + // we already added the subkey as parent + // therefore, no changes are needed + (structure, allKeys) } copy( @@ -63,9 +65,6 @@ final case class ReceiveHierarchicalDataMap[K, V]( ) } - def addExpectedKey(key: K): ReceiveHierarchicalDataMap[K, V] = - copy(expectedKeys = expectedKeys + key) - def addExpectedKeys(keys: Set[K]): ReceiveHierarchicalDataMap[K, V] = copy(expectedKeys = expectedKeys ++ keys) diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala index e5de59b498..9f0b2b8444 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala @@ -10,13 +10,19 @@ import edu.ie3.datamodel.models.result.system.EmResult import edu.ie3.simona.agent.participant.data.Data.PrimaryData.ComplexPower import edu.ie3.simona.config.RuntimeConfig.EmRuntimeConfig import edu.ie3.simona.event.ResultEvent -import edu.ie3.simona.event.ResultEvent.{FlexOptionsResultEvent, ParticipantResultEvent} +import edu.ie3.simona.event.ResultEvent.{ + FlexOptionsResultEvent, + ParticipantResultEvent, +} import edu.ie3.simona.event.notifier.NotifierConfig import edu.ie3.simona.ontology.messages.SchedulerMessage import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EmMessage -import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + WrappedFlexRequest, + WrappedFlexResponse, +} import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.test.common.input.EmInputTestData import edu.ie3.simona.test.matchers.SquantsMatchers @@ -26,7 +32,10 @@ import edu.ie3.util.TimeUtil import edu.ie3.util.quantities.QuantityMatchers.equalWithTolerance import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import edu.ie3.util.scala.quantities.{Kilovars, ReactivePower} -import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} import org.scalatest.matchers.should import org.scalatest.wordspec.AnyWordSpecLike import org.scalatestplus.mockito.MockitoSugar @@ -173,7 +182,7 @@ class EmAgentWithServiceSpec Kilowatts(-5), Kilowatts(-5), Kilowatts(0), - ) + ), ) pvAgent.expectNoMessage() @@ -185,7 +194,7 @@ class EmAgentWithServiceSpec Kilowatts(2), Kilowatts(-11), Kilowatts(11), - ) + ), ) resultListener.expectMessageType[FlexOptionsResultEvent] match { @@ -205,7 +214,7 @@ class EmAgentWithServiceSpec referencePower, minPower, maxPower, - ) + ), ), Right(receiver), ) => @@ -524,7 +533,7 @@ class EmAgentWithServiceSpec Kilowatts(-5), Kilowatts(-5), Kilowatts(0), - ) + ), ) pvAgent.expectNoMessage() @@ -536,7 +545,7 @@ class EmAgentWithServiceSpec Kilowatts(2), Kilowatts(-11), Kilowatts(11), - ) + ), ) resultListener.expectMessageType[FlexOptionsResultEvent] match { @@ -550,14 +559,14 @@ class EmAgentWithServiceSpec service.expectMessageType[WrappedFlexResponse] match { case WrappedFlexResponse( - ProvideFlexOptions( - modelUuid, - MinMaxFlexOptions( - referencePower, - minPower, - maxPower, - ) - ), + ProvideFlexOptions( + modelUuid, + MinMaxFlexOptions( + referencePower, + minPower, + maxPower, + ), + ), Right(receiver), ) => modelUuid shouldBe updatedEmInput.getUuid @@ -574,19 +583,19 @@ class EmAgentWithServiceSpec Kilowatts(0), Kilowatts(-16), Kilowatts(6), - ) + ), ) service.expectMessageType[WrappedFlexResponse] match { case WrappedFlexResponse( - ProvideFlexOptions( - modelUuid, - MinMaxFlexOptions( - referencePower, - minPower, - maxPower, - ) - ), + ProvideFlexOptions( + modelUuid, + MinMaxFlexOptions( + referencePower, + minPower, + maxPower, + ), + ), Left(self), ) => modelUuid shouldBe parentEmInput.getUuid diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index 3608a52ef3..e9449560be 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -1,25 +1,61 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.agent.participant.statedata.ParticipantStateData.SimpleInputContainer -import edu.ie3.simona.agent.participant2.ParticipantAgent.{RegistrationFailedMessage, RegistrationSuccessfulMessage} -import edu.ie3.simona.agent.participant2.ParticipantAgentInit -import edu.ie3.simona.agent.participant2.ParticipantAgentInit.{ParticipantRefs, SimulationParameters} -import edu.ie3.simona.api.data.em.model.{ExtendedFlexOptionsResult, FlexOptionRequest, FlexOptions, FlexRequestResult} -import edu.ie3.simona.api.data.em.ontology.{EmCompletion, EmSetPointDataResponse, FlexOptionsResponse, FlexRequestResponse} +import edu.ie3.simona.agent.participant2.ParticipantAgent.{ + DataProvision, + RegistrationFailedMessage, + RegistrationSuccessfulMessage, +} +import edu.ie3.simona.agent.participant2.ParticipantAgentInit.ParticipantRefs +import edu.ie3.simona.agent.participant2.{ + ParticipantAgent, + ParticipantAgentInit, +} +import edu.ie3.simona.api.data.em.model.{ + FlexOptionRequest, + FlexOptions, + FlexRequestResult, +} +import edu.ie3.simona.api.data.em.ontology.{ + EmCompletion, + EmSetPointDataResponse, + FlexOptionsResponse, + FlexRequestResponse, +} import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.api.data.ontology.{ + DataMessageFromExt, + ScheduleDataServiceMessage, +} import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt -import edu.ie3.simona.config.RuntimeConfig.{LoadRuntimeConfig, PvRuntimeConfig, StorageRuntimeConfig} +import edu.ie3.simona.config.RuntimeConfig.{ + LoadRuntimeConfig, + PvRuntimeConfig, + StorageRuntimeConfig, +} import edu.ie3.simona.event.ResultEvent -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ -import edu.ie3.simona.ontology.messages.flex.{FlexibilityMessage, MinMaxFlexOptions} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{Create, PrimaryServiceRegistrationMessage} -import edu.ie3.simona.ontology.messages.services.WeatherMessage.RegisterForWeatherMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + Create, + PrimaryServiceRegistrationMessage, +} +import edu.ie3.simona.ontology.messages.services.WeatherMessage.{ + RegisterForWeatherMessage, + WeatherData, +} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceType @@ -29,33 +65,46 @@ import edu.ie3.simona.test.common.input.EmCommunicationTestData import edu.ie3.simona.test.matchers.QuantityMatchers import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.util.quantities.QuantityUtils._ -import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW -import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import edu.ie3.util.scala.quantities.WattsPerSquareMeter +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} +import org.apache.pekko.actor.typed.ActorRef import org.scalatest.OptionValues.convertOptionToValuable import org.scalatest.wordspec.AnyWordSpecLike import org.slf4j.{Logger, LoggerFactory} -import squants.Each -import squants.energy.Kilowatts +import squants.motion.MetersPerSecond +import squants.thermal.Celsius +import tech.units.indriya.ComparableQuantity import java.util.{Optional, UUID} +import javax.measure.quantity.Power import scala.concurrent.duration.{DurationInt, FiniteDuration} -import scala.jdk.CollectionConverters.{MapHasAsJava, MapHasAsScala, SeqHasAsJava} +import scala.jdk.CollectionConverters.{ + MapHasAsJava, + MapHasAsScala, + SeqHasAsJava, +} import scala.jdk.OptionConverters.RichOptional class ExtEmCommunicationIT - extends ScalaTestWithActorTestKit - with AnyWordSpecLike - with EmCommunicationTestData + extends ScalaTestWithActorTestKit + with AnyWordSpecLike + with EmCommunicationTestData with QuantityMatchers - with TestSpawnerTyped { + with TestSpawnerTyped { protected val messageTimeout: FiniteDuration = 30.seconds protected val log: Logger = LoggerFactory.getLogger("ExtEmCommunicationIT") - private val emSupUuid = UUID.fromString("858f3d3d-4189-49cd-9fe5-3cd49b88dc70") - private val emNode3Uuid = UUID.fromString("fd1a8de9-722a-4304-8799-e1e976d9979c") - private val emNode4Uuid = UUID.fromString("ff0b995a-86ff-4f4d-987e-e475a64f2180") + private val emSupUuid = + UUID.fromString("858f3d3d-4189-49cd-9fe5-3cd49b88dc70") + private val emNode3Uuid = + UUID.fromString("fd1a8de9-722a-4304-8799-e1e976d9979c") + private val emNode4Uuid = + UUID.fromString("ff0b995a-86ff-4f4d-987e-e475a64f2180") private val connection = new ExtEmDataConnection( List(emSupUuid, emNode3Uuid, emNode4Uuid).asJava, @@ -63,16 +112,30 @@ class ExtEmCommunicationIT ) private val scheduler = TestProbe[SchedulerMessage]("scheduler") - private val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") + private val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") + private val gridAgent = TestProbe[GridAgent.Request]("GridAgent") private val resultListener = TestProbe[ResultEvent]("ResultListener") + private val primaryServiceProxy = + TestProbe[ServiceMessage]("PrimaryServiceProxy") + private val weatherService = TestProbe[ServiceMessage]("WeatherService") + + private val participantRefs = ParticipantRefs( + gridAgent = gridAgent.ref, + primaryServiceProxy = primaryServiceProxy.ref, + services = Map(ServiceType.WeatherService -> weatherService.ref), + resultListener = Iterable(resultListener.ref), + ) "An ExtEmDataService im communication mode" should { val service = spawn(ExtEmDataService(scheduler.ref)) val serviceRef = service.ref - val adapter = spawn(ExtEmDataService.adapter(service)) + implicit val adapter: ActorRef[DataMessageFromExt] = + spawn(ExtEmDataService.adapter(service)) connection.setActorRefs(adapter, extSimAdapter.ref) - val emAgentSup = spawn( + "with participant agents work correctly" in { + val emAgentSup = spawn( EmAgent( emSup, modelConfig, @@ -85,7 +148,7 @@ class ExtEmCommunicationIT ) ) - val emAgentNode3 = spawn( + val emAgentNode3 = spawn( EmAgent( emNode3, modelConfig, @@ -98,7 +161,7 @@ class ExtEmCommunicationIT ) ) - val emAgentNode4 = spawn( + val emAgentNode4 = spawn( EmAgent( emNode4, modelConfig, @@ -111,268 +174,6 @@ class ExtEmCommunicationIT ) ) - "with participant probes work correctly" in { - val pvAgentNode3 = TestProbe[FlexibilityMessage.FlexRequest]("PvAgentNode3") - val pvAgentNode4 = TestProbe[FlexibilityMessage.FlexRequest]("PvAgentNode4") - val storageAgentNode3 = TestProbe[FlexibilityMessage.FlexRequest]("storageAgentNode3") - val loadAgentNode4 = TestProbe[FlexibilityMessage.FlexRequest]("LoadAgentNode4") - - /* PRE_INIT */ - - val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, PRE_INIT_TICK) - scheduler.expectMessageType[ScheduleActivation] // lock activation scheduled - - service ! Create( - InitExtEmData(connection, simulationStart), - key, - ) - - val activationMsg = scheduler.expectMessageType[ScheduleActivation] - activationMsg.tick shouldBe INIT_SIM_TICK - activationMsg.unlockKey shouldBe Some(key) - val serviceActivation = activationMsg.actor - - /* INIT */ - - //register all system participant agents - emAgentNode3 ! RegisterControlledAsset(pvAgentNode3.ref, pvNode3) - emAgentNode3 ! ScheduleFlexActivation(pvNode3.getUuid, INIT_SIM_TICK) - - emAgentNode3 ! RegisterControlledAsset(storageAgentNode3.ref, storageInput) - emAgentNode3 ! ScheduleFlexActivation(storageInput.getUuid, INIT_SIM_TICK) - - emAgentNode4 ! RegisterControlledAsset(pvAgentNode4.ref, pvNode4) - emAgentNode4 ! ScheduleFlexActivation(pvNode4.getUuid, INIT_SIM_TICK) - - emAgentNode4 ! RegisterControlledAsset(loadAgentNode4.ref, loadInput) - emAgentNode4 ! ScheduleFlexActivation(loadInput.getUuid, INIT_SIM_TICK) - - // activate the service for init tick - serviceActivation ! Activation(INIT_SIM_TICK) - scheduler.expectMessage(Completion(serviceActivation)) - - // the agents will receive a flex activation - pvAgentNode3.expectMessage(FlexActivation(INIT_SIM_TICK)) - storageAgentNode3.expectMessage(FlexActivation(INIT_SIM_TICK)) - - pvAgentNode4.expectMessage(FlexActivation(INIT_SIM_TICK)) - loadAgentNode4.expectMessage(FlexActivation(INIT_SIM_TICK)) - - // all agents should answer with a flex completion for the init tick - emAgentNode3 ! FlexCompletion(pvNode3.getUuid, requestAtTick = Some(0)) - emAgentNode3 ! FlexCompletion(storageInput.getUuid, requestAtTick = Some(0)) - - emAgentNode4 ! FlexCompletion(pvNode4.getUuid, requestAtTick = Some(0)) - emAgentNode4 ! FlexCompletion(loadInput.getUuid, requestAtTick = Some(0)) - - - /* TICK: 0 */ - - /* start communication */ - - // we first send a flex option request to the superior em agent - connection.sendFlexRequests( - 0, - Map(emSupUuid -> new FlexOptionRequest(emSupUuid, Optional.empty())).asJava, - Optional.of(900), - log - ) - - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(0) - - // we expect to receive a request per inferior em agent - val requestsToInferior = connection.receiveWithType(classOf[FlexRequestResponse]) - .flexRequests() - .asScala - - requestsToInferior shouldBe Map(emSupUuid -> new FlexRequestResult(simulationStart, emSupUuid, List(emNode3Uuid, emNode4Uuid).asJava)) - - // we send a request to each inferior em agent - connection.sendFlexRequests( - 0, - Map( - emNode3Uuid -> new FlexOptionRequest(emNode3Uuid, Optional.of(emSupUuid)), - emNode4Uuid -> new FlexOptionRequest(emNode4Uuid, Optional.of(emSupUuid)), - ).asJava, - Optional.of(900), - log, - ) - - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(0) - - // we expect flex request from inferior em agents - pvAgentNode3.expectMessage(messageTimeout, FlexActivation(0)) - emAgentNode3 ! ProvideFlexOptions(pvNode3.getUuid, MinMaxFlexOptions(zeroKW, zeroKW, zeroKW)) - - storageAgentNode3.expectMessage(messageTimeout, FlexActivation(0)) - emAgentNode3 ! ProvideFlexOptions(storageInput.getUuid, MinMaxFlexOptions(zeroKW, zeroKW, Kilowatts(4))) - - pvAgentNode4.expectMessage(messageTimeout, FlexActivation(0)) - emAgentNode4 ! ProvideFlexOptions(pvNode4.getUuid, MinMaxFlexOptions(zeroKW, zeroKW, zeroKW)) - - loadAgentNode4.expectMessage(messageTimeout, FlexActivation(0)) - emAgentNode4 ! ProvideFlexOptions(loadInput.getUuid, MinMaxFlexOptions(Kilowatts(2.200000413468004), Kilowatts(2.200000413468004), Kilowatts(2.200000413468004))) - - // we expect to receive flex options from the inferior em agents - val flexOptionResponseInferior = connection.receiveWithType(classOf[FlexOptionsResponse]) - .receiverToFlexOptions() - .asScala - - if (flexOptionResponseInferior.size == 1) { - flexOptionResponseInferior.addAll( - connection.receiveWithType(classOf[FlexOptionsResponse]) - .receiverToFlexOptions() - .asScala - ) - } - - flexOptionResponseInferior(emNode3Uuid) shouldBe new ExtendedFlexOptionsResult( - simulationStart, - emNode3Uuid, - emSupUuid, - 0.0.asMegaWatt, - 0.0.asMegaWatt, - 0.004.asMegaWatt - ) - - flexOptionResponseInferior(emNode4Uuid) shouldBe new ExtendedFlexOptionsResult( - simulationStart, - emNode4Uuid, - emSupUuid, - 0.002200000413468004.asMegaWatt, - 0.002200000413468004.asMegaWatt, - 0.002200000413468004.asMegaWatt - ) - - // we send the flex options to the superior em agent - connection.sendFlexOptions( - 0, - Map( - emSupUuid -> List( - new FlexOptions( - emSupUuid, - emNode3Uuid, - 0.0.asKiloWatt, - 0.0.asKiloWatt, - 4.asKiloWatt - ), - new FlexOptions( - emSupUuid, - emNode4Uuid, - 2.200000413468004.asKiloWatt, - 2.200000413468004.asKiloWatt, - 2.200000413468004.asKiloWatt - ), - ).asJava - ).asJava, - Optional.of(900), - log - ) - - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(0) - - // we expect the total flex options of the grid from the superior em agent - val totalFlexOptions = connection.receiveWithType(classOf[FlexOptionsResponse]) - .receiverToFlexOptions() - .asScala - - totalFlexOptions shouldBe Map( - emSupUuid -> new ExtendedFlexOptionsResult( - simulationStart, - emSupUuid, - emSupUuid, - 0.002200000413468004.asMegaWatt, - 0.002200000413468004.asMegaWatt, - 0.0062000004134680035.asMegaWatt - ) - ) - - // after we received all options we will send a message, to keep the current set point - connection.sendSetPoints( - 0, - Map(emSupUuid -> new PValue(2.200000413468004.asKiloWatt)).asJava, - Optional.of(900), - log - ) - - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(0) - - // we expect a new set point for each inferior em agent - val inferiorSetPoints = connection.receiveWithType(classOf[EmSetPointDataResponse]) - .emData().asScala - .flatMap(_._2.getReceiverToSetPoint.asScala) - - if (inferiorSetPoints.size == 1) { - inferiorSetPoints.addAll( - connection.receiveWithType(classOf[EmSetPointDataResponse]) - .emData().asScala - .flatMap(_._2.getReceiverToSetPoint.asScala) - ) - } - - inferiorSetPoints(emNode3Uuid).getP.toScala.value should equalWithTolerance(0.asKiloWatt) - inferiorSetPoints(emNode4Uuid).getP.toScala.value should equalWithTolerance(2.200000413468004.asKiloWatt) - - // we send the new set points to the inferior em agents - connection.sendSetPoints( - 0, - Map( - emNode3Uuid -> new PValue(0.0.asMegaWatt), - emNode4Uuid -> new PValue(0.002200000413468004.asMegaWatt), - ).asJava, - Optional.of(900), - log - ) - - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(0) - - // we expect new flex control message for the participant agents - pvAgentNode3.expectMessage(IssueNoControl(0)) - emAgentNode3 ! FlexCompletion(pvNode3.getUuid) - - storageAgentNode3.expectMessage(IssueNoControl(0)) - emAgentNode3 ! FlexCompletion(storageInput.getUuid) - - - pvAgentNode4.expectMessage(IssueNoControl(0)) - emAgentNode4 ! FlexCompletion(pvNode4.getUuid) - - loadAgentNode4.expectMessage(IssueNoControl(0)) - emAgentNode4 ! FlexCompletion(loadInput.getUuid) - - - // we expect a finish message - connection.receiveWithType(classOf[EmCompletion]) - - - } - - "with participant agents work correctly" in { - val gridAgent = TestProbe[GridAgent.Request]("GridAgent") - val resultListener = TestProbe[ResultEvent]("ResultListener") - val primaryServiceProxy = - TestProbe[ServiceMessage]("PrimaryServiceProxy") - val weatherService = TestProbe[ServiceMessage]("WeatherService") - - val participantRefs = ParticipantRefs( - gridAgent = gridAgent.ref, - primaryServiceProxy = primaryServiceProxy.ref, - services = Map(ServiceType.WeatherService -> weatherService.ref), - resultListener = Iterable(resultListener.ref), - ) - - val simulationParams = SimulationParameters( - expectedPowerRequestTick = Long.MaxValue, - requestVoltageDeviationTolerance = Each(1e-14d), - simulationStart = simulationStart, - simulationEnd = simulationEnd, - ) - val keys = ScheduleLock .multiKey(TSpawner, scheduler.ref, PRE_INIT_TICK, 4) .iterator @@ -388,9 +189,9 @@ class ExtEmCommunicationIT participantRefs, simulationParams, Right(emAgentNode3), - keys.next() + keys.next(), ), - "PvAgentNode3" + "PvAgentNode3", ) val storageAgentNode3 = spawn( @@ -401,9 +202,9 @@ class ExtEmCommunicationIT participantRefs, simulationParams, Right(emAgentNode3), - keys.next() + keys.next(), ), - "storageAgentNode3" + "storageAgentNode3", ) val pvAgentNode4 = spawn( @@ -414,9 +215,9 @@ class ExtEmCommunicationIT participantRefs, simulationParams, Right(emAgentNode4), - keys.next() + keys.next(), ), - "PvAgentNode4" + "PvAgentNode4", ) val loadAgentNode4 = spawn( @@ -427,15 +228,15 @@ class ExtEmCommunicationIT participantRefs, simulationParams, Right(emAgentNode4), - keys.next() + keys.next(), ), - "LoadAgentNode4" + "LoadAgentNode4", ) /* PRE_INIT */ - val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, PRE_INIT_TICK) - scheduler.expectMessageType[ScheduleActivation] // lock activation scheduled + scheduler + .expectMessageType[ScheduleActivation] // lock activation scheduled service ! Create( InitExtEmData(connection, simulationStart), @@ -445,19 +246,21 @@ class ExtEmCommunicationIT val activationMsg = scheduler.expectMessageType[ScheduleActivation] activationMsg.tick shouldBe INIT_SIM_TICK activationMsg.unlockKey shouldBe Some(key) - val serviceActivation = activationMsg.actor + implicit val serviceActivation: ActorRef[Activation] = activationMsg.actor // we expect a completion for the participant locks scheduler.expectMessage(Completion(lockActivation)) - /* INIT */ // activate the service for init tick serviceActivation ! Activation(INIT_SIM_TICK) scheduler.expectMessage(Completion(serviceActivation)) - primaryServiceProxy.receiveMessages(4) should contain allOf ( + primaryServiceProxy.receiveMessages( + 4, + messageTimeout, + ) should contain allOf ( PrimaryServiceRegistrationMessage( pvAgentNode3, pvNode3.getUuid, @@ -473,7 +276,7 @@ class ExtEmCommunicationIT PrimaryServiceRegistrationMessage( loadAgentNode4, loadInput.getUuid, - ), + ) ) // pv agent 3 @@ -508,163 +311,278 @@ class ExtEmCommunicationIT // load loadAgentNode4 ! RegistrationFailedMessage(primaryServiceProxy.ref) + implicit val pvAgents: Seq[ActorRef[ParticipantAgent.Request]] = + Seq(pvAgentNode3, pvAgentNode4) + /* TICK: 0 */ + val weatherData0 = WeatherData( + WattsPerSquareMeter(0), + WattsPerSquareMeter(0), + Celsius(0d), + MetersPerSecond(0d), + ) + + communicate( + 0, + 900, + weatherData0, + Map( + emSupUuid -> new FlexOptions( + emSupUuid, + emSupUuid, + 0.002200000413468004.asMegaWatt, + 0.002200000413468004.asMegaWatt, + 0.006200000413468004.asMegaWatt, + ), + emNode3Uuid -> new FlexOptions( + emSupUuid, + emNode3Uuid, + 0.asMegaWatt, + 0.asMegaWatt, + 0.004.asMegaWatt, + ), + emNode4Uuid -> new FlexOptions( + emSupUuid, + emNode4Uuid, + 0.002200000413468004.asMegaWatt, + 0.002200000413468004.asMegaWatt, + 0.002200000413468004.asMegaWatt, + ), + ), + Map( + emSupUuid -> 2.200000413468004.asKiloWatt, + emNode3Uuid -> 0.asKiloWatt, + emNode4Uuid -> 2.200000413468004.asKiloWatt, + ), + ) + + /* TICK: 900 */ + + val weatherData900 = WeatherData( + WattsPerSquareMeter(0), + WattsPerSquareMeter(0), + Celsius(0d), + MetersPerSecond(0d), + ) + + communicate( + 900, + 1800, + weatherData900, + Map( + emSupUuid -> new FlexOptions( + emSupUuid, + emSupUuid, + 0.002200000413468004.asMegaWatt, + 0.002200000413468004.asMegaWatt, + 0.006200000413468004.asMegaWatt, + ), + emNode3Uuid -> new FlexOptions( + emSupUuid, + emNode3Uuid, + 0.asMegaWatt, + 0.asMegaWatt, + 0.004.asMegaWatt, + ), + emNode4Uuid -> new FlexOptions( + emSupUuid, + emNode4Uuid, + 0.002200000413468004.asMegaWatt, + 0.002200000413468004.asMegaWatt, + 0.002200000413468004.asMegaWatt, + ), + ), + Map( + emSupUuid -> 2.200000413468004.asKiloWatt, + emNode3Uuid -> 0.asKiloWatt, + emNode4Uuid -> 2.200000413468004.asKiloWatt, + ), + ) + + } + + // helper methods + + def communicate( + tick: Long, + nextTick: Long, + weatherData: WeatherData, + flexOptions: Map[UUID, FlexOptions], + setPoints: Map[UUID, ComparableQuantity[Power]], + )(implicit + serviceActivation: ActorRef[Activation], + adapter: ActorRef[DataMessageFromExt], + pvAgents: Seq[ActorRef[ParticipantAgent.Request]], + ): Unit = { + /* start communication */ + val inferiorEms = Set(emNode3Uuid, emNode4Uuid) // we first send a flex option request to the superior em agent connection.sendFlexRequests( - 0, - Map(emSupUuid -> new FlexOptionRequest(emSupUuid, Optional.empty())).asJava, - Optional.of(900), - log + tick, + Map( + emSupUuid -> new FlexOptionRequest(emSupUuid, Optional.empty()) + ).asJava, + Optional.of(nextTick), + log, ) extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(0) + serviceActivation ! Activation(tick) // we expect to receive a request per inferior em agent - val requestsToInferior = connection.receiveWithType(classOf[FlexRequestResponse]) + val requestsToInferior = connection + .receiveWithType(classOf[FlexRequestResponse]) .flexRequests() .asScala - requestsToInferior shouldBe Map(emSupUuid -> new FlexRequestResult(simulationStart, emSupUuid, List(emNode3Uuid, emNode4Uuid).asJava)) + requestsToInferior.size shouldBe 1 + requestsToInferior(emSupUuid) shouldBe new FlexRequestResult( + simulationStart.plusSeconds(tick), + emSupUuid, + List(emNode3Uuid, emNode4Uuid).asJava, + ) // we send a request to each inferior em agent connection.sendFlexRequests( - 0, + tick, Map( - emNode3Uuid -> new FlexOptionRequest(emNode3Uuid, Optional.of(emSupUuid)), - emNode4Uuid -> new FlexOptionRequest(emNode4Uuid, Optional.of(emSupUuid)), + emNode3Uuid -> new FlexOptionRequest( + emNode3Uuid, + Optional.of(emSupUuid), + ), + emNode4Uuid -> new FlexOptionRequest( + emNode4Uuid, + Optional.of(emSupUuid), + ), ).asJava, - Optional.of(900), + Optional.of(nextTick), log, ) extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(0) + serviceActivation ! Activation(tick) + + val data = DataProvision( + tick, + weatherService.ref, + weatherData, + Some(nextTick), + ) + pvAgents.foreach(_ ! data) // we expect to receive flex options from the inferior em agents - val flexOptionResponseInferior = connection.receiveWithType(classOf[FlexOptionsResponse]) + val flexOptionResponseInferior = connection + .receiveWithType(classOf[FlexOptionsResponse]) .receiverToFlexOptions() .asScala - if (flexOptionResponseInferior.size == 1) { + if (flexOptionResponseInferior.size != 2) { flexOptionResponseInferior.addAll( - connection.receiveWithType(classOf[FlexOptionsResponse]) + connection + .receiveWithType(classOf[FlexOptionsResponse]) .receiverToFlexOptions() .asScala ) } - flexOptionResponseInferior(emNode3Uuid) shouldBe new ExtendedFlexOptionsResult( - simulationStart, - emNode3Uuid, - emSupUuid, - 0.0.asMegaWatt, - 0.0.asMegaWatt, - 0.004.asMegaWatt - ) + flexOptionResponseInferior.keySet shouldBe inferiorEms - flexOptionResponseInferior(emNode4Uuid) shouldBe new ExtendedFlexOptionsResult( - simulationStart, - emNode4Uuid, - emSupUuid, - 0.002200000413468004.asMegaWatt, - 0.002200000413468004.asMegaWatt, - 0.002200000413468004.asMegaWatt - ) + flexOptionResponseInferior.foreach { case (receiver, results) => + val expectedOptions = flexOptions(receiver) + + results.getReceiver shouldBe expectedOptions.receiver + results.getSender shouldBe expectedOptions.sender + results.getpMin() should equalWithTolerance(expectedOptions.pMin) + results.getpRef() should equalWithTolerance(expectedOptions.pRef) + results.getpMax() should equalWithTolerance(expectedOptions.pMax) + } // we send the flex options to the superior em agent connection.sendFlexOptions( - 0, - Map( - emSupUuid -> List( - new FlexOptions( - emSupUuid, - emNode3Uuid, - 0.0.asKiloWatt, - 0.0.asKiloWatt, - 4.asKiloWatt - ), - new FlexOptions( - emSupUuid, - emNode4Uuid, - 2.200000413468004.asKiloWatt, - 2.200000413468004.asKiloWatt, - 2.200000413468004.asKiloWatt - ), - ).asJava - ).asJava, - Optional.of(900), - log + tick, + Map(emSupUuid -> inferiorEms.map(flexOptions).toList.asJava).asJava, + Optional.of(nextTick), + log, ) extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(0) + serviceActivation ! Activation(tick) // we expect the total flex options of the grid from the superior em agent - val totalFlexOptions = connection.receiveWithType(classOf[FlexOptionsResponse]) + val totalFlexOptions = connection + .receiveWithType(classOf[FlexOptionsResponse]) .receiverToFlexOptions() .asScala - totalFlexOptions shouldBe Map( - emSupUuid -> new ExtendedFlexOptionsResult( - simulationStart, - emSupUuid, - emSupUuid, - 0.002200000413468004.asMegaWatt, - 0.002200000413468004.asMegaWatt, - 0.0062000004134680035.asMegaWatt - ) - ) + totalFlexOptions.keySet shouldBe Set(emSupUuid) + + totalFlexOptions.foreach { case (receiver, result) => + val expectedOptions = flexOptions(receiver) + + result.getReceiver shouldBe expectedOptions.receiver + result.getSender shouldBe expectedOptions.sender + result.getpMin() should equalWithTolerance(expectedOptions.pMin) + result.getpRef() should equalWithTolerance(expectedOptions.pRef) + result.getpMax() should equalWithTolerance(expectedOptions.pMax) + } // after we received all options we will send a message, to keep the current set point connection.sendSetPoints( - 0, - Map(emSupUuid -> new PValue(2.200000413468004.asKiloWatt)).asJava, - Optional.of(900), - log + tick, + Map(emSupUuid -> new PValue(setPoints(emSupUuid))).asJava, + Optional.of(nextTick), + log, ) extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(0) + serviceActivation ! Activation(tick) // we expect a new set point for each inferior em agent - val inferiorSetPoints = connection.receiveWithType(classOf[EmSetPointDataResponse]) - .emData().asScala + val inferiorSetPoints = connection + .receiveWithType(classOf[EmSetPointDataResponse]) + .emData() + .asScala .flatMap(_._2.getReceiverToSetPoint.asScala) - if (inferiorSetPoints.size == 1) { + if (inferiorSetPoints.size != 2) { inferiorSetPoints.addAll( - connection.receiveWithType(classOf[EmSetPointDataResponse]) - .emData().asScala + connection + .receiveWithType(classOf[EmSetPointDataResponse]) + .emData() + .asScala .flatMap(_._2.getReceiverToSetPoint.asScala) ) } - inferiorSetPoints(emNode3Uuid).getP.toScala.value should equalWithTolerance(0.asKiloWatt) - inferiorSetPoints(emNode4Uuid).getP.toScala.value should equalWithTolerance(2.200000413468004.asKiloWatt) + inferiorSetPoints.keySet shouldBe inferiorEms + + inferiorSetPoints.foreach { case (receiver, results) => + results.getP.toScala.value should equalWithTolerance( + setPoints(receiver) + ) + } + + def toPValue(uuid: UUID): (UUID, PValue) = + uuid -> new PValue(setPoints(uuid)) // we send the new set points to the inferior em agents connection.sendSetPoints( - 0, - Map( - emNode3Uuid -> new PValue(0.0.asMegaWatt), - emNode4Uuid -> new PValue(0.002200000413468004.asMegaWatt), - ).asJava, - Optional.of(900), - log + tick, + inferiorEms.map(toPValue).toMap.asJava, + Optional.of(nextTick), + log, ) extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(0) + serviceActivation ! Activation(tick) // we expect a finish message connection.receiveWithType(classOf[EmCompletion]) - - } } + } 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 1bec70dc2a..1f245e8a8b 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -8,16 +8,34 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.{EmSetPointResult, ExtendedFlexOptionsResult, FlexOptions} +import edu.ie3.simona.api.data.em.model.{ + EmSetPointResult, + ExtendedFlexOptionsResult, + FlexOptions, +} import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{FlexActivation, FlexRequest, IssuePowerControl, ProvideFlexOptions} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ + FlexActivation, + FlexRequest, + IssuePowerControl, + ProvideFlexOptions, +} import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions -import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{Create, RegisterForEmDataService} +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + WrappedFlexRequest, + WrappedFlexResponse, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + Create, + RegisterForEmDataService, +} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData @@ -26,7 +44,10 @@ import edu.ie3.simona.test.common.input.EmInputTestData import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.quantities.QuantityUtils._ import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW -import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import org.apache.pekko.testkit.TestKit.awaitCond import org.scalatest.wordspec.AnyWordSpecLike @@ -49,7 +70,8 @@ class ExtEmDataServiceSpec private val emptyControlled = List.empty[UUID].asJava - private val emAgentSupUUID = UUID.fromString("d797fe9c-e4af-49a3-947d-44f81933887e") + private val emAgentSupUUID = + UUID.fromString("d797fe9c-e4af-49a3-947d-44f81933887e") private val emAgent1UUID = UUID.fromString("06a14909-366e-4e94-a593-1016e1455b30") private val emAgent2UUID = @@ -58,11 +80,13 @@ class ExtEmDataServiceSpec "An uninitialized em service" must { "send correct completion message after initialisation" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val emService = spawn(ExtEmDataService.apply(scheduler.ref)) val adapter = spawn(ExtEmDataService.adapter(emService)) - val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) + val extEmDataConnection = + new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) extEmDataConnection.setActorRefs( adapter, extSimAdapter.ref, @@ -90,11 +114,13 @@ class ExtEmDataServiceSpec "stash registration request and handle it correctly once initialized" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val emService = spawn(ExtEmDataService.apply(scheduler.ref)) val adapter = spawn(ExtEmDataService.adapter(emService)) - val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) + val extEmDataConnection = + new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) extEmDataConnection.setActorRefs( adapter, extSimAdapter.ref, @@ -139,11 +165,13 @@ class ExtEmDataServiceSpec "fail when activated without having received ExtEmMessage" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val emService = spawn(ExtEmDataService.apply(scheduler.ref)) val adapter = spawn(ExtEmDataService.adapter(emService)) - val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) + val extEmDataConnection = + new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) extEmDataConnection.setActorRefs( adapter, extSimAdapter.ref, @@ -178,11 +206,13 @@ class ExtEmDataServiceSpec "handle flex option request correctly" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val emService = spawn(ExtEmDataService.apply(scheduler.ref)) val adapter = spawn(ExtEmDataService.adapter(emService)) - val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) + val extEmDataConnection = + new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) extEmDataConnection.setActorRefs( adapter, extSimAdapter.ref, @@ -258,7 +288,7 @@ class ExtEmDataServiceSpec extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - //scheduler.expectMessage(Completion(serviceActivation)) + // scheduler.expectMessage(Completion(serviceActivation)) serviceActivation ! Activation(0) @@ -279,7 +309,7 @@ class ExtEmDataServiceSpec Kilowatts(5), Kilowatts(0), Kilowatts(10), - ) + ), ), Left(emAgent1UUID), ) @@ -308,11 +338,13 @@ class ExtEmDataServiceSpec "handle flex option provision correctly" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val emService = spawn(ExtEmDataService.apply(scheduler.ref)) val adapter = spawn(ExtEmDataService.adapter(emService)) - val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) + val extEmDataConnection = + new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) extEmDataConnection.setActorRefs( adapter, extSimAdapter.ref, @@ -391,7 +423,7 @@ class ExtEmDataServiceSpec Kilowatts(-1), Kilowatts(-3), Kilowatts(1), - ) + ), ) ) @@ -400,11 +432,13 @@ class ExtEmDataServiceSpec "handle set point provision correctly" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val emService = spawn(ExtEmDataService.apply(scheduler.ref)) val adapter = spawn(ExtEmDataService.adapter(emService)) - val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) + val extEmDataConnection = + new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) extEmDataConnection.setActorRefs( adapter, extSimAdapter.ref, @@ -484,11 +518,13 @@ class ExtEmDataServiceSpec "handle set point request correctly" in { val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val emService = spawn(ExtEmDataService.apply(scheduler.ref)) val adapter = spawn(ExtEmDataService.adapter(emService)) - val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) + val extEmDataConnection = + new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) extEmDataConnection.setActorRefs( adapter, extSimAdapter.ref, @@ -577,7 +613,7 @@ class ExtEmDataServiceSpec emAgent1.expectNoMessage() emAgent2.expectNoMessage() - //scheduler.expectMessage(Completion(serviceActivation)) + // scheduler.expectMessage(Completion(serviceActivation)) extEmDataConnection.receiveTriggerQueue shouldBe empty diff --git a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala index bc31168426..4abf022fe7 100644 --- a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala @@ -7,23 +7,40 @@ package edu.ie3.simona.service.ev import edu.ie3.simona.agent.participant2.ParticipantAgent -import edu.ie3.simona.agent.participant2.ParticipantAgent.{DataProvision, RegistrationSuccessfulMessage} +import edu.ie3.simona.agent.participant2.ParticipantAgent.{ + DataProvision, + RegistrationSuccessfulMessage, +} import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.data.ev.model.EvModel import edu.ie3.simona.api.data.ev.ontology._ -import edu.ie3.simona.api.data.ontology.{DataMessageFromExt, ScheduleDataServiceMessage} +import edu.ie3.simona.api.data.ontology.{ + DataMessageFromExt, + ScheduleDataServiceMessage, +} import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt import edu.ie3.simona.model.participant2.evcs.EvModelWrapper -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} import edu.ie3.simona.ontology.messages.services.EvMessage._ -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{Create, RegisterForEvDataMessage, WrappedActivation, WrappedExternalMessage} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + Create, + RegisterForEvDataMessage, + WrappedActivation, + WrappedExternalMessage, +} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ev.ExtEvDataService.InitExtEvData import edu.ie3.simona.test.common.{EvTestData, TestSpawnerTyped, UnitSpec} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.quantities.PowerSystemUnits -import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} import org.apache.pekko.testkit.TestKit.awaitCond import tech.units.indriya.quantity.Quantities diff --git a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala index 1afc331353..10d0593d6d 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala @@ -9,19 +9,31 @@ package edu.ie3.simona.service.primary import com.typesafe.scalalogging.LazyLogging import edu.ie3.datamodel.models.value.Value import edu.ie3.simona.agent.participant.data.Data.PrimaryData -import edu.ie3.simona.agent.participant2.ParticipantAgent.{DataProvision, RegistrationSuccessfulMessage} +import edu.ie3.simona.agent.participant2.ParticipantAgent.{ + DataProvision, + RegistrationSuccessfulMessage, +} import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{PrimaryServiceRegistrationMessage, WrappedActivation} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + PrimaryServiceRegistrationMessage, + WrappedActivation, +} import edu.ie3.simona.ontology.messages.services.WeatherMessage.RegisterForWeatherMessage import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.primary.ExtPrimaryDataService.InitExtPrimaryData import edu.ie3.simona.test.common.TestSpawnerTyped import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK -import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import org.scalatest.PrivateMethodTester import org.scalatest.matchers.should diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala index 19799f3059..c0c0260e23 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala @@ -15,23 +15,50 @@ import edu.ie3.datamodel.models.value.{PValue, SValue, Value} import edu.ie3.simona.agent.participant2.ParticipantAgent import edu.ie3.simona.agent.participant2.ParticipantAgent.RegistrationFailedMessage import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection -import edu.ie3.simona.config.ConfigParams.{CouchbaseParams, TimeStampedCsvParams, TimeStampedInfluxDb1xParams} +import edu.ie3.simona.config.ConfigParams.{ + CouchbaseParams, + TimeStampedCsvParams, + TimeStampedInfluxDb1xParams, +} import edu.ie3.simona.config.InputConfig.{Primary => PrimaryConfig} -import edu.ie3.simona.exceptions.{InitializationException, InvalidConfigParameterException} -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.exceptions.{ + InitializationException, + InvalidConfigParameterException, +} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{Create, PrimaryServiceRegistrationMessage, WorkerRegistrationMessage, WrappedActivation} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + Create, + PrimaryServiceRegistrationMessage, + WorkerRegistrationMessage, + WrappedActivation, +} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock.LockMsg import edu.ie3.simona.service.ServiceStateData.ServiceConstantStateData -import edu.ie3.simona.service.primary.PrimaryServiceProxy.{InitPrimaryServiceProxyStateData, PrimaryServiceStateData, SourceRef} +import edu.ie3.simona.service.primary.PrimaryServiceProxy.{ + InitPrimaryServiceProxyStateData, + PrimaryServiceStateData, + SourceRef, +} import edu.ie3.simona.service.primary.PrimaryServiceWorker.CsvInitPrimaryServiceStateData import edu.ie3.simona.test.common.TestSpawnerTyped import edu.ie3.simona.test.common.input.TimeSeriesTestData import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.TimeUtil -import org.apache.pekko.actor.testkit.typed.Effect.{NoEffects, Spawned, SpawnedAnonymous} -import org.apache.pekko.actor.testkit.typed.scaladsl.{BehaviorTestKit, ScalaTestWithActorTestKit, TestProbe} +import org.apache.pekko.actor.testkit.typed.Effect.{ + NoEffects, + Spawned, + SpawnedAnonymous, +} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + BehaviorTestKit, + ScalaTestWithActorTestKit, + TestProbe, +} import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} import org.apache.pekko.actor.typed.{ActorRef, Behavior} import org.mockito.ArgumentMatchers.any @@ -128,12 +155,16 @@ class PrimaryServiceProxySpec m } - private val validExtPrimaryDataService = spawn(ExtPrimaryDataService(scheduler.ref)) + private val validExtPrimaryDataService = spawn( + ExtPrimaryDataService(scheduler.ref) + ) private val extEntityId = UUID.fromString("07bbe1aa-1f39-4dfb-b41b-339dec816ec4") - private val valueMap: Map[UUID, Class[_ <: Value]] = Map(extEntityId -> classOf[PValue]) + private val valueMap: Map[UUID, Class[_ <: Value]] = Map( + extEntityId -> classOf[PValue] + ) private val extPrimaryDataConnection = new ExtPrimaryDataConnection( valueMap.asJava @@ -351,7 +382,7 @@ class PrimaryServiceProxySpec } "build proxy correctly when there is an external simulation" in { - PrimaryServiceProxy.prepareStateData( + PrimaryServiceProxy.prepareStateData( validPrimaryConfig, simulationStart, Seq((extPrimaryDataConnection, validExtPrimaryDataService)), diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala index 3109035cdd..1abe835535 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala @@ -13,7 +13,10 @@ import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.api.data.results.ExtResultDataConnection import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.test.common.UnitSpec -import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} import java.util.UUID import scala.jdk.CollectionConverters.{MapHasAsJava, SeqHasAsJava} @@ -74,7 +77,8 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val evConnection = new ExtEvDataConnection() val evRef = TestProbe[ServiceMessage]("ev_service").ref - val emConnection = new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) + val emConnection = + new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) val emRef = TestProbe[ServiceMessage]("em_service").ref val cases = Table( @@ -136,7 +140,8 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val evConnection = new ExtEvDataConnection() val evRef = TestProbe[ServiceMessage]("ev_service").ref - val emConnection = new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) + val emConnection = + new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) val emRef = TestProbe[ServiceMessage]("em_service").ref val resultConnection = @@ -168,7 +173,8 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { } "return emDataService correctly" in { - val emConnection = new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) + val emConnection = + new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) val emRef = TestProbe("em_service").ref val cases = Table( @@ -208,7 +214,8 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { } "return emDataService correctly" in { - val emConnection = new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) + val emConnection = + new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) val emRef = TestProbe[ServiceMessage]("em_service").ref val cases = Table( diff --git a/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala b/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala index 93ffe567a4..d5a7b15ecc 100644 --- a/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala @@ -1,3 +1,9 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + package edu.ie3.simona.test.common.input import edu.ie3.datamodel.models.input.system.`type`.StorageTypeInput @@ -19,11 +25,12 @@ import squants.Each import java.time.ZonedDateTime import java.util.UUID -trait EmCommunicationTestData - extends DefaultTestData { +trait EmCommunicationTestData extends DefaultTestData { - protected implicit val simulationStart: ZonedDateTime = TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:00:00Z") - protected implicit val simulationEnd: ZonedDateTime = simulationStart.plusHours(2) + protected implicit val simulationStart: ZonedDateTime = + TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:00:00Z") + protected implicit val simulationEnd: ZonedDateTime = + simulationStart.plusHours(2) protected val simonaConfig: SimonaConfig = createSimonaConfig() @@ -48,7 +55,6 @@ trait EmCommunicationTestData curtailRegenerative = false, ) - val node3 = new NodeInput( UUID.fromString("33f29587-f63e-45b7-960b-037bda37a3cb"), "Node_3", @@ -56,7 +62,7 @@ trait EmCommunicationTestData false, GeoUtils.buildPoint(51.4843281, 7.4116482), GermanVoltageLevelUtils.LV, - 2 + 2, ) val node4 = new NodeInput( @@ -66,28 +72,28 @@ trait EmCommunicationTestData false, GeoUtils.buildPoint(51.4843281, 7.4116482), GermanVoltageLevelUtils.LV, - 2 + 2, ) val emSup = new EmInput( UUID.fromString("858f3d3d-4189-49cd-9fe5-3cd49b88dc70"), "EM_SUP", "PROPORTIONAL", - null + null, ) val emNode3 = new EmInput( UUID.fromString("fd1a8de9-722a-4304-8799-e1e976d9979c"), "emNode3", "PRIORITIZED", - emSup + emSup, ) val emNode4 = new EmInput( UUID.fromString("ff0b995a-86ff-4f4d-987e-e475a64f2180"), "emNode4", "PRIORITIZED", - emSup + emSup, ) val pvNode3 = new PvInput( @@ -104,7 +110,7 @@ trait EmCommunicationTestData 1.0, false, 10.0.asKiloVoltAmpere, - 0.8999999761581421 + 0.8999999761581421, ) val pvNode4 = new PvInput( @@ -121,20 +127,20 @@ trait EmCommunicationTestData 1.0, false, 10.0.asKiloVoltAmpere, - 0.8999999761581421 + 0.8999999761581421, ) val storageType = new StorageTypeInput( UUID.fromString("95d4c980-d9e1-4813-9f2a-b0942488a570"), "Typ_1", 0.0.asEuro, - 0.65.asEuroPerKiloWattHour, + 0.65.asEuroPerKiloWattHour, 16.0.asKiloWattHour, 4.166666666666667.asKiloVoltAmpere, 0.96, 4.0.asKiloWatt, 1.0.asPercentPerHour, - 93.0.asPercent + 93.0.asPercent, ) val storageInput: StorageInput = new StorageInput( @@ -143,7 +149,7 @@ trait EmCommunicationTestData node3, ReactivePowerCharacteristic.parse("cosPhiFixed:{(0.0,0.98)}"), emNode3, - storageType + storageType, ) val loadInput: LoadInput = new LoadInput( @@ -155,6 +161,6 @@ trait EmCommunicationTestData BdewStandardLoadProfile.H0, 4000.0.asKiloWattHour, 2.3157899379730225.asKiloVoltAmpere, - 0.949999988079071 + 0.949999988079071, ) } From 95f884662ba3433a3e5b9b3d417c4a370ac1bcec Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 16 Apr 2025 15:31:32 +0200 Subject: [PATCH 038/125] Fixing some issues. --- .../edu/ie3/simona/service/SimonaService.scala | 15 +++++++++++---- .../ie3/simona/service/em/ExtEmDataService.scala | 12 ++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/SimonaService.scala b/src/main/scala/edu/ie3/simona/service/SimonaService.scala index 8f2dbefc59..ca37a8b88c 100644 --- a/src/main/scala/edu/ie3/simona/service/SimonaService.scala +++ b/src/main/scala/edu/ie3/simona/service/SimonaService.scala @@ -209,10 +209,17 @@ abstract class SimonaService[ val (updatedStateData, maybeNextTick) = announceInformation(tick)(stateData, ctx) - constantData.scheduler ! Completion( - constantData.activationAdapter, - maybeNextTick, - ) + maybeNextTick match { + case Some(nextTick) if nextTick == tick => + // we need to do an additional activation of this service + ctx.self ! WrappedActivation(Activation(tick)) + + case _ => + constantData.scheduler ! Completion( + constantData.activationAdapter, + maybeNextTick, + ) + } idle(updatedStateData, constantData) } 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 c24561bd03..2d8233d12c 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -177,18 +177,18 @@ object ExtEmDataService // check the last finished tick of the core val lastFinishedTick = serviceStateData.serviceCore.lastFinishedTick - // we request a new activation for the same tick - ctx.self ! ServiceMessage.WrappedActivation(Activation(tick)) - - if (lastFinishedTick == stateTick) { + val updatedStateData = if (lastFinishedTick == stateTick) { // we finished the last tick and update the core with the requested tick - (serviceStateData.copy(tick = tick), None) + serviceStateData.copy(tick = tick) } else { // we are still waiting for data for the state data tick - (serviceStateData, None) + serviceStateData } + // we request a new activation for the same tick + (updatedStateData, Some(tick)) + } else { val extMsg = serviceStateData.extEmDataMessage.getOrElse( throw ServiceException( From 03df72e10c8077e81b895a78f5a65ca0b506fae1 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 17 Apr 2025 12:21:33 +0200 Subject: [PATCH 039/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 72 +++++-------------- .../simona/service/em/EmServiceBaseCore.scala | 29 +++++++- .../ie3/simona/service/em/EmServiceCore.scala | 43 +++++++++++ .../simona/service/em/ExtEmDataService.scala | 25 ++++--- .../util/ReceiveHierarchicalDataMap.scala | 18 ----- 5 files changed, 104 insertions(+), 83 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 453ae1e421..b2b129ca82 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -7,7 +7,6 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue -import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.em.model.{ EmSetPointResult, ExtendedFlexOptionsResult, @@ -18,10 +17,11 @@ import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService -import edu.ie3.simona.service.em.EmCommunicationCore.{DataMap, EmHierarchy} -import edu.ie3.simona.util.{ReceiveDataMap, ReceiveHierarchicalDataMap} +import edu.ie3.simona.service.em.EmCommunicationCore.DataMap +import edu.ie3.simona.service.em.EmServiceCore.EmHierarchy import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.TickLong +import edu.ie3.simona.util.{ReceiveDataMap, ReceiveHierarchicalDataMap} import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger @@ -46,11 +46,10 @@ final case class EmCommunicationCore( uuidToPRef: Map[UUID, ComparableQuantity[Power]] = Map.empty, toSchedule: Map[UUID, ScheduleFlexActivation] = Map.empty, flexRequestReceived: DataMap[UUID, Boolean] = - ReceiveHierarchicalDataMap.empty(false), + ReceiveHierarchicalDataMap.empty, flexOptionResponse: DataMap[UUID, ExtendedFlexOptionsResult] = ReceiveHierarchicalDataMap.empty, - setPointResponse: DataMap[UUID, PValue] = - ReceiveHierarchicalDataMap.empty(false), + setPointResponse: DataMap[UUID, PValue] = ReceiveHierarchicalDataMap.empty, completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, ) extends EmServiceCore { @@ -63,12 +62,7 @@ final case class EmCommunicationCore( val parentEm = registerMsg.parentEm val parentUuid = registerMsg.parentUuid - val updatedHierarchy = parentEm.zip(parentUuid) match { - case Some((parent, parentUuid)) => - hierarchy.add(uuid, ref, parent, parentUuid) - case None => - hierarchy.add(uuid, ref, ref, uuid) - } + val updatedHierarchy = hierarchy.add(uuid, ref, parentEm, parentUuid) copy( hierarchy = updatedHierarchy, @@ -120,7 +114,18 @@ final case class EmCommunicationCore( log.warn(s"Em refs: $refs") refs.foreach(_ ! FlexActivation(tick)) - (addSubKeysToExpectedKeys(emEntities), None) + ( + copy( + flexRequestReceived = + flexRequestReceived.addSubKeysToExpectedKeys(emEntities), + flexOptionResponse = + flexOptionResponse.addSubKeysToExpectedKeys(emEntities), + setPointResponse = + setPointResponse.addSubKeysToExpectedKeys(emEntities), + completions = completions.addExpectedKeys(emEntities), + ), + None, + ) case provideFlexOptions: ProvideEmFlexOptionData => log.info(s"Handling of: $provideFlexOptions") @@ -381,52 +386,11 @@ final case class EmCommunicationCore( } } } - - private def addSubKeysToExpectedKeys(keys: Set[UUID]): EmCommunicationCore = - copy( - flexRequestReceived = flexRequestReceived.addSubKeysToExpectedKeys(keys), - flexOptionResponse = flexOptionResponse.addSubKeysToExpectedKeys(keys), - setPointResponse = setPointResponse.addSubKeysToExpectedKeys(keys), - completions = completions.addExpectedKeys(keys), - ) } object EmCommunicationCore { type DataMap[K, V] = ReceiveHierarchicalDataMap[K, V] - final case class EmHierarchy( - refToUuid: Map[ActorRef[EmAgent.Request], UUID] = Map.empty, - uuidToRef: Map[UUID, ActorRef[EmAgent.Request]] = Map.empty, - uuidToFlexResponse: Map[UUID, ActorRef[FlexResponse]] = Map.empty, - flexResponseToUuid: Map[ActorRef[FlexResponse], UUID] = Map.empty, - ) { - def add(model: UUID, ref: ActorRef[EmAgent.Request]): EmHierarchy = copy( - uuidToRef = uuidToRef + (model -> ref), - refToUuid = refToUuid + (ref -> model), - ) - - def add( - model: UUID, - ref: ActorRef[EmAgent.Request], - parent: ActorRef[FlexResponse], - parentUuid: UUID, - ): EmHierarchy = { - - copy( - uuidToRef = uuidToRef + (model -> ref), - refToUuid = refToUuid + (ref -> model), - uuidToFlexResponse = uuidToFlexResponse + (parentUuid -> parent), - flexResponseToUuid = flexResponseToUuid + (parent -> parentUuid), - ) - } - - def getUuid(ref: ActorRef[FlexResponse]): UUID = - flexResponseToUuid(ref) - - def getResponseRef(uuid: UUID): Option[ActorRef[FlexResponse]] = - uuidToFlexResponse.get(uuid) - } - def empty: EmCommunicationCore = EmCommunicationCore() } 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 d040979551..25698ca643 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -7,6 +7,7 @@ package edu.ie3.simona.service.em import edu.ie3.simona.api.data.em.ontology._ +import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ FlexRequest, FlexResponse, @@ -35,7 +36,17 @@ case class EmServiceBaseCore( override def handleExtMessage(tick: Long, extMSg: EmDataMessageFromExt)( implicit log: Logger - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = ??? + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMSg match { + case provideEmSetPoints: ProvideEmSetPointData => + handleSetPoint(tick, provideEmSetPoints, log) + + (this, None) + + case _ => + throw new CriticalFailureException( + s"The EmServiceBaseCore is not able to handle the message: $extMSg" + ) + } override def handleFlexResponse( tick: Long, @@ -44,7 +55,14 @@ case class EmServiceBaseCore( )(implicit startTime: ZonedDateTime, log: Logger, - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = ??? + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { + receiver.map { ref => + log.warn(s"$ref: $flexResponse") + ref ! flexResponse + } + + (this, None) + } override def handleFlexRequest( flexRequest: FlexRequest, @@ -52,7 +70,12 @@ case class EmServiceBaseCore( )(implicit startTime: ZonedDateTime, log: Logger, - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = ??? + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { + log.warn(s"$receiver: $flexRequest") + receiver ! flexRequest + + (this, None) + } } object EmServiceBaseCore { 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 599377c8f5..54f9d2b3dc 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.service.em +import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.em.model.NoSetPointValue import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ @@ -125,3 +126,45 @@ trait EmServiceCore { log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) } + +object EmServiceCore { + + final case class EmHierarchy( + structure: Map[UUID, Set[UUID]] = Map.empty, + private val refToUuid: Map[ActorRef[EmAgent.Request], UUID] = Map.empty, + private val uuidToRef: Map[UUID, ActorRef[EmAgent.Request]] = Map.empty, + private val uuidToFlexResponse: Map[UUID, ActorRef[FlexResponse]] = + Map.empty, + private val flexResponseToUuid: Map[ActorRef[FlexResponse], UUID] = + Map.empty, + ) { + + def add( + model: UUID, + ref: ActorRef[EmAgent.Request], + parentEm: Option[ActorRef[FlexResponse]] = None, + parentUuid: Option[UUID] = None, + ): EmHierarchy = parentEm.zip(parentUuid) match { + case Some((parent, uuid)) => + copy( + uuidToRef = uuidToRef + (model -> ref), + refToUuid = refToUuid + (ref -> model), + uuidToFlexResponse = uuidToFlexResponse + (uuid -> parent), + flexResponseToUuid = flexResponseToUuid + (parent -> uuid), + ) + case None => + copy( + uuidToRef = uuidToRef + (model -> ref), + refToUuid = refToUuid + (ref -> model), + ) + } + + def getUuid(ref: ActorRef[FlexResponse]): UUID = + flexResponseToUuid(ref) + + def getResponseRef(uuid: UUID): Option[ActorRef[FlexResponse]] = + uuidToFlexResponse.get(uuid) + + } + +} 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 2d8233d12c..3733d6147d 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -7,13 +7,17 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.ExtEmDataConnection import edu.ie3.simona.api.data.em.ontology._ +import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException -import edu.ie3.simona.exceptions.{InitializationException, ServiceException} -import edu.ie3.simona.ontology.messages.Activation +import edu.ie3.simona.exceptions.{ + CriticalFailureException, + InitializationException, + ServiceException, +} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ +import edu.ie3.simona.ontology.messages.services.EmMessage import edu.ie3.simona.ontology.messages.services.EmMessage.{ WrappedFlexRequest, WrappedFlexResponse, @@ -23,7 +27,6 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ ServiceRegistrationMessage, ServiceResponseMessage, } -import edu.ie3.simona.ontology.messages.services.{EmMessage, ServiceMessage} import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceBaseStateData, @@ -36,7 +39,6 @@ import org.slf4j.{Logger, LoggerFactory} import java.time.ZonedDateTime import java.util.UUID -import scala.annotation.tailrec import scala.util.{Failure, Success, Try} object ExtEmDataService @@ -122,9 +124,16 @@ object ExtEmDataService initServiceData: InitializeServiceStateData ): Try[(ExtEmDataStateData, Option[Long])] = initServiceData match { case InitExtEmData(extEmDataConnection, startTime) => - val serviceCore = if (extEmDataConnection.useCommunication) { - EmCommunicationCore.empty - } else EmServiceBaseCore.empty + val serviceCore = extEmDataConnection.mode match { + case EmMode.SET_POINT => + EmServiceBaseCore.empty + case EmMode.EM_COMMUNICATION => + EmCommunicationCore.empty + case EmMode.EM_OPTIMIZATION => + throw new CriticalFailureException( + s"Em mode ${EmMode.EM_OPTIMIZATION} is currently not supported!" + ) + } val emDataInitializedStateData = ExtEmDataStateData(extEmDataConnection, startTime, serviceCore) diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala index 9c29a7c8fd..36f0cbd9c7 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala @@ -6,17 +6,12 @@ package edu.ie3.simona.util -import org.slf4j.{Logger, LoggerFactory} - final case class ReceiveHierarchicalDataMap[K, V]( - withExpected: Boolean, structure: Map[K, Set[K]], allKeys: Set[K], expectedKeys: Set[K], receivedData: Map[K, V], ) { - private val log: Logger = - LoggerFactory.getLogger(ReceiveHierarchicalDataMap.getClass) def hasCompletedKeys: Boolean = structure.keySet.exists(isComplete) @@ -29,8 +24,6 @@ final case class ReceiveHierarchicalDataMap[K, V]( key: Option[K], subKey: K, ): ReceiveHierarchicalDataMap[K, V] = { - log.debug(s"Added agent '$subKey' with parent '$key' to structure.") - val (updatedStructure, updatedKeys): (Map[K, Set[K]], Set[K]) = key match { case Some(parent) => structure.get(parent) match { @@ -119,21 +112,10 @@ object ReceiveHierarchicalDataMap { def empty[K, V]: ReceiveHierarchicalDataMap[K, V] = ReceiveHierarchicalDataMap( - withExpected = true, Map.empty, Set.empty, Set.empty, Map.empty, ) - def empty[K, V]( - withExpected: Boolean = true - ): ReceiveHierarchicalDataMap[K, V] = ReceiveHierarchicalDataMap( - withExpected, - Map.empty, - Set.empty, - Set.empty, - Map.empty, - ) - } From 309d1ea459faa74e499f3d34d929748769ed926a Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 17 Apr 2025 12:23:12 +0200 Subject: [PATCH 040/125] Saving changes. --- .../ie3/simona/service/em/EmCommunicationCore.scala | 1 + .../edu/ie3/simona/service/em/EmServiceCore.scala | 10 +--------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index b2b129ca82..d26f9c5938 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -23,6 +23,7 @@ import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.simona.util.{ReceiveDataMap, ReceiveHierarchicalDataMap} import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW +import edu.ie3.util.scala.quantities.QuantityConversionUtils.PowerConversionSimona import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger import tech.units.indriya.ComparableQuantity 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 54f9d2b3dc..937adb4f50 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -23,13 +23,12 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ RegisterForEmDataService, ServiceResponseMessage, } -import edu.ie3.util.quantities.PowerSystemUnits import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW +import edu.ie3.util.scala.quantities.QuantityConversionUtils.PowerConversionSimona import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger import squants.Power -import squants.energy.Kilowatts import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime @@ -46,13 +45,6 @@ trait EmServiceCore { implicit class SquantsToQuantity(private val value: Power) { def toQuantity: ComparableQuantity[PsdmPower] = value.toMegawatts.asMegaWatt } - implicit class QuantityToSquants( - private val value: ComparableQuantity[PsdmPower] - ) { - def toSquants: Power = Kilowatts( - value.to(PowerSystemUnits.KILOWATT).getValue.doubleValue() - ) - } def handleRegistration( registerForEmDataService: RegisterForEmDataService From 230dd0b7644d1aa943fc602316367b00a0e9f2a1 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 17 Apr 2025 16:06:50 +0200 Subject: [PATCH 041/125] Saving changes. --- .../service/primary/ExtPrimaryDataService.scala | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala index 17f64e5507..71813e8cf3 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala @@ -6,13 +6,7 @@ package edu.ie3.simona.service.primary -import edu.ie3.simona.agent.participant.data.Data.PrimaryData -import edu.ie3.simona.agent.participant.data.Data.PrimaryData.RichValue -import edu.ie3.simona.agent.participant2.ParticipantAgent -import edu.ie3.simona.agent.participant2.ParticipantAgent.{ - DataProvision, - PrimaryRegistrationSuccessfulMessage, -} +import edu.ie3.simona.agent.participant.ParticipantAgent import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.api.data.primarydata.ontology.{ @@ -26,6 +20,8 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ PrimaryServiceRegistrationMessage, ServiceResponseMessage, } +import edu.ie3.simona.service.Data.PrimaryData +import edu.ie3.simona.service.Data.PrimaryData.RichValue import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceBaseStateData, @@ -33,7 +29,6 @@ import edu.ie3.simona.service.ServiceStateData.{ import edu.ie3.simona.service.{ExtDataSupport, ServiceStateData, SimonaService} import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext -import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import java.util.UUID import scala.jdk.CollectionConverters.MapHasAsScala From bf249934a482c803dc48e482ce778aff390d8a2c Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 17 Apr 2025 16:09:26 +0200 Subject: [PATCH 042/125] fmt --- .../ie3/simona/service/primary/ExtPrimaryDataService.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala index 71813e8cf3..28079a97da 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala @@ -7,6 +7,10 @@ package edu.ie3.simona.service.primary import edu.ie3.simona.agent.participant.ParticipantAgent +import edu.ie3.simona.agent.participant.ParticipantAgent.{ + DataProvision, + PrimaryRegistrationSuccessfulMessage, +} import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.api.data.primarydata.ontology.{ From 26317b2e383d5ca1514f907f146f8a437fb4f1c5 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 28 Apr 2025 12:32:07 +0200 Subject: [PATCH 043/125] Improving `EmServiceBaseCore`. --- .../service/em/EmCommunicationCore.scala | 24 +- .../simona/service/em/EmServiceBaseCore.scala | 155 +++++- .../simona/service/em/ExtEmDataService.scala | 6 +- .../agent/em/EmAgentWithServiceSpec.scala | 11 +- .../ie3/simona/service/em/ExtEmBaseIT.scala | 487 ++++++++++++++++++ .../service/em/ExtEmCommunicationIT.scala | 13 +- .../service/em/ExtEmDataServiceSpec.scala | 8 +- .../service/ev/ExtEvDataServiceSpec.scala | 2 +- .../primary/ExtPrimaryDataServiceSpec.scala | 4 +- .../input/EmCommunicationTestData.scala | 2 +- 10 files changed, 644 insertions(+), 68 deletions(-) create mode 100644 src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index d26f9c5938..39220d7d49 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -101,8 +101,6 @@ final case class EmCommunicationCore( } case provideFlexRequests: ProvideFlexRequestData => - log.info(s"Handling of: $provideFlexRequests") - // entities for which flex options are requested val emEntities: Set[UUID] = provideFlexRequests .flexRequests() @@ -112,7 +110,6 @@ final case class EmCommunicationCore( val refs = emEntities.map(uuidToFlexAdapter) - log.warn(s"Em refs: $refs") refs.foreach(_ ! FlexActivation(tick)) ( @@ -129,8 +126,6 @@ final case class EmCommunicationCore( ) case provideFlexOptions: ProvideEmFlexOptionData => - log.info(s"Handling of: $provideFlexOptions") - provideFlexOptions .flexOptions() .asScala @@ -170,8 +165,6 @@ final case class EmCommunicationCore( log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = flexResponse match { case scheduleFlexActivation: ScheduleFlexActivation => - log.warn(s"Flex activation: $scheduleFlexActivation") - if (scheduleFlexActivation.tick == INIT_SIM_TICK) { receiver match { case Right(ref) => @@ -231,8 +224,6 @@ final case class EmCommunicationCore( flexOptionResponse } - log.warn(s"Updated data map: $updated") - if (updated.hasCompletedKeys) { // all responses received, forward them to external simulation in a bundle @@ -260,16 +251,13 @@ final case class EmCommunicationCore( } } - case FlexResult(modelUuid, result) => - log.info(s"Flex result '$result' for model '$modelUuid'.") + case _: FlexResult => (this, None) case completion: FlexCompletion => receiver.map(_ ! completion) - log.warn(s"Completion: $completion") - val model = completion.modelUuid - val updated = completions.addData(model, completion) + val updated = completions.addData(completion.modelUuid, completion) if (updated.isComplete) { val allKeys = updated.receivedData.keySet @@ -304,22 +292,16 @@ final case class EmCommunicationCore( } else { val uuid = flexAdapterToUuid(receiver) - log.warn(s"Receiver: $uuid") - val updated = flexRequestReceived.addData( uuid, true, ) - log.warn(s"$updated") - if (updated.hasCompletedKeys) { val (dataMap, _, updatedFlexRequest) = updated.getFinishedDataHierarchical - log.warn(s"Data to be send: $dataMap") - val map = dataMap.map { case (sender, receivers) => sender -> new FlexRequestResult( flexActivation.tick.toDateTime, @@ -358,8 +340,6 @@ final case class EmCommunicationCore( val updated = setPointResponse.addData(uuid, power) - log.warn(s"Updated set point response: $updated") - if (updated.hasCompletedKeys) { val (structureMap, dataMap, updatedSetPointResponse) = 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 25698ca643..cb0089ad45 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -6,41 +6,88 @@ package edu.ie3.simona.service.em +import edu.ie3.simona.api.data.em.model.ExtendedFlexOptionsResult import edu.ie3.simona.api.data.em.ontology._ import edu.ie3.simona.exceptions.CriticalFailureException -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ - FlexRequest, - FlexResponse, -} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ +import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService -import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK +import edu.ie3.simona.util.ReceiveDataMap +import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} +import edu.ie3.simona.util.TickUtil.TickLong 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, + SetHasAsScala, +} -case class EmServiceBaseCore( +final case class EmServiceBaseCore( override val lastFinishedTick: Long = PRE_INIT_TICK, - override val uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = Map.empty, + override val uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = + Map.empty, + flexOptions: ReceiveDataMap[UUID, ExtendedFlexOptionsResult] = + ReceiveDataMap.empty, + completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, + sendOptionsToExt: Boolean = false, + canHandleSetPoints: Boolean = false, + setPointOption: Option[ProvideEmSetPointData] = None, ) extends EmServiceCore { override def handleRegistration( registrationMsg: RegisterForEmDataService ): EmServiceBaseCore = - copy(uuidToFlexAdapter = - uuidToFlexAdapter ++ Map( + copy( + uuidToFlexAdapter = uuidToFlexAdapter ++ Map( registrationMsg.modelUuid -> registrationMsg.flexAdapter - ) + ), + completions = completions.addExpectedKeys(Set(registrationMsg.modelUuid)), ) override def handleExtMessage(tick: Long, extMSg: EmDataMessageFromExt)( implicit log: Logger ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMSg match { + case requestEmFlexResults: RequestEmFlexResults => + val tick = requestEmFlexResults.tick + val emEntities = requestEmFlexResults.emEntities.asScala + + emEntities.map(uuidToFlexAdapter).foreach { ref => + ref ! FlexActivation(tick) + } + + ( + copy( + flexOptions = ReceiveDataMap(emEntities.toSet), + sendOptionsToExt = true, + ), + None, + ) + case provideEmSetPoints: ProvideEmSetPointData => - handleSetPoint(tick, provideEmSetPoints, log) + if (canHandleSetPoints) { + handleSetPoint(tick, provideEmSetPoints, log) - (this, None) + (this, None) + } else { + val tick = provideEmSetPoints.tick + val emEntities = provideEmSetPoints.emData.keySet.asScala + + emEntities.map(uuidToFlexAdapter).foreach { ref => + ref ! FlexActivation(tick) + } + + ( + copy( + flexOptions = ReceiveDataMap(emEntities.toSet), + setPointOption = Some(provideEmSetPoints), + ), + None, + ) + } case _ => throw new CriticalFailureException( @@ -56,12 +103,86 @@ case class EmServiceBaseCore( startTime: ZonedDateTime, log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { - receiver.map { ref => - log.warn(s"$ref: $flexResponse") - ref ! flexResponse - } + receiver.foreach(_ ! flexResponse) - (this, None) + flexResponse match { + case provideFlexOptions: ProvideFlexOptions => + val updated = provideFlexOptions match { + case ProvideFlexOptions( + modelUuid, + MinMaxFlexOptions(ref, min, max), + ) if flexOptions.getExpectedKeys.contains(modelUuid) => + flexOptions.addData( + modelUuid, + new ExtendedFlexOptionsResult( + tick.toDateTime(startTime), + modelUuid, + modelUuid, + min.toQuantity, + ref.toQuantity, + max.toQuantity, + ), + ) + + case _ => + flexOptions + } + + if (updated.isComplete) { + // we received all flex options and can now handle set points + + val data = updated.receivedData + val updatedCore = copy( + flexOptions = ReceiveDataMap(updated.receivedData.keySet), + canHandleSetPoints = true, + ) + + if (sendOptionsToExt) { + // we have received an option request, that will now be answered + (updatedCore, Some(new FlexOptionsResponse(data.asJava))) + + } else { + setPointOption match { + case Some(setPoints) => + // we have received new set points, that are not handled yet => we will handle them now + handleSetPoint(tick, setPoints, log) + + (updatedCore, None) + case None => + // we are now able to handle set points, but we have not yet received any + (updatedCore, None) + } + } + + } else (copy(flexOptions = updated), None) + + case completion: FlexCompletion => + val updated = completions.addData(completion.modelUuid, completion) + + if (updated.isComplete) { + val allKeys = updated.receivedData.keySet + + val extMsgOption = if (tick != INIT_SIM_TICK) { + // send completion message to external simulation, if we aren't in the INIT_SIM_TICK + Some(new EmCompletion()) + } else None + + // every em agent has sent a completion message + ( + copy( + lastFinishedTick = tick, + completions = ReceiveDataMap(allKeys), + sendOptionsToExt = false, + canHandleSetPoints = false, + ), + extMsgOption, + ) + + } else (copy(completions = updated), None) + + case _ => + (this, None) + } } override def handleFlexRequest( 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 3733d6147d..75d3194d9b 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -107,14 +107,14 @@ object ExtEmDataService scheduleFlexActivation: ScheduleFlexActivation, receiver, ) => - log.info(s"Received response message: $scheduleFlexActivation") + log.debug(s"Received response message: $scheduleFlexActivation") receiver match { case Right(ref) => - log.info(s"Forwarding the message to: $ref") + log.debug(s"Forwarding the message to: $ref") ref ! scheduleFlexActivation case Left(_) => - log.info(s"Unlocking msg: $scheduleFlexActivation") + log.debug(s"Unlocking msg: $scheduleFlexActivation") scheduleFlexActivation.scheduleKey.foreach(_.unlock()) } diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala index 9f0b2b8444..956c0fe1fb 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala @@ -7,8 +7,6 @@ package edu.ie3.simona.agent.em import edu.ie3.datamodel.models.result.system.EmResult -import edu.ie3.simona.agent.participant.data.Data.PrimaryData.ComplexPower -import edu.ie3.simona.config.RuntimeConfig.EmRuntimeConfig import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.event.ResultEvent.{ FlexOptionsResultEvent, @@ -24,6 +22,7 @@ import edu.ie3.simona.ontology.messages.services.EmMessage.{ WrappedFlexResponse, } import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService +import edu.ie3.simona.service.Data.PrimaryData.ComplexPower import edu.ie3.simona.test.common.input.EmInputTestData import edu.ie3.simona.test.matchers.SquantsMatchers import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK @@ -62,14 +61,6 @@ class EmAgentWithServiceSpec flexResult = true, // also test FlexOptionsResult if EM-controlled ) - override protected val modelConfig: EmRuntimeConfig = EmRuntimeConfig( - calculateMissingReactivePowerWithModel = false, - scaling = 1, - uuids = List("default"), - aggregateFlex = "SELF_OPT_EXCL_REG", - curtailRegenerative = false, - ) - private implicit val activePowerTolerance: Power = Kilowatts(1e-10) private implicit val reactivePowerTolerance: ReactivePower = Kilovars(1e-10) diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala new file mode 100644 index 0000000000..1b37f164c5 --- /dev/null +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala @@ -0,0 +1,487 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.em + +import edu.ie3.datamodel.models.value.PValue +import edu.ie3.simona.agent.em.EmAgent +import edu.ie3.simona.agent.grid.GridAgent +import edu.ie3.simona.agent.participant.ParticipantAgent.{ + DataProvision, + RegistrationFailedMessage, + RegistrationSuccessfulMessage, +} +import edu.ie3.simona.agent.participant.ParticipantAgentInit +import edu.ie3.simona.agent.participant.ParticipantAgentInit.ParticipantRefs +import edu.ie3.simona.api.data.em.model.NoSetPointValue +import edu.ie3.simona.api.data.em.ontology.{ + EmCompletion, + FlexOptionsResponse, + RequestEmFlexResults, +} +import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} +import edu.ie3.simona.api.data.ontology.{ + DataMessageFromExt, + ScheduleDataServiceMessage, +} +import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt +import edu.ie3.simona.config.RuntimeConfig.{ + LoadRuntimeConfig, + PvRuntimeConfig, + StorageRuntimeConfig, +} +import edu.ie3.simona.event.ResultEvent +import edu.ie3.simona.model.InputModelContainer.SimpleInputContainer +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + Create, + PrimaryServiceRegistrationMessage, +} +import edu.ie3.simona.ontology.messages.services.WeatherMessage.{ + RegisterForWeatherMessage, + WeatherData, +} +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.scheduler.ScheduleLock +import edu.ie3.simona.service.ServiceType +import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData +import edu.ie3.simona.test.common.TestSpawnerTyped +import edu.ie3.simona.test.common.input.EmCommunicationTestData +import edu.ie3.simona.test.matchers.QuantityMatchers +import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} +import edu.ie3.util.quantities.QuantityUtils._ +import edu.ie3.util.scala.quantities.WattsPerSquareMeter +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} +import org.apache.pekko.actor.typed.ActorRef +import org.scalatest.wordspec.AnyWordSpecLike +import org.slf4j.{Logger, LoggerFactory} +import squants.motion.MetersPerSecond +import squants.thermal.Celsius + +import java.util.{Optional, UUID} +import scala.concurrent.duration.{DurationInt, FiniteDuration} +import scala.jdk.CollectionConverters.{ + MapHasAsJava, + MapHasAsScala, + SeqHasAsJava, +} + +class ExtEmBaseIT + extends ScalaTestWithActorTestKit + with AnyWordSpecLike + with EmCommunicationTestData + with QuantityMatchers + with TestSpawnerTyped { + + protected val messageTimeout: FiniteDuration = 30.seconds + + protected val log: Logger = LoggerFactory.getLogger("ExtEmCommunicationIT") + + private val emSupUuid = + UUID.fromString("858f3d3d-4189-49cd-9fe5-3cd49b88dc70") + private val emNode3Uuid = + UUID.fromString("fd1a8de9-722a-4304-8799-e1e976d9979c") + private val emNode4Uuid = + UUID.fromString("ff0b995a-86ff-4f4d-987e-e475a64f2180") + + private val connection = new ExtEmDataConnection( + List(emSupUuid, emNode3Uuid, emNode4Uuid).asJava, + EmMode.SET_POINT, + ) + + private val gridAgent = TestProbe[GridAgent.Request]("GridAgent") + private val resultListener = TestProbe[ResultEvent]("ResultListener") + private val primaryServiceProxy = + TestProbe[ServiceMessage]("PrimaryServiceProxy") + private val weatherService = TestProbe[ServiceMessage]("WeatherService") + + private val participantRefs = ParticipantRefs( + gridAgent = gridAgent.ref, + primaryServiceProxy = primaryServiceProxy.ref, + services = Map(ServiceType.WeatherService -> weatherService.ref), + resultListener = Iterable(resultListener.ref), + ) + + "An ExtEmDataService in base mode" should { + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") + + val service = spawn(ExtEmDataService(scheduler.ref)) + val serviceRef = service.ref + implicit val adapter: ActorRef[DataMessageFromExt] = + spawn(ExtEmDataService.adapter(service)) + + connection.setActorRefs(adapter, extSimAdapter.ref) + + val emAgentSup = spawn( + EmAgent( + emSup, + modelConfig, + outputConfig, + "PROPORTIONAL", + simulationStart, + parent = Left(scheduler.ref), + listener = Iterable(resultListener.ref), + Some(serviceRef), + ) + ) + + val emAgentNode3 = spawn( + EmAgent( + emNode3, + modelConfig, + outputConfig, + "PRIORITIZED", + simulationStart, + parent = Right(emAgentSup), + listener = Iterable(resultListener.ref), + Some(serviceRef), + ) + ) + + val emAgentNode4 = spawn( + EmAgent( + emNode4, + modelConfig, + outputConfig, + "PRIORITIZED", + simulationStart, + parent = Right(emAgentSup), + listener = Iterable(resultListener.ref), + Some(serviceRef), + ) + ) + + val keys = ScheduleLock + .multiKey(TSpawner, scheduler.ref, PRE_INIT_TICK, 4) + .iterator + val lockActivation = + scheduler.expectMessageType[ScheduleActivation].actor + lockActivation ! Activation(PRE_INIT_TICK) + + val pvAgentNode3 = spawn( + ParticipantAgentInit( + SimpleInputContainer(pvNode3), + PvRuntimeConfig(), + outputConfig, + participantRefs, + simulationParams, + Right(emAgentNode3), + keys.next(), + ) + ) + + val storageAgentNode3 = spawn( + ParticipantAgentInit( + SimpleInputContainer(storageInput), + StorageRuntimeConfig(), + outputConfig, + participantRefs, + simulationParams, + Right(emAgentNode3), + keys.next(), + ) + ) + + val pvAgentNode4 = spawn( + ParticipantAgentInit( + SimpleInputContainer(pvNode4), + PvRuntimeConfig(), + outputConfig, + participantRefs, + simulationParams, + Right(emAgentNode4), + keys.next(), + ) + ) + + val loadAgentNode4 = spawn( + ParticipantAgentInit( + SimpleInputContainer(loadInput), + LoadRuntimeConfig(), + outputConfig, + participantRefs, + simulationParams, + Right(emAgentNode4), + keys.next(), + ) + ) + + /* PRE_INIT */ + val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, PRE_INIT_TICK) + scheduler + .expectMessageType[ScheduleActivation] // lock activation scheduled + + service ! Create( + InitExtEmData(connection, simulationStart), + key, + ) + + val activationMsg = scheduler.expectMessageType[ScheduleActivation] + activationMsg.tick shouldBe INIT_SIM_TICK + activationMsg.unlockKey shouldBe Some(key) + implicit val serviceActivation: ActorRef[Activation] = activationMsg.actor + + // we expect a completion for the participant locks + scheduler.expectMessage(Completion(lockActivation)) + + /* INIT */ + + // activate the service for init tick + serviceActivation ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(serviceActivation)) + + primaryServiceProxy.receiveMessages( + 4, + messageTimeout, + ) should contain allOf ( + PrimaryServiceRegistrationMessage( + pvAgentNode3, + pvNode3.getUuid, + ), + PrimaryServiceRegistrationMessage( + storageAgentNode3, + storageInput.getUuid, + ), + PrimaryServiceRegistrationMessage( + pvAgentNode4, + pvNode4.getUuid, + ), + PrimaryServiceRegistrationMessage( + loadAgentNode4, + loadInput.getUuid, + ) + ) + + // pv agent 3 + pvAgentNode3 ! RegistrationFailedMessage(primaryServiceProxy.ref) + + // deal with weather service registration + weatherService.expectMessage( + RegisterForWeatherMessage( + pvAgentNode3, + pvNode3.getNode.getGeoPosition.getY, + pvNode3.getNode.getGeoPosition.getX, + ) + ) + + pvAgentNode3 ! RegistrationSuccessfulMessage(weatherService.ref, 0L) + + // pv agent 4 + pvAgentNode4 ! RegistrationFailedMessage(primaryServiceProxy.ref) + + weatherService.expectMessage( + RegisterForWeatherMessage( + pvAgentNode4, + pvNode4.getNode.getGeoPosition.getY, + pvNode4.getNode.getGeoPosition.getX, + ) + ) + pvAgentNode4 ! RegistrationSuccessfulMessage(weatherService.ref, 0L) + + // storage + storageAgentNode3 ! RegistrationFailedMessage(primaryServiceProxy.ref) + + // load + loadAgentNode4 ! RegistrationFailedMessage(primaryServiceProxy.ref) + + "with requesting flex options works correctly" in { + /* TICK: 0 */ + + val weatherData0 = DataProvision( + 0L, + weatherService.ref, + WeatherData( + WattsPerSquareMeter(0), + WattsPerSquareMeter(0), + Celsius(0d), + MetersPerSecond(0d), + ), + Some(900L), + ) + + pvAgentNode3 ! weatherData0 + pvAgentNode4 ! weatherData0 + + // we request the em option for the superior em agent + connection.sendExtMsg( + new RequestEmFlexResults(0L, List(emSupUuid).asJava) + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0L) + + val receivedFlexOptions0 = connection + .receiveWithType(classOf[FlexOptionsResponse]) + .receiverToFlexOptions() + .asScala + + receivedFlexOptions0.size shouldBe 1 + val flexOptions0 = receivedFlexOptions0(emSupUuid) + flexOptions0.getSender shouldBe emSupUuid + flexOptions0.getReceiver shouldBe emSupUuid + flexOptions0.getpMin should equalWithTolerance( + 0.002200000413468004.asMegaWatt + ) + flexOptions0.getpRef should equalWithTolerance( + 0.002200000413468004.asMegaWatt + ) + flexOptions0.getpMax should equalWithTolerance( + 0.006200000413468004.asMegaWatt + ) + + // we return a new set point + val setPoints0: Map[UUID, PValue] = + Map(emSupUuid -> new NoSetPointValue(0.002200000413468004.asMegaWatt)) + + connection.sendSetPoints(0L, setPoints0.asJava, Optional.of(900L), log) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(0L) + + connection.receiveWithType(classOf[EmCompletion]) + + /* TICK: 900 */ + + val weatherData900 = DataProvision( + 900L, + weatherService.ref, + WeatherData( + WattsPerSquareMeter(0), + WattsPerSquareMeter(0), + Celsius(0d), + MetersPerSecond(0d), + ), + Some(1800L), + ) + + pvAgentNode3 ! weatherData900 + pvAgentNode4 ! weatherData900 + + // we request the em option for the superior em agent + connection.sendExtMsg( + new RequestEmFlexResults(900L, List(emSupUuid).asJava) + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(900L) + + val receivedFlexOptions900 = connection + .receiveWithType(classOf[FlexOptionsResponse]) + .receiverToFlexOptions() + .asScala + + receivedFlexOptions900.size shouldBe 1 + val flexOptions900 = receivedFlexOptions900(emSupUuid) + flexOptions900.getSender shouldBe emSupUuid + flexOptions900.getReceiver shouldBe emSupUuid + flexOptions900.getpMin should equalWithTolerance( + 0.002200000413468004.asMegaWatt + ) + flexOptions900.getpRef should equalWithTolerance( + 0.002200000413468004.asMegaWatt + ) + flexOptions900.getpMax should equalWithTolerance( + 0.006200000413468004.asMegaWatt + ) + + // we return a new set point + val setPoints900: Map[UUID, PValue] = + Map(emSupUuid -> new PValue(0.006200000413468004.asMegaWatt)) + + connection.sendSetPoints( + 900L, + setPoints900.asJava, + Optional.of(1800L), + log, + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(900L) + + connection.receiveWithType(classOf[EmCompletion]) + } + + "without requesting flex options works correctly" in { + /* TICK: 1800 */ + + val weatherData0 = DataProvision( + 1800L, + weatherService.ref, + WeatherData( + WattsPerSquareMeter(0), + WattsPerSquareMeter(0), + Celsius(0d), + MetersPerSecond(0d), + ), + Some(2700L), + ) + + pvAgentNode3 ! weatherData0 + pvAgentNode4 ! weatherData0 + + // we send a new set point + val setPoints0: Map[UUID, PValue] = + Map(emSupUuid -> new NoSetPointValue(0.002200000413468004.asMegaWatt)) + + connection.sendSetPoints( + 1800L, + setPoints0.asJava, + Optional.of(2700L), + log, + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(1800L) + + connection.receiveWithType(classOf[EmCompletion]) + + /* TICK: 2700 */ + + val weatherData900 = DataProvision( + 2700L, + weatherService.ref, + WeatherData( + WattsPerSquareMeter(0), + WattsPerSquareMeter(0), + Celsius(0d), + MetersPerSecond(0d), + ), + Some(3600L), + ) + + pvAgentNode3 ! weatherData900 + pvAgentNode4 ! weatherData900 + + // we send a new set point + val setPoints900: Map[UUID, PValue] = + Map(emSupUuid -> new PValue(0.006200000413468004.asMegaWatt)) + + connection.sendSetPoints( + 2700L, + setPoints900.asJava, + Optional.of(3600L), + log, + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(2700L) + + connection.receiveWithType(classOf[EmCompletion]) + + } + + } + +} diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index e9449560be..fec446d023 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -9,17 +9,13 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.grid.GridAgent -import edu.ie3.simona.agent.participant.statedata.ParticipantStateData.SimpleInputContainer -import edu.ie3.simona.agent.participant2.ParticipantAgent.{ +import edu.ie3.simona.agent.participant.ParticipantAgent.{ DataProvision, RegistrationFailedMessage, RegistrationSuccessfulMessage, } -import edu.ie3.simona.agent.participant2.ParticipantAgentInit.ParticipantRefs -import edu.ie3.simona.agent.participant2.{ - ParticipantAgent, - ParticipantAgentInit, -} +import edu.ie3.simona.agent.participant.ParticipantAgentInit.ParticipantRefs +import edu.ie3.simona.agent.participant.{ParticipantAgent, ParticipantAgentInit} import edu.ie3.simona.api.data.em.model.{ FlexOptionRequest, FlexOptions, @@ -43,6 +39,7 @@ import edu.ie3.simona.config.RuntimeConfig.{ StorageRuntimeConfig, } import edu.ie3.simona.event.ResultEvent +import edu.ie3.simona.model.InputModelContainer.SimpleInputContainer import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, @@ -127,7 +124,7 @@ class ExtEmCommunicationIT resultListener = Iterable(resultListener.ref), ) - "An ExtEmDataService im communication mode" should { + "An ExtEmDataService in communication mode" should { val service = spawn(ExtEmDataService(scheduler.ref)) val serviceRef = service.ref implicit val adapter: ActorRef[DataMessageFromExt] = 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 1f245e8a8b..90ec4d5af9 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -263,7 +263,7 @@ class ExtEmDataServiceSpec extEmDataConnection.sendExtMsg( new RequestEmFlexResults( INIT_SIM_TICK, - Map.empty[UUID, util.List[UUID]].asJava, + List.empty[UUID].asJava, ) ) @@ -282,7 +282,7 @@ class ExtEmDataServiceSpec extEmDataConnection.sendExtMsg( new RequestEmFlexResults( 0, - Map(emAgentSupUUID -> List(emAgent1UUID).asJava).asJava, + List(emAgentSupUUID).asJava, ) ) @@ -304,14 +304,14 @@ class ExtEmDataServiceSpec emService ! WrappedFlexResponse( ProvideFlexOptions( - emAgent1UUID, + emAgentSupUUID, MinMaxFlexOptions( Kilowatts(5), Kilowatts(0), Kilowatts(10), ), ), - Left(emAgent1UUID), + Left(emAgentSupUUID), ) awaitCond( diff --git a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala index ae9a752c92..ddfc86c26d 100644 --- a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala @@ -22,7 +22,7 @@ import edu.ie3.simona.api.data.ontology.{ ScheduleDataServiceMessage, } import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt -import edu.ie3.simona.model.participant2.evcs.EvModelWrapper +import edu.ie3.simona.model.participant.evcs.EvModelWrapper import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, diff --git a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala index 10d0593d6d..b70f7379ca 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala @@ -8,8 +8,7 @@ package edu.ie3.simona.service.primary import com.typesafe.scalalogging.LazyLogging import edu.ie3.datamodel.models.value.Value -import edu.ie3.simona.agent.participant.data.Data.PrimaryData -import edu.ie3.simona.agent.participant2.ParticipantAgent.{ +import edu.ie3.simona.agent.participant.ParticipantAgent.{ DataProvision, RegistrationSuccessfulMessage, } @@ -27,6 +26,7 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ import edu.ie3.simona.ontology.messages.services.WeatherMessage.RegisterForWeatherMessage import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock +import edu.ie3.simona.service.Data.PrimaryData import edu.ie3.simona.service.primary.ExtPrimaryDataService.InitExtPrimaryData import edu.ie3.simona.test.common.TestSpawnerTyped import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK diff --git a/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala b/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala index d5a7b15ecc..b0ddce068f 100644 --- a/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala @@ -12,7 +12,7 @@ import edu.ie3.datamodel.models.input.system.{LoadInput, PvInput, StorageInput} import edu.ie3.datamodel.models.input.{EmInput, NodeInput} import edu.ie3.datamodel.models.profile.BdewStandardLoadProfile import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils -import edu.ie3.simona.agent.participant2.ParticipantAgentInit.SimulationParameters +import edu.ie3.simona.agent.participant.ParticipantAgentInit.SimulationParameters import edu.ie3.simona.config.RuntimeConfig.EmRuntimeConfig import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.event.notifier.NotifierConfig From 5d5a94701d7c94c3dc71e4499811c5b8099383a4 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 28 Apr 2025 15:54:52 +0200 Subject: [PATCH 044/125] Saving changes. --- .../scala/edu/ie3/simona/agent/em/EmAgent.scala | 7 +------ .../edu/ie3/simona/service/ExtDataSupport.scala | 4 +--- .../ie3/simona/service/em/EmServiceBaseCore.scala | 13 ++++++++++++- .../ie3/simona/service/em/ExtEmDataService.scala | 4 +--- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index be001d7fbc..4e0e0eabf4 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -230,14 +230,11 @@ object EmAgent { inactive(emData, modelShell, newCore) case (ctx, msg: ActivationRequest) => - ctx.log.warn(s"${ctx.self}: $msg") - val flexOptionsCore = core.activate(msg.tick) msg match { case Flex(_: FlexActivation) | EmActivation(_) => val (toActivate, newCore) = flexOptionsCore.takeNewFlexRequests() - ctx.log.info(s"${ctx.self}, to activate: $toActivate") toActivate.foreach { _ ! FlexActivation(msg.tick) @@ -270,9 +267,7 @@ object EmAgent { modelShell: EmModelShell, flexOptionsCore: EmDataCore.AwaitingFlexOptions, ): Behavior[Request] = Behaviors.receivePartial { - case (ctx, provideFlex: ProvideFlexOptions) => - ctx.log.warn(s"${ctx.self} from: ${provideFlex.modelUuid}") - + case (_, provideFlex: ProvideFlexOptions) => val updatedCore = flexOptionsCore.handleFlexOptions( provideFlex.modelUuid, provideFlex.flexOptions, diff --git a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala index 18410dc2d2..7fd7274342 100644 --- a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala +++ b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala @@ -54,9 +54,7 @@ trait ExtDataSupport[T >: ServiceMessage] { idle(updatedStateData, constantData) - case (ctx, extResponseMsg: ServiceResponseMessage) => - ctx.log.warn(s"Response: $extResponseMsg") - + case (_, extResponseMsg: ServiceResponseMessage) => val updatedStateData = handleDataResponseMessage(extResponseMsg) idle(updatedStateData, constantData) 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 cb0089ad45..fb5292eb99 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -33,6 +33,7 @@ final case class EmServiceBaseCore( flexOptions: ReceiveDataMap[UUID, ExtendedFlexOptionsResult] = ReceiveDataMap.empty, completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, + disaggregatedFlex: Boolean = false, sendOptionsToExt: Boolean = false, canHandleSetPoints: Boolean = false, setPointOption: Option[ProvideEmSetPointData] = None, @@ -54,6 +55,11 @@ final case class EmServiceBaseCore( case requestEmFlexResults: RequestEmFlexResults => val tick = requestEmFlexResults.tick val emEntities = requestEmFlexResults.emEntities.asScala + val disaggregated = requestEmFlexResults.disaggregated + + if (disaggregated) { + log.warn(s"Disaggregated flex options are currently not supported!") + } emEntities.map(uuidToFlexAdapter).foreach { ref => ref ! FlexActivation(tick) @@ -62,6 +68,7 @@ final case class EmServiceBaseCore( ( copy( flexOptions = ReceiveDataMap(emEntities.toSet), + disaggregatedFlex = disaggregated, sendOptionsToExt = true, ), None, @@ -132,6 +139,9 @@ final case class EmServiceBaseCore( // we received all flex options and can now handle set points val data = updated.receivedData + + log.warn(s"Expect data from: ${updated.getExpectedKeys}") + val updatedCore = copy( flexOptions = ReceiveDataMap(updated.receivedData.keySet), canHandleSetPoints = true, @@ -172,6 +182,7 @@ final case class EmServiceBaseCore( copy( lastFinishedTick = tick, completions = ReceiveDataMap(allKeys), + disaggregatedFlex = false, sendOptionsToExt = false, canHandleSetPoints = false, ), @@ -192,7 +203,7 @@ final case class EmServiceBaseCore( startTime: ZonedDateTime, log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { - log.warn(s"$receiver: $flexRequest") + log.debug(s"$receiver: $flexRequest") receiver ! flexRequest (this, None) 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 75d3194d9b..1cf9972be5 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -130,9 +130,7 @@ object ExtEmDataService case EmMode.EM_COMMUNICATION => EmCommunicationCore.empty case EmMode.EM_OPTIMIZATION => - throw new CriticalFailureException( - s"Em mode ${EmMode.EM_OPTIMIZATION} is currently not supported!" - ) + EmServiceBaseCore.empty } val emDataInitializedStateData = From de9e5755290b38b59b34ecef921895fd97ce7879 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 6 May 2025 14:50:31 +0200 Subject: [PATCH 045/125] Saving changes. --- .../scala/edu/ie3/simona/service/em/ExtEmDataService.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 1cf9972be5..3d1e05bc07 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -125,12 +125,10 @@ object ExtEmDataService ): Try[(ExtEmDataStateData, Option[Long])] = initServiceData match { case InitExtEmData(extEmDataConnection, startTime) => val serviceCore = extEmDataConnection.mode match { - case EmMode.SET_POINT => + case EmMode.BASE => EmServiceBaseCore.empty case EmMode.EM_COMMUNICATION => EmCommunicationCore.empty - case EmMode.EM_OPTIMIZATION => - EmServiceBaseCore.empty } val emDataInitializedStateData = From ef5178431787dda0399882a6898d94cb259ed6a1 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 7 May 2025 16:27:02 +0200 Subject: [PATCH 046/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 15 +++++---- .../simona/service/em/EmServiceBaseCore.scala | 32 +++++++++++++------ .../ie3/simona/service/em/EmServiceCore.scala | 11 +++---- .../util/ReceiveHierarchicalDataMap.scala | 20 +++++++++--- 4 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 39220d7d49..091fa7d0ab 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -42,6 +42,8 @@ final case class EmCommunicationCore( override val lastFinishedTick: Long = PRE_INIT_TICK, override val uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = Map.empty, + override val completions: ReceiveDataMap[UUID, FlexCompletion] = + ReceiveDataMap.empty, hierarchy: EmHierarchy = EmHierarchy(), flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, uuidToPRef: Map[UUID, ComparableQuantity[Power]] = Map.empty, @@ -51,17 +53,16 @@ final case class EmCommunicationCore( flexOptionResponse: DataMap[UUID, ExtendedFlexOptionsResult] = ReceiveHierarchicalDataMap.empty, setPointResponse: DataMap[UUID, PValue] = ReceiveHierarchicalDataMap.empty, - completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, ) extends EmServiceCore { override def handleRegistration( - registerMsg: RegisterForEmDataService + registrationMsg: RegisterForEmDataService ): EmServiceCore = { - val uuid = registerMsg.modelUuid - val ref = registerMsg.requestingActor - val flexAdapter = registerMsg.flexAdapter - val parentEm = registerMsg.parentEm - val parentUuid = registerMsg.parentUuid + val uuid = registrationMsg.modelUuid + val ref = registrationMsg.requestingActor + val flexAdapter = registrationMsg.flexAdapter + val parentEm = registrationMsg.parentEm + val parentUuid = registrationMsg.parentUuid val updatedHierarchy = hierarchy.add(uuid, ref, parentEm, parentUuid) 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 fb5292eb99..63e0006951 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -12,9 +12,9 @@ import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService -import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.TickLong +import edu.ie3.simona.util.{ReceiveDataMap, ReceiveHierarchicalDataMap} import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger @@ -30,9 +30,10 @@ final case class EmServiceBaseCore( override val lastFinishedTick: Long = PRE_INIT_TICK, override val uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = Map.empty, - flexOptions: ReceiveDataMap[UUID, ExtendedFlexOptionsResult] = + override val completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, - completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, + flexOptions: ReceiveHierarchicalDataMap[UUID, ExtendedFlexOptionsResult] = + ReceiveHierarchicalDataMap.empty, disaggregatedFlex: Boolean = false, sendOptionsToExt: Boolean = false, canHandleSetPoints: Boolean = false, @@ -67,7 +68,7 @@ final case class EmServiceBaseCore( ( copy( - flexOptions = ReceiveDataMap(emEntities.toSet), + flexOptions = ReceiveHierarchicalDataMap(emEntities.toSet), disaggregatedFlex = disaggregated, sendOptionsToExt = true, ), @@ -89,7 +90,7 @@ final case class EmServiceBaseCore( ( copy( - flexOptions = ReceiveDataMap(emEntities.toSet), + flexOptions = ReceiveHierarchicalDataMap(emEntities.toSet), setPointOption = Some(provideEmSetPoints), ), None, @@ -118,7 +119,7 @@ final case class EmServiceBaseCore( case ProvideFlexOptions( modelUuid, MinMaxFlexOptions(ref, min, max), - ) if flexOptions.getExpectedKeys.contains(modelUuid) => + ) => flexOptions.addData( modelUuid, new ExtendedFlexOptionsResult( @@ -136,14 +137,25 @@ final case class EmServiceBaseCore( } if (updated.isComplete) { - // we received all flex options and can now handle set points + // we received all flex options - val data = updated.receivedData + val expectedKeys = updated.getExpectedKeys + val receiveDataMap = updated.receivedData + val data = expectedKeys.map(key => key -> receiveDataMap(key)).toMap - log.warn(s"Expect data from: ${updated.getExpectedKeys}") + if (disaggregatedFlex) { + // we add the disaggregated flex options + val structure = updated.structure + + data.foreach { case (key, value) => + structure(key).foreach { inferior => + value.addDisaggregated(inferior, receiveDataMap(inferior)) + } + } + } val updatedCore = copy( - flexOptions = ReceiveDataMap(updated.receivedData.keySet), + flexOptions = ReceiveHierarchicalDataMap(updated.getExpectedKeys), canHandleSetPoints = true, ) 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 937adb4f50..e5b9ca13c0 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -9,12 +9,7 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.em.model.NoSetPointValue import edu.ie3.simona.api.data.em.ontology._ -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ - FlexRequest, - FlexResponse, - IssueNoControl, - IssuePowerControl, -} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.services.EmMessage.{ WrappedFlexRequest, WrappedFlexResponse, @@ -23,6 +18,8 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ RegisterForEmDataService, ServiceResponseMessage, } +import edu.ie3.simona.service.em.EmServiceCore.EmHierarchy +import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import edu.ie3.util.scala.quantities.QuantityConversionUtils.PowerConversionSimona @@ -42,6 +39,8 @@ trait EmServiceCore { def uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] + def completions: ReceiveDataMap[UUID, FlexCompletion] + implicit class SquantsToQuantity(private val value: Power) { def toQuantity: ComparableQuantity[PsdmPower] = value.toMegawatts.asMegaWatt } diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala index 36f0cbd9c7..6d8dfced67 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala @@ -7,15 +7,17 @@ package edu.ie3.simona.util final case class ReceiveHierarchicalDataMap[K, V]( + private val allKeys: Set[K], + private val expectedKeys: Set[K], structure: Map[K, Set[K]], - allKeys: Set[K], - expectedKeys: Set[K], receivedData: Map[K, V], ) { def hasCompletedKeys: Boolean = structure.keySet.exists(isComplete) - def isComplete(key: K): Boolean = structure + def isComplete: Boolean = expectedKeys.isEmpty + + private def isComplete(key: K): Boolean = structure .get(key) .map(_.intersect(expectedKeys)) .forall(_.forall(receivedData.contains)) @@ -80,6 +82,8 @@ final case class ReceiveHierarchicalDataMap[K, V]( ) } + def getExpectedKeys: Set[K] = expectedKeys + def getFinishedData: (Map[K, V], ReceiveHierarchicalDataMap[K, V]) = { val dataMap = if (expectedKeys.nonEmpty) { structure.keySet @@ -110,12 +114,20 @@ final case class ReceiveHierarchicalDataMap[K, V]( object ReceiveHierarchicalDataMap { - def empty[K, V]: ReceiveHierarchicalDataMap[K, V] = + def apply[K, V](expected: Set[K]): ReceiveHierarchicalDataMap[K, V] = ReceiveHierarchicalDataMap( + Set.empty, + expected, + Map.empty, Map.empty, + ) + + def empty[K, V]: ReceiveHierarchicalDataMap[K, V] = + ReceiveHierarchicalDataMap( Set.empty, Set.empty, Map.empty, + Map.empty, ) } From a0cdff61d03a8e50f74c3bb5bc5e67b8843133b8 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 7 May 2025 18:39:43 +0200 Subject: [PATCH 047/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 12 +- .../simona/service/em/EmServiceBaseCore.scala | 96 +++++--- .../ie3/simona/service/em/EmServiceCore.scala | 9 +- .../util/ReceiveHierarchicalDataMap.scala | 3 - .../ie3/simona/service/em/ExtEmBaseIT.scala | 215 ++++++++++++++++-- .../service/em/ExtEmDataServiceSpec.scala | 3 +- .../sim/setup/ExtSimSetupDataSpec.scala | 8 +- 7 files changed, 275 insertions(+), 71 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 091fa7d0ab..9687dd7553 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -18,7 +18,7 @@ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.service.em.EmCommunicationCore.DataMap -import edu.ie3.simona.service.em.EmServiceCore.EmHierarchy +import edu.ie3.simona.service.em.EmServiceCore.EmRefMaps import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.simona.util.{ReceiveDataMap, ReceiveHierarchicalDataMap} @@ -44,7 +44,7 @@ final case class EmCommunicationCore( Map.empty, override val completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, - hierarchy: EmHierarchy = EmHierarchy(), + refs: EmRefMaps = EmRefMaps(), flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, uuidToPRef: Map[UUID, ComparableQuantity[Power]] = Map.empty, toSchedule: Map[UUID, ScheduleFlexActivation] = Map.empty, @@ -64,10 +64,10 @@ final case class EmCommunicationCore( val parentEm = registrationMsg.parentEm val parentUuid = registrationMsg.parentUuid - val updatedHierarchy = hierarchy.add(uuid, ref, parentEm, parentUuid) + val updatedRefs = refs.add(uuid, ref, parentEm, parentUuid) copy( - hierarchy = updatedHierarchy, + refs = updatedRefs, uuidToFlexAdapter = uuidToFlexAdapter + (uuid -> flexAdapter), flexAdapterToUuid = flexAdapterToUuid + (flexAdapter -> uuid), flexRequestReceived = @@ -131,7 +131,7 @@ final case class EmCommunicationCore( .flexOptions() .asScala .foreach { case (agent, flexOptions) => - hierarchy.getResponseRef(agent) match { + refs.getResponse(agent) match { case Some(receiver) => flexOptions.asScala.foreach { option => receiver ! ProvideFlexOptions( @@ -199,7 +199,7 @@ final case class EmCommunicationCore( val receiverUuid = receiver match { case Right(otherRef) => - hierarchy.getUuid(otherRef) + refs.getUuid(otherRef) case Left(self: UUID) => self } 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 63e0006951..98c7726141 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -12,9 +12,9 @@ import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService +import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.TickLong -import edu.ie3.simona.util.{ReceiveDataMap, ReceiveHierarchicalDataMap} import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger @@ -30,10 +30,12 @@ final case class EmServiceBaseCore( override val lastFinishedTick: Long = PRE_INIT_TICK, override val uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = Map.empty, + flexOptions: ReceiveDataMap[UUID, ExtendedFlexOptionsResult] = + ReceiveDataMap.empty, + additionalFlexOptions: Map[UUID, ExtendedFlexOptionsResult] = Map.empty, override val completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, - flexOptions: ReceiveHierarchicalDataMap[UUID, ExtendedFlexOptionsResult] = - ReceiveHierarchicalDataMap.empty, + structure: Map[UUID, Set[UUID]] = Map.empty, disaggregatedFlex: Boolean = false, sendOptionsToExt: Boolean = false, canHandleSetPoints: Boolean = false, @@ -42,13 +44,37 @@ final case class EmServiceBaseCore( override def handleRegistration( registrationMsg: RegisterForEmDataService - ): EmServiceBaseCore = + ): EmServiceBaseCore = { + val modelUuid = registrationMsg.modelUuid + val parentUuid = registrationMsg.parentUuid + + val updatedStructure = parentUuid match { + case Some(parent) => + structure.get(parent) match { + case Some(subEm) => + val allSubEms = subEm + modelUuid + + structure ++ Map(parent -> allSubEms) + case None => + structure ++ Map(parent -> Set(modelUuid)) + } + + case None if !structure.contains(modelUuid) => + structure ++ Map(modelUuid -> Set.empty[UUID]) + + case _ => + // we already added the model as parent + // therefore, no changes are needed + structure + } + copy( - uuidToFlexAdapter = uuidToFlexAdapter ++ Map( - registrationMsg.modelUuid -> registrationMsg.flexAdapter - ), - completions = completions.addExpectedKeys(Set(registrationMsg.modelUuid)), + uuidToFlexAdapter = + uuidToFlexAdapter ++ Map(modelUuid -> registrationMsg.flexAdapter), + completions = completions.addExpectedKeys(Set(modelUuid)), + structure = updatedStructure, ) + } override def handleExtMessage(tick: Long, extMSg: EmDataMessageFromExt)( implicit log: Logger @@ -68,7 +94,7 @@ final case class EmServiceBaseCore( ( copy( - flexOptions = ReceiveHierarchicalDataMap(emEntities.toSet), + flexOptions = ReceiveDataMap(emEntities.toSet), disaggregatedFlex = disaggregated, sendOptionsToExt = true, ), @@ -90,7 +116,7 @@ final case class EmServiceBaseCore( ( copy( - flexOptions = ReceiveHierarchicalDataMap(emEntities.toSet), + flexOptions = ReceiveDataMap(emEntities.toSet), setPointOption = Some(provideEmSetPoints), ), None, @@ -115,47 +141,53 @@ final case class EmServiceBaseCore( flexResponse match { case provideFlexOptions: ProvideFlexOptions => - val updated = provideFlexOptions match { + val (updated, updatedAdditional) = provideFlexOptions match { case ProvideFlexOptions( modelUuid, MinMaxFlexOptions(ref, min, max), ) => - flexOptions.addData( + val result = new ExtendedFlexOptionsResult( + tick.toDateTime(startTime), modelUuid, - new ExtendedFlexOptionsResult( - tick.toDateTime(startTime), - modelUuid, - modelUuid, - min.toQuantity, - ref.toQuantity, - max.toQuantity, - ), + modelUuid, + min.toQuantity, + ref.toQuantity, + max.toQuantity, ) + if (flexOptions.getExpectedKeys.contains(modelUuid)) { + ( + flexOptions.addData(modelUuid, result), + additionalFlexOptions, + ) + } else { + ( + flexOptions, + additionalFlexOptions.updated(modelUuid, result), + ) + } + case _ => - flexOptions + (flexOptions, additionalFlexOptions) } if (updated.isComplete) { // we received all flex options - val expectedKeys = updated.getExpectedKeys - val receiveDataMap = updated.receivedData - val data = expectedKeys.map(key => key -> receiveDataMap(key)).toMap + val data = updated.receivedData if (disaggregatedFlex) { // we add the disaggregated flex options - val structure = updated.structure data.foreach { case (key, value) => structure(key).foreach { inferior => - value.addDisaggregated(inferior, receiveDataMap(inferior)) + value.addDisaggregated(inferior, updatedAdditional(inferior)) } } } val updatedCore = copy( - flexOptions = ReceiveHierarchicalDataMap(updated.getExpectedKeys), + flexOptions = ReceiveDataMap.empty, canHandleSetPoints = true, ) @@ -176,7 +208,15 @@ final case class EmServiceBaseCore( } } - } else (copy(flexOptions = updated), None) + } else { + ( + copy( + flexOptions = updated, + additionalFlexOptions = updatedAdditional, + ), + None, + ) + } case completion: FlexCompletion => val updated = completions.addData(completion.modelUuid, completion) 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 e5b9ca13c0..5e5d376aa8 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -18,7 +18,6 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ RegisterForEmDataService, ServiceResponseMessage, } -import edu.ie3.simona.service.em.EmServiceCore.EmHierarchy import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW @@ -120,8 +119,7 @@ trait EmServiceCore { object EmServiceCore { - final case class EmHierarchy( - structure: Map[UUID, Set[UUID]] = Map.empty, + final case class EmRefMaps( private val refToUuid: Map[ActorRef[EmAgent.Request], UUID] = Map.empty, private val uuidToRef: Map[UUID, ActorRef[EmAgent.Request]] = Map.empty, private val uuidToFlexResponse: Map[UUID, ActorRef[FlexResponse]] = @@ -135,7 +133,7 @@ object EmServiceCore { ref: ActorRef[EmAgent.Request], parentEm: Option[ActorRef[FlexResponse]] = None, parentUuid: Option[UUID] = None, - ): EmHierarchy = parentEm.zip(parentUuid) match { + ): EmRefMaps = parentEm.zip(parentUuid) match { case Some((parent, uuid)) => copy( uuidToRef = uuidToRef + (model -> ref), @@ -153,9 +151,8 @@ object EmServiceCore { def getUuid(ref: ActorRef[FlexResponse]): UUID = flexResponseToUuid(ref) - def getResponseRef(uuid: UUID): Option[ActorRef[FlexResponse]] = + def getResponse(uuid: UUID): Option[ActorRef[FlexResponse]] = uuidToFlexResponse.get(uuid) - } } diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala index 6d8dfced67..7d338210c2 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala @@ -60,9 +60,6 @@ final case class ReceiveHierarchicalDataMap[K, V]( ) } - def addExpectedKeys(keys: Set[K]): ReceiveHierarchicalDataMap[K, V] = - copy(expectedKeys = expectedKeys ++ keys) - def addSubKeysToExpectedKeys(keys: Set[K]): ReceiveHierarchicalDataMap[K, V] = copy(expectedKeys = expectedKeys ++ keys.flatMap(structure.get).flatten) diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala index 1b37f164c5..87438c3a2a 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala @@ -94,11 +94,6 @@ class ExtEmBaseIT private val emNode4Uuid = UUID.fromString("ff0b995a-86ff-4f4d-987e-e475a64f2180") - private val connection = new ExtEmDataConnection( - List(emSupUuid, emNode3Uuid, emNode4Uuid).asJava, - EmMode.SET_POINT, - ) - private val gridAgent = TestProbe[GridAgent.Request]("GridAgent") private val resultListener = TestProbe[ResultEvent]("ResultListener") private val primaryServiceProxy = @@ -122,6 +117,10 @@ class ExtEmBaseIT implicit val adapter: ActorRef[DataMessageFromExt] = spawn(ExtEmDataService.adapter(service)) + val connection = new ExtEmDataConnection( + List(emSupUuid, emNode3Uuid, emNode4Uuid).asJava, + EmMode.BASE, + ) connection.setActorRefs(adapter, extSimAdapter.ref) val emAgentSup = spawn( @@ -296,7 +295,7 @@ class ExtEmBaseIT // load loadAgentNode4 ! RegistrationFailedMessage(primaryServiceProxy.ref) - "with requesting flex options works correctly" in { + "work correctly when requesting flex options" in { /* TICK: 0 */ val weatherData0 = DataProvision( @@ -316,7 +315,7 @@ class ExtEmBaseIT // we request the em option for the superior em agent connection.sendExtMsg( - new RequestEmFlexResults(0L, List(emSupUuid).asJava) + new RequestEmFlexResults(0L, List(emSupUuid).asJava, false) ) extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) @@ -371,7 +370,7 @@ class ExtEmBaseIT // we request the em option for the superior em agent connection.sendExtMsg( - new RequestEmFlexResults(900L, List(emSupUuid).asJava) + new RequestEmFlexResults(900L, List(emSupUuid).asJava, false) ) extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) @@ -413,10 +412,10 @@ class ExtEmBaseIT connection.receiveWithType(classOf[EmCompletion]) } - "without requesting flex options works correctly" in { + "work correctly when requesting disaggregated flex options" in { /* TICK: 1800 */ - val weatherData0 = DataProvision( + val weatherData1800 = DataProvision( 1800L, weatherService.ref, WeatherData( @@ -428,16 +427,68 @@ class ExtEmBaseIT Some(2700L), ) - pvAgentNode3 ! weatherData0 - pvAgentNode4 ! weatherData0 + pvAgentNode3 ! weatherData1800 + pvAgentNode4 ! weatherData1800 - // we send a new set point - val setPoints0: Map[UUID, PValue] = + // we request the em option for the superior em agent + connection.sendExtMsg( + new RequestEmFlexResults(1800L, List(emSupUuid).asJava, true) + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(1800L) + + val receivedFlexOptions1800 = connection + .receiveWithType(classOf[FlexOptionsResponse]) + .receiverToFlexOptions() + .asScala + + receivedFlexOptions1800.size shouldBe 1 + val flexOptions1800 = receivedFlexOptions1800(emSupUuid) + flexOptions1800.getSender shouldBe emSupUuid + flexOptions1800.getReceiver shouldBe emSupUuid + flexOptions1800.getpMin should equalWithTolerance( + 0.002200000413468004.asMegaWatt + ) + flexOptions1800.getpRef should equalWithTolerance( + 0.002200000413468004.asMegaWatt + ) + flexOptions1800.getpMax should equalWithTolerance( + 0.006200000413468004.asMegaWatt + ) + + flexOptions1800.hasDisaggregated shouldBe true + val disaggregated1800 = flexOptions1800.getDisaggregated.asScala + + val node3Flex1800 = disaggregated1800(emNode3Uuid) + node3Flex1800.getpMin should equalWithTolerance( + 0.asMegaWatt + ) + node3Flex1800.getpRef should equalWithTolerance( + 0.asMegaWatt + ) + node3Flex1800.getpMax should equalWithTolerance( + 0.004.asMegaWatt + ) + + val node4Flex1800 = disaggregated1800(emNode4Uuid) + node4Flex1800.getpMin should equalWithTolerance( + 0.002200000413468004.asMegaWatt + ) + node4Flex1800.getpRef should equalWithTolerance( + 0.002200000413468004.asMegaWatt + ) + node4Flex1800.getpMax should equalWithTolerance( + 0.002200000413468004.asMegaWatt + ) + + // we return a new set point + val setPoints1800: Map[UUID, PValue] = Map(emSupUuid -> new NoSetPointValue(0.002200000413468004.asMegaWatt)) connection.sendSetPoints( 1800L, - setPoints0.asJava, + setPoints1800.asJava, Optional.of(2700L), log, ) @@ -449,7 +500,7 @@ class ExtEmBaseIT /* TICK: 2700 */ - val weatherData900 = DataProvision( + val weatherData2700 = DataProvision( 2700L, weatherService.ref, WeatherData( @@ -461,16 +512,68 @@ class ExtEmBaseIT Some(3600L), ) - pvAgentNode3 ! weatherData900 - pvAgentNode4 ! weatherData900 + pvAgentNode3 ! weatherData2700 + pvAgentNode4 ! weatherData2700 - // we send a new set point - val setPoints900: Map[UUID, PValue] = + // we request the em option for the superior em agent + connection.sendExtMsg( + new RequestEmFlexResults(2700L, List(emSupUuid).asJava, true) + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(2700L) + + val receivedFlexOptions2700 = connection + .receiveWithType(classOf[FlexOptionsResponse]) + .receiverToFlexOptions() + .asScala + + receivedFlexOptions2700.size shouldBe 1 + val flexOptions2700 = receivedFlexOptions2700(emSupUuid) + flexOptions2700.getSender shouldBe emSupUuid + flexOptions2700.getReceiver shouldBe emSupUuid + flexOptions2700.getpMin should equalWithTolerance( + 0.002200000413468004.asMegaWatt + ) + flexOptions2700.getpRef should equalWithTolerance( + 0.002200000413468004.asMegaWatt + ) + flexOptions2700.getpMax should equalWithTolerance( + 0.006200000413468004.asMegaWatt + ) + + flexOptions2700.hasDisaggregated shouldBe true + val disaggregated2700 = flexOptions2700.getDisaggregated.asScala + + val node3Flex2700 = disaggregated2700(emNode3Uuid) + node3Flex2700.getpMin should equalWithTolerance( + 0.asMegaWatt + ) + node3Flex2700.getpRef should equalWithTolerance( + 0.asMegaWatt + ) + node3Flex2700.getpMax should equalWithTolerance( + 0.004.asMegaWatt + ) + + val node4Flex2700 = disaggregated2700(emNode4Uuid) + node4Flex2700.getpMin should equalWithTolerance( + 0.002200000413468004.asMegaWatt + ) + node4Flex2700.getpRef should equalWithTolerance( + 0.002200000413468004.asMegaWatt + ) + node4Flex2700.getpMax should equalWithTolerance( + 0.002200000413468004.asMegaWatt + ) + + // we return a new set point + val setPoints2700: Map[UUID, PValue] = Map(emSupUuid -> new PValue(0.006200000413468004.asMegaWatt)) connection.sendSetPoints( 2700L, - setPoints900.asJava, + setPoints2700.asJava, Optional.of(3600L), log, ) @@ -479,9 +582,75 @@ class ExtEmBaseIT serviceActivation ! Activation(2700L) connection.receiveWithType(classOf[EmCompletion]) - } - } + "work correctly without requesting flex options" in { + /* TICK: 3600 */ + val weatherData3600 = DataProvision( + 3600L, + weatherService.ref, + WeatherData( + WattsPerSquareMeter(0), + WattsPerSquareMeter(0), + Celsius(0d), + MetersPerSecond(0d), + ), + Some(4500L), + ) + + pvAgentNode3 ! weatherData3600 + pvAgentNode4 ! weatherData3600 + + // we send a new set point + val setPoints3600: Map[UUID, PValue] = + Map(emSupUuid -> new NoSetPointValue(0.002200000413468004.asMegaWatt)) + + connection.sendSetPoints( + 3600L, + setPoints3600.asJava, + Optional.of(4500L), + log, + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(3600L) + + connection.receiveWithType(classOf[EmCompletion]) + + /* TICK: 4500 */ + + val weatherData4500 = DataProvision( + 4500L, + weatherService.ref, + WeatherData( + WattsPerSquareMeter(0), + WattsPerSquareMeter(0), + Celsius(0d), + MetersPerSecond(0d), + ), + Some(5400L), + ) + + pvAgentNode3 ! weatherData4500 + pvAgentNode4 ! weatherData4500 + + // we send a new set point + val setPoints4500: Map[UUID, PValue] = + Map(emSupUuid -> new PValue(0.006200000413468004.asMegaWatt)) + + connection.sendSetPoints( + 4500L, + setPoints4500.asJava, + Optional.of(5400L), + log, + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + serviceActivation ! Activation(4500L) + + connection.receiveWithType(classOf[EmCompletion]) + + } + } } 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 90ec4d5af9..480a3eb6be 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -54,7 +54,6 @@ import org.scalatest.wordspec.AnyWordSpecLike import squants.energy.Kilowatts import java.time.ZonedDateTime -import java._ import java.util.UUID import scala.concurrent.duration.DurationInt import scala.jdk.CollectionConverters._ @@ -264,6 +263,7 @@ class ExtEmDataServiceSpec new RequestEmFlexResults( INIT_SIM_TICK, List.empty[UUID].asJava, + false, ) ) @@ -283,6 +283,7 @@ class ExtEmDataServiceSpec new RequestEmFlexResults( 0, List(emAgentSupUUID).asJava, + false, ) ) diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala index 1abe835535..cbcc57fc08 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala @@ -78,7 +78,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val evRef = TestProbe[ServiceMessage]("ev_service").ref val emConnection = - new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) + new ExtEmDataConnection(emptyListInput, EmMode.BASE) val emRef = TestProbe[ServiceMessage]("em_service").ref val cases = Table( @@ -141,7 +141,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val evRef = TestProbe[ServiceMessage]("ev_service").ref val emConnection = - new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) + new ExtEmDataConnection(emptyListInput, EmMode.BASE) val emRef = TestProbe[ServiceMessage]("em_service").ref val resultConnection = @@ -174,7 +174,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { "return emDataService correctly" in { val emConnection = - new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) + new ExtEmDataConnection(emptyListInput, EmMode.BASE) val emRef = TestProbe("em_service").ref val cases = Table( @@ -215,7 +215,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { "return emDataService correctly" in { val emConnection = - new ExtEmDataConnection(emptyListInput, EmMode.SET_POINT) + new ExtEmDataConnection(emptyListInput, EmMode.BASE) val emRef = TestProbe[ServiceMessage]("em_service").ref val cases = Table( From 05afd8fbf2446a113e0f07a94dcce00997332fc8 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 12 May 2025 09:54:52 +0200 Subject: [PATCH 048/125] Small adaptions, due to scala3 update. --- .../scala/edu/ie3/simona/config/SimonaConfig.scala | 7 ++++--- .../edu/ie3/simona/service/em/EmServiceCore.scala | 2 +- .../simona/service/results/ExtResultProvider.scala | 4 ++-- .../edu/ie3/simona/sim/setup/ExtSimSetupData.scala | 14 +++----------- .../service/primary/PrimaryServiceProxySpec.scala | 1 - 5 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index 7f0f379bd6..a4f4319079 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -9,10 +9,11 @@ package edu.ie3.simona.config import com.typesafe.config.{Config, ConfigValue} import edu.ie3.simona.config.SimonaConfig.writer import edu.ie3.simona.exceptions.CriticalFailureException -import pureconfig.error._ -import pureconfig.generic._ +import edu.ie3.util.TimeUtil +import pureconfig.* +import pureconfig.error.* +import pureconfig.generic.* import pureconfig.generic.semiauto.deriveConvert -import pureconfig._ import java.time.ZonedDateTime import scala.concurrent.duration.{DurationInt, FiniteDuration} 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 5e5d376aa8..bb4c94a6ef 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -19,7 +19,7 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ ServiceResponseMessage, } import edu.ie3.simona.util.ReceiveDataMap -import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import edu.ie3.util.quantities.QuantityUtils.asMegaWatt import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import edu.ie3.util.scala.quantities.QuantityConversionUtils.PowerConversionSimona import org.apache.pekko.actor.typed.ActorRef diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala index 378cd2646f..90b48da1c6 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala @@ -57,8 +57,8 @@ object ExtResultProvider extResultSchedule: ExtResultSchedule, extResultsMessage: Option[ResultDataMessageFromExt] = None, receiveDataMap: ReceiveDataMap[UUID, ResultEntity] = ReceiveDataMap.empty, - resultStorage: Map[UUID, ResultEntity] = Map.empty, - implicit val startTime: ZonedDateTime, + resultStorage: Map[UUID, ResultEntity] = Map.empty, + startTime: ZonedDateTime, ) extends ServiceBaseStateData final case class InitExtResultData( diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index a91aab3c9a..adc0be2d4e 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -12,14 +12,9 @@ import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.api.data.results.ExtResultDataConnection -import edu.ie3.simona.ontology.messages.services.{ - EmMessage, - EvMessage, - ServiceMessage, -} -import edu.ie3.simona.sim.setup.ExtSimSetupData.Input +import edu.ie3.simona.ontology.messages.services.{EmMessage, EvMessage, ServiceMessage} import org.apache.pekko.actor.typed.ActorRef -import org.apache.pekko.actor.{ActorRef => ClassicRef} +import org.apache.pekko.actor.ActorRef as ClassicRef /** Case class that holds information regarding the external data connections as * well as the actor references of the created services. @@ -38,7 +33,7 @@ final case class ExtSimSetupData( extPrimaryDataServices: Seq[ (ExtPrimaryDataConnection, ActorRef[ServiceMessage]) ], - extDataServices: Seq[Input[_ <: DataMessageFromExt]], + extDataServices: Seq[(ExtInputDataConnection[_], ActorRef[_ >: ServiceMessage])], extResultListeners: Seq[(ExtResultDataConnection, ActorRef[ServiceMessage])], ) { @@ -105,9 +100,6 @@ final case class ExtSimSetupData( object ExtSimSetupData { - type Input[T <: DataMessageFromExt] = - (ExtInputDataConnection[T], ActorRef[_ >: ServiceMessage]) - /** Returns an empty [[ExtSimSetupData]]. */ def apply: ExtSimSetupData = ExtSimSetupData( diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala index af52be08ac..03e9e5355d 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala @@ -285,7 +285,6 @@ class PrimaryServiceProxySpec _, _, _, - _, extSubscribersToService, ) ) => From fc2198d402f5e5556b53ec3a2997d68e56b5ea58 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 12 May 2025 14:08:13 +0200 Subject: [PATCH 049/125] Added parsing of flex message delay. --- .../grid/congestion/CongestedComponents.scala | 4 +-- .../data/CongestionManagementData.scala | 2 +- .../service/em/EmCommunicationCore.scala | 20 +++--------- .../simona/service/em/EmServiceBaseCore.scala | 2 +- .../ie3/simona/service/em/EmServiceCore.scala | 32 +++++++------------ 5 files changed, 21 insertions(+), 39 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/congestion/CongestedComponents.scala b/src/main/scala/edu/ie3/simona/agent/grid/congestion/CongestedComponents.scala index 731aef987e..f67de33b15 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/congestion/CongestedComponents.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/congestion/CongestedComponents.scala @@ -108,7 +108,7 @@ object CongestedComponents { val vMag = vNom * nodeRes(transformer.lvNodeUuid).getvMag.toSquants.toEach - val power = sqrt(3.0) * res.getiBMag.toSquants * vMag + val power = res.getiBMag.toSquants * sqrt(3.0) * vMag transformer -> Kilovoltamperes(power.toKilowatts) } .filter { case (transformer, result) => result > transformer.sRated } @@ -132,7 +132,7 @@ object CongestedComponents { val vMag = vNom * nodeRes(nodeUuid).getvMag.toSquants.toEach - val power = sqrt(3.0) * res.currentMagnitude * vMag + val power = res.currentMagnitude * sqrt(3.0) * vMag transformer -> Kilovoltamperes(power.toKilowatts) } .filter { case (transformer, result) => result > transformer.sRated } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala b/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala index 3ba7b2825e..f800e54936 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala @@ -15,7 +15,7 @@ import edu.ie3.simona.agent.grid.GridAgentData.{ } import edu.ie3.simona.agent.grid.congestion.{CongestedComponents, Congestions} import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent -import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import edu.ie3.util.quantities.QuantityUtils.asPercent import org.apache.pekko.actor.typed.ActorRef import java.time.ZonedDateTime diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 9687dd7553..7141a33f83 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -7,14 +7,9 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue -import edu.ie3.simona.api.data.em.model.{ - EmSetPointResult, - ExtendedFlexOptionsResult, - FlexRequestResult, - NoSetPointValue, -} -import edu.ie3.simona.api.data.em.ontology._ -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ +import edu.ie3.simona.api.data.em.model.{EmSetPoint, EmSetPointResult, ExtendedFlexOptionsResult, FlexRequestResult} +import edu.ie3.simona.api.data.em.ontology.* +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.service.em.EmCommunicationCore.DataMap @@ -31,12 +26,7 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.Power -import scala.jdk.CollectionConverters.{ - IterableHasAsScala, - MapHasAsJava, - MapHasAsScala, - SetHasAsJava, -} +import scala.jdk.CollectionConverters.{IterableHasAsScala, MapHasAsJava, MapHasAsScala, SetHasAsJava} final case class EmCommunicationCore( override val lastFinishedTick: Long = PRE_INIT_TICK, @@ -333,7 +323,7 @@ final case class EmCommunicationCore( val (time, power) = issueFlexControl match { case IssueNoControl(tick) => - (tick.toDateTime, new NoSetPointValue(uuidToPRef(uuid))) + (tick.toDateTime, new PValue(uuidToPRef(uuid))) case IssuePowerControl(tick, setPower) => (tick.toDateTime, new PValue(setPower.toQuantity)) 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 98c7726141..28138df4be 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -108,7 +108,7 @@ final case class EmServiceBaseCore( (this, None) } else { val tick = provideEmSetPoints.tick - val emEntities = provideEmSetPoints.emData.keySet.asScala + val emEntities = provideEmSetPoints.emSetPoints.keySet.asScala emEntities.map(uuidToFlexAdapter).foreach { ref => ref ! FlexActivation(tick) 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 bb4c94a6ef..8d4692cc42 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -7,17 +7,10 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.NoSetPointValue -import edu.ie3.simona.api.data.em.ontology._ -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - WrappedFlexRequest, - WrappedFlexResponse, -} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - RegisterForEmDataService, - ServiceResponseMessage, -} +import edu.ie3.simona.api.data.em.ontology.* +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* +import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{RegisterForEmDataService, ServiceResponseMessage} import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.util.quantities.QuantityUtils.asMegaWatt import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW @@ -29,7 +22,7 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID -import javax.measure.quantity.{Power => PsdmPower} +import javax.measure.quantity.Power as PsdmPower import scala.jdk.CollectionConverters.MapHasAsScala import scala.jdk.OptionConverters.RichOptional @@ -77,19 +70,18 @@ trait EmServiceCore { log.info(s"Handling of: $provideEmSetPoints") provideEmSetPoints - .emData() + .emSetPoints .asScala .foreach { case (agent, setPoint) => uuidToFlexAdapter.get(agent) match { case Some(receiver) => - setPoint match { - case _: NoSetPointValue => - receiver ! IssueNoControl(tick) - case _ => - val power = - setPoint.getP.toScala.map(_.toSquants).getOrElse(zeroKW) - receiver ! IssuePowerControl(tick, power) + (setPoint.p.toScala, setPoint.q.toScala) match { + case (Some(activePower), _) => + receiver ! IssuePowerControl(tick, activePower.toSquants) + + case (None, _) => + receiver ! IssueNoControl(tick) } case None => From 9dc67a69332927fcd2c9d2cac83977af78034ba1 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 20 May 2025 16:03:29 +0200 Subject: [PATCH 050/125] Saving congestion result fixes. --- .../grid/congestion/CongestedComponents.scala | 4 +- .../agent/grid/congestion/DCMAlgorithm.scala | 2 +- .../data/CongestionManagementData.scala | 9 +- .../event/listener/ResultEventListener.scala | 2 +- .../io/result/FixedResultEntityProcessor.java | 139 ++++++++++++++++++ .../service/results/ExtResultProvider.scala | 2 +- .../simona/sim/setup/ExtSimSetupData.scala | 10 +- 7 files changed, 158 insertions(+), 10 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/io/result/FixedResultEntityProcessor.java diff --git a/src/main/scala/edu/ie3/simona/agent/grid/congestion/CongestedComponents.scala b/src/main/scala/edu/ie3/simona/agent/grid/congestion/CongestedComponents.scala index 260d0c19a9..ddf71c6753 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/congestion/CongestedComponents.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/congestion/CongestedComponents.scala @@ -108,7 +108,7 @@ object CongestedComponents { val vMag = vNom * nodeRes(transformer.lvNodeUuid).getvMag.toSquants.toEach - val power = res.getiBMag.toSquants * sqrt(3.0) * vMag + val power = sqrt(3.0) * res.getiBMag.toSquants * vMag transformer -> Kilovoltamperes(power.toKilowatts) } .filter { case (transformer, result) => result > transformer.sRated } @@ -132,7 +132,7 @@ object CongestedComponents { val vMag = vNom * nodeRes(nodeUuid).getvMag.toSquants.toEach - val power = res.currentMagnitude * sqrt(3.0) * vMag + val power = sqrt(3.0) * res.currentMagnitude * vMag transformer -> Kilovoltamperes(power.toKilowatts) } .filter { case (transformer, result) => result > transformer.sRated } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/congestion/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/congestion/DCMAlgorithm.scala index dff5b8fc59..0aa8c37c8c 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/congestion/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/congestion/DCMAlgorithm.scala @@ -94,7 +94,7 @@ trait DCMAlgorithm extends CongestionDetection { // return to idle GridAgent.gotoIdle( stateData.gridAgentBaseData, - stateData.currentTick, + stateData.currentTick + constantData.resolution, Some(powerFlowResults), ctx, ) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala b/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala index 2434204ce0..7f6491a4e9 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala @@ -17,6 +17,7 @@ import edu.ie3.simona.agent.grid.congestion.{CongestedComponents, Congestions} import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.util.quantities.QuantityUtils.asPercent import org.apache.pekko.actor.typed.ActorRef +import squants.Each import java.time.ZonedDateTime import java.util.UUID @@ -68,7 +69,7 @@ final case class CongestionManagementData( } val lines = congestedComponents.lines.map { case (lineModel, current) => - val utilisation = (current / lineModel.iNom).asPercent + val utilisation = Each(current / lineModel.iNom).toPercent.asPercent new CongestionResult( startTime.plusSeconds(currentTick), @@ -83,7 +84,8 @@ final case class CongestionManagementData( val transformer2W = congestedComponents.transformer2Ws.map { case (transformerModel, power) => - val utilisation = (power / transformerModel.sRated).asPercent + val utilisation = + Each(power / transformerModel.sRated).toPercent.asPercent new CongestionResult( startTime.plusSeconds(currentTick), @@ -98,7 +100,8 @@ final case class CongestionManagementData( val transformer3W = congestedComponents.transformer3Ws.map { case (transformerModel, power) => - val utilisation = (power / transformerModel.sRated).asPercent + val utilisation = + Each(power / transformerModel.sRated).toPercent.asPercent new CongestionResult( startTime.plusSeconds(currentTick), diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index 0a3a7efcd8..3739347907 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -107,7 +107,7 @@ object ResultEventListener extends Transformer3wResultSupport { resultClass, ResultEntityCsvSink( finalFileName, - new ResultEntityProcessor(resultClass), + new FixedResultEntityProcessor(resultClass), enableCompression, ), ) diff --git a/src/main/scala/edu/ie3/simona/io/result/FixedResultEntityProcessor.java b/src/main/scala/edu/ie3/simona/io/result/FixedResultEntityProcessor.java new file mode 100644 index 0000000000..2de0ec3f16 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/io/result/FixedResultEntityProcessor.java @@ -0,0 +1,139 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.io.result; + +import edu.ie3.datamodel.exceptions.EntityProcessorException; +import edu.ie3.datamodel.io.processor.result.ResultEntityProcessor; +import edu.ie3.datamodel.models.OperationTime; +import edu.ie3.datamodel.models.UniqueEntity; +import edu.ie3.datamodel.models.input.OperatorInput; +import edu.ie3.datamodel.models.input.system.characteristic.CharacteristicInput; +import edu.ie3.datamodel.models.profile.LoadProfile; +import edu.ie3.datamodel.models.result.CongestionResult; +import edu.ie3.datamodel.models.result.ResultEntity; +import edu.ie3.datamodel.models.voltagelevels.VoltageLevel; +import edu.ie3.datamodel.utils.Try; +import java.lang.reflect.Method; +import java.time.ZonedDateTime; +import java.util.Optional; +import javax.measure.Quantity; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.io.geojson.GeoJsonWriter; + +public class FixedResultEntityProcessor extends ResultEntityProcessor { + public FixedResultEntityProcessor(Class registeredClass) + throws EntityProcessorException { + super(registeredClass); + } + + private static final GeoJsonWriter geoJsonWriter = new GeoJsonWriter(); + + protected String processMethodResult(Object methodReturnObject, Method method, String fieldName) + throws EntityProcessorException { + + StringBuilder resultStringBuilder = new StringBuilder(); + + switch (method.getReturnType().getSimpleName()) { + // primitives (Boolean, Character, Byte, Short, Integer, Long, Float, Double, String, + case "UUID", + "boolean", + "int", + "double", + "String", + "DayOfWeek", + "Season", + "ChargingPointType", + "EvcsLocationType" -> + resultStringBuilder.append(methodReturnObject.toString()); + case "Quantity", "ComparableQuantity" -> + resultStringBuilder.append(handleQuantity((Quantity) methodReturnObject, fieldName)); + case "Optional" -> + // only quantity optionals are expected here! + // if optional and present, unpack value and call this method again, if not present return + // an empty string as by convention null == missing value == "" when persisting data + resultStringBuilder.append( + ((Optional) methodReturnObject) + .map( + o -> { + if (o instanceof Quantity quantity) { + return Try.of( + () -> handleQuantity(quantity, fieldName), + EntityProcessorException.class); + } else if (o instanceof UniqueEntity entity) { + return Try.of(entity::getUuid, EntityProcessorException.class); + } else { + return Try.Failure.of( + new EntityProcessorException( + "Handling of " + + o.getClass().getSimpleName() + + ".class instance wrapped into Optional is currently not supported by entity processors!")); + } + }) + .orElse(Try.Success.of("")) // (in case of empty optional) + .getOrThrow()); + case "ZonedDateTime" -> + resultStringBuilder.append(processZonedDateTime((ZonedDateTime) methodReturnObject)); + case "OperationTime" -> + resultStringBuilder.append( + processOperationTime((OperationTime) methodReturnObject, fieldName)); + case "VoltageLevel" -> + resultStringBuilder.append( + processVoltageLevel((VoltageLevel) methodReturnObject, fieldName)); + case "Point", "LineString" -> + resultStringBuilder.append(geoJsonWriter.write((Geometry) methodReturnObject)); + case "LoadProfile", "BdewStandardLoadProfile", "RandomLoadProfile" -> + resultStringBuilder.append(((LoadProfile) methodReturnObject).getKey()); + case "AssetTypeInput", + "BmTypeInput", + "ChpTypeInput", + "EvTypeInput", + "HpTypeInput", + "LineTypeInput", + "LineInput", + "NodeInput", + "StorageTypeInput", + "SystemParticipantInput", + "ThermalBusInput", + "ThermalStorageInput", + "TimeSeries", + "Transformer2WTypeInput", + "Transformer3WTypeInput", + "WecTypeInput", + "EmInput" -> + resultStringBuilder.append(((UniqueEntity) methodReturnObject).getUuid()); + case "OperatorInput" -> + resultStringBuilder.append( + ((OperatorInput) methodReturnObject).getId().equalsIgnoreCase("NO_OPERATOR_ASSIGNED") + ? "" + : ((OperatorInput) methodReturnObject).getUuid()); + case "EvCharacteristicInput", + "OlmCharacteristicInput", + "WecCharacteristicInput", + "CosPhiFixed", + "CosPhiP", + "QV", + "ReactivePowerCharacteristic", + "CharacteristicInput" -> + resultStringBuilder.append(((CharacteristicInput) methodReturnObject).serialize()); + case "InputModelType" -> + resultStringBuilder.append(((CongestionResult.InputModelType) methodReturnObject).type); + default -> + throw new EntityProcessorException( + "Unable to process value for attribute/field '" + + fieldName + + "' and method return type '" + + method.getReturnType().getSimpleName() + + "' for method with name '" + + method.getName() + + "' in in entity model " + + getRegisteredClass().getSimpleName() + + ".class."); + } + + return resultStringBuilder.toString(); + } +} diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala index 90b48da1c6..25e496cc89 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala @@ -57,7 +57,7 @@ object ExtResultProvider extResultSchedule: ExtResultSchedule, extResultsMessage: Option[ResultDataMessageFromExt] = None, receiveDataMap: ReceiveDataMap[UUID, ResultEntity] = ReceiveDataMap.empty, - resultStorage: Map[UUID, ResultEntity] = Map.empty, + resultStorage: Map[UUID, ResultEntity] = Map.empty, startTime: ZonedDateTime, ) extends ServiceBaseStateData diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index adc0be2d4e..4333171129 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -12,7 +12,11 @@ import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.api.data.results.ExtResultDataConnection -import edu.ie3.simona.ontology.messages.services.{EmMessage, EvMessage, ServiceMessage} +import edu.ie3.simona.ontology.messages.services.{ + EmMessage, + EvMessage, + ServiceMessage, +} import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.ActorRef as ClassicRef @@ -33,7 +37,9 @@ final case class ExtSimSetupData( extPrimaryDataServices: Seq[ (ExtPrimaryDataConnection, ActorRef[ServiceMessage]) ], - extDataServices: Seq[(ExtInputDataConnection[_], ActorRef[_ >: ServiceMessage])], + extDataServices: Seq[ + (ExtInputDataConnection[_], ActorRef[_ >: ServiceMessage]) + ], extResultListeners: Seq[(ExtResultDataConnection, ActorRef[ServiceMessage])], ) { From 1ccbd0351525dcc0c9adb47cef05b87d067162cb Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 21 May 2025 11:50:32 +0200 Subject: [PATCH 051/125] Saving changes. --- .../simona/agent/grid/GridAgentBuilder.scala | 2 + .../service/em/EmCommunicationCore.scala | 8 +- .../ie3/simona/service/em/EmServiceCore.scala | 13 ++- .../agent/em/EmAgentWithServiceSpec.scala | 2 +- .../congestion/CongestionTestBaseData.scala | 1 + .../ie3/simona/service/em/ExtEmBaseIT.scala | 71 ++++----------- .../service/em/ExtEmCommunicationIT.scala | 86 ++++++------------- .../service/em/ExtEmDataServiceSpec.scala | 48 +++-------- .../input/EmCommunicationTestData.scala | 2 +- 9 files changed, 80 insertions(+), 153 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala index 8110236157..678d91804d 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala @@ -292,6 +292,8 @@ class GridAgentBuilder( // might need to be built at the next recursion level. val controllingEms = controlledEmInputs.toMap.flatMap { case (_, emInput) => + log.warn(s"Controlling em: ${emInput.getControllingEm}") + emInput.getControllingEm.toScala.map(em => em.getUuid -> em) } diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 7141a33f83..4e7c596136 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -7,7 +7,7 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue -import edu.ie3.simona.api.data.em.model.{EmSetPoint, EmSetPointResult, ExtendedFlexOptionsResult, FlexRequestResult} +import edu.ie3.simona.api.data.em.model.{EmSetPointResult, ExtendedFlexOptionsResult, FlexRequestResult} import edu.ie3.simona.api.data.em.ontology.* import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions @@ -56,6 +56,8 @@ final case class EmCommunicationCore( val updatedRefs = refs.add(uuid, ref, parentEm, parentUuid) + println(s"$uuid") + copy( refs = updatedRefs, uuidToFlexAdapter = uuidToFlexAdapter + (uuid -> flexAdapter), @@ -99,9 +101,9 @@ final case class EmCommunicationCore( .map { case (agent, _) => agent } .toSet - val refs = emEntities.map(uuidToFlexAdapter) + val agents = emEntities.map(uuidToFlexAdapter) - refs.foreach(_ ! FlexActivation(tick)) + agents.foreach(_ ! FlexActivation(tick)) ( copy( 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 8d4692cc42..d9a494d71e 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.service.em +import edu.ie3.datamodel.models.value.{PValue, SValue} import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.em.ontology.* import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* @@ -13,7 +14,6 @@ import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, import edu.ie3.simona.ontology.messages.services.ServiceMessage.{RegisterForEmDataService, ServiceResponseMessage} import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.util.quantities.QuantityUtils.asMegaWatt -import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW import edu.ie3.util.scala.quantities.QuantityConversionUtils.PowerConversionSimona import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger @@ -76,7 +76,16 @@ trait EmServiceCore { uuidToFlexAdapter.get(agent) match { case Some(receiver) => - (setPoint.p.toScala, setPoint.q.toScala) match { + 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) diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala index 956c0fe1fb..9c719e6b65 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala @@ -29,7 +29,7 @@ import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.util.TimeUtil import edu.ie3.util.quantities.QuantityMatchers.equalWithTolerance -import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import edu.ie3.util.quantities.QuantityUtils.{asMegaWatt, asMegaVar} import edu.ie3.util.scala.quantities.{Kilovars, ReactivePower} import org.apache.pekko.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, diff --git a/src/test/scala/edu/ie3/simona/agent/grid/congestion/CongestionTestBaseData.scala b/src/test/scala/edu/ie3/simona/agent/grid/congestion/CongestionTestBaseData.scala index a5306e9453..6b46491e5e 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/congestion/CongestionTestBaseData.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/congestion/CongestionTestBaseData.scala @@ -74,6 +74,7 @@ trait CongestionTestBaseData primaryServiceProxy = primaryService.ref, weather = weatherService.ref, loadProfiles = loadProfileService.ref, + emDataService = None, evDataService = None, ) diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala index 87438c3a2a..bf7b0fc818 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala @@ -9,45 +9,21 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.grid.GridAgent -import edu.ie3.simona.agent.participant.ParticipantAgent.{ - DataProvision, - RegistrationFailedMessage, - RegistrationSuccessfulMessage, -} +import edu.ie3.simona.agent.participant.ParticipantAgent.{DataProvision, RegistrationFailedMessage, RegistrationSuccessfulMessage} import edu.ie3.simona.agent.participant.ParticipantAgentInit import edu.ie3.simona.agent.participant.ParticipantAgentInit.ParticipantRefs -import edu.ie3.simona.api.data.em.model.NoSetPointValue -import edu.ie3.simona.api.data.em.ontology.{ - EmCompletion, - FlexOptionsResponse, - RequestEmFlexResults, -} +import edu.ie3.simona.api.data.em.model.EmSetPoint +import edu.ie3.simona.api.data.em.ontology.{EmCompletion, FlexOptionsResponse, RequestEmFlexResults} import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} -import edu.ie3.simona.api.data.ontology.{ - DataMessageFromExt, - ScheduleDataServiceMessage, -} +import edu.ie3.simona.api.data.ontology.{DataMessageFromExt, ScheduleDataServiceMessage} import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt -import edu.ie3.simona.config.RuntimeConfig.{ - LoadRuntimeConfig, - PvRuntimeConfig, - StorageRuntimeConfig, -} +import edu.ie3.simona.config.RuntimeConfig.{LoadRuntimeConfig, PvRuntimeConfig, StorageRuntimeConfig} import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.model.InputModelContainer.SimpleInputContainer -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - Create, - PrimaryServiceRegistrationMessage, -} -import edu.ie3.simona.ontology.messages.services.WeatherMessage.{ - RegisterForWeatherMessage, - WeatherData, -} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{Create, PrimaryServiceRegistrationMessage} +import edu.ie3.simona.ontology.messages.services.WeatherMessage.{RegisterForWeatherMessage, WeatherData} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceType @@ -56,12 +32,9 @@ import edu.ie3.simona.test.common.TestSpawnerTyped import edu.ie3.simona.test.common.input.EmCommunicationTestData import edu.ie3.simona.test.matchers.QuantityMatchers import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} -import edu.ie3.util.quantities.QuantityUtils._ +import edu.ie3.util.quantities.QuantityUtils.* import edu.ie3.util.scala.quantities.WattsPerSquareMeter -import org.apache.pekko.actor.testkit.typed.scaladsl.{ - ScalaTestWithActorTestKit, - TestProbe, -} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} import org.apache.pekko.actor.typed.ActorRef import org.scalatest.wordspec.AnyWordSpecLike import org.slf4j.{Logger, LoggerFactory} @@ -70,11 +43,7 @@ import squants.thermal.Celsius import java.util.{Optional, UUID} import scala.concurrent.duration.{DurationInt, FiniteDuration} -import scala.jdk.CollectionConverters.{ - MapHasAsJava, - MapHasAsScala, - SeqHasAsJava, -} +import scala.jdk.CollectionConverters.{MapHasAsJava, MapHasAsScala, SeqHasAsJava} class ExtEmBaseIT extends ScalaTestWithActorTestKit @@ -341,8 +310,7 @@ class ExtEmBaseIT ) // we return a new set point - val setPoints0: Map[UUID, PValue] = - Map(emSupUuid -> new NoSetPointValue(0.002200000413468004.asMegaWatt)) + val setPoints0 = Map(emSupUuid -> new EmSetPoint(emSupUuid)) connection.sendSetPoints(0L, setPoints0.asJava, Optional.of(900L), log) @@ -396,8 +364,7 @@ class ExtEmBaseIT ) // we return a new set point - val setPoints900: Map[UUID, PValue] = - Map(emSupUuid -> new PValue(0.006200000413468004.asMegaWatt)) + val setPoints900 = Map(emSupUuid -> new EmSetPoint(emSupUuid, 0.006200000413468004.asMegaWatt)) connection.sendSetPoints( 900L, @@ -483,8 +450,7 @@ class ExtEmBaseIT ) // we return a new set point - val setPoints1800: Map[UUID, PValue] = - Map(emSupUuid -> new NoSetPointValue(0.002200000413468004.asMegaWatt)) + val setPoints1800 = Map(emSupUuid -> new EmSetPoint(emSupUuid)) connection.sendSetPoints( 1800L, @@ -568,8 +534,7 @@ class ExtEmBaseIT ) // we return a new set point - val setPoints2700: Map[UUID, PValue] = - Map(emSupUuid -> new PValue(0.006200000413468004.asMegaWatt)) + val setPoints2700 = Map(emSupUuid -> new EmSetPoint(emSupUuid, 0.006200000413468004.asMegaWatt)) connection.sendSetPoints( 2700L, @@ -603,8 +568,7 @@ class ExtEmBaseIT pvAgentNode4 ! weatherData3600 // we send a new set point - val setPoints3600: Map[UUID, PValue] = - Map(emSupUuid -> new NoSetPointValue(0.002200000413468004.asMegaWatt)) + val setPoints3600 = Map(emSupUuid -> new EmSetPoint(emSupUuid, 0.002200000413468004.asMegaWatt)) connection.sendSetPoints( 3600L, @@ -636,8 +600,7 @@ class ExtEmBaseIT pvAgentNode4 ! weatherData4500 // we send a new set point - val setPoints4500: Map[UUID, PValue] = - Map(emSupUuid -> new PValue(0.006200000413468004.asMegaWatt)) + val setPoints4500 = Map(emSupUuid -> new EmSetPoint(emSupUuid, 0.006200000413468004.asMegaWatt)) connection.sendSetPoints( 4500L, diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index fec446d023..d2edcb61a8 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -6,53 +6,23 @@ package edu.ie3.simona.service.em -import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.grid.GridAgent -import edu.ie3.simona.agent.participant.ParticipantAgent.{ - DataProvision, - RegistrationFailedMessage, - RegistrationSuccessfulMessage, -} +import edu.ie3.simona.agent.participant.ParticipantAgent.{DataProvision, RegistrationFailedMessage, RegistrationSuccessfulMessage} import edu.ie3.simona.agent.participant.ParticipantAgentInit.ParticipantRefs import edu.ie3.simona.agent.participant.{ParticipantAgent, ParticipantAgentInit} -import edu.ie3.simona.api.data.em.model.{ - FlexOptionRequest, - FlexOptions, - FlexRequestResult, -} -import edu.ie3.simona.api.data.em.ontology.{ - EmCompletion, - EmSetPointDataResponse, - FlexOptionsResponse, - FlexRequestResponse, -} +import edu.ie3.simona.api.data.em.model.{EmSetPoint, FlexOptionRequest, FlexOptions, FlexRequestResult} +import edu.ie3.simona.api.data.em.ontology.{EmCompletion, EmSetPointDataResponse, FlexOptionsResponse, FlexRequestResponse} import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} -import edu.ie3.simona.api.data.ontology.{ - DataMessageFromExt, - ScheduleDataServiceMessage, -} +import edu.ie3.simona.api.data.ontology.{DataMessageFromExt, ScheduleDataServiceMessage} import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt -import edu.ie3.simona.config.RuntimeConfig.{ - LoadRuntimeConfig, - PvRuntimeConfig, - StorageRuntimeConfig, -} +import edu.ie3.simona.config.RuntimeConfig.{LoadRuntimeConfig, PvRuntimeConfig, StorageRuntimeConfig} import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.model.InputModelContainer.SimpleInputContainer -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - Create, - PrimaryServiceRegistrationMessage, -} -import edu.ie3.simona.ontology.messages.services.WeatherMessage.{ - RegisterForWeatherMessage, - WeatherData, -} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{Create, PrimaryServiceRegistrationMessage} +import edu.ie3.simona.ontology.messages.services.WeatherMessage.{RegisterForWeatherMessage, WeatherData} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceType @@ -61,12 +31,9 @@ import edu.ie3.simona.test.common.TestSpawnerTyped import edu.ie3.simona.test.common.input.EmCommunicationTestData import edu.ie3.simona.test.matchers.QuantityMatchers import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} -import edu.ie3.util.quantities.QuantityUtils._ +import edu.ie3.util.quantities.QuantityUtils.* import edu.ie3.util.scala.quantities.WattsPerSquareMeter -import org.apache.pekko.actor.testkit.typed.scaladsl.{ - ScalaTestWithActorTestKit, - TestProbe, -} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} import org.apache.pekko.actor.typed.ActorRef import org.scalatest.OptionValues.convertOptionToValuable import org.scalatest.wordspec.AnyWordSpecLike @@ -78,11 +45,7 @@ import tech.units.indriya.ComparableQuantity import java.util.{Optional, UUID} import javax.measure.quantity.Power import scala.concurrent.duration.{DurationInt, FiniteDuration} -import scala.jdk.CollectionConverters.{ - MapHasAsJava, - MapHasAsScala, - SeqHasAsJava, -} +import scala.jdk.CollectionConverters.{MapHasAsJava, MapHasAsScala, SeqHasAsJava} import scala.jdk.OptionConverters.RichOptional class ExtEmCommunicationIT @@ -127,8 +90,7 @@ class ExtEmCommunicationIT "An ExtEmDataService in communication mode" should { val service = spawn(ExtEmDataService(scheduler.ref)) val serviceRef = service.ref - implicit val adapter: ActorRef[DataMessageFromExt] = - spawn(ExtEmDataService.adapter(service)) + given adapter: ActorRef[DataMessageFromExt] = spawn(ExtEmDataService.adapter(service)) connection.setActorRefs(adapter, extSimAdapter.ref) "with participant agents work correctly" in { @@ -243,7 +205,7 @@ class ExtEmCommunicationIT val activationMsg = scheduler.expectMessageType[ScheduleActivation] activationMsg.tick shouldBe INIT_SIM_TICK activationMsg.unlockKey shouldBe Some(key) - implicit val serviceActivation: ActorRef[Activation] = activationMsg.actor + given serviceActivation: ActorRef[Activation] = activationMsg.actor // we expect a completion for the participant locks scheduler.expectMessage(Completion(lockActivation)) @@ -308,7 +270,7 @@ class ExtEmCommunicationIT // load loadAgentNode4 ! RegistrationFailedMessage(primaryServiceProxy.ref) - implicit val pvAgents: Seq[ActorRef[ParticipantAgent.Request]] = + given pvAgents: Seq[ActorRef[ParticipantAgent.Request]] = Seq(pvAgentNode3, pvAgentNode4) /* TICK: 0 */ @@ -331,6 +293,7 @@ class ExtEmCommunicationIT 0.002200000413468004.asMegaWatt, 0.002200000413468004.asMegaWatt, 0.006200000413468004.asMegaWatt, + Optional.empty, ), emNode3Uuid -> new FlexOptions( emSupUuid, @@ -338,6 +301,7 @@ class ExtEmCommunicationIT 0.asMegaWatt, 0.asMegaWatt, 0.004.asMegaWatt, + Optional.empty, ), emNode4Uuid -> new FlexOptions( emSupUuid, @@ -345,6 +309,7 @@ class ExtEmCommunicationIT 0.002200000413468004.asMegaWatt, 0.002200000413468004.asMegaWatt, 0.002200000413468004.asMegaWatt, + Optional.empty, ), ), Map( @@ -374,6 +339,7 @@ class ExtEmCommunicationIT 0.002200000413468004.asMegaWatt, 0.002200000413468004.asMegaWatt, 0.006200000413468004.asMegaWatt, + Optional.empty, ), emNode3Uuid -> new FlexOptions( emSupUuid, @@ -381,6 +347,7 @@ class ExtEmCommunicationIT 0.asMegaWatt, 0.asMegaWatt, 0.004.asMegaWatt, + Optional.empty, ), emNode4Uuid -> new FlexOptions( emSupUuid, @@ -388,6 +355,7 @@ class ExtEmCommunicationIT 0.002200000413468004.asMegaWatt, 0.002200000413468004.asMegaWatt, 0.002200000413468004.asMegaWatt, + Optional.empty, ), ), Map( @@ -407,7 +375,7 @@ class ExtEmCommunicationIT weatherData: WeatherData, flexOptions: Map[UUID, FlexOptions], setPoints: Map[UUID, ComparableQuantity[Power]], - )(implicit + )(using serviceActivation: ActorRef[Activation], adapter: ActorRef[DataMessageFromExt], pvAgents: Seq[ActorRef[ParticipantAgent.Request]], @@ -420,7 +388,7 @@ class ExtEmCommunicationIT connection.sendFlexRequests( tick, Map( - emSupUuid -> new FlexOptionRequest(emSupUuid, Optional.empty()) + emSupUuid -> new FlexOptionRequest(emSupUuid, Optional.empty, Optional.empty) ).asJava, Optional.of(nextTick), log, @@ -449,10 +417,12 @@ class ExtEmCommunicationIT emNode3Uuid -> new FlexOptionRequest( emNode3Uuid, Optional.of(emSupUuid), + Optional.empty, ), emNode4Uuid -> new FlexOptionRequest( emNode4Uuid, Optional.of(emSupUuid), + Optional.empty, ), ).asJava, Optional.of(nextTick), @@ -529,7 +499,7 @@ class ExtEmCommunicationIT // after we received all options we will send a message, to keep the current set point connection.sendSetPoints( tick, - Map(emSupUuid -> new PValue(setPoints(emSupUuid))).asJava, + Map(emSupUuid -> new EmSetPoint(emSupUuid, setPoints(emSupUuid))).asJava, Optional.of(nextTick), log, ) @@ -562,13 +532,13 @@ class ExtEmCommunicationIT ) } - def toPValue(uuid: UUID): (UUID, PValue) = - uuid -> new PValue(setPoints(uuid)) + def toSetPoint(uuid: UUID): (UUID, EmSetPoint) = + uuid -> new EmSetPoint(uuid, setPoints(uuid)) // we send the new set points to the inferior em agents connection.sendSetPoints( tick, - inferiorEms.map(toPValue).toMap.asJava, + inferiorEms.map(toSetPoint).toMap.asJava, Optional.of(nextTick), log, ) 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 480a3eb6be..dbdc18b00e 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -8,55 +8,34 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.{ - EmSetPointResult, - ExtendedFlexOptionsResult, - FlexOptions, -} -import edu.ie3.simona.api.data.em.ontology._ +import edu.ie3.simona.api.data.em.model.{EmSetPoint, EmSetPointResult, ExtendedFlexOptionsResult, FlexOptions} +import edu.ie3.simona.api.data.em.ontology.* import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ - FlexActivation, - FlexRequest, - IssuePowerControl, - ProvideFlexOptions, -} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{FlexActivation, FlexRequest, IssuePowerControl, ProvideFlexOptions} import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - WrappedFlexRequest, - WrappedFlexResponse, -} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - Create, - RegisterForEmDataService, -} +import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{Create, RegisterForEmDataService} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData import edu.ie3.simona.test.common.TestSpawnerTyped import edu.ie3.simona.test.common.input.EmInputTestData import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK -import edu.ie3.util.quantities.QuantityUtils._ +import edu.ie3.util.quantities.QuantityUtils.* import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW -import org.apache.pekko.actor.testkit.typed.scaladsl.{ - ScalaTestWithActorTestKit, - TestProbe, -} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import org.apache.pekko.testkit.TestKit.awaitCond import org.scalatest.wordspec.AnyWordSpecLike import squants.energy.Kilowatts import java.time.ZonedDateTime -import java.util.UUID +import java.util.{Optional, UUID} import scala.concurrent.duration.DurationInt -import scala.jdk.CollectionConverters._ +import scala.jdk.CollectionConverters.* import scala.jdk.OptionConverters.RichOption class ExtEmDataServiceSpec @@ -405,6 +384,7 @@ class ExtEmDataServiceSpec -3.asKiloWatt, -1.asKiloWatt, 1.asKiloWatt, + Optional.empty, ) ).asJava ).asJava, @@ -493,8 +473,8 @@ class ExtEmDataServiceSpec new ProvideEmSetPointData( 0, Map( - emAgent1UUID -> new PValue(-3.asKiloWatt), - emAgent2UUID -> new PValue(0.asKiloWatt), + emAgent1UUID -> new EmSetPoint(emAgent1UUID, -3.asKiloWatt), + emAgent2UUID -> new EmSetPoint(emAgent2UUID, 0.asKiloWatt), ).asJava, None.toJava, ) @@ -580,7 +560,7 @@ class ExtEmDataServiceSpec extEmDataConnection.sendExtMsg( new ProvideEmSetPointData( 0, - Map(emAgent2UUID -> new PValue(0.asKiloWatt)).asJava, + Map(emAgent2UUID -> new EmSetPoint(emAgent2UUID, 0.asKiloWatt)).asJava, None.toJava, ) ) diff --git a/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala b/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala index b0ddce068f..6a9a44b60d 100644 --- a/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala @@ -19,7 +19,7 @@ import edu.ie3.simona.event.notifier.NotifierConfig import edu.ie3.simona.test.common.DefaultTestData import edu.ie3.util.TimeUtil import edu.ie3.util.geo.GeoUtils -import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import edu.ie3.util.quantities.QuantityUtils.* import squants.Each import java.time.ZonedDateTime From 4a5182226ecc0e72ecbd0f507e246f569c53cf72 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 21 May 2025 17:24:02 +0200 Subject: [PATCH 052/125] Saving changes. --- .../simona/agent/grid/GridAgentBuilder.scala | 18 ++--- .../edu/ie3/simona/model/em/EmTools.scala | 25 ++++--- .../service/em/EmCommunicationCore.scala | 15 ++-- .../ie3/simona/service/em/EmServiceCore.scala | 15 ++-- .../ie3/simona/service/em/ExtEmBaseIT.scala | 65 +++++++++++++---- .../service/em/ExtEmCommunicationIT.scala | 70 +++++++++++++++---- .../service/em/ExtEmDataServiceSpec.scala | 38 ++++++++-- 7 files changed, 183 insertions(+), 63 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala index 678d91804d..0ebbfd09fc 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala @@ -8,8 +8,8 @@ package edu.ie3.simona.agent.grid import edu.ie3.datamodel.models.input.EmInput import edu.ie3.datamodel.models.input.container.{SubGridContainer, ThermalGrid} -import edu.ie3.datamodel.models.input.system._ -import edu.ie3.simona.actor.SimonaActorNaming._ +import edu.ie3.datamodel.models.input.system.* +import edu.ie3.simona.actor.SimonaActorNaming.* import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.participant.ParticipantAgentInit.{ @@ -18,7 +18,7 @@ import edu.ie3.simona.agent.participant.ParticipantAgentInit.{ } import edu.ie3.simona.agent.participant.{ParticipantAgent, ParticipantAgentInit} import edu.ie3.simona.config.OutputConfig.ParticipantOutputConfig -import edu.ie3.simona.config.RuntimeConfig._ +import edu.ie3.simona.config.RuntimeConfig.* import edu.ie3.simona.config.SimonaConfig.AssetConfigs import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.event.notifier.NotifierConfig @@ -31,15 +31,11 @@ import edu.ie3.simona.model.InputModelContainer.{ } import edu.ie3.simona.ontology.messages.SchedulerMessage import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.FlexResponse -import edu.ie3.simona.ontology.messages.services.{ - EmMessage, - ServiceMessage, - WeatherMessage, -} +import edu.ie3.simona.ontology.messages.services.{EmMessage, ServiceMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceType import edu.ie3.simona.util.ConfigUtil -import edu.ie3.simona.util.ConfigUtil._ +import edu.ie3.simona.util.ConfigUtil.* import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext @@ -48,7 +44,7 @@ import squants.Each import java.time.ZonedDateTime import java.util.UUID -import scala.jdk.CollectionConverters._ +import scala.jdk.CollectionConverters.* import scala.jdk.OptionConverters.RichOptional /** Holds all methods that should be available to a [[GridAgent]] @@ -292,8 +288,6 @@ class GridAgentBuilder( // might need to be built at the next recursion level. val controllingEms = controlledEmInputs.toMap.flatMap { case (_, emInput) => - log.warn(s"Controlling em: ${emInput.getControllingEm}") - emInput.getControllingEm.toScala.map(em => em.getUuid -> em) } diff --git a/src/main/scala/edu/ie3/simona/model/em/EmTools.scala b/src/main/scala/edu/ie3/simona/model/em/EmTools.scala index 97f48ec38a..76e602575d 100644 --- a/src/main/scala/edu/ie3/simona/model/em/EmTools.scala +++ b/src/main/scala/edu/ie3/simona/model/em/EmTools.scala @@ -8,12 +8,9 @@ package edu.ie3.simona.model.em import edu.ie3.simona.exceptions.FlexException import edu.ie3.simona.ontology.messages.flex.{FlexOptions, MinMaxFlexOptions} -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ - IssueFlexControl, - IssueNoControl, - IssuePowerControl, -} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{IssueFlexControl, IssueNoControl, IssuePowerControl} import squants.Power +import squants.energy.Watts /** Tools used by agents that engage with energy management and flexibility */ @@ -37,10 +34,22 @@ object EmTools { case minMaxFlexOptions: MinMaxFlexOptions => flexCtrl match { case IssuePowerControl(_, setPower) => - // sanity check: setPower is in range of latest flex options - checkSetPower(minMaxFlexOptions, setPower) + // fixed rounding issues + given powerTolerance: Power = Watts(1) + + val min = minMaxFlexOptions.min + val max = minMaxFlexOptions.max + + if (setPower < min && (setPower ~= min)) { + min + } else if (setPower > max && (setPower ~= max)) { + max + } else { + // sanity check: setPower is in range of latest flex options + checkSetPower(minMaxFlexOptions, setPower) - setPower + setPower + } case IssueNoControl(_) => // no override, take reference power diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 4e7c596136..110422ceb6 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -7,7 +7,11 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue -import edu.ie3.simona.api.data.em.model.{EmSetPointResult, ExtendedFlexOptionsResult, FlexRequestResult} +import edu.ie3.simona.api.data.em.model.{ + EmSetPointResult, + ExtendedFlexOptionsResult, + FlexRequestResult, +} import edu.ie3.simona.api.data.em.ontology.* import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions @@ -26,7 +30,12 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.Power -import scala.jdk.CollectionConverters.{IterableHasAsScala, MapHasAsJava, MapHasAsScala, SetHasAsJava} +import scala.jdk.CollectionConverters.{ + IterableHasAsScala, + MapHasAsJava, + MapHasAsScala, + SetHasAsJava, +} final case class EmCommunicationCore( override val lastFinishedTick: Long = PRE_INIT_TICK, @@ -56,8 +65,6 @@ final case class EmCommunicationCore( val updatedRefs = refs.add(uuid, ref, parentEm, parentUuid) - println(s"$uuid") - copy( refs = updatedRefs, uuidToFlexAdapter = uuidToFlexAdapter + (uuid -> flexAdapter), 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 d9a494d71e..868f3918cc 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -10,8 +10,14 @@ import edu.ie3.datamodel.models.value.{PValue, SValue} import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.em.ontology.* import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{RegisterForEmDataService, ServiceResponseMessage} +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + WrappedFlexRequest, + WrappedFlexResponse, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + RegisterForEmDataService, + ServiceResponseMessage, +} import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.util.quantities.QuantityUtils.asMegaWatt import edu.ie3.util.scala.quantities.QuantityConversionUtils.PowerConversionSimona @@ -69,13 +75,10 @@ trait EmServiceCore { ): Unit = { log.info(s"Handling of: $provideEmSetPoints") - provideEmSetPoints - .emSetPoints - .asScala + provideEmSetPoints.emSetPoints.asScala .foreach { case (agent, setPoint) => uuidToFlexAdapter.get(agent) match { case Some(receiver) => - val (pOption, qOption) = setPoint.power.toScala match { case Some(sValue: SValue) => (sValue.getP.toScala, sValue.getQ.toScala) diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala index bf7b0fc818..15d71f0c07 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala @@ -9,21 +9,45 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.grid.GridAgent -import edu.ie3.simona.agent.participant.ParticipantAgent.{DataProvision, RegistrationFailedMessage, RegistrationSuccessfulMessage} +import edu.ie3.simona.agent.participant.ParticipantAgent.{ + DataProvision, + RegistrationFailedMessage, + RegistrationSuccessfulMessage, +} import edu.ie3.simona.agent.participant.ParticipantAgentInit import edu.ie3.simona.agent.participant.ParticipantAgentInit.ParticipantRefs import edu.ie3.simona.api.data.em.model.EmSetPoint -import edu.ie3.simona.api.data.em.ontology.{EmCompletion, FlexOptionsResponse, RequestEmFlexResults} +import edu.ie3.simona.api.data.em.ontology.{ + EmCompletion, + FlexOptionsResponse, + RequestEmFlexResults, +} import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} -import edu.ie3.simona.api.data.ontology.{DataMessageFromExt, ScheduleDataServiceMessage} +import edu.ie3.simona.api.data.ontology.{ + DataMessageFromExt, + ScheduleDataServiceMessage, +} import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt -import edu.ie3.simona.config.RuntimeConfig.{LoadRuntimeConfig, PvRuntimeConfig, StorageRuntimeConfig} +import edu.ie3.simona.config.RuntimeConfig.{ + LoadRuntimeConfig, + PvRuntimeConfig, + StorageRuntimeConfig, +} import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.model.InputModelContainer.SimpleInputContainer -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{Create, PrimaryServiceRegistrationMessage} -import edu.ie3.simona.ontology.messages.services.WeatherMessage.{RegisterForWeatherMessage, WeatherData} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + Create, + PrimaryServiceRegistrationMessage, +} +import edu.ie3.simona.ontology.messages.services.WeatherMessage.{ + RegisterForWeatherMessage, + WeatherData, +} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceType @@ -34,7 +58,10 @@ import edu.ie3.simona.test.matchers.QuantityMatchers import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.util.quantities.QuantityUtils.* import edu.ie3.util.scala.quantities.WattsPerSquareMeter -import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} import org.apache.pekko.actor.typed.ActorRef import org.scalatest.wordspec.AnyWordSpecLike import org.slf4j.{Logger, LoggerFactory} @@ -43,7 +70,11 @@ import squants.thermal.Celsius import java.util.{Optional, UUID} import scala.concurrent.duration.{DurationInt, FiniteDuration} -import scala.jdk.CollectionConverters.{MapHasAsJava, MapHasAsScala, SeqHasAsJava} +import scala.jdk.CollectionConverters.{ + MapHasAsJava, + MapHasAsScala, + SeqHasAsJava, +} class ExtEmBaseIT extends ScalaTestWithActorTestKit @@ -364,7 +395,9 @@ class ExtEmBaseIT ) // we return a new set point - val setPoints900 = Map(emSupUuid -> new EmSetPoint(emSupUuid, 0.006200000413468004.asMegaWatt)) + val setPoints900 = Map( + emSupUuid -> new EmSetPoint(emSupUuid, 0.006200000413468004.asMegaWatt) + ) connection.sendSetPoints( 900L, @@ -534,7 +567,9 @@ class ExtEmBaseIT ) // we return a new set point - val setPoints2700 = Map(emSupUuid -> new EmSetPoint(emSupUuid, 0.006200000413468004.asMegaWatt)) + val setPoints2700 = Map( + emSupUuid -> new EmSetPoint(emSupUuid, 0.006200000413468004.asMegaWatt) + ) connection.sendSetPoints( 2700L, @@ -568,7 +603,9 @@ class ExtEmBaseIT pvAgentNode4 ! weatherData3600 // we send a new set point - val setPoints3600 = Map(emSupUuid -> new EmSetPoint(emSupUuid, 0.002200000413468004.asMegaWatt)) + val setPoints3600 = Map( + emSupUuid -> new EmSetPoint(emSupUuid, 0.002200000413468004.asMegaWatt) + ) connection.sendSetPoints( 3600L, @@ -600,7 +637,9 @@ class ExtEmBaseIT pvAgentNode4 ! weatherData4500 // we send a new set point - val setPoints4500 = Map(emSupUuid -> new EmSetPoint(emSupUuid, 0.006200000413468004.asMegaWatt)) + val setPoints4500 = Map( + emSupUuid -> new EmSetPoint(emSupUuid, 0.006200000413468004.asMegaWatt) + ) connection.sendSetPoints( 4500L, diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index d2edcb61a8..d527a5afe5 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -8,21 +8,51 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.grid.GridAgent -import edu.ie3.simona.agent.participant.ParticipantAgent.{DataProvision, RegistrationFailedMessage, RegistrationSuccessfulMessage} +import edu.ie3.simona.agent.participant.ParticipantAgent.{ + DataProvision, + RegistrationFailedMessage, + RegistrationSuccessfulMessage, +} import edu.ie3.simona.agent.participant.ParticipantAgentInit.ParticipantRefs import edu.ie3.simona.agent.participant.{ParticipantAgent, ParticipantAgentInit} -import edu.ie3.simona.api.data.em.model.{EmSetPoint, FlexOptionRequest, FlexOptions, FlexRequestResult} -import edu.ie3.simona.api.data.em.ontology.{EmCompletion, EmSetPointDataResponse, FlexOptionsResponse, FlexRequestResponse} +import edu.ie3.simona.api.data.em.model.{ + EmSetPoint, + FlexOptionRequest, + FlexOptions, + FlexRequestResult, +} +import edu.ie3.simona.api.data.em.ontology.{ + EmCompletion, + EmSetPointDataResponse, + FlexOptionsResponse, + FlexRequestResponse, +} import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} -import edu.ie3.simona.api.data.ontology.{DataMessageFromExt, ScheduleDataServiceMessage} +import edu.ie3.simona.api.data.ontology.{ + DataMessageFromExt, + ScheduleDataServiceMessage, +} import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt -import edu.ie3.simona.config.RuntimeConfig.{LoadRuntimeConfig, PvRuntimeConfig, StorageRuntimeConfig} +import edu.ie3.simona.config.RuntimeConfig.{ + LoadRuntimeConfig, + PvRuntimeConfig, + StorageRuntimeConfig, +} import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.model.InputModelContainer.SimpleInputContainer -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{Create, PrimaryServiceRegistrationMessage} -import edu.ie3.simona.ontology.messages.services.WeatherMessage.{RegisterForWeatherMessage, WeatherData} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + Create, + PrimaryServiceRegistrationMessage, +} +import edu.ie3.simona.ontology.messages.services.WeatherMessage.{ + RegisterForWeatherMessage, + WeatherData, +} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceType @@ -33,7 +63,10 @@ import edu.ie3.simona.test.matchers.QuantityMatchers import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.util.quantities.QuantityUtils.* import edu.ie3.util.scala.quantities.WattsPerSquareMeter -import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} import org.apache.pekko.actor.typed.ActorRef import org.scalatest.OptionValues.convertOptionToValuable import org.scalatest.wordspec.AnyWordSpecLike @@ -45,7 +78,11 @@ import tech.units.indriya.ComparableQuantity import java.util.{Optional, UUID} import javax.measure.quantity.Power import scala.concurrent.duration.{DurationInt, FiniteDuration} -import scala.jdk.CollectionConverters.{MapHasAsJava, MapHasAsScala, SeqHasAsJava} +import scala.jdk.CollectionConverters.{ + MapHasAsJava, + MapHasAsScala, + SeqHasAsJava, +} import scala.jdk.OptionConverters.RichOptional class ExtEmCommunicationIT @@ -90,7 +127,8 @@ class ExtEmCommunicationIT "An ExtEmDataService in communication mode" should { val service = spawn(ExtEmDataService(scheduler.ref)) val serviceRef = service.ref - given adapter: ActorRef[DataMessageFromExt] = spawn(ExtEmDataService.adapter(service)) + given adapter: ActorRef[DataMessageFromExt] = + spawn(ExtEmDataService.adapter(service)) connection.setActorRefs(adapter, extSimAdapter.ref) "with participant agents work correctly" in { @@ -388,7 +426,11 @@ class ExtEmCommunicationIT connection.sendFlexRequests( tick, Map( - emSupUuid -> new FlexOptionRequest(emSupUuid, Optional.empty, Optional.empty) + emSupUuid -> new FlexOptionRequest( + emSupUuid, + Optional.empty, + Optional.empty, + ) ).asJava, Optional.of(nextTick), log, @@ -499,7 +541,9 @@ class ExtEmCommunicationIT // after we received all options we will send a message, to keep the current set point connection.sendSetPoints( tick, - Map(emSupUuid -> new EmSetPoint(emSupUuid, setPoints(emSupUuid))).asJava, + Map( + emSupUuid -> new EmSetPoint(emSupUuid, setPoints(emSupUuid)) + ).asJava, Optional.of(nextTick), log, ) 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 dbdc18b00e..35359da361 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -8,16 +8,35 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.{EmSetPoint, EmSetPointResult, ExtendedFlexOptionsResult, FlexOptions} +import edu.ie3.simona.api.data.em.model.{ + EmSetPoint, + EmSetPointResult, + ExtendedFlexOptionsResult, + FlexOptions, +} import edu.ie3.simona.api.data.em.ontology.* import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{FlexActivation, FlexRequest, IssuePowerControl, ProvideFlexOptions} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ + FlexActivation, + FlexRequest, + IssuePowerControl, + ProvideFlexOptions, +} import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions -import edu.ie3.simona.ontology.messages.services.EmMessage.{WrappedFlexRequest, WrappedFlexResponse} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{Create, RegisterForEmDataService} +import edu.ie3.simona.ontology.messages.services.EmMessage.{ + WrappedFlexRequest, + WrappedFlexResponse, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ + Create, + RegisterForEmDataService, +} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData @@ -26,7 +45,10 @@ import edu.ie3.simona.test.common.input.EmInputTestData import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.quantities.QuantityUtils.* import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW -import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import org.apache.pekko.testkit.TestKit.awaitCond import org.scalatest.wordspec.AnyWordSpecLike @@ -560,7 +582,9 @@ class ExtEmDataServiceSpec extEmDataConnection.sendExtMsg( new ProvideEmSetPointData( 0, - Map(emAgent2UUID -> new EmSetPoint(emAgent2UUID, 0.asKiloWatt)).asJava, + Map( + emAgent2UUID -> new EmSetPoint(emAgent2UUID, 0.asKiloWatt) + ).asJava, None.toJava, ) ) From d87abe85c3f7d55f6ab059082cc9fe70d09015fa Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 22 May 2025 10:14:50 +0200 Subject: [PATCH 053/125] Adding scaling for primary participant model. --- .../scala/edu/ie3/simona/model/em/EmTools.scala | 6 +++++- .../model/participant/ParticipantModelInit.scala | 1 + .../participant/PrimaryDataParticipantModel.scala | 13 +++++++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/em/EmTools.scala b/src/main/scala/edu/ie3/simona/model/em/EmTools.scala index 76e602575d..129c11c22f 100644 --- a/src/main/scala/edu/ie3/simona/model/em/EmTools.scala +++ b/src/main/scala/edu/ie3/simona/model/em/EmTools.scala @@ -8,7 +8,11 @@ package edu.ie3.simona.model.em import edu.ie3.simona.exceptions.FlexException import edu.ie3.simona.ontology.messages.flex.{FlexOptions, MinMaxFlexOptions} -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{IssueFlexControl, IssueNoControl, IssuePowerControl} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ + IssueFlexControl, + IssueNoControl, + IssuePowerControl, +} import squants.Power import squants.energy.Watts diff --git a/src/main/scala/edu/ie3/simona/model/participant/ParticipantModelInit.scala b/src/main/scala/edu/ie3/simona/model/participant/ParticipantModelInit.scala index 36e8fd4c27..53e2f69e6a 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/ParticipantModelInit.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/ParticipantModelInit.scala @@ -121,6 +121,7 @@ object ParticipantModelInit { PrimaryDataParticipantModel.Factory( modelFactory.create(), primaryDataExtra, + modelConfig.scaling, ) } diff --git a/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala index cd1c97f339..e37414b628 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala @@ -41,6 +41,8 @@ import scala.reflect.ClassTag * physical [[ParticipantModel]]. * @param primaryDataExtra * Extra functionality specific to the primary data class. + * @param scalingFactor + * The scaling factor from the runtime config. * @tparam PD * The type of primary data. */ @@ -52,6 +54,7 @@ final case class PrimaryDataParticipantModel[PD <: PrimaryData]( override val qControl: QControl, private val primaryDataResultFunc: PrimaryResultFunc, private val primaryDataExtra: PrimaryDataExtra[PD], + private val scalingFactor: Double, ) extends ParticipantModel[ PrimaryOperatingPoint[PD], PrimaryDataState[PD], @@ -78,8 +81,10 @@ final case class PrimaryDataParticipantModel[PD <: PrimaryData]( override def determineOperatingPoint( state: PrimaryDataState[PD] - ): (PrimaryOperatingPoint[PD], Option[Long]) = - (PrimaryOperatingPoint(state.data), None) + ): (PrimaryOperatingPoint[PD], Option[Long]) = { + val scaledData = primaryDataExtra.scale(state.data, scalingFactor) + (PrimaryOperatingPoint(scaledData), None) + } override def zeroPowerOperatingPoint: PrimaryOperatingPoint[PD] = PrimaryOperatingPoint(primaryDataExtra.zero) @@ -140,10 +145,13 @@ object PrimaryDataParticipantModel { * The physical participant model. * @param primaryDataExtra * Extra functionality specific to the primary data class. + * @param scalingFactor + * The scaling factor from the runtime config. */ final case class Factory[PD <: PrimaryData]( physicalModel: ParticipantModel[_, _], primaryDataExtra: PrimaryDataExtra[PD], + scalingFactor: Double, ) extends ParticipantModelFactory[PrimaryDataState[PD]] { override def getRequiredSecondaryServices: Iterable[ServiceType] = @@ -175,6 +183,7 @@ object PrimaryDataParticipantModel { physicalModel.qControl, primaryResultFunc, primaryDataExtra, + scalingFactor, ) } } From de3fabeaa590a9344bcd36e546fdd1ccfa190a41 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 22 May 2025 11:04:34 +0200 Subject: [PATCH 054/125] Clean up. --- .../service/em/ExtEmDataServiceSpec.scala | 128 ------------------ 1 file changed, 128 deletions(-) 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 35359da361..fd6264bbaa 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -519,133 +519,5 @@ class ExtEmDataServiceSpec scheduler.expectMessage(Completion(serviceActivation)) } - "handle set point request correctly" in { - val scheduler = TestProbe[SchedulerMessage]("scheduler") - val extSimAdapter = - TestProbe[ControlResponseMessageFromExt]("extSimAdapter") - - val emService = spawn(ExtEmDataService.apply(scheduler.ref)) - val adapter = spawn(ExtEmDataService.adapter(emService)) - val extEmDataConnection = - new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) - extEmDataConnection.setActorRefs( - adapter, - extSimAdapter.ref, - ) - - val key = - ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) - scheduler - .expectMessageType[ScheduleActivation] // lock activation scheduled - - emService ! Create( - InitExtEmData(extEmDataConnection, simulationStart), - key, - ) - - val activationMsg = scheduler.expectMessageType[ScheduleActivation] - activationMsg.tick shouldBe INIT_SIM_TICK - activationMsg.unlockKey shouldBe Some(key) - - val serviceActivation = activationMsg.actor - - serviceActivation ! Activation(INIT_SIM_TICK) - scheduler.expectMessage(Completion(serviceActivation)) - - val emAgent1 = TestProbe[EmAgent.Request]("emAgent1") - val emAgentFlex1 = TestProbe[FlexRequest]("emAgentFlex1") - val emAgent2 = TestProbe[EmAgent.Request]("emAgent2") - val emAgentFlex2 = TestProbe[FlexRequest]("emAgentFlex2") - - emService ! RegisterForEmDataService( - emAgent1UUID, - emAgent1.ref, - emAgentFlex1.ref, - None, - None, - ) - emAgent1.expectNoMessage() - emAgentFlex1.expectMessage(FlexActivation(-1)) - - emService ! RegisterForEmDataService( - emAgent2UUID, - emAgent2.ref, - emAgentFlex2.ref, - None, - None, - ) - emAgent2.expectNoMessage() - emAgentFlex2.expectMessage(FlexActivation(-1)) - - // parent em normally sets itself a set point to 0 kW - // we replace this behavior with the external data service - extEmDataConnection.sendExtMsg( - new ProvideEmSetPointData( - 0, - Map( - emAgent2UUID -> new EmSetPoint(emAgent2UUID, 0.asKiloWatt) - ).asJava, - None.toJava, - ) - ) - - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - - serviceActivation ! Activation(0) - - emAgent1.expectNoMessage() - emAgentFlex1.expectNoMessage() - - emAgent2.expectNoMessage() - emAgentFlex2.expectMessage( - IssuePowerControl(0, zeroKW) - ) - - scheduler.expectMessage(Completion(serviceActivation)) - - // we now request the set points of the controlled em agent - extEmDataConnection.sendExtMsg( - new RequestEmSetPoints( - 0, - List(emAgent1UUID).asJava, - ) - ) - - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - - serviceActivation ! Activation(0) - - emAgent1.expectNoMessage() - emAgent2.expectNoMessage() - - // scheduler.expectMessage(Completion(serviceActivation)) - - extEmDataConnection.receiveTriggerQueue shouldBe empty - - // the parent em agent sends the controlled em agent an IssuePowerControl message through the service - emService ! WrappedFlexRequest( - IssuePowerControl(0, Kilowatts(2)), - emAgentFlex1.ref, - ) - - awaitCond( - !extEmDataConnection.receiveTriggerQueue.isEmpty, - max = 3.seconds, - ) - - extEmDataConnection.receiveTriggerQueue.size() shouldBe 1 - - extEmDataConnection.receiveTriggerQueue - .take() shouldBe new EmSetPointDataResponse( - Map( - emAgent1UUID -> new EmSetPointResult( - simulationStart, - emAgentSupUUID, - Map(emAgent1UUID -> new PValue(2.asKiloWatt)).asJava, - ) - ).asJava - ) - } - } } From 122dc0c0aa41bc6f664ffbead09f493d3555e740 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 11 Jun 2025 18:24:55 +0200 Subject: [PATCH 055/125] Saving changes. --- .../scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala | 5 ++++- src/main/scala/edu/ie3/simona/config/OutputConfig.scala | 6 +++--- .../model/participant/PrimaryDataParticipantModel.scala | 5 ++++- .../scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala | 6 +++++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala index bdd38f61a9..7ddcca3ae0 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala @@ -85,7 +85,10 @@ object GridAgentBuilder { _.getControllingEm.toScala.map(em => em.getUuid -> em) }.toMap - val allEms = buildEmsRecursively(firstLevelEms, environmentRefs.emDataService) + val allEms = buildEmsRecursively( + firstLevelEms, + emDataService = constantData.environmentRefs.emDataService, + ) /* Browse through all system participants, build actors and map their node's UUID to the actor references */ buildParticipantToActorRef( diff --git a/src/main/scala/edu/ie3/simona/config/OutputConfig.scala b/src/main/scala/edu/ie3/simona/config/OutputConfig.scala index f42e5c0347..93df6cf4c6 100644 --- a/src/main/scala/edu/ie3/simona/config/OutputConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/OutputConfig.scala @@ -94,7 +94,7 @@ object OutputConfig { * If the power request reply should be written (default: false). */ final case class ParticipantOutputConfig( - override val notifier: String, + override val notifier: String = "default", override val simulationResult: Boolean = false, flexResult: Boolean = false, powerRequestReply: Boolean = false, @@ -108,8 +108,8 @@ object OutputConfig { * If simulation results should be written (default: false). */ final case class SimpleOutputConfig( - override val notifier: String, - override val simulationResult: Boolean, + override val notifier: String = "default", + override val simulationResult: Boolean = false, ) extends BaseOutputConfig derives ConfigConvert diff --git a/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala index e37414b628..0b091b41e2 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala @@ -128,7 +128,10 @@ final case class PrimaryDataParticipantModel[PD <: PrimaryData]( ): (PrimaryOperatingPoint[PD], OperationChangeIndicator) = { // scale the whole primary data by the same factor that // the active power set point was scaled by - val factor = state.data.p / setPower + val factor = if (setPower.value != 0.0) { + state.data.p / setPower + } else 1.0 + val scaledData: PD = primaryDataExtra.scale(state.data, factor) (PrimaryOperatingPoint(scaledData), OperationChangeIndicator()) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index 531070a9d0..9170b57657 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -13,7 +13,11 @@ import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.api.data.results.ExtResultDataConnection -import edu.ie3.simona.ontology.messages.services.{EvMessage, ServiceMessage} +import edu.ie3.simona.ontology.messages.services.{ + EmMessage, + EvMessage, + ServiceMessage, +} import org.apache.pekko.actor.typed.ActorRef /** Case class that holds information regarding the external data connections as From ada78e9df5c05e8a8f4a726f14a4965dcc02ea46 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 12 Jun 2025 16:32:29 +0200 Subject: [PATCH 056/125] Saving changes. --- src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala | 8 ++++---- .../ie3/simona/service/em/EmCommunicationCore.scala | 10 ++++++---- .../edu/ie3/simona/service/em/EmServiceBaseCore.scala | 4 +++- .../edu/ie3/simona/service/em/EmServiceCore.scala | 7 +++++++ 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index c6d1ea468f..f1916b4ef2 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -107,7 +107,7 @@ object EmAgent { listener: Iterable[ActorRef[ResultEvent]], emDataService: Option[ActorRef[EmMessage]], ): Behavior[Request] = Behaviors.setup[Request] { ctx => - val flexAdapter = ctx.messageAdapter[FlexRequest](Flex) + val flexAdapter = ctx.messageAdapter[FlexRequest](Flex.apply) val parentData = emDataService match { case Some(service) => @@ -127,7 +127,7 @@ object EmAgent { ExtEmDataService.emServiceRequestAdapter( service, flexAdapter, - )(ctx) + )(using ctx) parentOption.foreach( _ ! RegisterControlledAsset(serviceRequestAdapter, inputModel) @@ -138,7 +138,7 @@ object EmAgent { service, parentOption, inputModel.getUuid, - )(ctx) + )(using ctx) Right(FlexControlledData(serviceResponseAdapter, flexAdapter)) @@ -180,7 +180,7 @@ object EmAgent { inactive( constantData, modelShell, - EmDataCore.create(simulationStartDate), + EmDataCore.create(using simulationStartDate), ) } diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 110422ceb6..d943450508 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -27,6 +27,8 @@ import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger import tech.units.indriya.ComparableQuantity +import scala.jdk.OptionConverters.RichOption + import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.Power @@ -97,7 +99,7 @@ final case class EmCommunicationCore( adapter ! IssueNoControl(tick) } - (this, Some(new EmCompletion())) + (this, Some(new EmCompletion(getMaybeNextTick.toJava))) } case provideFlexRequests: ProvideFlexRequestData => @@ -211,11 +213,11 @@ final case class EmCommunicationCore( flexOptionResponse.addData( modelUuid, new ExtendedFlexOptionsResult( - tick.toDateTime(startTime), + tick.toDateTime(using startTime), modelUuid, receiverUuid, - min.toQuantity, ref.toQuantity, + min.toQuantity, max.toQuantity, ), ) @@ -264,7 +266,7 @@ final case class EmCommunicationCore( val extMsgOption = if (tick != INIT_SIM_TICK) { // send completion message to external simulation, if we aren't in the INIT_SIM_TICK - Some(new EmCompletion()) + Some(new EmCompletion(getMaybeNextTick.toJava)) } else None // every em agent has sent a completion message 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 28138df4be..e34b21b56a 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -18,6 +18,8 @@ import edu.ie3.simona.util.TickUtil.TickLong import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger +import scala.jdk.OptionConverters.RichOption + import java.time.ZonedDateTime import java.util.UUID import scala.jdk.CollectionConverters.{ @@ -226,7 +228,7 @@ final case class EmServiceBaseCore( val extMsgOption = if (tick != INIT_SIM_TICK) { // send completion message to external simulation, if we aren't in the INIT_SIM_TICK - Some(new EmCompletion()) + Some(new EmCompletion(getMaybeNextTick.toJava)) } else None // every em agent has sent a completion message 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 868f3918cc..7ebbae9641 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -119,6 +119,13 @@ trait EmServiceCore { startTime: ZonedDateTime, log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) + + final def getMaybeNextTick: Option[java.lang.Long] = completions.receivedData + .flatMap { case (k, completion) => + completion.requestAtTick + } + .minOption + .map(long2Long) } object EmServiceCore { From 636cad0e55bcea0d0eb92696ecbf72214177dd9a Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 18 Jun 2025 14:46:43 +0200 Subject: [PATCH 057/125] Adapting to changes in `dev`. --- .../ie3/simona/agent/EnvironmentRefs.scala | 3 +- .../edu/ie3/simona/agent/em/EmAgent.scala | 99 +++--- .../simona/agent/grid/GridAgentBuilder.scala | 9 +- .../event/listener/ResultEventListener.scala | 10 +- .../model/participant/FixedFeedInModel.scala | 1 - .../ontology/messages/ServiceMessage.scala | 30 +- .../messages/services/EmMessage.scala | 34 -- .../ie3/simona/service/ExtDataSupport.scala | 4 +- .../ie3/simona/service/SimonaService.scala | 11 +- .../service/em/EmCommunicationCore.scala | 45 ++- .../simona/service/em/EmServiceBaseCore.scala | 39 +-- .../ie3/simona/service/em/EmServiceCore.scala | 50 +-- .../simona/service/em/ExtEmDataService.scala | 97 +++-- .../simona/service/ev/ExtEvDataService.scala | 2 +- .../primary/ExtPrimaryDataService.scala | 33 +- .../service/primary/PrimaryServiceProxy.scala | 12 +- .../service/results/ExtResultProvider.scala | 30 +- .../service/results/ExtResultSchedule.scala | 2 +- .../service/weather/WeatherService.scala | 12 + .../ie3/simona/sim/setup/ExtSimSetup.scala | 115 ++---- .../simona/sim/setup/ExtSimSetupData.scala | 10 +- .../agent/em/EmAgentWithServiceSpec.scala | 180 +++++----- .../participant/ParticipantAgentSpec.scala | 2 + .../ie3/simona/service/em/ExtEmBaseIT.scala | 100 +++--- .../service/em/ExtEmCommunicationIT.scala | 91 +++-- .../service/em/ExtEmDataServiceSpec.scala | 331 +++++++++--------- .../primary/ExtPrimaryDataServiceSpec.scala | 64 ++-- .../sim/setup/ExtSimSetupDataSpec.scala | 5 +- .../input/EmCommunicationTestData.scala | 6 +- 29 files changed, 665 insertions(+), 762 deletions(-) delete mode 100644 src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala diff --git a/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala b/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala index 2d4307f1e3..c292f5238d 100644 --- a/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala +++ b/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala @@ -8,6 +8,7 @@ package edu.ie3.simona.agent import edu.ie3.simona.event.RuntimeEvent import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} +import edu.ie3.simona.service.em.ExtEmDataService import org.apache.pekko.actor.typed.ActorRef /** Container class, that gather together reference to relevant entities, that @@ -34,6 +35,6 @@ final case class EnvironmentRefs( primaryServiceProxy: ActorRef[ServiceMessage], weather: ActorRef[ServiceMessage], loadProfiles: ActorRef[ServiceMessage], - emDataService: Option[ActorRef[ServiceMessage]], + emDataService: Option[ActorRef[ExtEmDataService.Message]], evDataService: Option[ActorRef[ServiceMessage]], ) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index e36068f886..52908ef381 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -21,22 +21,26 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, } -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ -import edu.ie3.simona.ontology.messages.services.EmMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService -import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration +import edu.ie3.simona.ontology.messages.flex.FlexOptions +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* +import edu.ie3.simona.ontology.messages.{ + Activation, + SchedulerMessage, + ServiceMessage, +} import edu.ie3.simona.service.Data.PrimaryData.ComplexPower import edu.ie3.simona.service.em.ExtEmDataService import edu.ie3.simona.util.TickUtil.TickLong -import edu.ie3.util.quantities.QuantityUtils._ -import edu.ie3.util.scala.quantities.DefaultQuantities._ -import org.apache.pekko.actor.typed.scaladsl.Behaviors +import edu.ie3.util.quantities.QuantityUtils.* +import edu.ie3.util.scala.quantities.DefaultQuantities.* +import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} import org.apache.pekko.actor.typed.{ActorRef, Behavior} import java.time.ZonedDateTime +import java.util.UUID import scala.jdk.OptionConverters.RichOptional import scala.util.{Failure, Try} -import edu.ie3.simona.ontology.messages.flex.FlexOptions /** Energy management agent that receives flex options from and issues control * messages to connected agents @@ -76,53 +80,62 @@ object EmAgent { simulationStartDate: ZonedDateTime, parent: Either[ActorRef[SchedulerMessage], ActorRef[FlexResponse]], listener: Iterable[ActorRef[ResultEvent]], - emDataService: Option[ActorRef[EmMessage]], + emDataService: Option[ActorRef[ExtEmDataService.Message]], ): Behavior[Message] = Behaviors.setup[Message] { ctx => - val flexAdapter = ctx.messageAdapter[Message](Flex.apply) - val parentData = emDataService match { - case Some(service) => - // since we have a service, it will replace the default agent communication + val parentData: Either[ActorRef[SchedulerMessage], ActorRef[FlexResponse]] = + emDataService match { + case Some(service) => + // since we have a service, it will replace the default agent communication + given ActorContext[Message] = ctx - val parentOption = parent.toOption + val uuid = inputModel.getUuid - service ! RegisterForEmDataService( - inputModel.getUuid, - ctx.self, - flexAdapter, - parentOption, - inputModel.getControllingEm.toScala.map(_.getUuid), - ) + service ! EmServiceRegistration( + ctx.self, + uuid, + parent.toOption, + inputModel.getControllingEm.toScala.map(_.getUuid), + ) - val serviceRequestAdapter: ActorRef[FlexRequest] = - ExtEmDataService.emServiceRequestAdapter( + // given to the parent + val requestAdapter = ExtEmDataService.emServiceRequestAdapter( service, - flexAdapter, - )(using ctx) + ctx.self, + ) - parentOption.foreach( - _ ! RegisterControlledAsset(serviceRequestAdapter, inputModel) - ) + val adaptedParent = parent match { + case Left(value) => + Left(uuid) + case Right(value) => + Right(ctx.self) + } - val serviceResponseAdapter: ActorRef[FlexResponse] = - ExtEmDataService.emServiceResponseAdapter( + // used by this agent + val responseAdapter = ExtEmDataService.emServiceResponseAdapter( service, - parentOption, - inputModel.getUuid, - )(using ctx) + adaptedParent, + ) - Right(FlexControlledData(serviceResponseAdapter, flexAdapter)) + parent.map { + _ ! RegisterControlledAsset( + requestAdapter, + inputModel, + ) + } - case None => - parent.map { - _ ! RegisterControlledAsset( - ctx.self, - inputModel, - ) - } + Right(responseAdapter) - parent - } + case None => + parent.map { + _ ! RegisterControlledAsset( + ctx.self, + inputModel, + ) + } + + parent + } val constantData = EmData( outputConfig, diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala index 885470690f..3eae955817 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala @@ -18,7 +18,7 @@ import edu.ie3.simona.agent.participant.ParticipantAgentInit.{ SimulationParameters, } import edu.ie3.simona.agent.participant.{ParticipantAgent, ParticipantAgentInit} -import edu.ie3.simona.config.RuntimeConfig._ +import edu.ie3.simona.config.RuntimeConfig.* import edu.ie3.simona.config.OutputConfig.ParticipantOutputConfig import edu.ie3.simona.config.RuntimeConfig.* import edu.ie3.simona.config.SimonaConfig.AssetConfigs @@ -39,7 +39,8 @@ import edu.ie3.simona.ontology.messages.{ } import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceType -import edu.ie3.simona.util.ConfigUtil._ +import edu.ie3.simona.service.em.ExtEmDataService +import edu.ie3.simona.util.ConfigUtil.* import edu.ie3.simona.util.ConfigUtil import edu.ie3.simona.util.ConfigUtil.* import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK @@ -225,7 +226,7 @@ object GridAgentBuilder { private def buildEmsRecursively( emInputs: Map[UUID, EmInput], previousLevelEms: Map[UUID, ActorRef[FlexResponse]] = Map.empty, - emDataService: Option[ActorRef[EmMessage]], + emDataService: Option[ActorRef[ExtEmDataService.Message]], )(using constantData: GridAgentConstantData, gridAgentContext: ActorContext[GridAgent.Message], @@ -476,7 +477,7 @@ object GridAgentBuilder { private def buildEm( emInput: EmInput, maybeControllingEm: Option[ActorRef[FlexResponse]], - emDataService: Option[ActorRef[EmMessage]], + emDataService: Option[ActorRef[ExtEmDataService.Message]], )(using constantData: GridAgentConstantData, gridAgentContext: ActorContext[GridAgent.Message], diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index 3739347907..03290a6a3d 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -6,8 +6,6 @@ package edu.ie3.simona.event.listener -import org.apache.pekko.actor.typed.scaladsl.Behaviors -import org.apache.pekko.actor.typed.{ActorRef, Behavior, PostStop} import edu.ie3.datamodel.io.processor.result.ResultEntityProcessor import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult @@ -21,10 +19,12 @@ import edu.ie3.simona.exceptions.{ FileHierarchyException, ProcessResultEventException, } -import edu.ie3.simona.io.result._ -import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.service.results.ExtResultProvider.ResultResponseMessage +import edu.ie3.simona.io.result.* +import edu.ie3.simona.ontology.messages.ServiceMessage +import edu.ie3.simona.ontology.messages.ServiceMessage.ResultResponseMessage import edu.ie3.simona.util.ResultFileHierarchy +import org.apache.pekko.actor.typed.scaladsl.Behaviors +import org.apache.pekko.actor.typed.{ActorRef, Behavior, PostStop} import org.slf4j.Logger import scala.concurrent.ExecutionContext.Implicits.global diff --git a/src/main/scala/edu/ie3/simona/model/participant/FixedFeedInModel.scala b/src/main/scala/edu/ie3/simona/model/participant/FixedFeedInModel.scala index 84351522c5..9f0414c9f3 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/FixedFeedInModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/FixedFeedInModel.scala @@ -24,7 +24,6 @@ import edu.ie3.simona.service.Data.PrimaryData.{ PrimaryDataWithComplexPower, } import edu.ie3.simona.service.ServiceType -import edu.ie3.util.quantities.PowerSystemUnits import edu.ie3.util.quantities.QuantityUtils.{asMegaWatt, asMegaVar} import edu.ie3.util.scala.quantities.ApparentPower import edu.ie3.util.scala.quantities.QuantityConversionUtils.PowerConversionSimona 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 4298b59853..c08505c45b 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/ServiceMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/ServiceMessage.scala @@ -6,15 +6,22 @@ package edu.ie3.simona.ontology.messages +import edu.ie3.datamodel.models.result.ResultEntity +import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.participant.ParticipantAgent import edu.ie3.simona.agent.participant.ParticipantAgent.ParticipantRequest import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.model.participant.evcs.EvModelWrapper -import edu.ie3.simona.ontology.messages.Activation +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ + FlexRequest, + FlexResponse, +} import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.simona.service.ServiceStateData.InitializeServiceStateData +import edu.ie3.util.TimeUtil import org.apache.pekko.actor.typed.ActorRef +import java.time.ZonedDateTime import java.util.UUID /** Collections of all messages, that are send to and from the different @@ -51,6 +58,13 @@ object ServiceMessage { data: D, ) extends ServiceRegistrationMessage + final case class EmServiceRegistration( + requestingActor: ActorRef[EmAgent.Message], + inputUuid: UUID, + parentEm: Option[ActorRef[FlexResponse]], + parentUuid: Option[UUID], + ) extends ServiceRegistrationMessage + /** Message to register with a primary data service. * * @param requestingActor @@ -142,4 +156,18 @@ object ServiceMessage { evModels: Seq[EvModelWrapper], ) extends ServiceResponseMessage + final case class EmFlexMessage( + message: FlexRequest | FlexResponse, + receiver: Either[UUID, ActorRef[EmAgent.Message]], + ) extends ServiceResponseMessage + + final case class ResultResponseMessage(result: ResultEntity) + extends ServiceMessage + with ServiceResponseMessage { + def tick(using startTime: ZonedDateTime): Long = + TimeUtil.withDefaults.zonedDateTimeDifferenceInSeconds( + startTime, + result.getTime, + ) + } } diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala deleted file mode 100644 index 2ae59869e9..0000000000 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/EmMessage.scala +++ /dev/null @@ -1,34 +0,0 @@ -/* - * © 2025. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.ontology.messages.services - -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ - FlexRequest, - FlexResponse, -} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.ServiceResponseMessage -import org.apache.pekko.actor.typed.ActorRef - -import java.util.UUID - -sealed trait EmMessage - -object EmMessage { - - private[services] trait EmInternal extends EmMessage - - final case class WrappedFlexResponse( - flexResponse: FlexResponse, - receiver: Either[UUID, ActorRef[FlexResponse]], - ) extends ServiceResponseMessage - - final case class WrappedFlexRequest( - flexRequest: FlexRequest, - receiver: ActorRef[FlexRequest], - ) extends ServiceResponseMessage - -} diff --git a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala index a742520f22..2464ddcf15 100644 --- a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala +++ b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala @@ -25,8 +25,8 @@ import org.apache.pekko.actor.typed.{ActorRef, Behavior} trait ExtDataSupport { this: SimonaService => - override type Message = ServiceMessage | Activation | ServiceResponseMessage | - DataMessageFromExt + override type Message >: ServiceMessage | Activation | + ServiceResponseMessage | DataMessageFromExt override protected def idleExternal(using stateData: S, diff --git a/src/main/scala/edu/ie3/simona/service/SimonaService.scala b/src/main/scala/edu/ie3/simona/service/SimonaService.scala index 201ec59a64..2529366f56 100644 --- a/src/main/scala/edu/ie3/simona/service/SimonaService.scala +++ b/src/main/scala/edu/ie3/simona/service/SimonaService.scala @@ -14,6 +14,7 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ import edu.ie3.simona.ontology.messages.ServiceMessage.{ Create, ServiceRegistrationMessage, + ServiceResponseMessage, } import edu.ie3.simona.ontology.messages.{ Activation, @@ -130,7 +131,7 @@ abstract class SimonaService { Behaviors.same case (ctx, msg: ServiceResponseMessage) => - handleServiceResponse(msg)(ctx) + handleServiceResponse(msg)(using ctx) Behaviors.same // unhandled message @@ -189,10 +190,10 @@ abstract class SimonaService { maybeNextTick match { case Some(nextTick) if nextTick == tick => // we need to do an additional activation of this service - ctx.self ! WrappedActivation(Activation(tick)) + ctx.self ! Activation(tick) case _ => - constantData.scheduler ! Completion( + scheduler ! Completion( ctx.self, maybeNextTick, ) @@ -244,8 +245,8 @@ abstract class SimonaService { protected def handleServiceResponse( serviceResponse: ServiceResponseMessage - )(implicit - ctx: ActorContext[T] + )(using + ctx: ActorContext[Message] ): Unit = {} /** Handle a request to register for information from this service diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index d943450508..a372fd5221 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -13,9 +13,10 @@ import edu.ie3.simona.api.data.em.model.{ FlexRequestResult, } import edu.ie3.simona.api.data.em.ontology.* +import edu.ie3.simona.agent.em.EmAgent +import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions -import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.service.em.EmCommunicationCore.DataMap import edu.ie3.simona.service.em.EmServiceCore.EmRefMaps import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} @@ -28,7 +29,6 @@ import org.slf4j.Logger import tech.units.indriya.ComparableQuantity import scala.jdk.OptionConverters.RichOption - import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.Power @@ -41,8 +41,8 @@ import scala.jdk.CollectionConverters.{ final case class EmCommunicationCore( override val lastFinishedTick: Long = PRE_INIT_TICK, - override val uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = - Map.empty, + override val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, + agentToUuid: Map[ActorRef[EmAgent.Message], UUID] = Map.empty, override val completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, refs: EmRefMaps = EmRefMaps(), @@ -57,20 +57,19 @@ final case class EmCommunicationCore( ) extends EmServiceCore { override def handleRegistration( - registrationMsg: RegisterForEmDataService + emServiceRegistration: EmServiceRegistration ): EmServiceCore = { - val uuid = registrationMsg.modelUuid - val ref = registrationMsg.requestingActor - val flexAdapter = registrationMsg.flexAdapter - val parentEm = registrationMsg.parentEm - val parentUuid = registrationMsg.parentUuid + val ref = emServiceRegistration.requestingActor + val uuid = emServiceRegistration.inputUuid + val parentEm = emServiceRegistration.parentEm + val parentUuid = emServiceRegistration.parentUuid val updatedRefs = refs.add(uuid, ref, parentEm, parentUuid) copy( refs = updatedRefs, - uuidToFlexAdapter = uuidToFlexAdapter + (uuid -> flexAdapter), - flexAdapterToUuid = flexAdapterToUuid + (flexAdapter -> uuid), + uuidToAgent = uuidToAgent + (uuid -> ref), + agentToUuid = agentToUuid + (ref -> uuid), flexRequestReceived = flexRequestReceived.updateStructure(parentUuid, uuid), flexOptionResponse = flexOptionResponse.updateStructure(parentUuid, uuid), @@ -95,8 +94,8 @@ final case class EmCommunicationCore( } else { log.info(s"Receive a request for completion for tick '$tick'.") - uuidToFlexAdapter.foreach { case (_, adapter) => - adapter ! IssueNoControl(tick) + uuidToAgent.foreach { case (_, emAgent) => + emAgent ! IssueNoControl(tick) } (this, Some(new EmCompletion(getMaybeNextTick.toJava))) @@ -110,7 +109,7 @@ final case class EmCommunicationCore( .map { case (agent, _) => agent } .toSet - val agents = emEntities.map(uuidToFlexAdapter) + val agents = emEntities.map(uuidToAgent) agents.foreach(_ ! FlexActivation(tick)) @@ -161,8 +160,8 @@ final case class EmCommunicationCore( override def handleFlexResponse( tick: Long, flexResponse: FlexResponse, - receiver: Either[UUID, ActorRef[FlexResponse]], - )(implicit + receiver: Either[UUID, ActorRef[EmAgent.Message]], + )(using startTime: ZonedDateTime, log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = flexResponse match { @@ -174,7 +173,7 @@ final case class EmCommunicationCore( ref ! scheduleFlexActivation case Left(uuid) => - uuidToFlexAdapter(uuid) ! FlexActivation(INIT_SIM_TICK) + uuidToAgent(uuid) ! FlexActivation(INIT_SIM_TICK) } } else { log.warn(s"$scheduleFlexActivation not handled!") @@ -189,7 +188,7 @@ final case class EmCommunicationCore( otherRef ! provideFlexOptions case Left(self: UUID) => - uuidToFlexAdapter(self) ! IssuePowerControl( + uuidToAgent(self) ! IssuePowerControl( INIT_SIM_TICK, zeroKW, ) @@ -281,8 +280,8 @@ final case class EmCommunicationCore( override def handleFlexRequest( flexRequest: FlexRequest, - receiver: ActorRef[FlexRequest], - )(implicit + receiver: ActorRef[EmAgent.Message], + )(using startTime: ZonedDateTime, log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = flexRequest match { @@ -292,7 +291,7 @@ final case class EmCommunicationCore( (this, None) } else { - val uuid = flexAdapterToUuid(receiver) + val uuid = agentToUuid(receiver) val updated = flexRequestReceived.addData( uuid, @@ -330,7 +329,7 @@ final case class EmCommunicationCore( (this, None) } else { - val uuid = flexAdapterToUuid(receiver) + val uuid = agentToUuid(receiver) val (time, power) = issueFlexControl match { case IssueNoControl(tick) => 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 e34b21b56a..1f165c371c 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -6,12 +6,13 @@ package edu.ie3.simona.service.em +import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.em.model.ExtendedFlexOptionsResult -import edu.ie3.simona.api.data.em.ontology._ +import edu.ie3.simona.api.data.em.ontology.* import edu.ie3.simona.exceptions.CriticalFailureException -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ +import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions -import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.TickLong @@ -19,7 +20,6 @@ import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger import scala.jdk.OptionConverters.RichOption - import java.time.ZonedDateTime import java.util.UUID import scala.jdk.CollectionConverters.{ @@ -30,8 +30,7 @@ import scala.jdk.CollectionConverters.{ final case class EmServiceBaseCore( override val lastFinishedTick: Long = PRE_INIT_TICK, - override val uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] = - Map.empty, + override val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, flexOptions: ReceiveDataMap[UUID, ExtendedFlexOptionsResult] = ReceiveDataMap.empty, additionalFlexOptions: Map[UUID, ExtendedFlexOptionsResult] = Map.empty, @@ -45,10 +44,11 @@ final case class EmServiceBaseCore( ) extends EmServiceCore { override def handleRegistration( - registrationMsg: RegisterForEmDataService + emServiceRegistration: EmServiceRegistration ): EmServiceBaseCore = { - val modelUuid = registrationMsg.modelUuid - val parentUuid = registrationMsg.parentUuid + val ref = emServiceRegistration.requestingActor + val modelUuid = emServiceRegistration.inputUuid + val parentUuid = emServiceRegistration.parentUuid val updatedStructure = parentUuid match { case Some(parent) => @@ -71,15 +71,14 @@ final case class EmServiceBaseCore( } copy( - uuidToFlexAdapter = - uuidToFlexAdapter ++ Map(modelUuid -> registrationMsg.flexAdapter), + uuidToAgent = uuidToAgent + (modelUuid -> ref), completions = completions.addExpectedKeys(Set(modelUuid)), structure = updatedStructure, ) } - override def handleExtMessage(tick: Long, extMSg: EmDataMessageFromExt)( - implicit log: Logger + override def handleExtMessage(tick: Long, extMSg: EmDataMessageFromExt)(using + log: Logger ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMSg match { case requestEmFlexResults: RequestEmFlexResults => val tick = requestEmFlexResults.tick @@ -90,7 +89,7 @@ final case class EmServiceBaseCore( log.warn(s"Disaggregated flex options are currently not supported!") } - emEntities.map(uuidToFlexAdapter).foreach { ref => + emEntities.map(uuidToAgent).foreach { ref => ref ! FlexActivation(tick) } @@ -112,7 +111,7 @@ final case class EmServiceBaseCore( val tick = provideEmSetPoints.tick val emEntities = provideEmSetPoints.emSetPoints.keySet.asScala - emEntities.map(uuidToFlexAdapter).foreach { ref => + emEntities.map(uuidToAgent).foreach { ref => ref ! FlexActivation(tick) } @@ -134,8 +133,8 @@ final case class EmServiceBaseCore( override def handleFlexResponse( tick: Long, flexResponse: FlexResponse, - receiver: Either[UUID, ActorRef[FlexResponse]], - )(implicit + receiver: Either[UUID, ActorRef[EmAgent.Message]], + )(using startTime: ZonedDateTime, log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { @@ -149,7 +148,7 @@ final case class EmServiceBaseCore( MinMaxFlexOptions(ref, min, max), ) => val result = new ExtendedFlexOptionsResult( - tick.toDateTime(startTime), + tick.toDateTime, modelUuid, modelUuid, min.toQuantity, @@ -252,8 +251,8 @@ final case class EmServiceBaseCore( override def handleFlexRequest( flexRequest: FlexRequest, - receiver: ActorRef[FlexRequest], - )(implicit + receiver: ActorRef[EmAgent.Message], + )(using startTime: ZonedDateTime, log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { 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 7ebbae9641..c56c9c95ab 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -9,15 +9,12 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.{PValue, SValue} import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.em.ontology.* -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - WrappedFlexRequest, - WrappedFlexResponse, -} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - RegisterForEmDataService, +import edu.ie3.simona.ontology.messages.ServiceMessage.{ + EmFlexMessage, + EmServiceRegistration, ServiceResponseMessage, } +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.util.quantities.QuantityUtils.asMegaWatt import edu.ie3.util.scala.quantities.QuantityConversionUtils.PowerConversionSimona @@ -35,7 +32,7 @@ import scala.jdk.OptionConverters.RichOptional trait EmServiceCore { def lastFinishedTick: Long - def uuidToFlexAdapter: Map[UUID, ActorRef[FlexRequest]] + def uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] def completions: ReceiveDataMap[UUID, FlexCompletion] @@ -44,27 +41,34 @@ trait EmServiceCore { } def handleRegistration( - registerForEmDataService: RegisterForEmDataService + emServiceRegistration: EmServiceRegistration ): EmServiceCore def handleExtMessage( tick: Long, extMSg: EmDataMessageFromExt, - )(implicit + )(using log: Logger ): (EmServiceCore, Option[EmDataResponseMessageToExt]) final def handleDataResponseMessage( tick: Long, responseMsg: ServiceResponseMessage, - )(implicit + )(using startTime: ZonedDateTime, log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = responseMsg match { - case WrappedFlexRequest(flexRequest, receiver) => - handleFlexRequest(flexRequest, receiver) + case EmFlexMessage(flexRequest: FlexRequest, receiver) => + receiver match { + case Left(value) => + // should not happen + log.warn(s"No receiver found for msg: $flexRequest") + (this, None) + case Right(emAgent) => + handleFlexRequest(flexRequest, emAgent) + } - case WrappedFlexResponse(flexResponse, receiver) => + case EmFlexMessage(flexResponse: FlexResponse, receiver) => handleFlexResponse(tick, flexResponse, receiver) } @@ -77,7 +81,7 @@ trait EmServiceCore { provideEmSetPoints.emSetPoints.asScala .foreach { case (agent, setPoint) => - uuidToFlexAdapter.get(agent) match { + uuidToAgent.get(agent) match { case Some(receiver) => val (pOption, qOption) = setPoint.power.toScala match { case Some(sValue: SValue) => @@ -106,22 +110,22 @@ trait EmServiceCore { def handleFlexResponse( tick: Long, flexResponse: FlexResponse, - receiver: Either[UUID, ActorRef[FlexResponse]], - )(implicit + receiver: Either[UUID, ActorRef[EmAgent.Message]], + )(using startTime: ZonedDateTime, log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) def handleFlexRequest( flexRequest: FlexRequest, - receiver: ActorRef[FlexRequest], - )(implicit + receiver: ActorRef[EmAgent.Message], + )(using startTime: ZonedDateTime, log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) final def getMaybeNextTick: Option[java.lang.Long] = completions.receivedData - .flatMap { case (k, completion) => + .flatMap { case (_, completion) => completion.requestAtTick } .minOption @@ -131,8 +135,8 @@ trait EmServiceCore { object EmServiceCore { final case class EmRefMaps( - private val refToUuid: Map[ActorRef[EmAgent.Request], UUID] = Map.empty, - private val uuidToRef: Map[UUID, ActorRef[EmAgent.Request]] = Map.empty, + private val refToUuid: Map[ActorRef[EmAgent.Message], UUID] = Map.empty, + private val uuidToRef: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, private val uuidToFlexResponse: Map[UUID, ActorRef[FlexResponse]] = Map.empty, private val flexResponseToUuid: Map[ActorRef[FlexResponse], UUID] = @@ -141,7 +145,7 @@ object EmServiceCore { def add( model: UUID, - ref: ActorRef[EmAgent.Request], + ref: ActorRef[EmAgent.Message], parentEm: Option[ActorRef[FlexResponse]] = None, parentUuid: Option[UUID] = None, ): EmRefMaps = parentEm.zip(parentUuid) match { 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 3d1e05bc07..461ebf6294 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -7,26 +7,14 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.ontology._ +import edu.ie3.simona.api.data.em.ontology.* import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException -import edu.ie3.simona.exceptions.{ - CriticalFailureException, - InitializationException, - ServiceException, -} -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ -import edu.ie3.simona.ontology.messages.services.EmMessage -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - WrappedFlexRequest, - WrappedFlexResponse, -} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - RegisterForEmDataService, - ServiceRegistrationMessage, - ServiceResponseMessage, -} +import edu.ie3.simona.exceptions.{InitializationException, ServiceException} +import edu.ie3.simona.ontology.messages.ServiceMessage.* +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* +import edu.ie3.simona.ontology.messages.{Activation, ServiceMessage} import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceBaseStateData, @@ -41,45 +29,40 @@ import java.time.ZonedDateTime import java.util.UUID import scala.util.{Failure, Success, Try} -object ExtEmDataService - extends SimonaService[EmMessage] - with ExtDataSupport[EmMessage] { +object ExtEmDataService extends SimonaService with ExtDataSupport { private val log: Logger = LoggerFactory.getLogger(ExtEmDataService.getClass) override type S = ExtEmDataStateData def emServiceResponseAdapter( - emService: ActorRef[EmMessage], - receiver: Option[ActorRef[FlexResponse]], - self: UUID, - )(implicit ctx: ActorContext[EmAgent.Request]): ActorRef[FlexResponse] = { - - val request = Behaviors.receiveMessagePartial[FlexResponse] { - case response: FlexResponse => - emService ! WrappedFlexResponse( - response, - receiver.map(Right(_)).getOrElse(Left(self)), - ) + emService: ActorRef[ServiceResponseMessage], + receiver: Either[UUID, ActorRef[EmAgent.Message]], + )(using ctx: ActorContext[EmAgent.Message]): ActorRef[FlexResponse] = { + + val request = Behaviors.receiveMessagePartial[FlexResponse] { msg => + emService ! EmFlexMessage( + msg, + receiver, + ) - Behaviors.same + Behaviors.same } ctx.spawn(request, "response-adapter") } def emServiceRequestAdapter( - emService: ActorRef[EmMessage], - receiver: ActorRef[FlexRequest], - )(implicit ctx: ActorContext[EmAgent.Request]): ActorRef[FlexRequest] = { - val response = Behaviors.receiveMessagePartial[FlexRequest] { - case request: FlexRequest => - emService ! WrappedFlexRequest( - request, - receiver, - ) + emService: ActorRef[ServiceResponseMessage], + receiver: ActorRef[EmAgent.Message], + )(using ctx: ActorContext[EmAgent.Message]): ActorRef[FlexRequest] = { + val response = Behaviors.receiveMessagePartial[FlexRequest] { msg => + emService ! EmFlexMessage( + msg, + Right(receiver), + ) - Behaviors.same + Behaviors.same } ctx.spawn(response, "request-adapter") @@ -100,10 +83,10 @@ object ExtEmDataService override protected def handleServiceResponse( serviceResponse: ServiceResponseMessage - )(implicit - ctx: ActorContext[EmMessage] + )(using + ctx: ActorContext[Message] ): Unit = serviceResponse match { - case WrappedFlexResponse( + case EmFlexMessage( scheduleFlexActivation: ScheduleFlexActivation, receiver, ) => @@ -150,15 +133,15 @@ object ExtEmDataService registrationMessage: ServiceRegistrationMessage )(implicit serviceStateData: ExtEmDataStateData, - ctx: ActorContext[EmMessage], + ctx: ActorContext[Message], ): Try[ExtEmDataStateData] = registrationMessage match { - case registrationMsg: RegisterForEmDataService => + case emServiceRegistration: EmServiceRegistration => val updatedCore = - serviceStateData.serviceCore.handleRegistration(registrationMsg) + serviceStateData.serviceCore.handleRegistration(emServiceRegistration) - if (registrationMsg.parentEm.isEmpty) { - registrationMsg.flexAdapter ! FlexActivation(INIT_SIM_TICK) + if (emServiceRegistration.parentEm.isEmpty) { + emServiceRegistration.requestingActor ! FlexActivation(INIT_SIM_TICK) } Success(serviceStateData.copy(serviceCore = updatedCore)) @@ -170,9 +153,9 @@ object ExtEmDataService ) } - override protected def announceInformation(tick: Long)(implicit + override protected def announceInformation(tick: Long)(using serviceStateData: ExtEmDataStateData, - ctx: ActorContext[EmMessage], + ctx: ActorContext[Message], ): (ExtEmDataStateData, Option[Long]) = { val stateTick = serviceStateData.tick @@ -202,7 +185,9 @@ object ExtEmDataService ) val (updatedCore, msgToExt) = - serviceStateData.serviceCore.handleExtMessage(tick, extMsg)(ctx.log) + serviceStateData.serviceCore.handleExtMessage(tick, extMsg)(using + ctx.log + ) msgToExt.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) @@ -219,7 +204,7 @@ object ExtEmDataService override protected def handleDataMessage( extMsg: DataMessageFromExt - )(implicit + )(using serviceStateData: ExtEmDataStateData ): ExtEmDataStateData = { extMsg match { @@ -232,7 +217,7 @@ object ExtEmDataService override protected def handleDataResponseMessage( extResponseMsg: ServiceResponseMessage - )(implicit + )(using serviceStateData: ExtEmDataStateData ): ExtEmDataStateData = { @@ -240,7 +225,7 @@ object ExtEmDataService serviceStateData.serviceCore.handleDataResponseMessage( serviceStateData.tick, extResponseMsg, - )(serviceStateData.startTime, log) + )(using serviceStateData.startTime, log) extMsg.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) 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 ca2922fffb..f8b5f3847d 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -22,7 +22,7 @@ import edu.ie3.simona.exceptions.{ ServiceException, } import edu.ie3.simona.model.participant.evcs.EvModelWrapper -import edu.ie3.simona.ontology.messages.{Activation, ServiceMessage} +import edu.ie3.simona.ontology.messages.ServiceMessage import edu.ie3.simona.ontology.messages.ServiceMessage.* import edu.ie3.simona.service.Data.SecondaryData.ArrivingEvs import edu.ie3.simona.service.ServiceStateData.{ diff --git a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala index 28079a97da..a3d2e93419 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala @@ -19,9 +19,9 @@ import edu.ie3.simona.api.data.primarydata.ontology.{ } import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{InitializationException, ServiceException} -import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ +import edu.ie3.simona.ontology.messages.ServiceMessage.{ PrimaryServiceRegistrationMessage, + ServiceRegistrationMessage, ServiceResponseMessage, } import edu.ie3.simona.service.Data.PrimaryData @@ -39,9 +39,7 @@ import scala.jdk.CollectionConverters.MapHasAsScala import scala.jdk.OptionConverters.RichOptional import scala.util.{Failure, Success, Try} -object ExtPrimaryDataService - extends SimonaService[ServiceMessage] - with ExtDataSupport[ServiceMessage] { +object ExtPrimaryDataService extends SimonaService with ExtDataSupport { override type S = ExtPrimaryDataStateData @@ -79,10 +77,10 @@ object ExtPrimaryDataService } override protected def handleRegistrationRequest( - registrationMessage: ServiceMessage.ServiceRegistrationMessage - )(implicit + registrationMessage: ServiceRegistrationMessage + )(using serviceStateData: ExtPrimaryDataStateData, - ctx: ActorContext[ServiceMessage], + ctx: ActorContext[Message], ): Try[ExtPrimaryDataStateData] = registrationMessage match { case PrimaryServiceRegistrationMessage( requestingActor, @@ -100,9 +98,9 @@ object ExtPrimaryDataService private def handleRegistrationRequest( agentToBeRegistered: ActorRef[ParticipantAgent.Request], agentUUID: UUID, - )(implicit + )(using serviceStateData: ExtPrimaryDataStateData, - ctx: ActorContext[ServiceMessage], + ctx: ActorContext[Message], ): ExtPrimaryDataStateData = { serviceStateData.uuidToActorRef.get(agentUUID) match { case None => @@ -152,9 +150,9 @@ object ExtPrimaryDataService */ override protected def announceInformation( tick: Long - )(implicit + )(using serviceStateData: ExtPrimaryDataStateData, - ctx: ActorContext[ServiceMessage], + ctx: ActorContext[Message], ): (ExtPrimaryDataStateData, Option[Long]) = { // We got activated for this tick, so we expect incoming primary data serviceStateData.extPrimaryDataMessage.getOrElse( throw ServiceException( @@ -162,19 +160,16 @@ object ExtPrimaryDataService ) ) match { case providedPrimaryData: ProvidePrimaryData => - processDataAndAnnounce(tick, providedPrimaryData)( - serviceStateData, - ctx, - ) + processDataAndAnnounce(tick, providedPrimaryData) } } private def processDataAndAnnounce( tick: Long, primaryDataMessage: ProvidePrimaryData, - )(implicit + )(using serviceStateData: ExtPrimaryDataStateData, - ctx: ActorContext[ServiceMessage], + ctx: ActorContext[Message], ): ( ExtPrimaryDataStateData, Option[Long], @@ -228,7 +223,7 @@ object ExtPrimaryDataService override protected def handleDataMessage( extMsg: DataMessageFromExt - )(implicit + )(using serviceStateData: ExtPrimaryDataStateData ): ExtPrimaryDataStateData = { extMsg match { diff --git a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala index 33e838557b..113cc62292 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala @@ -359,13 +359,13 @@ object PrimaryServiceProxy { case Some(timeSeriesUuid) => /* There is a time series apparent for this model, try to get a worker for it */ val updatedStateData = handleCoveredModel( - modelUuid, - timeSeriesUuid, - stateData, - requestingActor, - )(using constantData, ctx) + modelUuid, + timeSeriesUuid, + stateData, + requestingActor, + )(using scheduler, ctx) - onMessage(updatedStateData) + onMessage(updatedStateData) case None => ctx.log.debug( diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala index 25e496cc89..678d635682 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala @@ -15,8 +15,8 @@ import edu.ie3.simona.api.data.results.ontology.{ ResultDataMessageFromExt, } import edu.ie3.simona.exceptions.{InitializationException, ServiceException} -import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ +import edu.ie3.simona.ontology.messages.ServiceMessage.{ + ResultResponseMessage, ServiceRegistrationMessage, ServiceResponseMessage, } @@ -27,7 +27,6 @@ import edu.ie3.simona.service.ServiceStateData.{ import edu.ie3.simona.service.{ExtDataSupport, SimonaService} import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK -import edu.ie3.util.TimeUtil import org.apache.pekko.actor.typed.scaladsl.ActorContext import java.time.ZonedDateTime @@ -35,21 +34,10 @@ import java.util.UUID import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava} import scala.util.{Failure, Success, Try} -object ExtResultProvider - extends SimonaService[ServiceMessage] - with ExtDataSupport[ServiceMessage] { +object ExtResultProvider extends SimonaService with ExtDataSupport { override type S = ExtResultStateData - final case class ResultResponseMessage(result: ResultEntity) - extends ServiceResponseMessage { - def tick(implicit startTime: ZonedDateTime): Long = - TimeUtil.withDefaults.zonedDateTimeDifferenceInSeconds( - startTime, - result.getTime, - ) - } - final case class ExtResultStateData( extResultDataConnection: ExtResultDataConnection, powerFlowResolution: Long, @@ -108,9 +96,9 @@ object ExtResultProvider override protected def handleRegistrationRequest( registrationMessage: ServiceRegistrationMessage - )(implicit + )(using serviceStateData: ExtResultStateData, - ctx: ActorContext[ServiceMessage], + ctx: ActorContext[Message], ): Try[ExtResultStateData] = { // this should not happen ctx.log.warn( @@ -119,9 +107,9 @@ object ExtResultProvider Success(serviceStateData) } - override protected def announceInformation(tick: Long)(implicit + override protected def announceInformation(tick: Long)(using serviceStateData: ExtResultStateData, - ctx: ActorContext[ServiceMessage], + ctx: ActorContext[Message], ): (ExtResultStateData, Option[Long]) = { val extMsg = serviceStateData.extResultsMessage.getOrElse( @@ -207,7 +195,7 @@ object ExtResultProvider override protected def handleDataMessage( extMsg: DataMessageFromExt - )(implicit serviceStateData: ExtResultStateData): ExtResultStateData = + )(using serviceStateData: ExtResultStateData): ExtResultStateData = extMsg match { case extMsg: ResultDataMessageFromExt => serviceStateData.copy( @@ -217,7 +205,7 @@ object ExtResultProvider override protected def handleDataResponseMessage( extResponseMsg: ServiceResponseMessage - )(implicit serviceStateData: ExtResultStateData): ExtResultStateData = + )(using serviceStateData: ExtResultStateData): ExtResultStateData = extResponseMsg match { case ResultResponseMessage(result) => val receiveDataMap = serviceStateData.receiveDataMap diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala index 0e4f3b1388..9116e8e3d7 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala @@ -6,7 +6,7 @@ package edu.ie3.simona.service.results -import edu.ie3.simona.service.results.ExtResultProvider.ResultResponseMessage +import edu.ie3.simona.ontology.messages.ServiceMessage.ResultResponseMessage import java.util.UUID diff --git a/src/main/scala/edu/ie3/simona/service/weather/WeatherService.scala b/src/main/scala/edu/ie3/simona/service/weather/WeatherService.scala index 3d1fb17122..93874a07e1 100644 --- a/src/main/scala/edu/ie3/simona/service/weather/WeatherService.scala +++ b/src/main/scala/edu/ie3/simona/service/weather/WeatherService.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.service.weather +import edu.ie3.datamodel.models.input.NodeInput import edu.ie3.simona.agent.participant.ParticipantAgent import edu.ie3.simona.agent.participant.ParticipantAgent.{ DataProvision, @@ -52,6 +53,17 @@ object WeatherService extends SimonaService { longitude: Double, ) + object Coordinate { + def apply(node: NodeInput): Coordinate = { + val geoPosition = node.getGeoPosition + + Coordinate( + geoPosition.getY, + geoPosition.getX, + ) + } + } + /** @param weatherSource * weather source to receive information from * @param coordsToActorRefMap diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index 289404a27b..af53fa15a4 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -16,14 +16,8 @@ import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt import edu.ie3.simona.api.simulation.{ExtSimAdapterData, ExtSimulation} import edu.ie3.simona.api.{ExtLinkInterface, ExtSimAdapter} import edu.ie3.simona.exceptions.ServiceException -import edu.ie3.simona.ontology.messages.ServiceMessage.ServiceResponseMessage -import edu.ie3.simona.ontology.messages.{ - Activation, - SchedulerMessage, - ServiceMessage, -} +import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.scheduler.ScheduleLock -import edu.ie3.simona.service.ExtDataSupport import edu.ie3.simona.service.ServiceStateData.InitializeServiceStateData import edu.ie3.simona.service.em.ExtEmDataService import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData @@ -34,8 +28,8 @@ import edu.ie3.simona.service.primary.ExtPrimaryDataService.InitExtPrimaryData import edu.ie3.simona.service.results.ExtResultProvider import edu.ie3.simona.service.results.ExtResultProvider.InitExtResultData import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK +import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext -import org.apache.pekko.actor.typed.{ActorRef, Behavior} import org.slf4j.{Logger, LoggerFactory} import java.time.ZonedDateTime @@ -157,12 +151,15 @@ object ExtSimSetup { case (setupData, connection) => connection match { case extPrimaryDataConnection: ExtPrimaryDataConnection => - val serviceRef = setupInputService( - extPrimaryDataConnection, - ExtPrimaryDataService.apply(scheduler), - ExtPrimaryDataService.adapter, + val serviceRef = context.spawn( + ExtPrimaryDataService(scheduler), "ExtPrimaryDataService", - InitExtPrimaryData(extPrimaryDataConnection), + ) + + setupService( + extPrimaryDataConnection, + serviceRef, + InitExtPrimaryData.apply, ) extSimSetupData.update(extPrimaryDataConnection, serviceRef) @@ -180,12 +177,15 @@ object ExtSimSetup { ) setupData } else { - val serviceRef = setupInputService( - extEmDataConnection, - ExtEmDataService.apply(scheduler), - ExtEmDataService.adapter, + val serviceRef = context.spawn( + ExtEmDataService(scheduler), "ExtEmDataService", - InitExtEmData(extEmDataConnection, startTime), + ) + + setupService( + extEmDataConnection, + serviceRef, + InitExtEmData(_, startTime), ) extSimSetupData.update(extEmDataConnection, serviceRef) @@ -212,7 +212,20 @@ object ExtSimSetup { extSimSetupData.update(extEvDataConnection, serviceRef) case extResultDataConnection: ExtResultDataConnection => - extResultDataSetup(setupData, extResultDataConnection) + val extResultProvider = context.spawn( + ExtResultProvider(scheduler), + s"ExtResultDataProvider", + ) + + val powerFlowResolution = resolution.toSeconds + + setupService( + extResultDataConnection, + extResultProvider, + InitExtResultData(_, powerFlowResolution, startTime), + ) + + extSimSetupData.update(extResultDataConnection, extResultProvider) case otherConnection => log.warn( @@ -248,7 +261,7 @@ object ExtSimSetup { * The reference to the service. */ private[setup] def setupService[ - C <: ExtInputDataConnection, + C <: ExtInputDataConnection[?], M, ]( extInputDataConnection: C, @@ -274,68 +287,6 @@ object ExtSimSetup { ) } - /** Method to set up an external result data service. - * - * @param extSimSetupData - * that contains information about all external simulations - * @param extResultDataConnection - * the data connection - * @param context - * the actor context of this actor system - * @param scheduler - * the scheduler of simona - * @param extSimAdapter - * the adapter for the external simulation - * @param startTime - * Of the simulation. - * @param resolution - * Of the power flow. - * @return - * an updated [[ExtSimSetupData]] - */ - private[setup] def extResultDataSetup( - extSimSetupData: ExtSimSetupData, - extResultDataConnection: ExtResultDataConnection, - )(implicit - context: ActorContext[_], - scheduler: ActorRef[SchedulerMessage], - extSimAdapter: ActorRef[ControlResponseMessageFromExt], - startTime: ZonedDateTime, - resolution: FiniteDuration, - ): ExtSimSetupData = { - val extResultProvider = context.spawn( - ExtResultProvider(scheduler), - s"ExtResultDataProvider", - ) - - val adapter = context.spawn( - ExtResultProvider.adapter(extResultProvider), - s"ExtResultDataProvider-adapter-to-external", - ) - - extResultDataConnection.setActorRefs( - adapter, - extSimAdapter, - ) - - val powerFlowResolution = resolution.toSeconds - - extResultProvider ! Create( - InitExtResultData( - extResultDataConnection, - powerFlowResolution, - startTime, - ), - ScheduleLock.singleKey( - context, - scheduler, - PRE_INIT_TICK, - ), - ) - - extSimSetupData.update(extResultDataConnection, extResultProvider) - } - /** Method for validating the external primary data connections. * @param extPrimaryDataConnection * All external primary data connections. diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index 81d48f5c53..f810ef9794 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -14,6 +14,7 @@ import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection import edu.ie3.simona.api.data.results.ExtResultDataConnection import edu.ie3.simona.ontology.messages.ServiceMessage +import edu.ie3.simona.service.em.ExtEmDataService import org.apache.pekko.actor.typed.ActorRef /** Case class that holds information regarding the external data connections as @@ -34,7 +35,7 @@ final case class ExtSimSetupData( (ExtPrimaryDataConnection, ActorRef[ServiceMessage]) ], extDataServices: Seq[ - (? <: ExtInputDataConnection, ActorRef[ServiceMessage]) + (? <: ExtInputDataConnection[?], ActorRef[ServiceMessage]) ], extResultListeners: Seq[(ExtResultDataConnection, ActorRef[ServiceMessage])], ) { @@ -48,7 +49,7 @@ final case class ExtSimSetupData( ) private[setup] def update( - connection: ExtInputDataConnection, + connection: ExtInputDataConnection[?], ref: ActorRef[ServiceMessage], ): ExtSimSetupData = connection match { case primaryConnection: ExtPrimaryDataConnection => @@ -73,9 +74,10 @@ final case class ExtSimSetupData( case (_: ExtEvDataConnection, ref: ActorRef[ServiceMessage]) => ref } - def emDataService: Option[ActorRef[EmMessage]] = + def emDataService: Option[ActorRef[ExtEmDataService.Message]] = extDataServices.collectFirst { - case (_: ExtEmDataConnection, ref: ActorRef[EmMessage]) => ref + case (_: ExtEmDataConnection, ref: ActorRef[ExtEmDataService.Message]) => + ref } def resultDataServices: Iterable[ActorRef[ServiceMessage]] = diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala index 9c719e6b65..a3fe717394 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala @@ -14,22 +14,21 @@ import edu.ie3.simona.event.ResultEvent.{ } import edu.ie3.simona.event.notifier.NotifierConfig import edu.ie3.simona.ontology.messages.SchedulerMessage -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ -import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions -import edu.ie3.simona.ontology.messages.services.EmMessage -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - WrappedFlexRequest, - WrappedFlexResponse, +import edu.ie3.simona.ontology.messages.ServiceMessage.{ + EmFlexMessage, + EmServiceRegistration, } -import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegisterForEmDataService +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* +import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.service.Data.PrimaryData.ComplexPower +import edu.ie3.simona.service.em.ExtEmDataService import edu.ie3.simona.test.common.input.EmInputTestData import edu.ie3.simona.test.matchers.SquantsMatchers import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.util.TimeUtil import edu.ie3.util.quantities.QuantityMatchers.equalWithTolerance -import edu.ie3.util.quantities.QuantityUtils.{asMegaWatt, asMegaVar} +import edu.ie3.util.quantities.QuantityUtils.{asMegaVar, asMegaWatt} import edu.ie3.util.scala.quantities.{Kilovars, ReactivePower} import org.apache.pekko.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, @@ -52,7 +51,7 @@ class EmAgentWithServiceSpec with MockitoSugar with SquantsMatchers { - protected implicit val simulationStartDate: ZonedDateTime = + protected given simulationStartDate: ZonedDateTime = TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:00:00Z") private val outputConfig = NotifierConfig( @@ -69,9 +68,9 @@ class EmAgentWithServiceSpec "be initialized correctly and run through some activations" in { val resultListener = TestProbe[ResultEvent]("ResultListener") - val parentEmAgent = TestProbe[FlexResponse]("ParentEmAgent") + val parentEmAgent = TestProbe[EmAgent.Message]("ParentEmAgent") - val service = TestProbe[EmMessage]("emService") + val service = TestProbe[ExtEmDataService.Message]("emService") val serviceRef = service.ref val emAgent = spawn( @@ -92,20 +91,19 @@ class EmAgentWithServiceSpec emAgent ! ScheduleFlexActivation(pvInput.getUuid, INIT_SIM_TICK) val emAgentFlex = - service.expectMessageType[RegisterForEmDataService] match { - case RegisterForEmDataService( - modelUuid, + service.expectMessageType[EmServiceRegistration] match { + case EmServiceRegistration( requestingActor, - flexAdapter, + inputUuid, parentEm, parentUuid, ) => - modelUuid shouldBe emInput.getUuid requestingActor shouldBe emAgent + inputUuid shouldBe emInput.getUuid parentEm shouldBe Some(parentEmAgent.ref) parentUuid shouldBe None - flexAdapter + requestingActor } parentEmAgent @@ -113,7 +111,7 @@ class EmAgentWithServiceSpec .inputModel shouldBe emInput service.expectMessage( - WrappedFlexResponse( + EmFlexMessage( ScheduleFlexActivation(emInput.getUuid, INIT_SIM_TICK), Right(parentEmAgent.ref), ) @@ -150,7 +148,7 @@ class EmAgentWithServiceSpec resultListener.expectNoMessage() // expect completion from EmAgent service.expectMessage( - WrappedFlexResponse( + EmFlexMessage( FlexCompletion( modelUuid = emInput.getUuid, requestAtTick = Some(0), @@ -191,14 +189,14 @@ class EmAgentWithServiceSpec resultListener.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(flexResult) => flexResult.getInputModel shouldBe emInput.getUuid - flexResult.getTime shouldBe 0.toDateTime(simulationStartDate) + flexResult.getTime shouldBe 0.toDateTime flexResult.getpRef() should equalWithTolerance(0.asMegaWatt) flexResult.getpMin() should equalWithTolerance(-.016.asMegaWatt) flexResult.getpMax() should equalWithTolerance(.006.asMegaWatt) } - service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse( + service.expectMessageType[EmFlexMessage] match { + case EmFlexMessage( ProvideFlexOptions( modelUuid, MinMaxFlexOptions( @@ -253,13 +251,13 @@ class EmAgentWithServiceSpec resultListener.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid - emResult.getTime shouldBe 0.toDateTime(simulationStartDate) + emResult.getTime shouldBe 0.toDateTime emResult.getP should equalWithTolerance(.006.asMegaWatt) emResult.getQ should equalWithTolerance(.0006.asMegaVar) } - service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse( + service.expectMessageType[EmFlexMessage] match { + case EmFlexMessage( FlexResult(modelUuid, result), Right(receiver), ) => @@ -271,7 +269,7 @@ class EmAgentWithServiceSpec } service.expectMessage( - WrappedFlexResponse( + EmFlexMessage( FlexCompletion( modelUuid = emInput.getUuid, requestAtTick = Some(300), @@ -311,13 +309,13 @@ class EmAgentWithServiceSpec resultListener.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid - emResult.getTime shouldBe 150.toDateTime(simulationStartDate) + emResult.getTime shouldBe 150.toDateTime emResult.getP should equalWithTolerance(0.asMegaWatt) emResult.getQ should equalWithTolerance(0.asMegaVar) } - service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse( + service.expectMessageType[EmFlexMessage] match { + case EmFlexMessage( FlexResult(modelUuid, result), Right(receiver), ) => @@ -328,7 +326,7 @@ class EmAgentWithServiceSpec receiver shouldBe parentEmAgent.ref } service.expectMessage( - WrappedFlexResponse( + EmFlexMessage( FlexCompletion( modelUuid = emInput.getUuid, requestAtTick = Some(600), @@ -343,7 +341,7 @@ class EmAgentWithServiceSpec val resultListener = TestProbe[ResultEvent]("ResultListener") val scheduler = TestProbe[SchedulerMessage]("Scheduler") - val service = TestProbe[EmMessage]("emService") + val service = TestProbe[ExtEmDataService.Message]("emService") val serviceRef = service.ref val parentEmInput = emInput @@ -367,22 +365,18 @@ class EmAgentWithServiceSpec ) ) - val parentEmAgentFlex = - service.expectMessageType[RegisterForEmDataService] match { - case RegisterForEmDataService( - modelUuid, - requestingActor, - flexAdapter, - parentEm, - parentUuid, - ) => - modelUuid shouldBe parentEmInput.getUuid - requestingActor shouldBe parentEmAgent - parentEm shouldBe None - parentUuid shouldBe None - - flexAdapter - } + service.expectMessageType[EmServiceRegistration] match { + case EmServiceRegistration( + requestingActor, + inputUuid, + parentEm, + parentUuid, + ) => + requestingActor shouldBe parentEmAgent + inputUuid shouldBe parentEmInput.getUuid + parentEm shouldBe None + parentUuid shouldBe None + } val emAgent = spawn( EmAgent( @@ -401,25 +395,21 @@ class EmAgentWithServiceSpec emAgent ! RegisterControlledAsset(pvAgent.ref, pvInput) emAgent ! ScheduleFlexActivation(pvInput.getUuid, INIT_SIM_TICK) - val emAgentFlex = - service.expectMessageType[RegisterForEmDataService] match { - case RegisterForEmDataService( - modelUuid, - requestingActor, - flexAdapter, - parentEm, - parentUuid, - ) => - modelUuid shouldBe updatedEmInput.getUuid - requestingActor shouldBe emAgent - parentEm shouldBe Some(parentEmAgent) - parentUuid shouldBe Some(parentEmInput.getUuid) - - flexAdapter - } + service.expectMessageType[EmServiceRegistration] match { + case EmServiceRegistration( + requestingActor, + inputUuid, + parentEm, + parentUuid, + ) => + requestingActor shouldBe emAgent + inputUuid shouldBe updatedEmInput.getUuid + parentEm shouldBe Some(parentEmAgent) + parentUuid shouldBe Some(parentEmInput.getUuid) + } service.expectMessage( - WrappedFlexResponse( + EmFlexMessage( ScheduleFlexActivation(updatedEmInput.getUuid, INIT_SIM_TICK), Right(parentEmAgent), ) @@ -431,7 +421,7 @@ class EmAgentWithServiceSpec ) service.expectMessage( - WrappedFlexResponse( + EmFlexMessage( ScheduleFlexActivation(parentEmInput.getUuid, INIT_SIM_TICK), Left(parentEmInput.getUuid), ) @@ -445,16 +435,16 @@ class EmAgentWithServiceSpec service.expectNoMessage() /* TICK -1 */ - parentEmAgentFlex ! FlexActivation(INIT_SIM_TICK) + parentEmAgent ! FlexActivation(INIT_SIM_TICK) service.expectMessage( - WrappedFlexRequest( + EmFlexMessage( FlexActivation(INIT_SIM_TICK), - emAgentFlex, + Right(emAgent), ) ) - emAgentFlex ! FlexActivation(INIT_SIM_TICK) + emAgent ! FlexActivation(INIT_SIM_TICK) // expect flex activations pvAgent.expectMessage(FlexActivation(INIT_SIM_TICK)) @@ -477,7 +467,7 @@ class EmAgentWithServiceSpec resultListener.expectNoMessage() // expect completion from EmAgent service.expectMessage( - WrappedFlexResponse( + EmFlexMessage( FlexCompletion( modelUuid = updatedEmInput.getUuid, requestAtTick = Some(0), @@ -492,7 +482,7 @@ class EmAgentWithServiceSpec ) service.expectMessage( - WrappedFlexResponse( + EmFlexMessage( FlexCompletion( modelUuid = parentEmInput.getUuid, requestAtTick = Some(0), @@ -502,16 +492,16 @@ class EmAgentWithServiceSpec ) /* TICK 0 */ - parentEmAgentFlex ! FlexActivation(0) + parentEmAgent ! FlexActivation(0) service.expectMessage( - WrappedFlexRequest( + EmFlexMessage( FlexActivation(0), - emAgentFlex, + Right(emAgent), ) ) - emAgentFlex ! FlexActivation(0) + emAgent ! FlexActivation(0) // expect activations and flex requests pvAgent.expectMessage(FlexActivation(0)) @@ -542,14 +532,14 @@ class EmAgentWithServiceSpec resultListener.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(flexResult) => flexResult.getInputModel shouldBe updatedEmInput.getUuid - flexResult.getTime shouldBe 0.toDateTime(simulationStartDate) + flexResult.getTime shouldBe 0.toDateTime flexResult.getpRef() should equalWithTolerance(0.asMegaWatt) flexResult.getpMin() should equalWithTolerance(-.016.asMegaWatt) flexResult.getpMax() should equalWithTolerance(.006.asMegaWatt) } - service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse( + service.expectMessageType[EmFlexMessage] match { + case EmFlexMessage( ProvideFlexOptions( modelUuid, MinMaxFlexOptions( @@ -577,8 +567,8 @@ class EmAgentWithServiceSpec ), ) - service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse( + service.expectMessageType[EmFlexMessage] match { + case EmFlexMessage( ProvideFlexOptions( modelUuid, MinMaxFlexOptions( @@ -597,18 +587,18 @@ class EmAgentWithServiceSpec self shouldBe parentEmInput.getUuid } - parentEmAgentFlex ! IssuePowerControl(0, Kilowatts(6)) + parentEmAgent ! IssuePowerControl(0, Kilowatts(6)) service.expectMessage( - WrappedFlexRequest( + EmFlexMessage( IssuePowerControl(0, Kilowatts(6)), - emAgentFlex, + Right(emAgent), ) ) // issue power control and expect EmAgent to distribute it // we want max power = 6 kW - emAgentFlex ! IssuePowerControl(0, Kilowatts(6)) + emAgent ! IssuePowerControl(0, Kilowatts(6)) // expect issue power control pvAgent.expectMessage(IssueNoControl(0)) @@ -649,13 +639,13 @@ class EmAgentWithServiceSpec resultListener.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe updatedEmInput.getUuid - emResult.getTime shouldBe 0.toDateTime(simulationStartDate) + emResult.getTime shouldBe 0.toDateTime emResult.getP should equalWithTolerance(.006.asMegaWatt) emResult.getQ should equalWithTolerance(.0006.asMegaVar) } - service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse( + service.expectMessageType[EmFlexMessage] match { + case EmFlexMessage( FlexResult(modelUuid, result), Right(receiver), ) => @@ -675,7 +665,7 @@ class EmAgentWithServiceSpec ) service.expectMessage( - WrappedFlexResponse( + EmFlexMessage( FlexCompletion( modelUuid = updatedEmInput.getUuid, requestAtTick = Some(300), @@ -684,17 +674,17 @@ class EmAgentWithServiceSpec ) ) - parentEmAgentFlex ! IssueNoControl(150) + parentEmAgent ! IssueNoControl(150) // TODO: FIX - // service.expectMessage(WrappedFlexRequest(IssueNoControl(150), emAgentFlex)) + // service.expectMessage(EmFlexMessage(IssueNoControl(150), emAgentFlex)) /* TICK 150 */ // The mock parent EM now acts as if the situation changed before tick 300, // so that the flex control changes before new flex option calculations are due // no control means reference power of the latest flex options = 0 kW - emAgentFlex ! IssueNoControl(150) + emAgent ! IssueNoControl(150) // We already sent NoControl at last tick, so we're still at -5 kW pvAgent.expectNoMessage() @@ -720,13 +710,13 @@ class EmAgentWithServiceSpec resultListener.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe updatedEmInput.getUuid - emResult.getTime shouldBe 150.toDateTime(simulationStartDate) + emResult.getTime shouldBe 150.toDateTime emResult.getP should equalWithTolerance(0.asMegaWatt) emResult.getQ should equalWithTolerance(0.asMegaVar) } - service.expectMessageType[WrappedFlexResponse] match { - case WrappedFlexResponse( + service.expectMessageType[EmFlexMessage] match { + case EmFlexMessage( FlexResult(modelUuid, result), Right(receiver), ) => @@ -743,7 +733,7 @@ class EmAgentWithServiceSpec ) service.expectMessage( - WrappedFlexResponse( + EmFlexMessage( FlexCompletion( modelUuid = updatedEmInput.getUuid, requestAtTick = Some(600), diff --git a/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentSpec.scala index 6669e2af16..c147c7255a 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentSpec.scala @@ -663,6 +663,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { val modelFactory = PrimaryDataParticipantModel.Factory( physicalModel, ActivePowerExtra, + 1.0, ) val participantAgent = spawn( @@ -1692,6 +1693,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { val modelFactory = PrimaryDataParticipantModel.Factory( physicalModel, ActivePowerExtra, + 1.0, ) val participantAgent = spawn( diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala index 15d71f0c07..2522b8eb49 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala @@ -6,7 +6,6 @@ package edu.ie3.simona.service.em -import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.agent.participant.ParticipantAgent.{ @@ -23,10 +22,7 @@ import edu.ie3.simona.api.data.em.ontology.{ RequestEmFlexResults, } import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} -import edu.ie3.simona.api.data.ontology.{ - DataMessageFromExt, - ScheduleDataServiceMessage, -} +import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt import edu.ie3.simona.config.RuntimeConfig.{ LoadRuntimeConfig, @@ -39,19 +35,19 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, } -import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ +import edu.ie3.simona.ontology.messages.ServiceMessage.{ Create, PrimaryServiceRegistrationMessage, -} -import edu.ie3.simona.ontology.messages.services.WeatherMessage.{ - RegisterForWeatherMessage, - WeatherData, + SecondaryServiceRegistrationMessage, } import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock +import edu.ie3.simona.service.Data.SecondaryData.WeatherData import edu.ie3.simona.service.ServiceType import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData +import edu.ie3.simona.service.primary.PrimaryServiceProxy +import edu.ie3.simona.service.weather.WeatherService +import edu.ie3.simona.service.weather.WeatherService.Coordinate import edu.ie3.simona.test.common.TestSpawnerTyped import edu.ie3.simona.test.common.input.EmCommunicationTestData import edu.ie3.simona.test.matchers.QuantityMatchers @@ -94,13 +90,14 @@ class ExtEmBaseIT private val emNode4Uuid = UUID.fromString("ff0b995a-86ff-4f4d-987e-e475a64f2180") - private val gridAgent = TestProbe[GridAgent.Request]("GridAgent") + private val gridAgent = TestProbe[GridAgent.Message]("GridAgent") private val resultListener = TestProbe[ResultEvent]("ResultListener") private val primaryServiceProxy = - TestProbe[ServiceMessage]("PrimaryServiceProxy") - private val weatherService = TestProbe[ServiceMessage]("WeatherService") + TestProbe[PrimaryServiceProxy.Message]("PrimaryServiceProxy") + private val weatherService = + TestProbe[WeatherService.Message]("WeatherService") - private val participantRefs = ParticipantRefs( + private given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryServiceProxy.ref, services = Map(ServiceType.WeatherService -> weatherService.ref), @@ -114,14 +111,12 @@ class ExtEmBaseIT val service = spawn(ExtEmDataService(scheduler.ref)) val serviceRef = service.ref - implicit val adapter: ActorRef[DataMessageFromExt] = - spawn(ExtEmDataService.adapter(service)) val connection = new ExtEmDataConnection( List(emSupUuid, emNode3Uuid, emNode4Uuid).asJava, EmMode.BASE, ) - connection.setActorRefs(adapter, extSimAdapter.ref) + connection.setActorRefs(serviceRef, extSimAdapter.ref) val emAgentSup = spawn( EmAgent( @@ -174,8 +169,6 @@ class ExtEmBaseIT SimpleInputContainer(pvNode3), PvRuntimeConfig(), outputConfig, - participantRefs, - simulationParams, Right(emAgentNode3), keys.next(), ) @@ -186,8 +179,6 @@ class ExtEmBaseIT SimpleInputContainer(storageInput), StorageRuntimeConfig(), outputConfig, - participantRefs, - simulationParams, Right(emAgentNode3), keys.next(), ) @@ -198,8 +189,6 @@ class ExtEmBaseIT SimpleInputContainer(pvNode4), PvRuntimeConfig(), outputConfig, - participantRefs, - simulationParams, Right(emAgentNode4), keys.next(), ) @@ -210,8 +199,6 @@ class ExtEmBaseIT SimpleInputContainer(loadInput), LoadRuntimeConfig(), outputConfig, - participantRefs, - simulationParams, Right(emAgentNode4), keys.next(), ) @@ -227,10 +214,9 @@ class ExtEmBaseIT key, ) - val activationMsg = scheduler.expectMessageType[ScheduleActivation] - activationMsg.tick shouldBe INIT_SIM_TICK - activationMsg.unlockKey shouldBe Some(key) - implicit val serviceActivation: ActorRef[Activation] = activationMsg.actor + scheduler.expectMessage( + ScheduleActivation(serviceRef, INIT_SIM_TICK, Some(key)) + ) // we expect a completion for the participant locks scheduler.expectMessage(Completion(lockActivation)) @@ -238,8 +224,8 @@ class ExtEmBaseIT /* INIT */ // activate the service for init tick - serviceActivation ! Activation(INIT_SIM_TICK) - scheduler.expectMessage(Completion(serviceActivation)) + serviceRef ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(serviceRef)) primaryServiceProxy.receiveMessages( 4, @@ -268,10 +254,9 @@ class ExtEmBaseIT // deal with weather service registration weatherService.expectMessage( - RegisterForWeatherMessage( + SecondaryServiceRegistrationMessage( pvAgentNode3, - pvNode3.getNode.getGeoPosition.getY, - pvNode3.getNode.getGeoPosition.getX, + Coordinate(pvNode3.getNode), ) ) @@ -281,10 +266,9 @@ class ExtEmBaseIT pvAgentNode4 ! RegistrationFailedMessage(primaryServiceProxy.ref) weatherService.expectMessage( - RegisterForWeatherMessage( + SecondaryServiceRegistrationMessage( pvAgentNode4, - pvNode4.getNode.getGeoPosition.getY, - pvNode4.getNode.getGeoPosition.getX, + Coordinate(pvNode4.getNode), ) ) pvAgentNode4 ! RegistrationSuccessfulMessage(weatherService.ref, 0L) @@ -318,8 +302,8 @@ class ExtEmBaseIT new RequestEmFlexResults(0L, List(emSupUuid).asJava, false) ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(0L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) + serviceRef ! Activation(0L) val receivedFlexOptions0 = connection .receiveWithType(classOf[FlexOptionsResponse]) @@ -345,8 +329,8 @@ class ExtEmBaseIT connection.sendSetPoints(0L, setPoints0.asJava, Optional.of(900L), log) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(0L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) + serviceRef ! Activation(0L) connection.receiveWithType(classOf[EmCompletion]) @@ -372,8 +356,8 @@ class ExtEmBaseIT new RequestEmFlexResults(900L, List(emSupUuid).asJava, false) ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(900L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) + serviceRef ! Activation(900L) val receivedFlexOptions900 = connection .receiveWithType(classOf[FlexOptionsResponse]) @@ -406,8 +390,8 @@ class ExtEmBaseIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(900L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) + serviceRef ! Activation(900L) connection.receiveWithType(classOf[EmCompletion]) } @@ -435,8 +419,8 @@ class ExtEmBaseIT new RequestEmFlexResults(1800L, List(emSupUuid).asJava, true) ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(1800L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) + serviceRef ! Activation(1800L) val receivedFlexOptions1800 = connection .receiveWithType(classOf[FlexOptionsResponse]) @@ -492,8 +476,8 @@ class ExtEmBaseIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(1800L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) + serviceRef ! Activation(1800L) connection.receiveWithType(classOf[EmCompletion]) @@ -519,8 +503,8 @@ class ExtEmBaseIT new RequestEmFlexResults(2700L, List(emSupUuid).asJava, true) ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(2700L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) + serviceRef ! Activation(2700L) val receivedFlexOptions2700 = connection .receiveWithType(classOf[FlexOptionsResponse]) @@ -578,8 +562,8 @@ class ExtEmBaseIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(2700L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) + serviceRef ! Activation(2700L) connection.receiveWithType(classOf[EmCompletion]) } @@ -614,8 +598,8 @@ class ExtEmBaseIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(3600L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) + serviceRef ! Activation(3600L) connection.receiveWithType(classOf[EmCompletion]) @@ -648,8 +632,8 @@ class ExtEmBaseIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(4500L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) + serviceRef ! Activation(4500L) connection.receiveWithType(classOf[EmCompletion]) diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index d527a5afe5..37b99b9b11 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -44,19 +44,23 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, } -import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ +import edu.ie3.simona.ontology.messages.ServiceMessage.{ Create, PrimaryServiceRegistrationMessage, + SecondaryServiceRegistrationMessage, } -import edu.ie3.simona.ontology.messages.services.WeatherMessage.{ - RegisterForWeatherMessage, - WeatherData, +import edu.ie3.simona.ontology.messages.{ + Activation, + SchedulerMessage, + ServiceMessage, } -import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock +import edu.ie3.simona.service.Data.SecondaryData.WeatherData import edu.ie3.simona.service.ServiceType import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData +import edu.ie3.simona.service.primary.PrimaryServiceProxy +import edu.ie3.simona.service.weather.WeatherService +import edu.ie3.simona.service.weather.WeatherService.Coordinate import edu.ie3.simona.test.common.TestSpawnerTyped import edu.ie3.simona.test.common.input.EmCommunicationTestData import edu.ie3.simona.test.matchers.QuantityMatchers @@ -111,13 +115,14 @@ class ExtEmCommunicationIT private val scheduler = TestProbe[SchedulerMessage]("scheduler") private val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") - private val gridAgent = TestProbe[GridAgent.Request]("GridAgent") + private val gridAgent = TestProbe[GridAgent.Message]("GridAgent") private val resultListener = TestProbe[ResultEvent]("ResultListener") private val primaryServiceProxy = - TestProbe[ServiceMessage]("PrimaryServiceProxy") - private val weatherService = TestProbe[ServiceMessage]("WeatherService") + TestProbe[PrimaryServiceProxy.Message]("PrimaryServiceProxy") + private val weatherService = + TestProbe[WeatherService.Message]("WeatherService") - private val participantRefs = ParticipantRefs( + private given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryServiceProxy.ref, services = Map(ServiceType.WeatherService -> weatherService.ref), @@ -125,11 +130,9 @@ class ExtEmCommunicationIT ) "An ExtEmDataService in communication mode" should { - val service = spawn(ExtEmDataService(scheduler.ref)) - val serviceRef = service.ref - given adapter: ActorRef[DataMessageFromExt] = - spawn(ExtEmDataService.adapter(service)) - connection.setActorRefs(adapter, extSimAdapter.ref) + given service: ActorRef[ExtEmDataService.Message] = + spawn(ExtEmDataService(scheduler.ref)) + connection.setActorRefs(service, extSimAdapter.ref) "with participant agents work correctly" in { val emAgentSup = spawn( @@ -141,7 +144,7 @@ class ExtEmCommunicationIT simulationStart, parent = Left(scheduler.ref), listener = Iterable(resultListener.ref), - Some(serviceRef), + Some(service), ) ) @@ -154,7 +157,7 @@ class ExtEmCommunicationIT simulationStart, parent = Right(emAgentSup), listener = Iterable(resultListener.ref), - Some(serviceRef), + Some(service), ) ) @@ -167,7 +170,7 @@ class ExtEmCommunicationIT simulationStart, parent = Right(emAgentSup), listener = Iterable(resultListener.ref), - Some(serviceRef), + Some(service), ) ) @@ -183,8 +186,6 @@ class ExtEmCommunicationIT SimpleInputContainer(pvNode3), PvRuntimeConfig(), outputConfig, - participantRefs, - simulationParams, Right(emAgentNode3), keys.next(), ), @@ -196,8 +197,6 @@ class ExtEmCommunicationIT SimpleInputContainer(storageInput), StorageRuntimeConfig(), outputConfig, - participantRefs, - simulationParams, Right(emAgentNode3), keys.next(), ), @@ -209,8 +208,6 @@ class ExtEmCommunicationIT SimpleInputContainer(pvNode4), PvRuntimeConfig(), outputConfig, - participantRefs, - simulationParams, Right(emAgentNode4), keys.next(), ), @@ -222,8 +219,6 @@ class ExtEmCommunicationIT SimpleInputContainer(loadInput), LoadRuntimeConfig(), outputConfig, - participantRefs, - simulationParams, Right(emAgentNode4), keys.next(), ), @@ -240,10 +235,9 @@ class ExtEmCommunicationIT key, ) - val activationMsg = scheduler.expectMessageType[ScheduleActivation] - activationMsg.tick shouldBe INIT_SIM_TICK - activationMsg.unlockKey shouldBe Some(key) - given serviceActivation: ActorRef[Activation] = activationMsg.actor + scheduler.expectMessage( + ScheduleActivation(service, INIT_SIM_TICK, Some(key)) + ) // we expect a completion for the participant locks scheduler.expectMessage(Completion(lockActivation)) @@ -251,8 +245,8 @@ class ExtEmCommunicationIT /* INIT */ // activate the service for init tick - serviceActivation ! Activation(INIT_SIM_TICK) - scheduler.expectMessage(Completion(serviceActivation)) + service ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(service)) primaryServiceProxy.receiveMessages( 4, @@ -281,10 +275,9 @@ class ExtEmCommunicationIT // deal with weather service registration weatherService.expectMessage( - RegisterForWeatherMessage( + SecondaryServiceRegistrationMessage( pvAgentNode3, - pvNode3.getNode.getGeoPosition.getY, - pvNode3.getNode.getGeoPosition.getX, + Coordinate(pvNode3.getNode), ) ) @@ -294,10 +287,9 @@ class ExtEmCommunicationIT pvAgentNode4 ! RegistrationFailedMessage(primaryServiceProxy.ref) weatherService.expectMessage( - RegisterForWeatherMessage( + SecondaryServiceRegistrationMessage( pvAgentNode4, - pvNode4.getNode.getGeoPosition.getY, - pvNode4.getNode.getGeoPosition.getX, + Coordinate(pvNode4.getNode), ) ) pvAgentNode4 ! RegistrationSuccessfulMessage(weatherService.ref, 0L) @@ -414,8 +406,7 @@ class ExtEmCommunicationIT flexOptions: Map[UUID, FlexOptions], setPoints: Map[UUID, ComparableQuantity[Power]], )(using - serviceActivation: ActorRef[Activation], - adapter: ActorRef[DataMessageFromExt], + service: ActorRef[ExtEmDataService.Message], pvAgents: Seq[ActorRef[ParticipantAgent.Request]], ): Unit = { @@ -436,8 +427,8 @@ class ExtEmCommunicationIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(tick) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(tick) // we expect to receive a request per inferior em agent val requestsToInferior = connection @@ -471,8 +462,8 @@ class ExtEmCommunicationIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(tick) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(tick) val data = DataProvision( tick, @@ -517,8 +508,8 @@ class ExtEmCommunicationIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(tick) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(tick) // we expect the total flex options of the grid from the superior em agent val totalFlexOptions = connection @@ -548,8 +539,8 @@ class ExtEmCommunicationIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(tick) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(tick) // we expect a new set point for each inferior em agent val inferiorSetPoints = connection @@ -587,8 +578,8 @@ class ExtEmCommunicationIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - serviceActivation ! Activation(tick) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(tick) // we expect a finish message connection.receiveWithType(classOf[EmCompletion]) 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 fd6264bbaa..60f53d2eb4 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -6,11 +6,9 @@ package edu.ie3.simona.service.em -import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.em.model.{ EmSetPoint, - EmSetPointResult, ExtendedFlexOptionsResult, FlexOptions, } @@ -22,21 +20,18 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, } +import edu.ie3.simona.ontology.messages.ServiceMessage.{ + Create, + EmFlexMessage, + EmServiceRegistration, +} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ FlexActivation, - FlexRequest, + FlexCompletion, IssuePowerControl, ProvideFlexOptions, } import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions -import edu.ie3.simona.ontology.messages.services.EmMessage.{ - WrappedFlexRequest, - WrappedFlexResponse, -} -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ - Create, - RegisterForEmDataService, -} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData @@ -49,7 +44,6 @@ import org.apache.pekko.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe, } -import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import org.apache.pekko.testkit.TestKit.awaitCond import org.scalatest.wordspec.AnyWordSpecLike import squants.energy.Kilowatts @@ -83,12 +77,12 @@ class ExtEmDataServiceSpec val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") - val emService = spawn(ExtEmDataService.apply(scheduler.ref)) - val adapter = spawn(ExtEmDataService.adapter(emService)) + val emService = spawn(ExtEmDataService(scheduler.ref)) val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) + extEmDataConnection.setActorRefs( - adapter, + emService, extSimAdapter.ref, ) @@ -102,14 +96,12 @@ class ExtEmDataServiceSpec key, ) - val activationMsg = scheduler.expectMessageType[ScheduleActivation] - activationMsg.tick shouldBe INIT_SIM_TICK - activationMsg.unlockKey shouldBe Some(key) - - val serviceActivation = activationMsg.actor + scheduler.expectMessage( + ScheduleActivation(emService, INIT_SIM_TICK, Some(key)) + ) - serviceActivation ! Activation(INIT_SIM_TICK) - scheduler.expectMessage(Completion(serviceActivation)) + emService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(emService)) } "stash registration request and handle it correctly once initialized" in { @@ -117,23 +109,21 @@ class ExtEmDataServiceSpec val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") - val emService = spawn(ExtEmDataService.apply(scheduler.ref)) - val adapter = spawn(ExtEmDataService.adapter(emService)) + val emService = spawn(ExtEmDataService(scheduler.ref)) val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) + extEmDataConnection.setActorRefs( - adapter, + emService, extSimAdapter.ref, ) - val emAgent = TestProbe[EmAgent.Request]("emAgent") - val emAgentFlex = TestProbe[FlexRequest]("emAgentFlex") + val emAgent = TestProbe[EmAgent.Message]("emAgent") // this one should be stashed - emService ! RegisterForEmDataService( - emInput.getUuid, + emService ! EmServiceRegistration( emAgent.ref, - emAgentFlex.ref, + emInput.getUuid, None, None, ) @@ -150,30 +140,30 @@ class ExtEmDataServiceSpec key, ) - val activationMsg = scheduler.expectMessageType[ScheduleActivation] - activationMsg.tick shouldBe INIT_SIM_TICK - activationMsg.unlockKey shouldBe Some(key) - - val serviceActivation = activationMsg.actor + scheduler.expectMessage( + ScheduleActivation(emService, INIT_SIM_TICK, Some(key)) + ) - serviceActivation ! Activation(INIT_SIM_TICK) - scheduler.expectMessage(Completion(serviceActivation)) + emService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(emService)) } } "An idle em service" must { "fail when activated without having received ExtEmMessage" in { + val emAgent = TestProbe[EmAgent.Message]("emAgent") + val scheduler = TestProbe[SchedulerMessage]("scheduler") val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") - val emService = spawn(ExtEmDataService.apply(scheduler.ref)) - val adapter = spawn(ExtEmDataService.adapter(emService)) + val emService = spawn(ExtEmDataService(scheduler.ref)) val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) + extEmDataConnection.setActorRefs( - adapter, + emService, extSimAdapter.ref, ) @@ -187,21 +177,32 @@ class ExtEmDataServiceSpec key, ) - val activationMsg = scheduler.expectMessageType[ScheduleActivation] - activationMsg.tick shouldBe INIT_SIM_TICK - activationMsg.unlockKey shouldBe Some(key) + scheduler.expectMessage( + ScheduleActivation(emService, INIT_SIM_TICK, Some(key)) + ) + + emService ! Activation(INIT_SIM_TICK) - val serviceActivation = activationMsg.actor + emService ! EmServiceRegistration( + emAgent.ref, + emAgent1UUID, + None, + None, + ) + emAgent.expectMessage(FlexActivation(-1)) + emService ! EmFlexMessage( + FlexCompletion(emAgent1UUID, requestAtTick = Some(0)), + Left(emAgent1UUID), + ) - serviceActivation ! Activation(INIT_SIM_TICK) - scheduler.expectMessage(Completion(serviceActivation)) + scheduler.expectMessage(Completion(emService)) // we trigger em service and expect an exception - serviceActivation ! Activation(0) + emService ! Activation(0) scheduler.expectNoMessage() val deathWatch = createTestProbe("deathWatch") - deathWatch.expectTerminated(emService.ref) + deathWatch.expectTerminated(emService) } "handle flex option request correctly" in { @@ -209,12 +210,12 @@ class ExtEmDataServiceSpec val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") - val emService = spawn(ExtEmDataService.apply(scheduler.ref)) - val adapter = spawn(ExtEmDataService.adapter(emService)) + val emService = spawn(ExtEmDataService(scheduler.ref)) val extEmDataConnection = - new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) + new ExtEmDataConnection(emptyControlled, EmMode.BASE) + extEmDataConnection.setActorRefs( - adapter, + emService, extSimAdapter.ref, ) @@ -228,92 +229,70 @@ class ExtEmDataServiceSpec key, ) - val activationMsg = scheduler.expectMessageType[ScheduleActivation] - activationMsg.tick shouldBe INIT_SIM_TICK - activationMsg.unlockKey shouldBe Some(key) - - val serviceActivation = activationMsg.actor + scheduler.expectMessage( + ScheduleActivation(emService, INIT_SIM_TICK, Some(key)) + ) - serviceActivation ! Activation(INIT_SIM_TICK) - scheduler.expectMessage(Completion(serviceActivation)) + emService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(emService)) - val emAgent1 = TestProbe[EmAgent.Request]("emAgent1") - val emAgentFlex1 = TestProbe[FlexRequest]("emAgentFlex1") - val emAgent2 = TestProbe[EmAgent.Request]("emAgent2") - val emAgentFlex2 = TestProbe[FlexRequest]("emAgentFlex2") + val emAgent1 = TestProbe[EmAgent.Message]("emAgent1") + val emAgent2 = TestProbe[EmAgent.Message]("emAgent2") - emService ! RegisterForEmDataService( - emAgent1UUID, + emService ! EmServiceRegistration( emAgent1.ref, - emAgentFlex1.ref, + emAgent1UUID, None, None, ) - emAgent1.expectNoMessage() + emAgent1.expectMessage(FlexActivation(-1)) + emService ! EmFlexMessage( + FlexCompletion(emAgent1UUID, requestAtTick = Some(0)), + Left(emAgent1UUID), + ) - emService ! RegisterForEmDataService( - emAgent2UUID, + emService ! EmServiceRegistration( emAgent2.ref, - emAgentFlex2.ref, + emAgent2UUID, None, None, ) - emAgent2.expectNoMessage() - - extEmDataConnection.sendExtMsg( - new RequestEmFlexResults( - INIT_SIM_TICK, - List.empty[UUID].asJava, - false, - ) + emAgent2.expectMessage(FlexActivation(-1)) + emService ! EmFlexMessage( + FlexCompletion(emAgent2UUID, requestAtTick = Some(0)), + Left(emAgent2UUID), ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - - scheduler.expectNoMessage() - - serviceActivation ! Activation(INIT_SIM_TICK) - - emAgent1.expectNoMessage() - emAgentFlex1.expectMessage(FlexActivation(-1)) - - emAgent2.expectNoMessage() - emAgentFlex2.expectMessage(FlexActivation(-1)) + // scheduler.expectMessage(Completion(emService)) extEmDataConnection.sendExtMsg( new RequestEmFlexResults( 0, - List(emAgentSupUUID).asJava, + List(emAgent1UUID).asJava, false, ) ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - - // scheduler.expectMessage(Completion(serviceActivation)) - - serviceActivation ! Activation(0) - - emAgent1.expectNoMessage() - emAgentFlex1.expectMessage(FlexActivation(0)) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(emService)) + emService ! Activation(0) + emAgent1.expectMessage(FlexActivation(0)) emAgent2.expectNoMessage() - emAgentFlex2.expectNoMessage() - scheduler.expectMessage(Completion(serviceActivation)) + scheduler.expectMessage(Completion(emService)) extEmDataConnection.receiveTriggerQueue shouldBe empty - emService ! WrappedFlexResponse( + emService ! EmFlexMessage( ProvideFlexOptions( - emAgentSupUUID, + emAgent1UUID, MinMaxFlexOptions( Kilowatts(5), Kilowatts(0), Kilowatts(10), ), ), - Left(emAgentSupUUID), + Left(emAgent1UUID), ) awaitCond( @@ -326,13 +305,13 @@ class ExtEmDataServiceSpec extEmDataConnection.receiveTriggerQueue .take() shouldBe new FlexOptionsResponse( Map( - emAgentSupUUID -> new ExtendedFlexOptionsResult( + emAgent1UUID -> new ExtendedFlexOptionsResult( simulationStart, emAgent1UUID, - emAgentSupUUID, - 0.asKiloWatt, - 5.asKiloWatt, - 10.asKiloWatt, + emAgent1UUID, + 0.asMegaWatt, + 0.005.asMegaWatt, + 0.01.asMegaWatt, ) ).asJava ) @@ -343,12 +322,12 @@ class ExtEmDataServiceSpec val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") - val emService = spawn(ExtEmDataService.apply(scheduler.ref)) - val adapter = spawn(ExtEmDataService.adapter(emService)) + val emService = spawn(ExtEmDataService(scheduler.ref)) val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) + extEmDataConnection.setActorRefs( - adapter, + emService, extSimAdapter.ref, ) @@ -362,43 +341,57 @@ class ExtEmDataServiceSpec key, ) - val activationMsg = scheduler.expectMessageType[ScheduleActivation] - activationMsg.tick shouldBe INIT_SIM_TICK - activationMsg.unlockKey shouldBe Some(key) - - val serviceActivation = activationMsg.actor + scheduler.expectMessage( + ScheduleActivation(emService, INIT_SIM_TICK, Some(key)) + ) - serviceActivation ! Activation(INIT_SIM_TICK) - scheduler.expectMessage(Completion(serviceActivation)) + emService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(emService)) - val emAgent1 = TestProbe[EmAgent.Request]("emAgent1") - val emAgentFlex1 = TestProbe[FlexRequest]("emAgentFlex1") - val emAgent2 = TestProbe[EmAgent.Request]("emAgent2") - val emAgentFlex2 = TestProbe[FlexRequest]("emAgentFlex2") + val emAgentSup = TestProbe[EmAgent.Message]("emAgentSup") + val emAgent1 = TestProbe[EmAgent.Message]("emAgent1") - emService ! RegisterForEmDataService( - emAgent1UUID, + emService ! EmServiceRegistration( emAgent1.ref, - emAgentFlex1.ref, - None, - None, + emAgent1UUID, + Some(emAgentSup.ref), + Some(emAgentSupUUID), ) emAgent1.expectNoMessage() - emService ! RegisterForEmDataService( - emAgent2UUID, - emAgent2.ref, - emAgentFlex2.ref, + emService ! EmServiceRegistration( + emAgentSup.ref, + emAgentSupUUID, None, None, ) - emAgent2.expectNoMessage() + emAgentSup.expectMessage(FlexActivation(-1)) + + // the em agent sup will send an activation message to the em agent1 + emService ! EmFlexMessage(FlexActivation(-1), Right(emAgent1.ref)) + + emAgent1.expectMessage(FlexActivation(-1)) + + // the em agent 1 will answer the activation with a flex completion message + emService ! EmFlexMessage( + FlexCompletion(emAgent1UUID, requestAtTick = Some(0)), + Right(emAgentSup.ref), + ) + + emAgentSup + .expectMessageType[FlexCompletion] + .modelUuid shouldBe emAgent1UUID + + emService ! EmFlexMessage( + FlexCompletion(emAgentSupUUID, requestAtTick = Some(0)), + Left(emAgentSupUUID), + ) extEmDataConnection.sendExtMsg( new ProvideEmFlexOptionData( 0, Map( - emAgent2UUID -> + emAgentSupUUID -> List( new FlexOptions( emAgentSupUUID, @@ -414,12 +407,12 @@ class ExtEmDataServiceSpec ) ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) - - serviceActivation ! Activation(0) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(emService)) + emService ! Activation(0) emAgent1.expectNoMessage() - emAgent2.expectMessage( + emAgentSup.expectMessage( + 60.seconds, ProvideFlexOptions( emAgent1UUID, MinMaxFlexOptions( @@ -427,10 +420,10 @@ class ExtEmDataServiceSpec Kilowatts(-3), Kilowatts(1), ), - ) + ), ) - scheduler.expectMessage(Completion(serviceActivation)) + scheduler.expectMessage(Completion(emService)) } "handle set point provision correctly" in { @@ -438,12 +431,12 @@ class ExtEmDataServiceSpec val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") - val emService = spawn(ExtEmDataService.apply(scheduler.ref)) - val adapter = spawn(ExtEmDataService.adapter(emService)) + val emService = spawn(ExtEmDataService(scheduler.ref)) val extEmDataConnection = new ExtEmDataConnection(emptyControlled, EmMode.EM_COMMUNICATION) + extEmDataConnection.setActorRefs( - adapter, + emService, extSimAdapter.ref, ) @@ -457,39 +450,39 @@ class ExtEmDataServiceSpec key, ) - val activationMsg = scheduler.expectMessageType[ScheduleActivation] - activationMsg.tick shouldBe INIT_SIM_TICK - activationMsg.unlockKey shouldBe Some(key) - - val serviceActivation = activationMsg.actor + scheduler.expectMessage( + ScheduleActivation(emService, INIT_SIM_TICK, Some(key)) + ) - serviceActivation ! Activation(INIT_SIM_TICK) - scheduler.expectMessage(Completion(serviceActivation)) + emService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(emService)) - val emAgent1 = TestProbe[EmAgent.Request]("emAgent1") - val emAgentFlex1 = TestProbe[FlexRequest]("emAgentFlex1") - val emAgent2 = TestProbe[EmAgent.Request]("emAgent2") - val emAgentFlex2 = TestProbe[FlexRequest]("emAgentFlex2") + val emAgent1 = TestProbe[EmAgent.Message]("emAgent1") + val emAgent2 = TestProbe[EmAgent.Message]("emAgent2") - emService ! RegisterForEmDataService( - emAgent1UUID, + emService ! EmServiceRegistration( emAgent1.ref, - emAgentFlex1.ref, + emAgent1UUID, None, None, ) - emAgent1.expectNoMessage() - emAgentFlex1.expectMessage(FlexActivation(-1)) + emAgent1.expectMessage(FlexActivation(-1)) + emService ! EmFlexMessage( + FlexCompletion(emAgent1UUID, requestAtTick = Some(0)), + Left(emAgent1UUID), + ) - emService ! RegisterForEmDataService( - emAgent2UUID, + emService ! EmServiceRegistration( emAgent2.ref, - emAgentFlex2.ref, + emAgent2UUID, None, None, ) - emAgent2.expectNoMessage() - emAgentFlex2.expectMessage(FlexActivation(-1)) + emAgent2.expectMessage(FlexActivation(-1)) + emService ! EmFlexMessage( + FlexCompletion(emAgent2UUID, requestAtTick = Some(0)), + Left(emAgent2UUID), + ) extEmDataConnection.sendExtMsg( new ProvideEmSetPointData( @@ -502,21 +495,19 @@ class ExtEmDataServiceSpec ) ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(adapter)) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(emService)) - serviceActivation ! Activation(0) + emService ! Activation(0) - emAgent1.expectNoMessage() - emAgentFlex1.expectMessage( + emAgent1.expectMessage( IssuePowerControl(0, Kilowatts(-3)) ) - emAgent2.expectNoMessage() - emAgentFlex2.expectMessage( + emAgent2.expectMessage( IssuePowerControl(0, zeroKW) ) - scheduler.expectMessage(Completion(serviceActivation)) + scheduler.expectMessage(Completion(emService)) } } diff --git a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala index b70f7379ca..8bcc0fe218 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala @@ -18,16 +18,20 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, } -import edu.ie3.simona.ontology.messages.services.ServiceMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ +import edu.ie3.simona.ontology.messages.ServiceMessage.{ + Create, PrimaryServiceRegistrationMessage, - WrappedActivation, + SecondaryServiceRegistrationMessage, +} +import edu.ie3.simona.ontology.messages.{ + Activation, + SchedulerMessage, + ServiceMessage, } -import edu.ie3.simona.ontology.messages.services.WeatherMessage.RegisterForWeatherMessage -import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.Data.PrimaryData import edu.ie3.simona.service.primary.ExtPrimaryDataService.InitExtPrimaryData +import edu.ie3.simona.service.weather.WeatherService.Coordinate import edu.ie3.simona.test.common.TestSpawnerTyped import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.testkit.typed.scaladsl.{ @@ -41,7 +45,7 @@ import org.scalatest.wordspec.AnyWordSpecLike import java.util.UUID import scala.concurrent.duration.DurationInt -import scala.jdk.CollectionConverters._ +import scala.jdk.CollectionConverters.* class ExtPrimaryDataServiceSpec extends ScalaTestWithActorTestKit @@ -56,14 +60,13 @@ class ExtPrimaryDataServiceSpec TestProbe[ControlResponseMessageFromExt]("extSimAdapter") private val extPrimaryDataConnection = new ExtPrimaryDataConnection( - Map.empty[UUID, Class[_ <: Value]].asJava + Map.empty[UUID, Class[? <: Value]].asJava ) "An uninitialized external primary data service" must { "send correct completion message after initialisation" in { - val primaryDataService = spawn(ExtPrimaryDataService.apply(scheduler.ref)) - val adapter = spawn(ExtPrimaryDataService.adapter(primaryDataService)) + val primaryDataService = spawn(ExtPrimaryDataService(scheduler.ref)) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) @@ -71,28 +74,26 @@ class ExtPrimaryDataServiceSpec .expectMessageType[ScheduleActivation] // lock activation scheduled extPrimaryDataConnection.setActorRefs( - adapter, + primaryDataService, extSimAdapter.ref, ) - primaryDataService ! ServiceMessage.Create( + primaryDataService ! Create( InitExtPrimaryData(extPrimaryDataConnection), key, ) - val activationMsg = scheduler.expectMessageType[ScheduleActivation] - activationMsg.tick shouldBe INIT_SIM_TICK - activationMsg.unlockKey shouldBe Some(key) - - val serviceActivation = activationMsg.actor + scheduler.expectMessage( + ScheduleActivation(primaryDataService, INIT_SIM_TICK, Some(key)) + ) - serviceActivation ! Activation(INIT_SIM_TICK) - scheduler.expectMessage(Completion(serviceActivation)) + primaryDataService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(primaryDataService)) } } "An external primary service actor" should { - val serviceRef = spawn(ExtPrimaryDataService.apply(scheduler.ref)) + val primaryDataService = spawn(ExtPrimaryDataService(scheduler.ref)) val systemParticipant = TestProbe[Any]("dummySystemParticipant") "refuse registration for wrong registration request" in { @@ -104,17 +105,16 @@ class ExtPrimaryDataServiceSpec val key = ScheduleLock.singleKey(TSpawner, schedulerProbe.ref, INIT_SIM_TICK) - service ! ServiceMessage.Create( + primaryDataService ! Create( InitExtPrimaryData(extPrimaryDataConnection), key, ) - service ! WrappedActivation(Activation(INIT_SIM_TICK)) + service ! Activation(INIT_SIM_TICK) - service ! RegisterForWeatherMessage( + service ! SecondaryServiceRegistrationMessage( systemParticipant.ref, - 51.4843281, - 7.4116482, + Coordinate(51.4843281, 7.4116482), ) val deathWatch = createTestProbe("deathWatch") @@ -122,23 +122,23 @@ class ExtPrimaryDataServiceSpec } "correctly register a forwarded request" ignore { - serviceRef ! PrimaryServiceRegistrationMessage( + primaryDataService ! PrimaryServiceRegistrationMessage( systemParticipant.ref, UUID.randomUUID(), ) /* Wait for request approval */ - val msg = - systemParticipant.expectMessageType[RegistrationSuccessfulMessage]( - 10.seconds + systemParticipant.expectMessage( + RegistrationSuccessfulMessage( + primaryDataService, + 0L, ) - msg.serviceRef shouldBe serviceRef.ref - msg.firstDataTick shouldBe 0L + ) /* We cannot directly check, if the requesting actor is among the subscribers, therefore we ask the actor to * provide data to all subscribed actors and check, if the subscribed probe gets one */ - serviceRef ! WrappedActivation(Activation(0)) - scheduler.expectMessageType[Completion] + primaryDataService ! Activation(0) + scheduler.expectMessage(Completion(primaryDataService)) systemParticipant.expectMessageType[DataProvision[PrimaryData]] } diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala index 690626154f..dcc9e051c8 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala @@ -27,7 +27,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { "An ExtSimSetupData" should { - val emptyMapInput = Map.empty[UUID, Class[_ <: Value]].asJava + val emptyMapInput = Map.empty[UUID, Class[? <: Value]].asJava val emptyListInput = List.empty[UUID].asJava val emptyResultList = List.empty[UUID].asJava @@ -127,7 +127,8 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { emptyResultList, emptyResultList, ) - val resultRef = TestProbe[ServiceMessage]("result_service").ref + + val resultRef = TestProbe[ServiceMessage]("result_service").ref val updated = extSimSetupData.update(resultConnection, resultRef) diff --git a/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala b/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala index 6a9a44b60d..fd585d133b 100644 --- a/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/input/EmCommunicationTestData.scala @@ -27,9 +27,9 @@ import java.util.UUID trait EmCommunicationTestData extends DefaultTestData { - protected implicit val simulationStart: ZonedDateTime = + protected given simulationStart: ZonedDateTime = TimeUtil.withDefaults.toZonedDateTime("2020-01-01T00:00:00Z") - protected implicit val simulationEnd: ZonedDateTime = + protected given simulationEnd: ZonedDateTime = simulationStart.plusHours(2) protected val simonaConfig: SimonaConfig = createSimonaConfig() @@ -40,7 +40,7 @@ trait EmCommunicationTestData extends DefaultTestData { flexResult = true, // also test FlexOptionsResult if EM-controlled ) - protected val simulationParams: SimulationParameters = SimulationParameters( + protected given SimulationParameters = SimulationParameters( expectedPowerRequestTick = Long.MaxValue, requestVoltageDeviationTolerance = Each(1e-14d), simulationStart = simulationStart, From 7714450c573414894ca9d14c2215ed222dabca4b Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 18 Jun 2025 19:02:24 +0200 Subject: [PATCH 058/125] Fixing bugs. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 23 ++------ .../ontology/messages/ServiceMessage.scala | 2 +- .../service/em/EmCommunicationCore.scala | 35 ++++++++--- .../simona/service/em/EmServiceBaseCore.scala | 4 +- .../ie3/simona/service/em/EmServiceCore.scala | 59 ++++--------------- .../simona/service/em/ExtEmDataService.scala | 24 ++++---- .../ie3/simona/service/em/ExtEmBaseIT.scala | 57 +++++++++--------- .../edu/ie3/simona/test/common/UnitSpec.scala | 2 - 8 files changed, 88 insertions(+), 118 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 52908ef381..e14345ff3c 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -10,25 +10,15 @@ import edu.ie3.datamodel.models.input.EmInput import edu.ie3.datamodel.models.result.system.{EmResult, FlexOptionsResult} import edu.ie3.simona.config.RuntimeConfig.EmRuntimeConfig import edu.ie3.simona.event.ResultEvent -import edu.ie3.simona.event.ResultEvent.{ - FlexOptionsResultEvent, - ParticipantResultEvent, -} +import edu.ie3.simona.event.ResultEvent.{FlexOptionsResultEvent, ParticipantResultEvent} import edu.ie3.simona.event.notifier.NotifierConfig import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.model.em.{EmModelShell, EmTools} -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration import edu.ie3.simona.ontology.messages.flex.FlexOptions import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.{ - Activation, - SchedulerMessage, - ServiceMessage, -} +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.service.Data.PrimaryData.ComplexPower import edu.ie3.simona.service.em.ExtEmDataService import edu.ie3.simona.util.TickUtil.TickLong @@ -38,7 +28,6 @@ import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} import org.apache.pekko.actor.typed.{ActorRef, Behavior} import java.time.ZonedDateTime -import java.util.UUID import scala.jdk.OptionConverters.RichOptional import scala.util.{Failure, Try} @@ -105,10 +94,10 @@ object EmAgent { ) val adaptedParent = parent match { - case Left(value) => - Left(uuid) + case Left(_) => + uuid case Right(value) => - Right(ctx.self) + value } // used by this agent 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 c08505c45b..9e42698481 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/ServiceMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/ServiceMessage.scala @@ -158,7 +158,7 @@ object ServiceMessage { final case class EmFlexMessage( message: FlexRequest | FlexResponse, - receiver: Either[UUID, ActorRef[EmAgent.Message]], + receiver: UUID | ActorRef[FlexResponse] | ActorRef[EmAgent.Message], ) extends ServiceResponseMessage final case class ResultResponseMessage(result: ResultEntity) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index a372fd5221..e1545fa5af 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -18,7 +18,6 @@ import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions import edu.ie3.simona.service.em.EmCommunicationCore.DataMap -import edu.ie3.simona.service.em.EmServiceCore.EmRefMaps import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.simona.util.{ReceiveDataMap, ReceiveHierarchicalDataMap} @@ -42,10 +41,11 @@ import scala.jdk.CollectionConverters.{ final case class EmCommunicationCore( override val lastFinishedTick: Long = PRE_INIT_TICK, override val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, - agentToUuid: Map[ActorRef[EmAgent.Message], UUID] = Map.empty, + agentToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, + uuidToFlexResponse: Map[UUID, ActorRef[FlexResponse]] = Map.empty, + flexResponseToUuid: Map[ActorRef[FlexResponse], UUID] = Map.empty, override val completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, - refs: EmRefMaps = EmRefMaps(), flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, uuidToPRef: Map[UUID, ComparableQuantity[Power]] = Map.empty, toSchedule: Map[UUID, ScheduleFlexActivation] = Map.empty, @@ -64,12 +64,22 @@ final case class EmCommunicationCore( val parentEm = emServiceRegistration.parentEm val parentUuid = emServiceRegistration.parentUuid - val updatedRefs = refs.add(uuid, ref, parentEm, parentUuid) + val (updatedResponseToUuid, updatedUuidToResponse) = parentEm.zip(parentUuid) match { + case Some((parent, uuid)) => + ( + flexResponseToUuid + (parent -> uuid), + uuidToFlexResponse + (uuid -> parent), + ) + + case None => + (flexResponseToUuid, uuidToFlexResponse) + } copy( - refs = updatedRefs, uuidToAgent = uuidToAgent + (uuid -> ref), agentToUuid = agentToUuid + (ref -> uuid), + uuidToFlexResponse = updatedUuidToResponse, + flexResponseToUuid = updatedResponseToUuid, flexRequestReceived = flexRequestReceived.updateStructure(parentUuid, uuid), flexOptionResponse = flexOptionResponse.updateStructure(parentUuid, uuid), @@ -102,6 +112,8 @@ final case class EmCommunicationCore( } case provideFlexRequests: ProvideFlexRequestData => + log.warn(s"Received message: $provideFlexRequests") + // entities for which flex options are requested val emEntities: Set[UUID] = provideFlexRequests .flexRequests() @@ -127,11 +139,14 @@ final case class EmCommunicationCore( ) case provideFlexOptions: ProvideEmFlexOptionData => + log.warn(s"Received message: $provideFlexOptions") + provideFlexOptions .flexOptions() .asScala .foreach { case (agent, flexOptions) => - refs.getResponse(agent) match { + + uuidToAgent.get(agent) match { case Some(receiver) => flexOptions.asScala.foreach { option => receiver ! ProvideFlexOptions( @@ -152,6 +167,8 @@ final case class EmCommunicationCore( (this, None) case providedSetPoints: ProvideEmSetPointData => + log.warn(s"Received message: $providedSetPoints") + handleSetPoint(tick, providedSetPoints, log) (this, None) @@ -160,7 +177,7 @@ final case class EmCommunicationCore( override def handleFlexResponse( tick: Long, flexResponse: FlexResponse, - receiver: Either[UUID, ActorRef[EmAgent.Message]], + receiver: Either[UUID, ActorRef[FlexResponse]], )(using startTime: ZonedDateTime, log: Logger, @@ -199,7 +216,7 @@ final case class EmCommunicationCore( val receiverUuid = receiver match { case Right(otherRef) => - refs.getUuid(otherRef) + flexResponseToUuid(otherRef) case Left(self: UUID) => self } @@ -280,7 +297,7 @@ final case class EmCommunicationCore( override def handleFlexRequest( flexRequest: FlexRequest, - receiver: ActorRef[EmAgent.Message], + receiver: ActorRef[FlexRequest], )(using startTime: ZonedDateTime, log: Logger, 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 1f165c371c..f0c20ffe77 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -133,7 +133,7 @@ final case class EmServiceBaseCore( override def handleFlexResponse( tick: Long, flexResponse: FlexResponse, - receiver: Either[UUID, ActorRef[EmAgent.Message]], + receiver: Either[UUID, ActorRef[FlexResponse]], )(using startTime: ZonedDateTime, log: Logger, @@ -251,7 +251,7 @@ final case class EmServiceBaseCore( override def handleFlexRequest( flexRequest: FlexRequest, - receiver: ActorRef[EmAgent.Message], + receiver: ActorRef[FlexRequest], )(using startTime: ZonedDateTime, log: Logger, 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 c56c9c95ab..1bd8678dff 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -60,16 +60,23 @@ trait EmServiceCore { ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = responseMsg match { case EmFlexMessage(flexRequest: FlexRequest, receiver) => receiver match { - case Left(value) => + case ref: ActorRef[FlexRequest] => + handleFlexRequest(flexRequest, ref) + + case _ => // should not happen log.warn(s"No receiver found for msg: $flexRequest") (this, None) - case Right(emAgent) => - handleFlexRequest(flexRequest, emAgent) } case EmFlexMessage(flexResponse: FlexResponse, receiver) => - handleFlexResponse(tick, flexResponse, receiver) + receiver match { + case uuid: UUID => + handleFlexResponse(tick, flexResponse, Left(uuid)) + + case ref: ActorRef[FlexResponse] => + handleFlexResponse(tick, flexResponse, Right(ref)) + } } final def handleSetPoint( @@ -110,7 +117,7 @@ trait EmServiceCore { def handleFlexResponse( tick: Long, flexResponse: FlexResponse, - receiver: Either[UUID, ActorRef[EmAgent.Message]], + receiver: Either[UUID, ActorRef[FlexResponse]], )(using startTime: ZonedDateTime, log: Logger, @@ -118,7 +125,7 @@ trait EmServiceCore { def handleFlexRequest( flexRequest: FlexRequest, - receiver: ActorRef[EmAgent.Message], + receiver: ActorRef[FlexRequest], )(using startTime: ZonedDateTime, log: Logger, @@ -131,43 +138,3 @@ trait EmServiceCore { .minOption .map(long2Long) } - -object EmServiceCore { - - final case class EmRefMaps( - private val refToUuid: Map[ActorRef[EmAgent.Message], UUID] = Map.empty, - private val uuidToRef: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, - private val uuidToFlexResponse: Map[UUID, ActorRef[FlexResponse]] = - Map.empty, - private val flexResponseToUuid: Map[ActorRef[FlexResponse], UUID] = - Map.empty, - ) { - - def add( - model: UUID, - ref: ActorRef[EmAgent.Message], - parentEm: Option[ActorRef[FlexResponse]] = None, - parentUuid: Option[UUID] = None, - ): EmRefMaps = parentEm.zip(parentUuid) match { - case Some((parent, uuid)) => - copy( - uuidToRef = uuidToRef + (model -> ref), - refToUuid = refToUuid + (ref -> model), - uuidToFlexResponse = uuidToFlexResponse + (uuid -> parent), - flexResponseToUuid = flexResponseToUuid + (parent -> uuid), - ) - case None => - copy( - uuidToRef = uuidToRef + (model -> ref), - refToUuid = refToUuid + (ref -> model), - ) - } - - def getUuid(ref: ActorRef[FlexResponse]): UUID = - flexResponseToUuid(ref) - - def getResponse(uuid: UUID): Option[ActorRef[FlexResponse]] = - uuidToFlexResponse.get(uuid) - } - -} 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 461ebf6294..7dd2baadcb 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -12,13 +12,10 @@ import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{InitializationException, ServiceException} +import edu.ie3.simona.ontology.messages.ServiceMessage import edu.ie3.simona.ontology.messages.ServiceMessage.* import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.{Activation, ServiceMessage} -import edu.ie3.simona.service.ServiceStateData.{ - InitializeServiceStateData, - ServiceBaseStateData, -} +import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} import edu.ie3.simona.service.{ExtDataSupport, SimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.typed.ActorRef @@ -37,7 +34,7 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { def emServiceResponseAdapter( emService: ActorRef[ServiceResponseMessage], - receiver: Either[UUID, ActorRef[EmAgent.Message]], + receiver: UUID | ActorRef[FlexResponse], )(using ctx: ActorContext[EmAgent.Message]): ActorRef[FlexResponse] = { val request = Behaviors.receiveMessagePartial[FlexResponse] { msg => @@ -59,7 +56,7 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { val response = Behaviors.receiveMessagePartial[FlexRequest] { msg => emService ! EmFlexMessage( msg, - Right(receiver), + receiver, ) Behaviors.same @@ -93,13 +90,17 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { log.debug(s"Received response message: $scheduleFlexActivation") receiver match { - case Right(ref) => + case uuid: 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 Left(_) => - log.debug(s"Unlocking msg: $scheduleFlexActivation") - scheduleFlexActivation.scheduleKey.foreach(_.unlock()) + case _ => + // this should not happen + log.warn(s"No receiver found for msg: $serviceResponse") } } @@ -116,6 +117,7 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { val emDataInitializedStateData = ExtEmDataStateData(extEmDataConnection, startTime, serviceCore) + Success( emDataInitializedStateData, None, diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala index 2522b8eb49..9616844321 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala @@ -110,13 +110,12 @@ class ExtEmBaseIT TestProbe[ControlResponseMessageFromExt]("extSimAdapter") val service = spawn(ExtEmDataService(scheduler.ref)) - val serviceRef = service.ref val connection = new ExtEmDataConnection( List(emSupUuid, emNode3Uuid, emNode4Uuid).asJava, EmMode.BASE, ) - connection.setActorRefs(serviceRef, extSimAdapter.ref) + connection.setActorRefs(service, extSimAdapter.ref) val emAgentSup = spawn( EmAgent( @@ -127,7 +126,7 @@ class ExtEmBaseIT simulationStart, parent = Left(scheduler.ref), listener = Iterable(resultListener.ref), - Some(serviceRef), + Some(service), ) ) @@ -140,7 +139,7 @@ class ExtEmBaseIT simulationStart, parent = Right(emAgentSup), listener = Iterable(resultListener.ref), - Some(serviceRef), + Some(service), ) ) @@ -153,7 +152,7 @@ class ExtEmBaseIT simulationStart, parent = Right(emAgentSup), listener = Iterable(resultListener.ref), - Some(serviceRef), + Some(service), ) ) @@ -214,9 +213,7 @@ class ExtEmBaseIT key, ) - scheduler.expectMessage( - ScheduleActivation(serviceRef, INIT_SIM_TICK, Some(key)) - ) + scheduler.expectMessage(ScheduleActivation(service, INIT_SIM_TICK, Some(key))) // we expect a completion for the participant locks scheduler.expectMessage(Completion(lockActivation)) @@ -224,8 +221,8 @@ class ExtEmBaseIT /* INIT */ // activate the service for init tick - serviceRef ! Activation(INIT_SIM_TICK) - scheduler.expectMessage(Completion(serviceRef)) + service ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(service)) primaryServiceProxy.receiveMessages( 4, @@ -302,8 +299,8 @@ class ExtEmBaseIT new RequestEmFlexResults(0L, List(emSupUuid).asJava, false) ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) - serviceRef ! Activation(0L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(0L) val receivedFlexOptions0 = connection .receiveWithType(classOf[FlexOptionsResponse]) @@ -329,8 +326,8 @@ class ExtEmBaseIT connection.sendSetPoints(0L, setPoints0.asJava, Optional.of(900L), log) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) - serviceRef ! Activation(0L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(0L) connection.receiveWithType(classOf[EmCompletion]) @@ -356,8 +353,8 @@ class ExtEmBaseIT new RequestEmFlexResults(900L, List(emSupUuid).asJava, false) ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) - serviceRef ! Activation(900L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(900L) val receivedFlexOptions900 = connection .receiveWithType(classOf[FlexOptionsResponse]) @@ -390,8 +387,8 @@ class ExtEmBaseIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) - serviceRef ! Activation(900L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(900L) connection.receiveWithType(classOf[EmCompletion]) } @@ -419,8 +416,8 @@ class ExtEmBaseIT new RequestEmFlexResults(1800L, List(emSupUuid).asJava, true) ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) - serviceRef ! Activation(1800L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(1800L) val receivedFlexOptions1800 = connection .receiveWithType(classOf[FlexOptionsResponse]) @@ -476,8 +473,8 @@ class ExtEmBaseIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) - serviceRef ! Activation(1800L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(1800L) connection.receiveWithType(classOf[EmCompletion]) @@ -503,8 +500,8 @@ class ExtEmBaseIT new RequestEmFlexResults(2700L, List(emSupUuid).asJava, true) ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) - serviceRef ! Activation(2700L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(2700L) val receivedFlexOptions2700 = connection .receiveWithType(classOf[FlexOptionsResponse]) @@ -562,8 +559,8 @@ class ExtEmBaseIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) - serviceRef ! Activation(2700L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(2700L) connection.receiveWithType(classOf[EmCompletion]) } @@ -598,8 +595,8 @@ class ExtEmBaseIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) - serviceRef ! Activation(3600L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(3600L) connection.receiveWithType(classOf[EmCompletion]) @@ -632,8 +629,8 @@ class ExtEmBaseIT log, ) - extSimAdapter.expectMessage(new ScheduleDataServiceMessage(serviceRef)) - serviceRef ! Activation(4500L) + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(service)) + service ! Activation(4500L) connection.receiveWithType(classOf[EmCompletion]) diff --git a/src/test/scala/edu/ie3/simona/test/common/UnitSpec.scala b/src/test/scala/edu/ie3/simona/test/common/UnitSpec.scala index b9ad362047..178bab8f6a 100644 --- a/src/test/scala/edu/ie3/simona/test/common/UnitSpec.scala +++ b/src/test/scala/edu/ie3/simona/test/common/UnitSpec.scala @@ -13,7 +13,6 @@ import edu.ie3.simona.test.matchers.{ SquantsMatchers, } import edu.ie3.util.scala.quantities.{QuantityUtil => PSQuantityUtil} -import org.apache.pekko.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest._ import org.scalatest.matchers.should import org.scalatest.prop.TableDrivenPropertyChecks @@ -35,7 +34,6 @@ trait UnitSpec with SquantsMatchers with DoubleMatchers with AnyWordSpecLike - with LogCapturing with OptionValues with Inside with Inspectors From fc40745c3de44038a553991dcf06c5445f743d62 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 18 Jun 2025 19:03:09 +0200 Subject: [PATCH 059/125] fmt --- .../edu/ie3/simona/agent/em/EmAgent.scala | 10 ++++++++-- .../service/em/EmCommunicationCore.scala | 20 +++++++++---------- .../simona/service/em/ExtEmDataService.scala | 5 ++++- .../ie3/simona/service/em/ExtEmBaseIT.scala | 4 +++- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index e14345ff3c..bcaec0f789 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -10,11 +10,17 @@ import edu.ie3.datamodel.models.input.EmInput import edu.ie3.datamodel.models.result.system.{EmResult, FlexOptionsResult} import edu.ie3.simona.config.RuntimeConfig.EmRuntimeConfig import edu.ie3.simona.event.ResultEvent -import edu.ie3.simona.event.ResultEvent.{FlexOptionsResultEvent, ParticipantResultEvent} +import edu.ie3.simona.event.ResultEvent.{ + FlexOptionsResultEvent, + ParticipantResultEvent, +} import edu.ie3.simona.event.notifier.NotifierConfig import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.model.em.{EmModelShell, EmTools} -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration import edu.ie3.simona.ontology.messages.flex.FlexOptions import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index e1545fa5af..0c702a44ca 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -64,16 +64,17 @@ final case class EmCommunicationCore( val parentEm = emServiceRegistration.parentEm val parentUuid = emServiceRegistration.parentUuid - val (updatedResponseToUuid, updatedUuidToResponse) = parentEm.zip(parentUuid) match { - case Some((parent, uuid)) => - ( - flexResponseToUuid + (parent -> uuid), - uuidToFlexResponse + (uuid -> parent), - ) + val (updatedResponseToUuid, updatedUuidToResponse) = + parentEm.zip(parentUuid) match { + case Some((parent, uuid)) => + ( + flexResponseToUuid + (parent -> uuid), + uuidToFlexResponse + (uuid -> parent), + ) - case None => - (flexResponseToUuid, uuidToFlexResponse) - } + case None => + (flexResponseToUuid, uuidToFlexResponse) + } copy( uuidToAgent = uuidToAgent + (uuid -> ref), @@ -145,7 +146,6 @@ final case class EmCommunicationCore( .flexOptions() .asScala .foreach { case (agent, flexOptions) => - uuidToAgent.get(agent) match { case Some(receiver) => flexOptions.asScala.foreach { option => 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 7dd2baadcb..f033de680a 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -15,7 +15,10 @@ import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.ServiceMessage import edu.ie3.simona.ontology.messages.ServiceMessage.* import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* -import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, +} import edu.ie3.simona.service.{ExtDataSupport, SimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.typed.ActorRef diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala index 9616844321..8e3f5d637f 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala @@ -213,7 +213,9 @@ class ExtEmBaseIT key, ) - scheduler.expectMessage(ScheduleActivation(service, INIT_SIM_TICK, Some(key))) + scheduler.expectMessage( + ScheduleActivation(service, INIT_SIM_TICK, Some(key)) + ) // we expect a completion for the participant locks scheduler.expectMessage(Completion(lockActivation)) From bb53e9f49ea688c80bb231fb9ea781f2c66bc757 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 24 Jun 2025 17:38:44 +0200 Subject: [PATCH 060/125] Saving changes. --- .../edu/ie3/simona/service/em/EmCommunicationCore.scala | 6 +----- .../scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala | 2 +- .../scala/edu/ie3/simona/service/em/ExtEmDataService.scala | 3 ++- .../edu/ie3/simona/service/em/ExtEmCommunicationIT.scala | 3 +-- .../edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala | 2 +- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 0c702a44ca..1530bc2a98 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -7,13 +7,9 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue -import edu.ie3.simona.api.data.em.model.{ - EmSetPointResult, - ExtendedFlexOptionsResult, - FlexRequestResult, -} import edu.ie3.simona.api.data.em.ontology.* import edu.ie3.simona.agent.em.EmAgent +import edu.ie3.simona.api.data.model.em.{EmSetPointResult, ExtendedFlexOptionsResult, FlexRequestResult} import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions 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 f0c20ffe77..9b73512b1f 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -7,8 +7,8 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.ExtendedFlexOptionsResult import edu.ie3.simona.api.data.em.ontology.* +import edu.ie3.simona.api.data.model.em.ExtendedFlexOptionsResult import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* 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 f033de680a..b666bb441e 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,8 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.em.ontology.* -import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} +import edu.ie3.simona.api.data.connection.ExtEmDataConnection +import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{InitializationException, ServiceException} diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index 37b99b9b11..919fd0fbf0 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -17,9 +17,7 @@ import edu.ie3.simona.agent.participant.ParticipantAgentInit.ParticipantRefs import edu.ie3.simona.agent.participant.{ParticipantAgent, ParticipantAgentInit} import edu.ie3.simona.api.data.em.model.{ EmSetPoint, - FlexOptionRequest, FlexOptions, - FlexRequestResult, } import edu.ie3.simona.api.data.em.ontology.{ EmCompletion, @@ -28,6 +26,7 @@ import edu.ie3.simona.api.data.em.ontology.{ FlexRequestResponse, } import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} +import edu.ie3.simona.api.data.model.em.{FlexOptionRequest, FlexRequestResult} import edu.ie3.simona.api.data.ontology.{ DataMessageFromExt, ScheduleDataServiceMessage, 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 60f53d2eb4..da461e691b 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -9,11 +9,11 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.em.model.{ EmSetPoint, - ExtendedFlexOptionsResult, FlexOptions, } import edu.ie3.simona.api.data.em.ontology.* import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} +import edu.ie3.simona.api.data.model.em.ExtendedFlexOptionsResult import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt import edu.ie3.simona.ontology.messages.SchedulerMessage.{ From 110737e381ca90324d213e357135575cba003cb6 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 25 Jun 2025 11:15:31 +0200 Subject: [PATCH 061/125] Adapting to changes in `simonaAPI`. --- .../edu/ie3/simona/api/ExtSimAdapter.scala | 10 ++--- .../participant/evcs/EvModelWrapper.scala | 2 +- .../ontology/messages/ServiceMessage.scala | 2 +- .../ie3/simona/service/ExtDataSupport.scala | 2 +- .../service/em/EmCommunicationCore.scala | 10 +++-- .../simona/service/em/EmServiceBaseCore.scala | 4 +- .../ie3/simona/service/em/EmServiceCore.scala | 2 +- .../simona/service/em/ExtEmDataService.scala | 4 +- .../simona/service/ev/ExtEvDataService.scala | 8 ++-- .../primary/ExtPrimaryDataService.scala | 6 +-- .../service/primary/PrimaryServiceProxy.scala | 2 +- .../service/results/ExtResultProvider.scala | 6 +-- .../ie3/simona/sim/setup/ExtSimSetup.scala | 16 ++++---- .../simona/sim/setup/ExtSimSetupData.scala | 8 +--- .../simona/test/common/model/MockEvModel.java | 2 +- .../agent/em/EmAgentWithServiceSpec.scala | 40 +++++++++---------- .../ie3/simona/api/ExtSimAdapterSpec.scala | 4 +- .../model/participant/evcs/EvcsModelIT.scala | 8 ++-- .../ie3/simona/service/em/ExtEmBaseIT.scala | 11 ++--- .../service/em/ExtEmCommunicationIT.scala | 20 +++++----- .../service/em/ExtEmDataServiceSpec.scala | 31 +++++++------- .../service/ev/ExtEvDataServiceSpec.scala | 13 +++--- .../primary/ExtPrimaryDataServiceSpec.scala | 4 +- .../primary/PrimaryServiceProxySpec.scala | 2 +- .../sim/setup/ExtSimSetupDataSpec.scala | 11 +++-- .../simona/sim/setup/ExtSimSetupSpec.scala | 2 +- 26 files changed, 118 insertions(+), 112 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala b/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala index 6b52853502..318d2b3a22 100644 --- a/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala +++ b/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala @@ -6,15 +6,15 @@ package edu.ie3.simona.api -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.api.simulation.ExtSimAdapterData -import edu.ie3.simona.api.simulation.ontology.{ +import edu.ie3.simona.api.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.api.ontology.simulation.{ ActivationMessage, ControlResponseMessageFromExt, TerminationCompleted, TerminationMessage, - CompletionMessage => ExtCompletionMessage, + CompletionMessage as ExtCompletionMessage, } +import edu.ie3.simona.api.simulation.ExtSimAdapterData import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, @@ -27,7 +27,7 @@ import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.{ActorRef, Behavior} -import scala.jdk.OptionConverters._ +import scala.jdk.OptionConverters.* object ExtSimAdapter { 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 2facf8bdd7..8c87463774 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 @@ -6,7 +6,7 @@ package edu.ie3.simona.model.participant.evcs -import edu.ie3.simona.api.data.ev.model.EvModel +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.{ 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 9e42698481..05223b2f1a 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/ServiceMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/ServiceMessage.scala @@ -10,7 +10,7 @@ import edu.ie3.datamodel.models.result.ResultEntity import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.participant.ParticipantAgent import edu.ie3.simona.agent.participant.ParticipantAgent.ParticipantRequest -import edu.ie3.simona.api.data.ontology.DataMessageFromExt +import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.model.participant.evcs.EvModelWrapper import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ FlexRequest, diff --git a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala index 2464ddcf15..672e0b3aab 100644 --- a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala +++ b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala @@ -6,7 +6,7 @@ package edu.ie3.simona.service -import edu.ie3.simona.api.data.ontology.DataMessageFromExt +import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.ontology.messages.SchedulerMessage.ScheduleActivation import edu.ie3.simona.ontology.messages.ServiceMessage.{ ScheduleServiceActivation, diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 1530bc2a98..1310215a2b 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -7,9 +7,13 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue -import edu.ie3.simona.api.data.em.ontology.* import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.model.em.{EmSetPointResult, ExtendedFlexOptionsResult, FlexRequestResult} +import edu.ie3.simona.api.data.model.em.{ + EmSetPointResult, + ExtendedFlexOptionsResult, + FlexRequestResult, +} +import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.flex.MinMaxFlexOptions @@ -23,7 +27,6 @@ import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger import tech.units.indriya.ComparableQuantity -import scala.jdk.OptionConverters.RichOption import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.Power @@ -33,6 +36,7 @@ import scala.jdk.CollectionConverters.{ MapHasAsScala, SetHasAsJava, } +import scala.jdk.OptionConverters.RichOption final case class EmCommunicationCore( override val lastFinishedTick: Long = PRE_INIT_TICK, 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 9b73512b1f..61087358cc 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -7,8 +7,8 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.ontology.* import edu.ie3.simona.api.data.model.em.ExtendedFlexOptionsResult +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.FlexibilityMessage.* @@ -19,7 +19,6 @@ import edu.ie3.simona.util.TickUtil.TickLong import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger -import scala.jdk.OptionConverters.RichOption import java.time.ZonedDateTime import java.util.UUID import scala.jdk.CollectionConverters.{ @@ -27,6 +26,7 @@ import scala.jdk.CollectionConverters.{ MapHasAsJava, SetHasAsScala, } +import scala.jdk.OptionConverters.RichOption final case class EmServiceBaseCore( override val lastFinishedTick: Long = PRE_INIT_TICK, 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 1bd8678dff..6e0548b1f2 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -8,7 +8,7 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.{PValue, SValue} import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.ontology.* +import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.ontology.messages.ServiceMessage.{ EmFlexMessage, EmServiceRegistration, 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 b666bb441e..a0180ee3df 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -7,10 +7,10 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.ontology.* +import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.api.data.connection.ExtEmDataConnection import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode -import edu.ie3.simona.api.data.ontology.DataMessageFromExt +import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.ServiceMessage 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 f8b5f3847d..0de1c2f224 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -11,10 +11,10 @@ import edu.ie3.simona.agent.participant.ParticipantAgent.{ DataProvision, RegistrationSuccessfulMessage, } -import edu.ie3.simona.api.data.ev.ExtEvDataConnection -import edu.ie3.simona.api.data.ev.model.EvModel -import edu.ie3.simona.api.data.ev.ontology.* -import edu.ie3.simona.api.data.ontology.DataMessageFromExt +import edu.ie3.simona.api.data.connection.ExtEvDataConnection +import edu.ie3.simona.api.data.model.ev.EvModel +import edu.ie3.simona.api.ontology.DataMessageFromExt +import edu.ie3.simona.api.ontology.ev.* import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{ CriticalFailureException, diff --git a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala index a3d2e93419..090d2c41d4 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala @@ -11,9 +11,9 @@ import edu.ie3.simona.agent.participant.ParticipantAgent.{ DataProvision, PrimaryRegistrationSuccessfulMessage, } -import edu.ie3.simona.api.data.ontology.DataMessageFromExt -import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection -import edu.ie3.simona.api.data.primarydata.ontology.{ +import edu.ie3.simona.api.data.connection.ExtPrimaryDataConnection +import edu.ie3.simona.api.ontology.DataMessageFromExt +import edu.ie3.simona.api.ontology.primary.{ PrimaryDataMessageFromExt, ProvidePrimaryData, } diff --git a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala index 113cc62292..8589eea931 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala @@ -27,7 +27,7 @@ import edu.ie3.datamodel.io.source.{ TimeSeriesMetaInformationSource, } import edu.ie3.datamodel.models.value.Value -import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection +import edu.ie3.simona.api.data.connection.ExtPrimaryDataConnection import edu.ie3.simona.agent.participant.ParticipantAgent import edu.ie3.simona.agent.participant.ParticipantAgent.RegistrationFailedMessage import edu.ie3.simona.config.ConfigParams.{SqlParams, TimeStampedCsvParams} diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala index 678d635682..6479f34d4e 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala @@ -7,9 +7,9 @@ package edu.ie3.simona.service.results import edu.ie3.datamodel.models.result.ResultEntity -import edu.ie3.simona.api.data.ontology.DataMessageFromExt -import edu.ie3.simona.api.data.results.ExtResultDataConnection -import edu.ie3.simona.api.data.results.ontology.{ +import edu.ie3.simona.api.data.connection.ExtResultDataConnection +import edu.ie3.simona.api.ontology.DataMessageFromExt +import edu.ie3.simona.api.ontology.results.{ ProvideResultEntities, RequestResultEntities, ResultDataMessageFromExt, diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index af53fa15a4..2a6ea09723 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -6,13 +6,15 @@ package edu.ie3.simona.sim.setup -import edu.ie3.simona.api.data.ExtInputDataConnection -import edu.ie3.simona.api.data.em.ExtEmDataConnection -import edu.ie3.simona.api.data.ev.ExtEvDataConnection -import edu.ie3.simona.api.data.ontology.DataMessageFromExt -import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection -import edu.ie3.simona.api.data.results.ExtResultDataConnection -import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt +import edu.ie3.simona.api.data.connection.ExtInputDataConnection +import edu.ie3.simona.api.data.connection.{ + ExtEmDataConnection, + ExtEvDataConnection, + ExtPrimaryDataConnection, + ExtResultDataConnection, +} +import edu.ie3.simona.api.ontology.DataMessageFromExt +import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt import edu.ie3.simona.api.simulation.{ExtSimAdapterData, ExtSimulation} import edu.ie3.simona.api.{ExtLinkInterface, ExtSimAdapter} import edu.ie3.simona.exceptions.ServiceException diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index f810ef9794..3154bd18a2 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -7,12 +7,8 @@ package edu.ie3.simona.sim.setup import edu.ie3.simona.api.ExtSimAdapter -import edu.ie3.simona.api.data.ExtInputDataConnection -import edu.ie3.simona.api.data.em.ExtEmDataConnection -import edu.ie3.simona.api.data.ev.ExtEvDataConnection -import edu.ie3.simona.api.data.ontology.DataMessageFromExt -import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection -import edu.ie3.simona.api.data.results.ExtResultDataConnection +import edu.ie3.simona.api.data.connection.* +import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.ontology.messages.ServiceMessage import edu.ie3.simona.service.em.ExtEmDataService import org.apache.pekko.actor.typed.ActorRef 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 fa431319ae..dba782a611 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 @@ -6,7 +6,7 @@ package edu.ie3.simona.test.common.model; -import edu.ie3.simona.api.data.ev.model.EvModel; +import edu.ie3.simona.api.data.model.ev.EvModel; import edu.ie3.util.quantities.PowerSystemUnits; import java.util.Objects; import java.util.UUID; diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala index a3fe717394..80b1dd23bd 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala @@ -113,7 +113,7 @@ class EmAgentWithServiceSpec service.expectMessage( EmFlexMessage( ScheduleFlexActivation(emInput.getUuid, INIT_SIM_TICK), - Right(parentEmAgent.ref), + parentEmAgent.ref, ) ) @@ -153,7 +153,7 @@ class EmAgentWithServiceSpec modelUuid = emInput.getUuid, requestAtTick = Some(0), ), - Right(parentEmAgent.ref), + parentEmAgent.ref, ) ) @@ -205,7 +205,7 @@ class EmAgentWithServiceSpec maxPower, ), ), - Right(receiver), + receiver, ) => modelUuid shouldBe emInput.getUuid referencePower shouldBe Kilowatts(0) @@ -259,7 +259,7 @@ class EmAgentWithServiceSpec service.expectMessageType[EmFlexMessage] match { case EmFlexMessage( FlexResult(modelUuid, result), - Right(receiver), + receiver, ) => modelUuid shouldBe emInput.getUuid result.p should approximate(Kilowatts(6)) @@ -274,7 +274,7 @@ class EmAgentWithServiceSpec modelUuid = emInput.getUuid, requestAtTick = Some(300), ), - Right(parentEmAgent.ref), + parentEmAgent.ref, ) ) @@ -317,7 +317,7 @@ class EmAgentWithServiceSpec service.expectMessageType[EmFlexMessage] match { case EmFlexMessage( FlexResult(modelUuid, result), - Right(receiver), + receiver, ) => modelUuid shouldBe emInput.getUuid result.p should approximate(Kilowatts(0)) @@ -331,7 +331,7 @@ class EmAgentWithServiceSpec modelUuid = emInput.getUuid, requestAtTick = Some(600), ), - Right(parentEmAgent.ref), + parentEmAgent.ref, ) ) @@ -411,7 +411,7 @@ class EmAgentWithServiceSpec service.expectMessage( EmFlexMessage( ScheduleFlexActivation(updatedEmInput.getUuid, INIT_SIM_TICK), - Right(parentEmAgent), + parentEmAgent, ) ) @@ -423,7 +423,7 @@ class EmAgentWithServiceSpec service.expectMessage( EmFlexMessage( ScheduleFlexActivation(parentEmInput.getUuid, INIT_SIM_TICK), - Left(parentEmInput.getUuid), + parentEmInput.getUuid, ) ) @@ -440,7 +440,7 @@ class EmAgentWithServiceSpec service.expectMessage( EmFlexMessage( FlexActivation(INIT_SIM_TICK), - Right(emAgent), + emAgent, ) ) @@ -472,7 +472,7 @@ class EmAgentWithServiceSpec modelUuid = updatedEmInput.getUuid, requestAtTick = Some(0), ), - Right(parentEmAgent), + parentEmAgent, ) ) @@ -487,7 +487,7 @@ class EmAgentWithServiceSpec modelUuid = parentEmInput.getUuid, requestAtTick = Some(0), ), - Left(parentEmInput.getUuid), + parentEmInput.getUuid, ) ) @@ -497,7 +497,7 @@ class EmAgentWithServiceSpec service.expectMessage( EmFlexMessage( FlexActivation(0), - Right(emAgent), + emAgent, ) ) @@ -548,7 +548,7 @@ class EmAgentWithServiceSpec maxPower, ), ), - Right(receiver), + receiver, ) => modelUuid shouldBe updatedEmInput.getUuid referencePower shouldBe Kilowatts(0) @@ -577,7 +577,7 @@ class EmAgentWithServiceSpec maxPower, ), ), - Left(self), + self: UUID, ) => modelUuid shouldBe parentEmInput.getUuid referencePower shouldBe Kilowatts(0) @@ -592,7 +592,7 @@ class EmAgentWithServiceSpec service.expectMessage( EmFlexMessage( IssuePowerControl(0, Kilowatts(6)), - Right(emAgent), + emAgent, ) ) @@ -647,7 +647,7 @@ class EmAgentWithServiceSpec service.expectMessageType[EmFlexMessage] match { case EmFlexMessage( FlexResult(modelUuid, result), - Right(receiver), + receiver, ) => modelUuid shouldBe updatedEmInput.getUuid result.p should approximate(Kilowatts(6)) @@ -670,7 +670,7 @@ class EmAgentWithServiceSpec modelUuid = updatedEmInput.getUuid, requestAtTick = Some(300), ), - Right(parentEmAgent), + parentEmAgent, ) ) @@ -718,7 +718,7 @@ class EmAgentWithServiceSpec service.expectMessageType[EmFlexMessage] match { case EmFlexMessage( FlexResult(modelUuid, result), - Right(receiver), + receiver, ) => modelUuid shouldBe updatedEmInput.getUuid result.p should approximate(Kilowatts(0)) @@ -738,7 +738,7 @@ class EmAgentWithServiceSpec modelUuid = updatedEmInput.getUuid, requestAtTick = Some(600), ), - Right(parentEmAgent), + parentEmAgent, ) ) } diff --git a/src/test/scala/edu/ie3/simona/api/ExtSimAdapterSpec.scala b/src/test/scala/edu/ie3/simona/api/ExtSimAdapterSpec.scala index 7666cc25cd..4ec887e301 100644 --- a/src/test/scala/edu/ie3/simona/api/ExtSimAdapterSpec.scala +++ b/src/test/scala/edu/ie3/simona/api/ExtSimAdapterSpec.scala @@ -7,12 +7,12 @@ package edu.ie3.simona.api import edu.ie3.simona.api.ExtSimAdapter.{ExtSimAdapterStateData, Stop} -import edu.ie3.simona.api.data.ontology.{ +import edu.ie3.simona.api.ontology.{ DataMessageFromExt, ScheduleDataServiceMessage, } import edu.ie3.simona.api.simulation.ExtSimAdapterData -import edu.ie3.simona.api.simulation.ontology.{ +import edu.ie3.simona.api.ontology.simulation.{ ActivationMessage, ControlResponseMessageFromExt, TerminationCompleted, diff --git a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelIT.scala b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelIT.scala index c840b00bcb..d0bf95bf5b 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelIT.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelIT.scala @@ -14,10 +14,10 @@ import edu.ie3.simona.agent.participant.ParticipantAgentInit.{ ParticipantRefs, SimulationParameters, } -import edu.ie3.simona.api.data.ev.ExtEvDataConnection -import edu.ie3.simona.api.data.ev.model.EvModel -import edu.ie3.simona.api.data.ev.ontology.* -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.api.data.connection.ExtEvDataConnection +import edu.ie3.simona.api.data.model.ev.EvModel +import edu.ie3.simona.api.ontology.ev.* +import edu.ie3.simona.api.ontology.ScheduleDataServiceMessage import edu.ie3.simona.config.RuntimeConfig.EvcsRuntimeConfig import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.event.ResultEvent.ParticipantResultEvent diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala index 8e3f5d637f..0bca7295d3 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmBaseIT.scala @@ -15,15 +15,16 @@ import edu.ie3.simona.agent.participant.ParticipantAgent.{ } import edu.ie3.simona.agent.participant.ParticipantAgentInit import edu.ie3.simona.agent.participant.ParticipantAgentInit.ParticipantRefs -import edu.ie3.simona.api.data.em.model.EmSetPoint -import edu.ie3.simona.api.data.em.ontology.{ +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 +import edu.ie3.simona.api.ontology.em.{ EmCompletion, FlexOptionsResponse, RequestEmFlexResults, } -import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt +import edu.ie3.simona.api.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt import edu.ie3.simona.config.RuntimeConfig.{ LoadRuntimeConfig, PvRuntimeConfig, diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index 919fd0fbf0..848edacdf7 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -15,23 +15,25 @@ import edu.ie3.simona.agent.participant.ParticipantAgent.{ } import edu.ie3.simona.agent.participant.ParticipantAgentInit.ParticipantRefs import edu.ie3.simona.agent.participant.{ParticipantAgent, ParticipantAgentInit} -import edu.ie3.simona.api.data.em.model.{ +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, + FlexOptionRequest, FlexOptions, + FlexRequestResult, } -import edu.ie3.simona.api.data.em.ontology.{ +import edu.ie3.simona.api.ontology.{ + DataMessageFromExt, + ScheduleDataServiceMessage, +} +import edu.ie3.simona.api.ontology.em.{ EmCompletion, EmSetPointDataResponse, FlexOptionsResponse, FlexRequestResponse, } -import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} -import edu.ie3.simona.api.data.model.em.{FlexOptionRequest, FlexRequestResult} -import edu.ie3.simona.api.data.ontology.{ - DataMessageFromExt, - ScheduleDataServiceMessage, -} -import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt +import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt import edu.ie3.simona.config.RuntimeConfig.{ LoadRuntimeConfig, PvRuntimeConfig, 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 da461e691b..13d741fd0e 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -7,15 +7,16 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.em.model.{ +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, FlexOptions, } -import edu.ie3.simona.api.data.em.ontology.* -import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} -import edu.ie3.simona.api.data.model.em.ExtendedFlexOptionsResult -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt +import edu.ie3.simona.api.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.api.ontology.em.* +import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, @@ -192,7 +193,7 @@ class ExtEmDataServiceSpec emAgent.expectMessage(FlexActivation(-1)) emService ! EmFlexMessage( FlexCompletion(emAgent1UUID, requestAtTick = Some(0)), - Left(emAgent1UUID), + emAgent1UUID, ) scheduler.expectMessage(Completion(emService)) @@ -248,7 +249,7 @@ class ExtEmDataServiceSpec emAgent1.expectMessage(FlexActivation(-1)) emService ! EmFlexMessage( FlexCompletion(emAgent1UUID, requestAtTick = Some(0)), - Left(emAgent1UUID), + emAgent1UUID, ) emService ! EmServiceRegistration( @@ -260,7 +261,7 @@ class ExtEmDataServiceSpec emAgent2.expectMessage(FlexActivation(-1)) emService ! EmFlexMessage( FlexCompletion(emAgent2UUID, requestAtTick = Some(0)), - Left(emAgent2UUID), + emAgent2UUID, ) // scheduler.expectMessage(Completion(emService)) @@ -292,7 +293,7 @@ class ExtEmDataServiceSpec Kilowatts(10), ), ), - Left(emAgent1UUID), + emAgent1UUID, ) awaitCond( @@ -368,14 +369,14 @@ class ExtEmDataServiceSpec emAgentSup.expectMessage(FlexActivation(-1)) // the em agent sup will send an activation message to the em agent1 - emService ! EmFlexMessage(FlexActivation(-1), Right(emAgent1.ref)) + emService ! EmFlexMessage(FlexActivation(-1), emAgent1.ref) emAgent1.expectMessage(FlexActivation(-1)) // the em agent 1 will answer the activation with a flex completion message emService ! EmFlexMessage( FlexCompletion(emAgent1UUID, requestAtTick = Some(0)), - Right(emAgentSup.ref), + emAgentSup.ref, ) emAgentSup @@ -384,7 +385,7 @@ class ExtEmDataServiceSpec emService ! EmFlexMessage( FlexCompletion(emAgentSupUUID, requestAtTick = Some(0)), - Left(emAgentSupUUID), + emAgentSupUUID, ) extEmDataConnection.sendExtMsg( @@ -469,7 +470,7 @@ class ExtEmDataServiceSpec emAgent1.expectMessage(FlexActivation(-1)) emService ! EmFlexMessage( FlexCompletion(emAgent1UUID, requestAtTick = Some(0)), - Left(emAgent1UUID), + emAgent1UUID, ) emService ! EmServiceRegistration( @@ -481,7 +482,7 @@ class ExtEmDataServiceSpec emAgent2.expectMessage(FlexActivation(-1)) emService ! EmFlexMessage( FlexCompletion(emAgent2UUID, requestAtTick = Some(0)), - Left(emAgent2UUID), + emAgent2UUID, ) extEmDataConnection.sendExtMsg( diff --git a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala index 205f4d7b1d..146ab217cc 100644 --- a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala @@ -11,17 +11,14 @@ import edu.ie3.simona.agent.participant.ParticipantAgent.{ DataProvision, RegistrationSuccessfulMessage, } -import edu.ie3.simona.api.data.ev.ExtEvDataConnection -import edu.ie3.simona.api.data.ev.model.EvModel -import edu.ie3.simona.api.data.ev.ontology.* -import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt -import edu.ie3.simona.model.participant.evcs.EvModelWrapper -import edu.ie3.simona.api.data.ontology.{ +import edu.ie3.simona.api.data.connection.ExtEvDataConnection +import edu.ie3.simona.api.data.model.ev.EvModel +import edu.ie3.simona.api.ontology.ev.* +import edu.ie3.simona.api.ontology.{ DataMessageFromExt, ScheduleDataServiceMessage, } -import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt +import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt import edu.ie3.simona.model.participant.evcs.EvModelWrapper import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, diff --git a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala index 8bcc0fe218..d9e4077cd2 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/ExtPrimaryDataServiceSpec.scala @@ -12,8 +12,8 @@ import edu.ie3.simona.agent.participant.ParticipantAgent.{ DataProvision, RegistrationSuccessfulMessage, } -import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection -import edu.ie3.simona.api.simulation.ontology.ControlResponseMessageFromExt +import edu.ie3.simona.api.data.connection.ExtPrimaryDataConnection +import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala index f7220452b4..6fda2a740c 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala @@ -12,7 +12,7 @@ import edu.ie3.datamodel.io.naming.timeseries.ColumnScheme import edu.ie3.datamodel.io.source.TimeSeriesMappingSource import edu.ie3.datamodel.io.source.csv.CsvTimeSeriesMappingSource import edu.ie3.datamodel.models.value.{PValue, SValue, Value} -import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection +import edu.ie3.simona.api.data.connection.ExtPrimaryDataConnection import edu.ie3.datamodel.models.value.SValue import edu.ie3.simona.agent.participant.ParticipantAgent import edu.ie3.simona.agent.participant.ParticipantAgent.RegistrationFailedMessage diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala index dcc9e051c8..afdaeb71b3 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala @@ -7,10 +7,13 @@ package edu.ie3.simona.sim.setup import edu.ie3.datamodel.models.value.Value -import edu.ie3.simona.api.data.em.{EmMode, ExtEmDataConnection} -import edu.ie3.simona.api.data.ev.ExtEvDataConnection -import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection -import edu.ie3.simona.api.data.results.ExtResultDataConnection +import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode +import edu.ie3.simona.api.data.connection.{ + ExtEmDataConnection, + ExtEvDataConnection, + ExtPrimaryDataConnection, + ExtResultDataConnection, +} import edu.ie3.simona.ontology.messages.ServiceMessage import edu.ie3.simona.service.ev.ExtEvDataService import edu.ie3.simona.service.primary.PrimaryServiceProxy diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala index c4925e6ea6..7e7a3c47ba 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala @@ -7,7 +7,7 @@ package edu.ie3.simona.sim.setup import edu.ie3.datamodel.models.value.{PValue, Value} -import edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection +import edu.ie3.simona.api.data.connection.ExtPrimaryDataConnection import edu.ie3.simona.exceptions.ServiceException import edu.ie3.simona.test.common.UnitSpec From 5b89eeb9a8851f52e4bb19571f0b5624ed076277 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 26 Jun 2025 12:32:28 +0200 Subject: [PATCH 062/125] Adapting to changes in `simonaAPI`. --- .../service/em/EmCommunicationCore.scala | 62 +++++++++++++------ .../simona/service/em/EmServiceBaseCore.scala | 2 + .../ie3/simona/service/em/EmServiceCore.scala | 1 - .../service/em/ExtEmCommunicationIT.scala | 4 +- 4 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 1310215a2b..050b61eb2e 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -11,7 +11,7 @@ import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.model.em.{ EmSetPointResult, ExtendedFlexOptionsResult, - FlexRequestResult, + FlexOptionRequestResult, } import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration @@ -53,7 +53,9 @@ final case class EmCommunicationCore( ReceiveHierarchicalDataMap.empty, flexOptionResponse: DataMap[UUID, ExtendedFlexOptionsResult] = ReceiveHierarchicalDataMap.empty, + additionalFlexOptions: Map[UUID, ExtendedFlexOptionsResult] = Map.empty, setPointResponse: DataMap[UUID, PValue] = ReceiveHierarchicalDataMap.empty, + disaggregatedFlex: Boolean = false, ) extends EmServiceCore { override def handleRegistration( @@ -221,25 +223,27 @@ final case class EmCommunicationCore( self } - val updated = provideFlexOptions match { + val (updated, updatedAdditional) = provideFlexOptions match { case ProvideFlexOptions( modelUuid, MinMaxFlexOptions(ref, min, max), ) => - flexOptionResponse.addData( + val result = new ExtendedFlexOptionsResult( + tick.toDateTime(using startTime), modelUuid, - new ExtendedFlexOptionsResult( - tick.toDateTime(using startTime), - modelUuid, - receiverUuid, - ref.toQuantity, - min.toQuantity, - max.toQuantity, - ), + receiverUuid, + ref.toQuantity, + min.toQuantity, + max.toQuantity, + ) + + ( + flexOptionResponse.addData(modelUuid, result), + additionalFlexOptions.updated(modelUuid, result), ) case _ => - flexOptionResponse + (flexOptionResponse, additionalFlexOptions) } if (updated.hasCompletedKeys) { @@ -251,13 +255,25 @@ final case class EmCommunicationCore( uuid -> options.getpRef } - val msgToExt = new FlexOptionsResponse(data.map { - case (entity, value) => entity -> value - }.asJava) + val processedData = data.map { case (entity, value) => + entity -> value + } + + if (disaggregatedFlex) { + processedData.foreach { case (key, value) => + updatedFlexOptionResponse.structure(key).foreach { inferior => + value.addDisaggregated(inferior, updatedAdditional(inferior)) + } + + } + } + + val msgToExt = new FlexOptionsResponse(processedData.asJava) ( copy( flexOptionResponse = updatedFlexOptionResponse, + additionalFlexOptions = updatedAdditional, uuidToPRef = uuidToPRef ++ pRefs, ), Some(msgToExt), @@ -265,7 +281,13 @@ final case class EmCommunicationCore( } else { // responses are still incomplete - (copy(flexOptionResponse = updated), None) + ( + copy( + flexOptionResponse = updated, + additionalFlexOptions = updatedAdditional, + ), + None, + ) } } @@ -287,7 +309,11 @@ final case class EmCommunicationCore( // every em agent has sent a completion message ( - copy(lastFinishedTick = tick, completions = ReceiveDataMap(allKeys)), + copy( + lastFinishedTick = tick, + additionalFlexOptions = Map.empty, + completions = ReceiveDataMap(allKeys), + ), extMsgOption, ) @@ -321,7 +347,7 @@ final case class EmCommunicationCore( updated.getFinishedDataHierarchical val map = dataMap.map { case (sender, receivers) => - sender -> new FlexRequestResult( + sender -> new FlexOptionRequestResult( flexActivation.tick.toDateTime, sender, receivers.asJava, 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 61087358cc..c54a080766 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -189,6 +189,7 @@ final case class EmServiceBaseCore( val updatedCore = copy( flexOptions = ReceiveDataMap.empty, + additionalFlexOptions = updatedAdditional, canHandleSetPoints = true, ) @@ -235,6 +236,7 @@ final case class EmServiceBaseCore( copy( lastFinishedTick = tick, completions = ReceiveDataMap(allKeys), + additionalFlexOptions = Map.empty, disaggregatedFlex = false, sendOptionsToExt = false, canHandleSetPoints = false, 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 6e0548b1f2..65d47246e0 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -111,7 +111,6 @@ trait EmServiceCore { log.warn(s"No em agent with uuid '$agent' registered!") } } - } def handleFlexResponse( diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index 848edacdf7..9e67f6eed5 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -20,8 +20,8 @@ import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode import edu.ie3.simona.api.data.model.em.{ EmSetPoint, FlexOptionRequest, + FlexOptionRequestResult, FlexOptions, - FlexRequestResult, } import edu.ie3.simona.api.ontology.{ DataMessageFromExt, @@ -438,7 +438,7 @@ class ExtEmCommunicationIT .asScala requestsToInferior.size shouldBe 1 - requestsToInferior(emSupUuid) shouldBe new FlexRequestResult( + requestsToInferior(emSupUuid) shouldBe new FlexOptionRequestResult( simulationStart.plusSeconds(tick), emSupUuid, List(emNode3Uuid, emNode4Uuid).asJava, From bf434e249b1f5e22e9309430589250bfa3af10a4 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 26 Jun 2025 15:27:36 +0200 Subject: [PATCH 063/125] Saving changes. --- .../ie3/simona/service/em/ExtEmCommunicationIT.scala | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index 9e67f6eed5..9b27d47a70 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -23,10 +23,7 @@ import edu.ie3.simona.api.data.model.em.{ FlexOptionRequestResult, FlexOptions, } -import edu.ie3.simona.api.ontology.{ - DataMessageFromExt, - ScheduleDataServiceMessage, -} +import edu.ie3.simona.api.ontology.ScheduleDataServiceMessage import edu.ie3.simona.api.ontology.em.{ EmCompletion, EmSetPointDataResponse, @@ -50,11 +47,7 @@ import edu.ie3.simona.ontology.messages.ServiceMessage.{ PrimaryServiceRegistrationMessage, SecondaryServiceRegistrationMessage, } -import edu.ie3.simona.ontology.messages.{ - Activation, - SchedulerMessage, - ServiceMessage, -} +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.Data.SecondaryData.WeatherData import edu.ie3.simona.service.ServiceType From 86f52161dd1a444478fe7475cfc23709f328b072 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 7 Jul 2025 14:07:22 +0200 Subject: [PATCH 064/125] ReCoDE InfluxDB. --- .../recode/RecodeDatabaseNamingStrategy.scala | 21 +++ .../ie3/simona/recode/RecodeInfluxDBSink.java | 124 ++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 src/main/scala/edu/ie3/simona/recode/RecodeDatabaseNamingStrategy.scala create mode 100644 src/main/scala/edu/ie3/simona/recode/RecodeInfluxDBSink.java diff --git a/src/main/scala/edu/ie3/simona/recode/RecodeDatabaseNamingStrategy.scala b/src/main/scala/edu/ie3/simona/recode/RecodeDatabaseNamingStrategy.scala new file mode 100644 index 0000000000..af27412e3b --- /dev/null +++ b/src/main/scala/edu/ie3/simona/recode/RecodeDatabaseNamingStrategy.scala @@ -0,0 +1,21 @@ +package edu.ie3.simona.recode + +import edu.ie3.datamodel.io.naming.EntityPersistenceNamingStrategy +import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} + +import java.util.Optional + +class RecodeDatabaseNamingStrategy extends EntityPersistenceNamingStrategy { + + + override def getResultEntityName(resultEntityClass: Class[? <: ResultEntity]): Optional[String] = { + val NodeRes = classOf[NodeResult] + + resultEntityClass match { + case NodeRes => + Optional.of("bus") + case _ => + super.getResultEntityName(resultEntityClass) + } + } +} diff --git a/src/main/scala/edu/ie3/simona/recode/RecodeInfluxDBSink.java b/src/main/scala/edu/ie3/simona/recode/RecodeInfluxDBSink.java new file mode 100644 index 0000000000..795e1abe59 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/recode/RecodeInfluxDBSink.java @@ -0,0 +1,124 @@ +package edu.ie3.simona.recode; + +import edu.ie3.datamodel.exceptions.EntityProcessorException; +import edu.ie3.datamodel.exceptions.ProcessorProviderException; +import edu.ie3.datamodel.io.connectors.InfluxDbConnector; +import edu.ie3.datamodel.io.processor.ProcessorProvider; +import edu.ie3.datamodel.io.sink.OutputDataSink; +import edu.ie3.datamodel.models.Entity; +import edu.ie3.datamodel.models.result.ResultEntity; +import edu.ie3.datamodel.models.timeseries.TimeSeries; +import edu.ie3.datamodel.models.timeseries.TimeSeriesEntry; +import edu.ie3.datamodel.models.value.Value; +import org.influxdb.dto.BatchPoints; +import org.influxdb.dto.Point; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class RecodeInfluxDBSink implements OutputDataSink { + public static final Logger log = LoggerFactory.getLogger(RecodeInfluxDBSink.class); + private final InfluxDbConnector connector; + private final RecodeDatabaseNamingStrategy entityPersistenceNamingStrategy; + private final ProcessorProvider processorProvider; + + + public RecodeInfluxDBSink(InfluxDbConnector connector, RecodeDatabaseNamingStrategy entityPersistenceNamingStrategy) throws EntityProcessorException { + this.connector = connector; + this.entityPersistenceNamingStrategy = entityPersistenceNamingStrategy; + this.processorProvider = new ProcessorProvider(ProcessorProvider.allResultEntityProcessors(), ProcessorProvider.allTimeSeriesProcessors()); + + } + + @Override + public void shutdown() { + connector.shutdown(); + } + + @Override + public void persist(C entity) throws ProcessorProviderException { + Set points = extractPoints(entity); + if (points.size() == 1) { + this.write(points.iterator().next()); + } else { + this.writeAll(points); + } + } + + @Override + public void persistAll(Collection entities) throws ProcessorProviderException { + Set points = new HashSet<>(); + + for(C entity : entities) { + points.addAll(this.extractPoints(entity)); + } + + writeAll(points); + } + + @Override + public , V extends Value, R extends Value> void persistTimeSeries(TimeSeries timeSeries) { + log.warn("Persisting time series is not supported!"); + } + + private Point transformToPoint(ResultEntity entity) throws ProcessorProviderException { + Optional measurementName = entityPersistenceNamingStrategy.getResultEntityName(entity.getClass()); + + if (measurementName.isEmpty()) { + log.warn("I could not get a measurement name for class {}. I am using its simple name instead.", entity.getClass().getSimpleName()); + } + + return transformToPoint(entity, measurementName.orElse(entity.getClass().getSimpleName())); + } + + private Point transformToPoint(ResultEntity entity, String measurementName) throws ProcessorProviderException { + LinkedHashMap entityFieldData = processorProvider.handleEntity(entity).getOrThrow(); + + if (entityFieldData.containsKey("p")) { + String value = entityFieldData.remove("p"); + entityFieldData.put("p_mw", value); + } + if (entityFieldData.containsKey("q")) { + String value = entityFieldData.remove("q"); + entityFieldData.put("q_mvar", value); + } + + entityFieldData.remove("time"); + return Point.measurement(transformToMeasurementName(measurementName)) + .time(entity.getTime().toInstant().toEpochMilli(), TimeUnit.MILLISECONDS) + .tag("input_model", entityFieldData.remove("inputModel")) + .tag("run", connector.getScenarioName()) + .fields(Collections.unmodifiableMap(entityFieldData)) + .build(); + } + + private Set extractPoints(C entity) throws ProcessorProviderException { + Set points = new HashSet<>(); + if (entity instanceof ResultEntity resultEntity) { + points.add(transformToPoint(resultEntity)); + } else { + log.error("I don't know how to handle an entity of class {}", entity.getClass().getSimpleName()); + } + + return points; + } + + private void write(Point point) { + if (point != null) { + connector.getSession().write(point); + } + } + + private void writeAll(Collection points) { + if (!points.isEmpty()) { + BatchPoints batchPoints = BatchPoints.builder().points(points).build(); + connector.getSession().write(batchPoints); + } + } + + private static String transformToMeasurementName(String filename) { + return filename.trim().replaceAll("\\W", "_"); + } +} From b5197e8c80c6020d8c50a3024b7abb04fbbff009 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 22 Jul 2025 15:18:57 +0200 Subject: [PATCH 065/125] Updating to PSDM version 8.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index efa6eed625..63a2ba8299 100644 --- a/build.gradle +++ b/build.gradle @@ -82,7 +82,7 @@ dependencies { /* Exclude our own nested dependencies */ exclude group: 'com.github.ie3-institute' } - implementation('com.github.ie3-institute:PowerSystemDataModel:7.0.0') { + implementation('com.github.ie3-institute:PowerSystemDataModel:8.0.0') { exclude group: 'org.apache.logging.log4j' exclude group: 'org.slf4j' /* Exclude our own nested dependencies */ From 578361f18aff1b54b9538c33961ac8dda509a41b Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 22 Jul 2025 15:27:52 +0200 Subject: [PATCH 066/125] Adding `ExtResultEventListener` and function to request last result from `ParticipantAgent`. --- .../simona/agent/grid/GridAgentBuilder.scala | 1 - .../agent/participant/ParticipantAgent.scala | 26 +++- .../participant/ParticipantGridAdapter.scala | 14 +- .../listener/ExtResultEventListener.scala | 84 +++++++++++ .../event/listener/ResultEventListener.scala | 4 +- .../io/result/FixedResultEntityProcessor.java | 139 ------------------ .../ontology/messages/ServiceMessage.scala | 11 +- .../service/results/ExtResultProvider.scala | 53 ++++--- .../service/results/ExtResultSchedule.scala | 2 +- .../sim/setup/ExtSimSetupDataSpec.scala | 21 --- 10 files changed, 159 insertions(+), 196 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/event/listener/ExtResultEventListener.scala delete mode 100644 src/main/scala/edu/ie3/simona/io/result/FixedResultEntityProcessor.java diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala index 3eae955817..f2b24936e7 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala @@ -21,7 +21,6 @@ import edu.ie3.simona.agent.participant.{ParticipantAgent, ParticipantAgentInit} import edu.ie3.simona.config.RuntimeConfig.* import edu.ie3.simona.config.OutputConfig.ParticipantOutputConfig import edu.ie3.simona.config.RuntimeConfig.* -import edu.ie3.simona.config.SimonaConfig.AssetConfigs import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.event.notifier.NotifierConfig import edu.ie3.simona.exceptions.CriticalFailureException diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index 7eecbc2a90..29e15af141 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -7,6 +7,7 @@ package edu.ie3.simona.agent.participant import breeze.numerics.{pow, sqrt} +import edu.ie3.datamodel.models.result.system.SystemParticipantResult import edu.ie3.simona.agent.grid.GridAgentMessages.{ AssetPowerChangedMessage, AssetPowerUnchangedMessage, @@ -18,9 +19,11 @@ import edu.ie3.simona.model.participant.ParticipantModelShell import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.ServiceMessage +import edu.ie3.simona.ontology.messages.ServiceMessage.ResultResponseMessage import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.service.Data import edu.ie3.simona.service.Data.{PrimaryData, PrimaryDataExtra} +import edu.ie3.simona.service.results.ExtResultProvider import edu.ie3.util.scala.Scope import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.{ActorRef, Behavior} @@ -147,6 +150,10 @@ object ParticipantAgent { replyTo: ActorRef[ProvidedPowerResponse], ) extends Request + final case class RequestLastResult( + replyTo: ActorRef[ExtResultProvider.Message] + ) extends Request + /** Message announcing that calculations by the * [[edu.ie3.simona.agent.grid.GridAgent]] have come to an end and regular * participant activities can continue. @@ -303,6 +310,17 @@ object ParticipantAgent { updatedGridAdapter, resultHandler, ) + + case (ctx, RequestLastResult(replyTo)) => + // send last calculated results to result service + replyTo ! ResultResponseMessage(gridAdapter.lastResults) + + ParticipantAgent( + modelShell, + inputHandler, + gridAdapter, + resultHandler, + ) } /** Starts a model calculation if all requirements have been met. A model @@ -368,8 +386,7 @@ object ParticipantAgent { results.modelResults.foreach(resultHandler.maybeSend) - val newGridAdapter = - gridAdapter.storePowerValue(results.totalPower, tick) + val newGridAdapter = gridAdapter.storeResults(results, tick) (newShell, newGridAdapter) } else @@ -428,10 +445,7 @@ object ParticipantAgent { results.modelResults.foreach(resultHandler.maybeSend) val gridAdapterWithResult = - gridAdapter.storePowerValue( - results.totalPower, - flexControl.tick, - ) + gridAdapter.storeResults(results, flexControl.tick) val changeIndicator = shellWithOP.getChangeIndicator( flexControl.tick, diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantGridAdapter.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantGridAdapter.scala index b6f71eb47c..d258df0a18 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantGridAdapter.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantGridAdapter.scala @@ -6,9 +6,11 @@ package edu.ie3.simona.agent.participant +import edu.ie3.datamodel.models.result.ResultEntity import edu.ie3.simona.agent.grid.GridAgent -import edu.ie3.simona.agent.participant.ParticipantGridAdapter._ +import edu.ie3.simona.agent.participant.ParticipantGridAdapter.* import edu.ie3.simona.exceptions.CriticalFailureException +import edu.ie3.simona.model.participant.ParticipantModelShell.ResultsContainer import edu.ie3.simona.service.Data.PrimaryData.ComplexPower import edu.ie3.util.scala.quantities.DefaultQuantities.{zeroMVAr, zeroMW} import edu.ie3.util.scala.quantities.{Megavars, QuantityUtil, ReactivePower} @@ -52,6 +54,7 @@ final case class ParticipantGridAdapter( private val expectedRequestTick: Long, private val tickToPower: SortedMap[Long, ComplexPower], avgPowerResult: Option[AvgPowerResult], + lastResults: Iterable[ResultEntity] = Seq.empty, )(private implicit val requestVoltageDeviationTolerance: Dimensionless) { /** Whether a power request is expected and has not yet arrived, thus is @@ -82,6 +85,15 @@ final case class ParticipantGridAdapter( ): ParticipantGridAdapter = copy(tickToPower = tickToPower.updated(tick, power)) + def storeResults( + result: ResultsContainer, + tick: Long, + ): ParticipantGridAdapter = + copy( + tickToPower = tickToPower.updated(tick, result.totalPower), + lastResults = result.modelResults, + ) + /** Handles a power request by making sure an average power value has been * calculated, taking into account the new voltage value. * diff --git a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEventListener.scala new file mode 100644 index 0000000000..0085cc587f --- /dev/null +++ b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEventListener.scala @@ -0,0 +1,84 @@ +package edu.ie3.simona.event.listener + +import edu.ie3.datamodel.models.result.ResultEntity +import edu.ie3.simona.api.data.connection.ExtResultListener +import edu.ie3.simona.api.ontology.results.ProvideResultEntities +import edu.ie3.simona.event.ResultEvent +import edu.ie3.simona.event.ResultEvent.* +import edu.ie3.simona.event.listener.DelayedStopHelper.StoppingMsg +import edu.ie3.simona.event.listener.ResultEventListener.{AggregatedTransformer3wResult, Transformer3wKey} +import org.apache.pekko.actor.typed.Behavior +import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} + +import scala.jdk.CollectionConverters.SeqHasAsJava +import scala.util.{Failure, Success} + +object ExtResultEventListener { + + trait Request + + def apply( + connection: ExtResultListener, + threeWindingResults: Map[ + Transformer3wKey, + AggregatedTransformer3wResult, + ] = Map.empty, + + ): Behavior[ResultEvent | DelayedStopHelper.StoppingMsg] = Behaviors.receivePartial[ResultEvent | DelayedStopHelper.StoppingMsg] { + case (_, ParticipantResultEvent(systemParticipantResult)) => + connection.queueExtResponseMsg(new ProvideResultEntities(systemParticipantResult)) + Behaviors.same + + case (ctx, PowerFlowResultEvent(nodeResults, switchResults, lineResults, transformer2wResults, transformer3wResults, congestionResults)) => + + // handle all results except the three winding transformer results + val results: Iterable[ResultEntity] = nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ congestionResults + connection.queueExtResponseMsg(new ProvideResultEntities(results.toList.asJava)) + + + // handling of three winding transformers + val updatedResults = transformer3wResults.foldLeft(threeWindingResults) { case (allResults, result) => + val key = Transformer3wKey(result.input, result.time) + // retrieve existing partial result or use empty one + val partialResult = + allResults.getOrElse( + key, + AggregatedTransformer3wResult.EMPTY, + ) + // add partial result + partialResult.add(result).map { updatedResult => + if (updatedResult.ready) { + // if result is complete, we can write it out + updatedResult.consolidate.foreach(res => connection.queueExtResponseMsg(new ProvideResultEntities(res))) + // also remove partial result from map + allResults.removed(key) + } else { + // if result is not complete yet, just update it + allResults + (key -> updatedResult) + } + } match { + case Success(results) => results + case Failure(exception) => + ctx.log.warn( + "Failure when handling partial Transformer3w result", + exception, + ) + // on failure, we just continue with previous results + allResults + } + } + + ExtResultEventListener(connection, updatedResults) + + case (_, ThermalResultEvent(thermalResult)) => + connection.queueExtResponseMsg(new ProvideResultEntities(thermalResult)) + Behaviors.same + + case (_, FlexOptionsResultEvent(flexOptionsResult)) => + connection.queueExtResponseMsg(new ProvideResultEntities(flexOptionsResult)) + Behaviors.same + + case (ctx, msg: DelayedStopHelper.StoppingMsg) => + DelayedStopHelper.handleMsg((ctx, msg)) + } +} diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index 252e21f9c4..e9e1786045 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -107,7 +107,7 @@ object ResultEventListener extends Transformer3wResultSupport { resultClass, ResultEntityCsvSink( finalFileName, - new FixedResultEntityProcessor(resultClass), + new ResultEntityProcessor(resultClass), enableCompression, csv.delimiter, ), @@ -235,7 +235,7 @@ object ResultEventListener extends Transformer3wResultSupport { log: Logger, ): Unit = Try { - extSink.foreach(_ ! ResultResponseMessage(resultEntity)) + extSink.foreach(_ ! ResultResponseMessage(Seq(resultEntity))) classToSink .get(resultEntity.getClass) diff --git a/src/main/scala/edu/ie3/simona/io/result/FixedResultEntityProcessor.java b/src/main/scala/edu/ie3/simona/io/result/FixedResultEntityProcessor.java deleted file mode 100644 index 2de0ec3f16..0000000000 --- a/src/main/scala/edu/ie3/simona/io/result/FixedResultEntityProcessor.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * © 2025. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.io.result; - -import edu.ie3.datamodel.exceptions.EntityProcessorException; -import edu.ie3.datamodel.io.processor.result.ResultEntityProcessor; -import edu.ie3.datamodel.models.OperationTime; -import edu.ie3.datamodel.models.UniqueEntity; -import edu.ie3.datamodel.models.input.OperatorInput; -import edu.ie3.datamodel.models.input.system.characteristic.CharacteristicInput; -import edu.ie3.datamodel.models.profile.LoadProfile; -import edu.ie3.datamodel.models.result.CongestionResult; -import edu.ie3.datamodel.models.result.ResultEntity; -import edu.ie3.datamodel.models.voltagelevels.VoltageLevel; -import edu.ie3.datamodel.utils.Try; -import java.lang.reflect.Method; -import java.time.ZonedDateTime; -import java.util.Optional; -import javax.measure.Quantity; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.io.geojson.GeoJsonWriter; - -public class FixedResultEntityProcessor extends ResultEntityProcessor { - public FixedResultEntityProcessor(Class registeredClass) - throws EntityProcessorException { - super(registeredClass); - } - - private static final GeoJsonWriter geoJsonWriter = new GeoJsonWriter(); - - protected String processMethodResult(Object methodReturnObject, Method method, String fieldName) - throws EntityProcessorException { - - StringBuilder resultStringBuilder = new StringBuilder(); - - switch (method.getReturnType().getSimpleName()) { - // primitives (Boolean, Character, Byte, Short, Integer, Long, Float, Double, String, - case "UUID", - "boolean", - "int", - "double", - "String", - "DayOfWeek", - "Season", - "ChargingPointType", - "EvcsLocationType" -> - resultStringBuilder.append(methodReturnObject.toString()); - case "Quantity", "ComparableQuantity" -> - resultStringBuilder.append(handleQuantity((Quantity) methodReturnObject, fieldName)); - case "Optional" -> - // only quantity optionals are expected here! - // if optional and present, unpack value and call this method again, if not present return - // an empty string as by convention null == missing value == "" when persisting data - resultStringBuilder.append( - ((Optional) methodReturnObject) - .map( - o -> { - if (o instanceof Quantity quantity) { - return Try.of( - () -> handleQuantity(quantity, fieldName), - EntityProcessorException.class); - } else if (o instanceof UniqueEntity entity) { - return Try.of(entity::getUuid, EntityProcessorException.class); - } else { - return Try.Failure.of( - new EntityProcessorException( - "Handling of " - + o.getClass().getSimpleName() - + ".class instance wrapped into Optional is currently not supported by entity processors!")); - } - }) - .orElse(Try.Success.of("")) // (in case of empty optional) - .getOrThrow()); - case "ZonedDateTime" -> - resultStringBuilder.append(processZonedDateTime((ZonedDateTime) methodReturnObject)); - case "OperationTime" -> - resultStringBuilder.append( - processOperationTime((OperationTime) methodReturnObject, fieldName)); - case "VoltageLevel" -> - resultStringBuilder.append( - processVoltageLevel((VoltageLevel) methodReturnObject, fieldName)); - case "Point", "LineString" -> - resultStringBuilder.append(geoJsonWriter.write((Geometry) methodReturnObject)); - case "LoadProfile", "BdewStandardLoadProfile", "RandomLoadProfile" -> - resultStringBuilder.append(((LoadProfile) methodReturnObject).getKey()); - case "AssetTypeInput", - "BmTypeInput", - "ChpTypeInput", - "EvTypeInput", - "HpTypeInput", - "LineTypeInput", - "LineInput", - "NodeInput", - "StorageTypeInput", - "SystemParticipantInput", - "ThermalBusInput", - "ThermalStorageInput", - "TimeSeries", - "Transformer2WTypeInput", - "Transformer3WTypeInput", - "WecTypeInput", - "EmInput" -> - resultStringBuilder.append(((UniqueEntity) methodReturnObject).getUuid()); - case "OperatorInput" -> - resultStringBuilder.append( - ((OperatorInput) methodReturnObject).getId().equalsIgnoreCase("NO_OPERATOR_ASSIGNED") - ? "" - : ((OperatorInput) methodReturnObject).getUuid()); - case "EvCharacteristicInput", - "OlmCharacteristicInput", - "WecCharacteristicInput", - "CosPhiFixed", - "CosPhiP", - "QV", - "ReactivePowerCharacteristic", - "CharacteristicInput" -> - resultStringBuilder.append(((CharacteristicInput) methodReturnObject).serialize()); - case "InputModelType" -> - resultStringBuilder.append(((CongestionResult.InputModelType) methodReturnObject).type); - default -> - throw new EntityProcessorException( - "Unable to process value for attribute/field '" - + fieldName - + "' and method return type '" - + method.getReturnType().getSimpleName() - + "' for method with name '" - + method.getName() - + "' in in entity model " - + getRegisteredClass().getSimpleName() - + ".class."); - } - - return resultStringBuilder.toString(); - } -} 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 05223b2f1a..ad74c9dba6 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/ServiceMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/ServiceMessage.scala @@ -161,13 +161,18 @@ object ServiceMessage { receiver: UUID | ActorRef[FlexResponse] | ActorRef[EmAgent.Message], ) extends ServiceResponseMessage - final case class ResultResponseMessage(result: ResultEntity) + final case class ResultResponseMessage(results: Iterable[ResultEntity]) extends ServiceMessage with ServiceResponseMessage { - def tick(using startTime: ZonedDateTime): Long = + def tick(using startTime: ZonedDateTime): Long = { + val time = results match { + case res :: el => res.getTime + } + TimeUtil.withDefaults.zonedDateTimeDifferenceInSeconds( startTime, - result.getTime, + time, ) + } } } diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala index 6479f34d4e..5de72a82d5 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala @@ -7,6 +7,7 @@ package edu.ie3.simona.service.results import edu.ie3.datamodel.models.result.ResultEntity +import edu.ie3.datamodel.models.result.system.SystemParticipantResult import edu.ie3.simona.api.data.connection.ExtResultDataConnection import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.api.ontology.results.{ @@ -207,49 +208,57 @@ object ExtResultProvider extends SimonaService with ExtDataSupport { extResponseMsg: ServiceResponseMessage )(using serviceStateData: ExtResultStateData): ExtResultStateData = extResponseMsg match { - case ResultResponseMessage(result) => + case ResultResponseMessage(results) => val receiveDataMap = serviceStateData.receiveDataMap + val participantResults = results.map { + case res: SystemParticipantResult => + res.getInputModel -> res + }.toMap + if (receiveDataMap.getExpectedKeys.isEmpty) { // we currently expect no result, // save received result in storage serviceStateData.copy( - resultStorage = - serviceStateData.resultStorage + (result.getInputModel -> result) + resultStorage = serviceStateData.resultStorage ++ participantResults ) } else { // we expect results, - val model = result.getInputModel - if (receiveDataMap.getExpectedKeys.contains(model)) { - // the received model is expected - // save in data map - val updated = receiveDataMap.addData(result.getInputModel, result) + val (expectedModels, otherModels) = participantResults.keys.partition( + receiveDataMap.getExpectedKeys.contains + ) - if (updated.isComplete) { + // storing the result, that were not requested + val updatedResultStorage = + serviceStateData.resultStorage ++ otherModels + .map(model => model -> participantResults(model)) + .toMap - serviceStateData.extResultDataConnection.queueExtResponseMsg( - new ProvideResultEntities(updated.receivedData.asJava) - ) + val updated = expectedModels.foldLeft(receiveDataMap) { + case (dataMap, model) => + dataMap.addData(model, participantResults(model)) + } - serviceStateData.copy(receiveDataMap = ReceiveDataMap.empty) - } else { + if (updated.isComplete) { - serviceStateData.copy( - receiveDataMap = updated - ) - } + serviceStateData.extResultDataConnection.queueExtResponseMsg( + new ProvideResultEntities(updated.receivedData.asJava) + ) + + serviceStateData.copy( + receiveDataMap = ReceiveDataMap.empty, + resultStorage = updatedResultStorage, + ) } else { - // the received result is not expected - // save in storage serviceStateData.copy( - resultStorage = serviceStateData.resultStorage + (model -> result) + receiveDataMap = updated, + resultStorage = updatedResultStorage, ) } } } - } diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala index 9116e8e3d7..87f78cdf16 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala @@ -78,7 +78,7 @@ final case class ExtResultSchedule( copy( scheduleMap = scheduleMap.updated( nextTick, - getScheduledKeys(nextTick) + msg.result.getInputModel, + getScheduledKeys(nextTick) ++ msg.results.map(_.getInputModel), ) ) } diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala index afdaeb71b3..cd0c2bde9d 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala @@ -183,27 +183,6 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { updated.extResultListeners shouldBe Seq((resultConnection, resultRef)) } - "return emDataService correctly" in { - val emConnection = - new ExtEmDataConnection(emptyListInput, EmMode.BASE) - val emRef = TestProbe("em_service").ref - - val cases = Table( - ("extSimSetupData", "expectedConnection", "expectedService"), - ( - ExtSimSetupData.apply.update(emConnection, emRef), - Some(emConnection), - Some(emRef), - ), - (ExtSimSetupData.apply, None, None), - ) - - forAll(cases) { (extSimSetupData, expectedConnection, expectedService) => - extSimSetupData.emDataConnection shouldBe expectedConnection - extSimSetupData.emDataService shouldBe expectedService - } - } - "return evDataService correctly" in { val evConnection = new ExtEvDataConnection() val evRef = TestProbe[ExtEvDataService.Message]("ev_service").ref From df09d4929643b89c8b61a9dbf080e9ff40808834 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 22 Jul 2025 16:33:37 +0200 Subject: [PATCH 067/125] Saving changes. --- .../listener/ExtResultEventListener.scala | 114 ++++++----- .../recode/RecodeDatabaseNamingStrategy.scala | 15 +- .../ie3/simona/recode/RecodeInfluxDBSink.java | 186 ++++++++++-------- .../scala/edu/ie3/simona/sim/SimonaSim.scala | 18 +- .../ie3/simona/sim/setup/ExtSimSetup.scala | 4 +- .../simona/sim/setup/ExtSimSetupData.scala | 115 +++++------ .../sim/setup/SimonaStandaloneSetup.scala | 4 +- .../service/em/ExtEmCommunicationIT.scala | 14 +- .../service/em/ExtEmDataServiceSpec.scala | 1 - .../edu/ie3/simona/sim/SimonaSimSpec.scala | 2 + .../sim/setup/ExtSimSetupDataSpec.scala | 56 +++--- 11 files changed, 286 insertions(+), 243 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEventListener.scala index 0085cc587f..e2fc6c6b89 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEventListener.scala @@ -1,3 +1,9 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + package edu.ie3.simona.event.listener import edu.ie3.datamodel.models.result.ResultEntity @@ -6,7 +12,10 @@ import edu.ie3.simona.api.ontology.results.ProvideResultEntities import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.event.ResultEvent.* import edu.ie3.simona.event.listener.DelayedStopHelper.StoppingMsg -import edu.ie3.simona.event.listener.ResultEventListener.{AggregatedTransformer3wResult, Transformer3wKey} +import edu.ie3.simona.event.listener.ResultEventListener.{ + AggregatedTransformer3wResult, + Transformer3wKey, +} import org.apache.pekko.actor.typed.Behavior import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} @@ -15,57 +24,72 @@ import scala.util.{Failure, Success} object ExtResultEventListener { - trait Request + type Message = ResultEvent | DelayedStopHelper.StoppingMsg def apply( - connection: ExtResultListener, - threeWindingResults: Map[ - Transformer3wKey, - AggregatedTransformer3wResult, - ] = Map.empty, - - ): Behavior[ResultEvent | DelayedStopHelper.StoppingMsg] = Behaviors.receivePartial[ResultEvent | DelayedStopHelper.StoppingMsg] { + connection: ExtResultListener, + threeWindingResults: Map[ + Transformer3wKey, + AggregatedTransformer3wResult, + ] = Map.empty, + ): Behavior[Message] = Behaviors.receivePartial[Message] { case (_, ParticipantResultEvent(systemParticipantResult)) => - connection.queueExtResponseMsg(new ProvideResultEntities(systemParticipantResult)) + connection.queueExtResponseMsg( + new ProvideResultEntities(systemParticipantResult) + ) Behaviors.same - case (ctx, PowerFlowResultEvent(nodeResults, switchResults, lineResults, transformer2wResults, transformer3wResults, congestionResults)) => - + case ( + ctx, + PowerFlowResultEvent( + nodeResults, + switchResults, + lineResults, + transformer2wResults, + transformer3wResults, + congestionResults, + ), + ) => // handle all results except the three winding transformer results - val results: Iterable[ResultEntity] = nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ congestionResults - connection.queueExtResponseMsg(new ProvideResultEntities(results.toList.asJava)) - + val results: Iterable[ResultEntity] = + nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ congestionResults + connection.queueExtResponseMsg( + new ProvideResultEntities(results.toList.asJava) + ) // handling of three winding transformers - val updatedResults = transformer3wResults.foldLeft(threeWindingResults) { case (allResults, result) => - val key = Transformer3wKey(result.input, result.time) - // retrieve existing partial result or use empty one - val partialResult = - allResults.getOrElse( - key, - AggregatedTransformer3wResult.EMPTY, - ) - // add partial result - partialResult.add(result).map { updatedResult => - if (updatedResult.ready) { - // if result is complete, we can write it out - updatedResult.consolidate.foreach(res => connection.queueExtResponseMsg(new ProvideResultEntities(res))) - // also remove partial result from map - allResults.removed(key) - } else { - // if result is not complete yet, just update it - allResults + (key -> updatedResult) - } - } match { - case Success(results) => results - case Failure(exception) => - ctx.log.warn( - "Failure when handling partial Transformer3w result", - exception, + val updatedResults = transformer3wResults.foldLeft(threeWindingResults) { + case (allResults, result) => + val key = Transformer3wKey(result.input, result.time) + // retrieve existing partial result or use empty one + val partialResult = + allResults.getOrElse( + key, + AggregatedTransformer3wResult.EMPTY, ) - // on failure, we just continue with previous results - allResults - } + // add partial result + partialResult.add(result).map { updatedResult => + if (updatedResult.ready) { + // if result is complete, we can write it out + updatedResult.consolidate.foreach(res => + connection.queueExtResponseMsg(new ProvideResultEntities(res)) + ) + // also remove partial result from map + allResults.removed(key) + } else { + // if result is not complete yet, just update it + allResults + (key -> updatedResult) + } + } match { + case Success(results) => results + case Failure(exception) => + ctx.log.warn( + "Failure when handling partial Transformer3w result", + exception, + ) + // on failure, we just continue with previous results + allResults + } } ExtResultEventListener(connection, updatedResults) @@ -75,7 +99,9 @@ object ExtResultEventListener { Behaviors.same case (_, FlexOptionsResultEvent(flexOptionsResult)) => - connection.queueExtResponseMsg(new ProvideResultEntities(flexOptionsResult)) + connection.queueExtResponseMsg( + new ProvideResultEntities(flexOptionsResult) + ) Behaviors.same case (ctx, msg: DelayedStopHelper.StoppingMsg) => diff --git a/src/main/scala/edu/ie3/simona/recode/RecodeDatabaseNamingStrategy.scala b/src/main/scala/edu/ie3/simona/recode/RecodeDatabaseNamingStrategy.scala index af27412e3b..74c02e0478 100644 --- a/src/main/scala/edu/ie3/simona/recode/RecodeDatabaseNamingStrategy.scala +++ b/src/main/scala/edu/ie3/simona/recode/RecodeDatabaseNamingStrategy.scala @@ -1,3 +1,9 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + package edu.ie3.simona.recode import edu.ie3.datamodel.io.naming.EntityPersistenceNamingStrategy @@ -7,15 +13,16 @@ import java.util.Optional class RecodeDatabaseNamingStrategy extends EntityPersistenceNamingStrategy { - - override def getResultEntityName(resultEntityClass: Class[? <: ResultEntity]): Optional[String] = { + override def getResultEntityName( + resultEntityClass: Class[? <: ResultEntity] + ): Optional[String] = { val NodeRes = classOf[NodeResult] - + resultEntityClass match { case NodeRes => Optional.of("bus") case _ => - super.getResultEntityName(resultEntityClass) + super.getResultEntityName(resultEntityClass) } } } diff --git a/src/main/scala/edu/ie3/simona/recode/RecodeInfluxDBSink.java b/src/main/scala/edu/ie3/simona/recode/RecodeInfluxDBSink.java index 795e1abe59..7c1e78f764 100644 --- a/src/main/scala/edu/ie3/simona/recode/RecodeInfluxDBSink.java +++ b/src/main/scala/edu/ie3/simona/recode/RecodeInfluxDBSink.java @@ -1,3 +1,9 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + package edu.ie3.simona.recode; import edu.ie3.datamodel.exceptions.EntityProcessorException; @@ -10,115 +16,125 @@ import edu.ie3.datamodel.models.timeseries.TimeSeries; import edu.ie3.datamodel.models.timeseries.TimeSeriesEntry; import edu.ie3.datamodel.models.value.Value; +import java.util.*; +import java.util.concurrent.TimeUnit; import org.influxdb.dto.BatchPoints; import org.influxdb.dto.Point; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; -import java.util.concurrent.TimeUnit; - public class RecodeInfluxDBSink implements OutputDataSink { - public static final Logger log = LoggerFactory.getLogger(RecodeInfluxDBSink.class); - private final InfluxDbConnector connector; - private final RecodeDatabaseNamingStrategy entityPersistenceNamingStrategy; - private final ProcessorProvider processorProvider; - - - public RecodeInfluxDBSink(InfluxDbConnector connector, RecodeDatabaseNamingStrategy entityPersistenceNamingStrategy) throws EntityProcessorException { - this.connector = connector; - this.entityPersistenceNamingStrategy = entityPersistenceNamingStrategy; - this.processorProvider = new ProcessorProvider(ProcessorProvider.allResultEntityProcessors(), ProcessorProvider.allTimeSeriesProcessors()); - + public static final Logger log = LoggerFactory.getLogger(RecodeInfluxDBSink.class); + private final InfluxDbConnector connector; + private final RecodeDatabaseNamingStrategy entityPersistenceNamingStrategy; + private final ProcessorProvider processorProvider; + + public RecodeInfluxDBSink( + InfluxDbConnector connector, RecodeDatabaseNamingStrategy entityPersistenceNamingStrategy) + throws EntityProcessorException { + this.connector = connector; + this.entityPersistenceNamingStrategy = entityPersistenceNamingStrategy; + this.processorProvider = + new ProcessorProvider( + ProcessorProvider.allResultEntityProcessors(), + ProcessorProvider.allTimeSeriesProcessors()); + } + + @Override + public void shutdown() { + connector.shutdown(); + } + + @Override + public void persist(C entity) throws ProcessorProviderException { + Set points = extractPoints(entity); + if (points.size() == 1) { + this.write(points.iterator().next()); + } else { + this.writeAll(points); } + } - @Override - public void shutdown() { - connector.shutdown(); - } + @Override + public void persistAll(Collection entities) + throws ProcessorProviderException { + Set points = new HashSet<>(); - @Override - public void persist(C entity) throws ProcessorProviderException { - Set points = extractPoints(entity); - if (points.size() == 1) { - this.write(points.iterator().next()); - } else { - this.writeAll(points); - } + for (C entity : entities) { + points.addAll(this.extractPoints(entity)); } - @Override - public void persistAll(Collection entities) throws ProcessorProviderException { - Set points = new HashSet<>(); + writeAll(points); + } - for(C entity : entities) { - points.addAll(this.extractPoints(entity)); - } + @Override + public , V extends Value, R extends Value> void persistTimeSeries( + TimeSeries timeSeries) { + log.warn("Persisting time series is not supported!"); + } - writeAll(points); - } + private Point transformToPoint(ResultEntity entity) throws ProcessorProviderException { + Optional measurementName = + entityPersistenceNamingStrategy.getResultEntityName(entity.getClass()); - @Override - public , V extends Value, R extends Value> void persistTimeSeries(TimeSeries timeSeries) { - log.warn("Persisting time series is not supported!"); + if (measurementName.isEmpty()) { + log.warn( + "I could not get a measurement name for class {}. I am using its simple name instead.", + entity.getClass().getSimpleName()); } - private Point transformToPoint(ResultEntity entity) throws ProcessorProviderException { - Optional measurementName = entityPersistenceNamingStrategy.getResultEntityName(entity.getClass()); - - if (measurementName.isEmpty()) { - log.warn("I could not get a measurement name for class {}. I am using its simple name instead.", entity.getClass().getSimpleName()); - } + return transformToPoint(entity, measurementName.orElse(entity.getClass().getSimpleName())); + } - return transformToPoint(entity, measurementName.orElse(entity.getClass().getSimpleName())); - } + private Point transformToPoint(ResultEntity entity, String measurementName) + throws ProcessorProviderException { + LinkedHashMap entityFieldData = + processorProvider.handleEntity(entity).getOrThrow(); - private Point transformToPoint(ResultEntity entity, String measurementName) throws ProcessorProviderException { - LinkedHashMap entityFieldData = processorProvider.handleEntity(entity).getOrThrow(); - - if (entityFieldData.containsKey("p")) { - String value = entityFieldData.remove("p"); - entityFieldData.put("p_mw", value); - } - if (entityFieldData.containsKey("q")) { - String value = entityFieldData.remove("q"); - entityFieldData.put("q_mvar", value); - } - - entityFieldData.remove("time"); - return Point.measurement(transformToMeasurementName(measurementName)) - .time(entity.getTime().toInstant().toEpochMilli(), TimeUnit.MILLISECONDS) - .tag("input_model", entityFieldData.remove("inputModel")) - .tag("run", connector.getScenarioName()) - .fields(Collections.unmodifiableMap(entityFieldData)) - .build(); + if (entityFieldData.containsKey("p")) { + String value = entityFieldData.remove("p"); + entityFieldData.put("p_mw", value); } - - private Set extractPoints(C entity) throws ProcessorProviderException { - Set points = new HashSet<>(); - if (entity instanceof ResultEntity resultEntity) { - points.add(transformToPoint(resultEntity)); - } else { - log.error("I don't know how to handle an entity of class {}", entity.getClass().getSimpleName()); - } - - return points; + if (entityFieldData.containsKey("q")) { + String value = entityFieldData.remove("q"); + entityFieldData.put("q_mvar", value); } - private void write(Point point) { - if (point != null) { - connector.getSession().write(point); - } + entityFieldData.remove("time"); + return Point.measurement(transformToMeasurementName(measurementName)) + .time(entity.getTime().toInstant().toEpochMilli(), TimeUnit.MILLISECONDS) + .tag("input_model", entityFieldData.remove("inputModel")) + .tag("run", connector.getScenarioName()) + .fields(Collections.unmodifiableMap(entityFieldData)) + .build(); + } + + private Set extractPoints(C entity) throws ProcessorProviderException { + Set points = new HashSet<>(); + if (entity instanceof ResultEntity resultEntity) { + points.add(transformToPoint(resultEntity)); + } else { + log.error( + "I don't know how to handle an entity of class {}", entity.getClass().getSimpleName()); } - private void writeAll(Collection points) { - if (!points.isEmpty()) { - BatchPoints batchPoints = BatchPoints.builder().points(points).build(); - connector.getSession().write(batchPoints); - } + return points; + } + + private void write(Point point) { + if (point != null) { + connector.getSession().write(point); } + } - private static String transformToMeasurementName(String filename) { - return filename.trim().replaceAll("\\W", "_"); + private void writeAll(Collection points) { + if (!points.isEmpty()) { + BatchPoints batchPoints = BatchPoints.builder().points(points).build(); + connector.getSession().write(batchPoints); } + } + + private static String transformToMeasurementName(String filename) { + return filename.trim().replaceAll("\\W", "_"); + } } diff --git a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala index 207b9b039a..0279b5e13a 100644 --- a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala +++ b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala @@ -87,8 +87,12 @@ object SimonaSim { val extSimulationData = simonaSetup.extSimulations(ctx, scheduler, extSimDir) + // TODO: Refactor ExtResultProvider handling and move to beginning again val resultEventListeners = - simonaSetup.resultEventListener(ctx, extSimulationData) + simonaSetup.resultEventListener( + ctx, + extSimulationData, + ) ++ extSimulationData.resultListeners /* start services */ // primary service proxy @@ -119,22 +123,18 @@ object SimonaSim { resultEventListeners, ) - val otherActors = Iterable[ActorRef[_]]( + val otherActors = Iterable[ActorRef[?]]( timeAdvancer, scheduler, primaryServiceProxy, weatherService, ) ++ gridAgents ++ - extSimulationData.extDataServices.map(_._2) + extSimulationData.allActorRefs /* watch all actors */ resultEventListeners.foreach(ctx.watch) ctx.watch(runtimeEventListener) - extSimulationData.extResultListeners.foreach { case (_, ref) => - ctx.watch(ref) - } - extSimulationData.extSimAdapters.foreach(ctx.watch) otherActors.foreach(ctx.watch) // End pre-initialization phase @@ -145,10 +145,6 @@ object SimonaSim { val delayedActors = resultEventListeners.appended(runtimeEventListener) - extSimulationData.extResultListeners.foreach(ref => - delayedActors.appended(ref) - ) - idle( ActorData( starter, diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index 2a6ea09723..d91f00e1c0 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -167,7 +167,7 @@ object ExtSimSetup { extSimSetupData.update(extPrimaryDataConnection, serviceRef) case extEmDataConnection: ExtEmDataConnection => - if (setupData.emDataConnection.nonEmpty) { + if (setupData.emDataService.nonEmpty) { throw ServiceException( s"Trying to connect another EmDataConnection. Currently only one is allowed." ) @@ -194,7 +194,7 @@ object ExtSimSetup { } case extEvDataConnection: ExtEvDataConnection => - if (setupData.evDataConnection.nonEmpty) { + if (setupData.evDataService.nonEmpty) { throw ServiceException( s"Trying to connect another EvDataConnection. Currently only one is allowed." ) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index 3154bd18a2..d680f2ab7b 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -9,8 +9,13 @@ package edu.ie3.simona.sim.setup import edu.ie3.simona.api.ExtSimAdapter import edu.ie3.simona.api.data.connection.* import edu.ie3.simona.api.ontology.DataMessageFromExt +import edu.ie3.simona.event.ResultEvent +import edu.ie3.simona.event.listener.ExtResultEventListener import edu.ie3.simona.ontology.messages.ServiceMessage import edu.ie3.simona.service.em.ExtEmDataService +import edu.ie3.simona.service.ev.ExtEvDataService +import edu.ie3.simona.service.primary.ExtPrimaryDataService +import edu.ie3.simona.service.results.ExtResultProvider import org.apache.pekko.actor.typed.ActorRef /** Case class that holds information regarding the external data connections as @@ -18,86 +23,82 @@ import org.apache.pekko.actor.typed.ActorRef * * @param extSimAdapters * All adapters to external simulations. - * @param extPrimaryDataServices + * @param primaryDataServices * Seq: external primary data connections to service references. - * @param extDataServices - * Seq: external input data connection to service references. - * @param extResultListeners - * Map: external result data connections to result data providers. + * @param emDataService + * Option for an external em data service. + * @param evDataService + * Option for an external ev data service. + * @param resultListeners + * Seq: external result listeners. + * @param resultServices + * Seq: external result services. */ final case class ExtSimSetupData( extSimAdapters: Iterable[ActorRef[ExtSimAdapter.Request]], - extPrimaryDataServices: Seq[ + primaryDataServices: Seq[ (ExtPrimaryDataConnection, ActorRef[ServiceMessage]) ], - extDataServices: Seq[ - (? <: ExtInputDataConnection[?], ActorRef[ServiceMessage]) - ], - extResultListeners: Seq[(ExtResultDataConnection, ActorRef[ServiceMessage])], + emDataService: Option[ActorRef[ExtEmDataService.Message]], + evDataService: Option[ActorRef[ExtEvDataService.Message]], + resultListeners: Seq[ActorRef[ExtResultEventListener.Message]], + resultServices: Seq[ActorRef[ServiceMessage]], ) { private[setup] def update( connection: ExtPrimaryDataConnection, ref: ActorRef[ServiceMessage], ): ExtSimSetupData = - copy(extPrimaryDataServices = - extPrimaryDataServices ++ Seq((connection, ref)) - ) + copy(primaryDataServices = primaryDataServices ++ Seq((connection, ref))) private[setup] def update( - connection: ExtInputDataConnection[?], - ref: ActorRef[ServiceMessage], - ): ExtSimSetupData = connection match { - case primaryConnection: ExtPrimaryDataConnection => - update(primaryConnection, ref) - case _ => - copy(extDataServices = extDataServices ++ Seq((connection, ref))) + connection: ExtDataConnection, + ref: ActorRef[? >: ServiceMessage], + ): ExtSimSetupData = (connection, ref) match { + case ( + primaryConnection: ExtPrimaryDataConnection, + serviceRef: ActorRef[ServiceMessage], + ) => + update(primaryConnection, serviceRef) + case ( + _: ExtEmDataConnection, + serviceRef: ActorRef[ExtEmDataService.Message], + ) => + copy(emDataService = Some(serviceRef)) + case ( + _: ExtEvDataConnection, + serviceRef: ActorRef[ExtEvDataService.Message], + ) => + copy(evDataService = Some(serviceRef)) + case ( + _: ExtResultDataConnection, + serviceRef: ActorRef[ServiceMessage], + ) => + copy(resultServices = resultServices ++ Seq(serviceRef)) + case ( + _: ExtResultListener, + serviceRef: ActorRef[ExtResultEventListener.Message], + ) => + copy(resultListeners = resultListeners ++ Seq(serviceRef)) + case (_, _) => + this } - private[setup] def update( - connection: ExtResultDataConnection, - ref: ActorRef[ServiceMessage], - ): ExtSimSetupData = - copy(extResultListeners = extResultListeners ++ Seq((connection, ref))) - private[setup] def updateAdapter( extSimAdapter: ActorRef[ExtSimAdapter.Request] ): ExtSimSetupData = copy(extSimAdapters = extSimAdapters ++ Set(extSimAdapter)) - def evDataService: Option[ActorRef[ServiceMessage]] = - extDataServices.collectFirst { - case (_: ExtEvDataConnection, ref: ActorRef[ServiceMessage]) => ref - } - - def emDataService: Option[ActorRef[ExtEmDataService.Message]] = - extDataServices.collectFirst { - case (_: ExtEmDataConnection, ref: ActorRef[ExtEmDataService.Message]) => - ref - } - - def resultDataServices: Iterable[ActorRef[ServiceMessage]] = - extResultListeners.map { case (_, ref) => ref } - - def evDataConnection: Option[ExtEvDataConnection] = - extDataServices.collectFirst { case (connection: ExtEvDataConnection, _) => - connection - } - - def emDataConnection: Option[ExtEmDataConnection] = - extDataServices.collectFirst { case (connection: ExtEmDataConnection, _) => - connection - } - def primaryDataConnections: Seq[ExtPrimaryDataConnection] = - extPrimaryDataServices.map { - case (connection: ExtPrimaryDataConnection, _) => connection - } - - def resultDataConnections: Seq[ExtResultDataConnection] = - extResultListeners.map { case (connection: ExtResultDataConnection, _) => + primaryDataServices.map { case (connection: ExtPrimaryDataConnection, _) => connection } + + def allActorRefs: Iterable[ActorRef[?]] = + extSimAdapters ++ primaryDataServices.map(_._2) ++ Seq( + emDataService, + evDataService, + ).flatten ++ resultListeners ++ resultServices } object ExtSimSetupData { @@ -107,6 +108,8 @@ object ExtSimSetupData { def apply: ExtSimSetupData = ExtSimSetupData( Iterable.empty, Seq.empty, + None, + None, Seq.empty, Seq.empty, ) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index 48f6a5662f..495ccc1057 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -157,7 +157,7 @@ class SimonaStandaloneSetup( InitPrimaryServiceProxyStateData( simonaConfig.simona.input.primary, simulationStart, - extSimSetupData.extPrimaryDataServices, + extSimSetupData.primaryDataServices, ), ), "primaryServiceProxyAgent", @@ -287,7 +287,7 @@ class SimonaStandaloneSetup( .spawn( ResultEventListener( resultFileHierarchy, - extSimSetupData.resultDataServices, + extSimSetupData.resultServices, ), ResultEventListener.getClass.getSimpleName, ) diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index 9b27d47a70..20435d0f23 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -317,7 +317,6 @@ class ExtEmCommunicationIT 0.002200000413468004.asMegaWatt, 0.002200000413468004.asMegaWatt, 0.006200000413468004.asMegaWatt, - Optional.empty, ), emNode3Uuid -> new FlexOptions( emSupUuid, @@ -325,7 +324,6 @@ class ExtEmCommunicationIT 0.asMegaWatt, 0.asMegaWatt, 0.004.asMegaWatt, - Optional.empty, ), emNode4Uuid -> new FlexOptions( emSupUuid, @@ -333,7 +331,6 @@ class ExtEmCommunicationIT 0.002200000413468004.asMegaWatt, 0.002200000413468004.asMegaWatt, 0.002200000413468004.asMegaWatt, - Optional.empty, ), ), Map( @@ -363,7 +360,6 @@ class ExtEmCommunicationIT 0.002200000413468004.asMegaWatt, 0.002200000413468004.asMegaWatt, 0.006200000413468004.asMegaWatt, - Optional.empty, ), emNode3Uuid -> new FlexOptions( emSupUuid, @@ -371,7 +367,6 @@ class ExtEmCommunicationIT 0.asMegaWatt, 0.asMegaWatt, 0.004.asMegaWatt, - Optional.empty, ), emNode4Uuid -> new FlexOptions( emSupUuid, @@ -379,7 +374,6 @@ class ExtEmCommunicationIT 0.002200000413468004.asMegaWatt, 0.002200000413468004.asMegaWatt, 0.002200000413468004.asMegaWatt, - Optional.empty, ), ), Map( @@ -411,11 +405,7 @@ class ExtEmCommunicationIT connection.sendFlexRequests( tick, Map( - emSupUuid -> new FlexOptionRequest( - emSupUuid, - Optional.empty, - Optional.empty, - ) + emSupUuid -> new FlexOptionRequest(emSupUuid, Optional.empty) ).asJava, Optional.of(nextTick), log, @@ -444,12 +434,10 @@ class ExtEmCommunicationIT emNode3Uuid -> new FlexOptionRequest( emNode3Uuid, Optional.of(emSupUuid), - Optional.empty, ), emNode4Uuid -> new FlexOptionRequest( emNode4Uuid, Optional.of(emSupUuid), - Optional.empty, ), ).asJava, Optional.of(nextTick), 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 13d741fd0e..1ef268a739 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmDataServiceSpec.scala @@ -400,7 +400,6 @@ class ExtEmDataServiceSpec -3.asKiloWatt, -1.asKiloWatt, 1.asKiloWatt, - Optional.empty, ) ).asJava ).asJava, diff --git a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala index 26b482b9ef..b19821a23d 100644 --- a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala @@ -72,6 +72,8 @@ class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { ExtSimSetupData( Iterable(extSim.toClassic), Seq.empty, + None, + None, Seq.empty, Seq.empty, ) diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala index cd0c2bde9d..d72c36ba16 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala @@ -15,6 +15,7 @@ import edu.ie3.simona.api.data.connection.{ ExtResultDataConnection, } import edu.ie3.simona.ontology.messages.ServiceMessage +import edu.ie3.simona.service.em.ExtEmDataService import edu.ie3.simona.service.ev.ExtEvDataService import edu.ie3.simona.service.primary.PrimaryServiceProxy import edu.ie3.simona.test.common.UnitSpec @@ -47,9 +48,11 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { ) updated.extSimAdapters shouldBe empty - updated.extPrimaryDataServices shouldBe Seq((connection, primaryRef)) - updated.extDataServices shouldBe empty - updated.extResultListeners shouldBe empty + updated.primaryDataServices shouldBe Seq((connection, primaryRef)) + updated.emDataService shouldBe None + updated.evDataService shouldBe None + updated.resultListeners shouldBe empty + updated.resultServices shouldBe empty } "be updated with multiple ExtPrimaryDataConnection correctly" in { @@ -68,12 +71,14 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { .update(connection2, primaryRef2) updated.extSimAdapters shouldBe empty - updated.extPrimaryDataServices shouldBe Seq( + updated.primaryDataServices shouldBe Seq( (connection1, primaryRef1), (connection2, primaryRef2), ) - updated.extDataServices shouldBe empty - updated.extResultListeners shouldBe empty + updated.emDataService shouldBe None + updated.evDataService shouldBe None + updated.resultListeners shouldBe empty + updated.resultServices shouldBe empty } "be updated with an ExtInputDataConnection correctly" in { @@ -88,36 +93,38 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val emConnection = new ExtEmDataConnection(emptyListInput, EmMode.BASE) - val emRef = TestProbe[ServiceMessage]("em_service").ref + val emRef = TestProbe[ExtEmDataService.Message]("em_service").ref val cases = Table( ("connection", "serviceRef", "expected"), ( primaryConnection, primaryRef, - extSimSetupData.copy(extPrimaryDataServices = + extSimSetupData.copy(primaryDataServices = Seq((primaryConnection, primaryRef)) ), ), ( evConnection, evRef, - extSimSetupData.copy(extDataServices = Seq((evConnection, evRef))), + extSimSetupData.copy(evDataService = Some(evRef)), ), ( emConnection, emRef, - extSimSetupData.copy(extDataServices = Seq((emConnection, emRef))), + extSimSetupData.copy(emDataService = Some(emRef)), ), ) forAll(cases) { (connection, serviceRef, expected) => val updated = extSimSetupData.update(connection, serviceRef) - updated.extSimAdapters shouldBe expected.extSimAdapters - updated.extPrimaryDataServices shouldBe expected.extPrimaryDataServices - updated.extDataServices shouldBe expected.extDataServices - updated.extResultListeners shouldBe expected.extResultListeners + updated.extSimAdapters shouldBe empty + updated.primaryDataServices shouldBe empty + updated.emDataService shouldBe expected.emDataService + updated.evDataService shouldBe expected.evDataService + updated.resultListeners shouldBe empty + updated.resultServices shouldBe empty } } @@ -136,9 +143,11 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val updated = extSimSetupData.update(resultConnection, resultRef) updated.extSimAdapters shouldBe empty - updated.extPrimaryDataServices shouldBe empty - updated.extDataServices shouldBe empty - updated.extResultListeners shouldBe Seq((resultConnection, resultRef)) + updated.primaryDataServices shouldBe empty + updated.emDataService shouldBe None + updated.evDataService shouldBe None + updated.resultListeners shouldBe empty + updated.resultServices shouldBe Seq(resultRef) } "be updated with multiple different connections correctly" in { @@ -170,17 +179,16 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { .update(resultConnection, resultRef) updated.extSimAdapters shouldBe empty - updated.extPrimaryDataServices shouldBe Seq( + updated.primaryDataServices shouldBe Seq( ( primaryConnection, primaryRef, ) ) - updated.extDataServices shouldBe Seq( - (evConnection, evRef), - (emConnection, emRef), - ) - updated.extResultListeners shouldBe Seq((resultConnection, resultRef)) + updated.emDataService shouldBe Some(emRef) + updated.evDataService shouldBe Some(evRef) + updated.resultListeners shouldBe empty + updated.resultServices shouldBe Seq(resultRef) } "return evDataService correctly" in { @@ -198,7 +206,6 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { ) forAll(cases) { (extSimSetupData, expectedConnection, expectedService) => - extSimSetupData.evDataConnection shouldBe expectedConnection extSimSetupData.evDataService shouldBe expectedService } } @@ -219,7 +226,6 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { ) forAll(cases) { (extSimSetupData, expectedConnection, expectedService) => - extSimSetupData.emDataConnection shouldBe expectedConnection extSimSetupData.emDataService shouldBe expectedService } } From 83a65151e3bfd531c9ac689fb1c087723d19dc92 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 29 Jul 2025 08:29:04 +0200 Subject: [PATCH 068/125] Saving changes. --- build.gradle | 2 +- src/main/scala/edu/ie3/simona/model/em/EmTools.scala | 4 ++-- .../model/participant/PrimaryDataParticipantModel.scala | 4 ++-- .../edu/ie3/simona/service/load/LoadProfileSources.scala | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index f47796f43a..4f56e9c91d 100644 --- a/build.gradle +++ b/build.gradle @@ -82,7 +82,7 @@ dependencies { /* Exclude our own nested dependencies */ exclude group: 'com.github.ie3-institute' } - implementation('com.github.ie3-institute:PowerSystemDataModel:8.0.0') { + implementation('com.github.ie3-institute:PowerSystemDataModel:8.1.0') { exclude group: 'org.apache.logging.log4j' exclude group: 'org.slf4j' /* Exclude our own nested dependencies */ diff --git a/src/main/scala/edu/ie3/simona/model/em/EmTools.scala b/src/main/scala/edu/ie3/simona/model/em/EmTools.scala index 129c11c22f..dd17f1d409 100644 --- a/src/main/scala/edu/ie3/simona/model/em/EmTools.scala +++ b/src/main/scala/edu/ie3/simona/model/em/EmTools.scala @@ -44,9 +44,9 @@ object EmTools { val min = minMaxFlexOptions.min val max = minMaxFlexOptions.max - if (setPower < min && (setPower ~= min)) { + if (setPower < min) { min - } else if (setPower > max && (setPower ~= max)) { + } else if (setPower > max) { max } else { // sanity check: setPower is in range of latest flex options diff --git a/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala index dbde993101..d7d2264061 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala @@ -199,8 +199,8 @@ object PrimaryDataParticipantModel { * The physical participant model. * @param primaryDataExtra * Extra functionality specific to the primary data class. - * @param scalingFactor - * The scaling factor from the runtime config. + * @param scalingFactor + * The scaling factor from the runtime config. */ final case class Factory[PD <: PrimaryData]( physicalModel: ParticipantModel[?, ?], diff --git a/src/main/scala/edu/ie3/simona/service/load/LoadProfileSources.scala b/src/main/scala/edu/ie3/simona/service/load/LoadProfileSources.scala index 7c15cfc7f4..34535599a4 100644 --- a/src/main/scala/edu/ie3/simona/service/load/LoadProfileSources.scala +++ b/src/main/scala/edu/ie3/simona/service/load/LoadProfileSources.scala @@ -193,7 +193,7 @@ object LoadProfileSources { */ private def buildSourcesFrom[ P <: LoadProfile, - V <: LoadValues, + V <: LoadValues[P], ]( datasource: DataSource, allMetaInformation: Map[String, LoadProfileMetaInformation], From 5d5c6a80c17894639c7d9dffe3c01c41108dacff Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 8 Aug 2025 11:10:11 +0200 Subject: [PATCH 069/125] Adding new external result event handling. --- build.gradle | 4 - .../agent/participant/ParticipantAgent.scala | 13 +- .../event/listener/ExtResultEvent.scala | 329 ++++++++++++++++++ .../listener/ExtResultEventListener.scala | 110 ------ .../event/listener/ResultEventListener.scala | 21 +- .../ie3/simona/service/em/EmServiceCore.scala | 4 + .../service/results/ExtResultProvider.scala | 1 + .../service/results/ExtResultSchedule.scala | 1 + .../scala/edu/ie3/simona/sim/SimonaSim.scala | 16 +- .../ie3/simona/sim/setup/ExtSimSetup.scala | 38 +- .../simona/sim/setup/ExtSimSetupData.scala | 26 +- .../ie3/simona/sim/setup/SimonaSetup.scala | 7 +- .../sim/setup/SimonaStandaloneSetup.scala | 11 +- .../edu/ie3/simona/util/ReceiveDataMap.scala | 2 + .../edu/ie3/simona/sim/SimonaSimSpec.scala | 6 +- .../sim/setup/ExtSimSetupDataSpec.scala | 10 +- .../simona/sim/setup/SimonaSetupSpec.scala | 3 +- 17 files changed, 394 insertions(+), 208 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala delete mode 100644 src/main/scala/edu/ie3/simona/event/listener/ExtResultEventListener.scala diff --git a/build.gradle b/build.gradle index 4f56e9c91d..4477adc9c4 100644 --- a/build.gradle +++ b/build.gradle @@ -205,8 +205,6 @@ shadowJar { tasks.withType(ScalaCompile).configureEach { scalaCompileOptions.additionalParameters = scala3compilerOptions + [ "-Xplugin:" + configurations.scalaCompilerPlugin.asPath, - "-P:scapegoat:dataDir:" + project.layout.buildDirectory.get().asFile.absolutePath + "/reports/scapegoat/src/", - "-P:scapegoat:disabledInspections:TryGet" ] scalaCompileOptions.forkOptions.jvmArgs = [ '-Xss2m', @@ -218,8 +216,6 @@ tasks.withType(ScalaCompile).configureEach { compileTestScala { scalaCompileOptions.additionalParameters = scala3compilerOptions + [ "-Xplugin:" + configurations.scalaCompilerPlugin.asPath, - "-P:scapegoat:dataDir:" + project.layout.buildDirectory.get().asFile.absolutePath + "/reports/scapegoat/testsrc/", - "-P:scapegoat:disabledInspections:TryGet" ] } diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index f852ce20ff..7d2d851837 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -8,19 +8,14 @@ package edu.ie3.simona.agent.participant import breeze.numerics.{pow, sqrt} import edu.ie3.datamodel.models.result.system.SystemParticipantResult -import edu.ie3.simona.agent.grid.GridAgentMessages.{ - AssetPowerChangedMessage, - AssetPowerUnchangedMessage, - ProvidedPowerResponse, -} +import edu.ie3.simona.agent.grid.GridAgentMessages.{AssetPowerChangedMessage, AssetPowerUnchangedMessage, ProvidedPowerResponse} import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.model.participant.ParticipantModel.AdditionalFactoryData import edu.ie3.simona.model.participant.ParticipantModelShell import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.ServiceMessage import edu.ie3.simona.ontology.messages.ServiceMessage.ResultResponseMessage -import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage, ServiceMessage} import edu.ie3.simona.service.Data import edu.ie3.simona.service.Data.{PrimaryData, PrimaryDataExtra} import edu.ie3.simona.service.results.ExtResultProvider @@ -311,7 +306,7 @@ object ParticipantAgent { case (ctx, RequestLastResult(replyTo)) => // send last calculated results to result service - replyTo ! ResultResponseMessage(gridAdapter.lastResults) + // replyTo ! ResultResponseMessage(gridAdapter.lastResults) ParticipantAgent( modelShell, diff --git a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala new file mode 100644 index 0000000000..31bf1667b9 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala @@ -0,0 +1,329 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.event.listener + +import edu.ie3.datamodel.models.result.ResultEntity +import edu.ie3.datamodel.models.result.connector.Transformer3WResult +import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult +import edu.ie3.simona.api.data.connection.{ + ExtResultDataConnection, + ExtResultListener, +} +import edu.ie3.simona.api.ontology.DataMessageFromExt +import edu.ie3.simona.api.ontology.results.{ + ProvideResultEntities, + RequestResultEntities, + ResultDataMessageFromExt, +} +import edu.ie3.simona.event.ResultEvent +import edu.ie3.simona.event.ResultEvent.* +import edu.ie3.simona.event.listener.ResultEventListener.{ + AggregatedTransformer3wResult, + Transformer3wKey, +} +import edu.ie3.simona.exceptions.CriticalFailureException +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} +import edu.ie3.simona.ontology.messages.ServiceMessage.ScheduleServiceActivation +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import org.apache.pekko.actor.typed.scaladsl.Behaviors +import org.apache.pekko.actor.typed.{ActorRef, Behavior} +import org.slf4j.Logger + +import java.time.ZonedDateTime +import java.util +import java.util.UUID +import scala.jdk.CollectionConverters.* +import scala.util.{Failure, Success} + +object ExtResultEvent { + + type Message = ResultEvent | DelayedStopHelper.StoppingMsg + + final case class ProviderState( + scheduler: ActorRef[SchedulerMessage], + connection: ExtResultDataConnection, + resultStore: Map[UUID, ResultEntity] = Map.empty, + threeWindingResults: Map[ + Transformer3wKey, + AggregatedTransformer3wResult, + ] = Map.empty, + extMessage: Option[ResultDataMessageFromExt] = None, + simStartTime: ZonedDateTime, + gridAssets: List[UUID] = List.empty, + ) { + def updateResultData( + updatedThreeWindingResults: Map[ + Transformer3wKey, + AggregatedTransformer3wResult, + ], + results: Seq[ResultEntity], + ): ProviderState = { + val updateStore = + resultStore ++ results.map(res => res.getInputModel -> res).toMap + + copy( + threeWindingResults = updatedThreeWindingResults, + resultStore = updateStore, + ) + } + } + + def listener( + connection: ExtResultListener, + threeWindingResults: Map[ + Transformer3wKey, + AggregatedTransformer3wResult, + ] = Map.empty, + ): Behavior[Message] = Behaviors.receivePartial[Message] { + case (ctx, resultEvent: ResultEvent) => + val (updatedThreeWinding, results) = + handleResultEvent(resultEvent, threeWindingResults)(using ctx.log) + + connection.queueExtResponseMsg( + new ProvideResultEntities(results.toList.asJava) + ) + + listener(connection, updatedThreeWinding) + + case (ctx, msg: DelayedStopHelper.StoppingMsg) => + DelayedStopHelper.handleMsg((ctx, msg)) + } + + def provider( + connection: ExtResultDataConnection, + scheduler: ActorRef[SchedulerMessage], + simStartTime: ZonedDateTime, + ): Behavior[Message | DataMessageFromExt | Activation] = { + val gridResults = connection.getGridResultDataAssets.asScala + val participantResults = connection.getParticipantResultDataAssets.asScala + val flexOptionResults = connection.getFlexOptionAssets.asScala + + val assets = (gridResults ++ participantResults ++ flexOptionResults).toSet + + val stateData = + ProviderState( + scheduler, + connection, + simStartTime = simStartTime, + gridAssets = gridResults.toList, + ) + + val resultFilter: ResultEntity => Boolean = + assets.contains.compose(_.getInputModel) + + provider(stateData)(using resultFilter) + } + + private def provider(stateData: ProviderState)(using + resultFilter: ResultEntity => Boolean + ): Behavior[Message | DataMessageFromExt | Activation] = + Behaviors.receivePartial[Message | DataMessageFromExt | Activation] { + case (ctx, resultEvent: ResultEvent) => + val (updatedThreeWinding, results) = + handleResultEvent( + resultEvent, + stateData.threeWindingResults, + resultFilter, + )(using ctx.log) + + val updatedState = + stateData.updateResultData(updatedThreeWinding, results) + + // reactivate this service, if we have an unanswered request + stateData.extMessage.foreach { case extMsg: RequestResultEntities => + ctx.self ! Activation(extMsg.tick) + } + + provider(updatedState) + + case (_, messageFromExt: ResultDataMessageFromExt) => + // save ext message + provider(stateData.copy(extMessage = Some(messageFromExt))) + + case (ctx, ScheduleServiceActivation(tick, unlockKey)) => + stateData.scheduler ! ScheduleActivation( + ctx.self, + tick, + Some(unlockKey), + ) + + Behaviors.same + + case (ctx, Activation(tick)) => + // handle ext message + + val extMsg = stateData.extMessage.getOrElse( + // this should not be possible because the external simulation schedules this provider + throw CriticalFailureException( + "ExtResultDataService was triggered without ResultDataMessageFromExt available" + ) + ) + + extMsg match { + case requestResultEntities: RequestResultEntities => + val uuids = + new util.ArrayList[UUID](requestResultEntities.requestedResults) + val receivedTick = requestResultEntities.tick + + // TODO: Check received tick + // receivedTick == tick + + if (receivedTick == 0) { + // we can't send grid results for the tick 0 + // therefore, we remove them + uuids.removeAll(stateData.gridAssets.asJava) + } + + val results = stateData.resultStore + + if (results.nonEmpty) { + val foundResults = uuids.asScala + .flatMap(uuid => results.get(uuid).map(data => uuid -> data)) + .toMap + + val updatedData = results.removedAll(foundResults.keys) + + // check if there are unanswered requests + uuids.removeAll(foundResults.keySet.asJava) + + val updatedStateData = if (uuids.isEmpty) { + // send results to ext + stateData.connection.queueExtResponseMsg( + new ProvideResultEntities(updatedData.values.toList.asJava) + ) + + // tell the scheduler that we are finished + stateData.scheduler ! Completion(ctx.self) + + stateData.copy( + resultStore = updatedData, + extMessage = None, + ) + } else { + val updatedRequest = + new RequestResultEntities(receivedTick, uuids) + + stateData.copy( + resultStore = updatedData, + extMessage = Some(updatedRequest), + ) + } + + provider(updatedStateData) + + } else if (results.isEmpty && uuids.isEmpty) { + // send no results to ext + stateData.connection.queueExtResponseMsg( + ProvideResultEntities.empty() + ) + + // tell the scheduler that we are finished + stateData.scheduler ! Completion(ctx.self) + + provider(stateData.copy(extMessage = None)) + } else { + ctx.log.warn(s"Could not find results! Waiting ...") + Behaviors.same + } + + case other => + ctx.log.warn(s"Cannot handle external result message: $other") + Behaviors.same + } + + case (ctx, msg: DelayedStopHelper.StoppingMsg) => + DelayedStopHelper.handleMsg((ctx, msg)) + + } + + private def handleResultEvent( + resultEvent: ResultEvent, + threeWindingResults: Map[Transformer3wKey, AggregatedTransformer3wResult], + resultFilter: ResultEntity => Boolean = _ => true, + )(using + log: Logger + ): (Map[Transformer3wKey, AggregatedTransformer3wResult], Seq[ResultEntity]) = + resultEvent match { + case ParticipantResultEvent(systemParticipantResult) => + (threeWindingResults, Seq(systemParticipantResult).filter(resultFilter)) + + case PowerFlowResultEvent( + nodeResults, + switchResults, + lineResults, + transformer2wResults, + partialTransformer3wResults, + congestionResults, + ) => + // handling of three winding transformers + val (updatedResults, transformer3wResults) = + handleThreeWindingTransformers( + partialTransformer3wResults, + threeWindingResults, + ) + + val results: Seq[ResultEntity] = + transformer3wResults ++ nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ congestionResults + + (updatedResults, results.filter(resultFilter)) + + case ThermalResultEvent(thermalResult) => + (threeWindingResults, Seq(thermalResult).filter(resultFilter)) + + case FlexOptionsResultEvent(flexOptionsResult) => + (threeWindingResults, Seq(flexOptionsResult).filter(resultFilter)) + } + + private def handleThreeWindingTransformers( + transformer3wResults: Iterable[PartialTransformer3wResult], + threeWindingResults: Map[Transformer3wKey, AggregatedTransformer3wResult], + )(using log: Logger) = transformer3wResults.foldLeft( + threeWindingResults, + Seq.empty[Transformer3WResult], + ) { case ((allPartialResults, allResults), result) => + val key = Transformer3wKey(result.input, result.time) + // retrieve existing partial result or use empty one + val partialResult = + allPartialResults.getOrElse( + key, + AggregatedTransformer3wResult.EMPTY, + ) + // add partial result + partialResult.add(result).map { updatedResult => + if (updatedResult.ready) { + // if result is complete, we can write it out + updatedResult.consolidate match { + case Failure(exception) => + log.warn( + "Failure when handling partial Transformer3w result", + exception, + ) + // on failure, we just continue with previous results + (allPartialResults, allResults) + case Success(res) => + (allPartialResults.removed(key), allResults.appended(res)) + } + + } else { + // if result is not complete yet, just update it + (allPartialResults + (key -> updatedResult), allResults) + } + } match { + case Success(results) => results + case Failure(exception) => + log.warn( + "Failure when handling partial Transformer3w result", + exception, + ) + // on failure, we just continue with previous results + (allPartialResults, allResults) + } + } +} diff --git a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEventListener.scala deleted file mode 100644 index e2fc6c6b89..0000000000 --- a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEventListener.scala +++ /dev/null @@ -1,110 +0,0 @@ -/* - * © 2025. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.event.listener - -import edu.ie3.datamodel.models.result.ResultEntity -import edu.ie3.simona.api.data.connection.ExtResultListener -import edu.ie3.simona.api.ontology.results.ProvideResultEntities -import edu.ie3.simona.event.ResultEvent -import edu.ie3.simona.event.ResultEvent.* -import edu.ie3.simona.event.listener.DelayedStopHelper.StoppingMsg -import edu.ie3.simona.event.listener.ResultEventListener.{ - AggregatedTransformer3wResult, - Transformer3wKey, -} -import org.apache.pekko.actor.typed.Behavior -import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} - -import scala.jdk.CollectionConverters.SeqHasAsJava -import scala.util.{Failure, Success} - -object ExtResultEventListener { - - type Message = ResultEvent | DelayedStopHelper.StoppingMsg - - def apply( - connection: ExtResultListener, - threeWindingResults: Map[ - Transformer3wKey, - AggregatedTransformer3wResult, - ] = Map.empty, - ): Behavior[Message] = Behaviors.receivePartial[Message] { - case (_, ParticipantResultEvent(systemParticipantResult)) => - connection.queueExtResponseMsg( - new ProvideResultEntities(systemParticipantResult) - ) - Behaviors.same - - case ( - ctx, - PowerFlowResultEvent( - nodeResults, - switchResults, - lineResults, - transformer2wResults, - transformer3wResults, - congestionResults, - ), - ) => - // handle all results except the three winding transformer results - val results: Iterable[ResultEntity] = - nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ congestionResults - connection.queueExtResponseMsg( - new ProvideResultEntities(results.toList.asJava) - ) - - // handling of three winding transformers - val updatedResults = transformer3wResults.foldLeft(threeWindingResults) { - case (allResults, result) => - val key = Transformer3wKey(result.input, result.time) - // retrieve existing partial result or use empty one - val partialResult = - allResults.getOrElse( - key, - AggregatedTransformer3wResult.EMPTY, - ) - // add partial result - partialResult.add(result).map { updatedResult => - if (updatedResult.ready) { - // if result is complete, we can write it out - updatedResult.consolidate.foreach(res => - connection.queueExtResponseMsg(new ProvideResultEntities(res)) - ) - // also remove partial result from map - allResults.removed(key) - } else { - // if result is not complete yet, just update it - allResults + (key -> updatedResult) - } - } match { - case Success(results) => results - case Failure(exception) => - ctx.log.warn( - "Failure when handling partial Transformer3w result", - exception, - ) - // on failure, we just continue with previous results - allResults - } - } - - ExtResultEventListener(connection, updatedResults) - - case (_, ThermalResultEvent(thermalResult)) => - connection.queueExtResponseMsg(new ProvideResultEntities(thermalResult)) - Behaviors.same - - case (_, FlexOptionsResultEvent(flexOptionsResult)) => - connection.queueExtResponseMsg( - new ProvideResultEntities(flexOptionsResult) - ) - Behaviors.same - - case (ctx, msg: DelayedStopHelper.StoppingMsg) => - DelayedStopHelper.handleMsg((ctx, msg)) - } -} diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index e9e1786045..6af3e36789 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -48,12 +48,9 @@ object ResultEventListener extends Transformer3wResultSupport { * @param classToSink * a map containing the sink for each class that should be processed by the * listener - * @param extSink - * actors for external result data services */ private final case class BaseData( classToSink: Map[Class[_], ResultEntitySink], - extSink: Iterable[ActorRef[ServiceMessage]] = Iterable.empty, threeWindingResults: Map[ Transformer3wKey, AggregatedTransformer3wResult, @@ -166,7 +163,7 @@ object ResultEventListener extends Transformer3wResultSupport { baseData: BaseData, log: Logger, ): BaseData = { - handOverToSink(resultEntity, baseData.classToSink, baseData.extSink, log) + handOverToSink(resultEntity, baseData.classToSink, log) baseData } @@ -198,7 +195,7 @@ object ResultEventListener extends Transformer3wResultSupport { if (updatedResult.ready) { // if result is complete, we can write it out updatedResult.consolidate.foreach { - handOverToSink(_, baseData.classToSink, baseData.extSink, log) + handOverToSink(_, baseData.classToSink, log) } // also remove partial result from map baseData.threeWindingResults.removed(key) @@ -231,12 +228,9 @@ object ResultEventListener extends Transformer3wResultSupport { private def handOverToSink( resultEntity: ResultEntity, classToSink: Map[Class[_], ResultEntitySink], - extSink: Iterable[ActorRef[ServiceMessage]], log: Logger, ): Unit = Try { - extSink.foreach(_ ! ResultResponseMessage(Seq(resultEntity))) - classToSink .get(resultEntity.getClass) .foreach(_.handleResultEntity(resultEntity)) @@ -245,8 +239,7 @@ object ResultEventListener extends Transformer3wResultSupport { } def apply( - resultFileHierarchy: ResultFileHierarchy, - extResultListeners: Iterable[ActorRef[ServiceMessage]] = Iterable.empty, + resultFileHierarchy: ResultFileHierarchy ): Behavior[Request] = Behaviors.setup[Request] { ctx => ctx.log.debug("Starting initialization!") resultFileHierarchy.resultSinkType match { @@ -270,16 +263,14 @@ object ResultEventListener extends Transformer3wResultSupport { case Success(result) => SinkResponse(result.toMap) } - init(extResultListeners) + init } - private def init( - extResultListeners: Iterable[ActorRef[ServiceMessage]] = Iterable.empty - ): Behavior[Request] = Behaviors.withStash(200) { buffer => + private def init: Behavior[Request] = Behaviors.withStash(200) { buffer => Behaviors.receive[Request] { case (ctx, SinkResponse(response)) => ctx.log.debug("Initialization complete!") - buffer.unstashAll(idle(BaseData(response, extResultListeners))) + buffer.unstashAll(idle(BaseData(response))) case (ctx, InitFailed(ex)) => ctx.log.error("Unable to setup ResultEventListener.", ex) 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 65d47246e0..4cf94d933e 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -59,6 +59,8 @@ trait EmServiceCore { log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = responseMsg match { case EmFlexMessage(flexRequest: FlexRequest, receiver) => + log.warn(s"$receiver <- $flexRequest") + receiver match { case ref: ActorRef[FlexRequest] => handleFlexRequest(flexRequest, ref) @@ -70,6 +72,8 @@ trait EmServiceCore { } case EmFlexMessage(flexResponse: FlexResponse, receiver) => + log.warn(s"$receiver <- $flexResponse") + receiver match { case uuid: UUID => handleFlexResponse(tick, flexResponse, Left(uuid)) diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala index 5de72a82d5..50537d2700 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala @@ -35,6 +35,7 @@ import java.util.UUID import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava} import scala.util.{Failure, Success, Try} +@deprecated object ExtResultProvider extends SimonaService with ExtDataSupport { override type S = ExtResultStateData diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala index 87f78cdf16..bbed15fdd6 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala @@ -10,6 +10,7 @@ import edu.ie3.simona.ontology.messages.ServiceMessage.ResultResponseMessage import java.util.UUID +@deprecated final case class ExtResultSchedule( scheduleMap: Map[Long, Set[UUID]] = Map.empty, unscheduledList: Set[UUID] = Set.empty, diff --git a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala index 0279b5e13a..189e4edcd0 100644 --- a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala +++ b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala @@ -71,6 +71,7 @@ object SimonaSim { Behaviors .receivePartial[Request] { case (ctx, Start(_)) => val runtimeEventListener = simonaSetup.runtimeEventListener(ctx) + val resultEventListeners = simonaSetup.resultEventListener(ctx) val timeAdvancer = simonaSetup.timeAdvancer(ctx, ctx.self, runtimeEventListener) @@ -87,12 +88,8 @@ object SimonaSim { val extSimulationData = simonaSetup.extSimulations(ctx, scheduler, extSimDir) - // TODO: Refactor ExtResultProvider handling and move to beginning again - val resultEventListeners = - simonaSetup.resultEventListener( - ctx, - extSimulationData, - ) ++ extSimulationData.resultListeners + val allResultEventListeners = + resultEventListeners ++ extSimulationData.resultListeners ++ extSimulationData.resultProviders /* start services */ // primary service proxy @@ -120,7 +117,7 @@ object SimonaSim { val gridAgents = simonaSetup.gridAgents( ctx, environmentRefs, - resultEventListeners, + allResultEventListeners, ) val otherActors = Iterable[ActorRef[?]]( @@ -133,7 +130,7 @@ object SimonaSim { extSimulationData.allActorRefs /* watch all actors */ - resultEventListeners.foreach(ctx.watch) + allResultEventListeners.foreach(ctx.watch) ctx.watch(runtimeEventListener) otherActors.foreach(ctx.watch) @@ -143,7 +140,8 @@ object SimonaSim { // Start simulation timeAdvancer ! TimeAdvancer.Start - val delayedActors = resultEventListeners.appended(runtimeEventListener) + val delayedActors = + allResultEventListeners.appended(runtimeEventListener) idle( ActorData( diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index d91f00e1c0..487a20dbbf 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -6,17 +6,12 @@ package edu.ie3.simona.sim.setup -import edu.ie3.simona.api.data.connection.ExtInputDataConnection -import edu.ie3.simona.api.data.connection.{ - ExtEmDataConnection, - ExtEvDataConnection, - ExtPrimaryDataConnection, - ExtResultDataConnection, -} +import edu.ie3.simona.api.data.connection.* import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt import edu.ie3.simona.api.simulation.{ExtSimAdapterData, ExtSimulation} import edu.ie3.simona.api.{ExtLinkInterface, ExtSimAdapter} +import edu.ie3.simona.event.listener.ExtResultEvent import edu.ie3.simona.exceptions.ServiceException import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.scheduler.ScheduleLock @@ -27,8 +22,6 @@ import edu.ie3.simona.service.ev.ExtEvDataService import edu.ie3.simona.service.ev.ExtEvDataService.InitExtEvData import edu.ie3.simona.service.primary.ExtPrimaryDataService import edu.ie3.simona.service.primary.ExtPrimaryDataService.InitExtPrimaryData -import edu.ie3.simona.service.results.ExtResultProvider -import edu.ie3.simona.service.results.ExtResultProvider.InitExtResultData import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext @@ -36,7 +29,6 @@ import org.slf4j.{Logger, LoggerFactory} import java.time.ZonedDateTime import java.util.UUID -import scala.concurrent.duration.FiniteDuration import scala.jdk.CollectionConverters.{ListHasAsScala, SetHasAsScala} import scala.util.{Failure, Success, Try} @@ -54,8 +46,6 @@ object ExtSimSetup { * The actor context of this actor system. * @param scheduler * The scheduler of simona. - * @param resolution - * The resolution of the power flow. * @return * An [[ExtSimSetupData]] that holds information regarding the external * data connections as well as the actor references of the created @@ -68,7 +58,6 @@ object ExtSimSetup { context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], startTime: ZonedDateTime, - resolution: FiniteDuration, ): ExtSimSetupData = extLinks.zipWithIndex.foldLeft(ExtSimSetupData.apply) { case (extSimSetupData, (extLink, index)) => // external simulation always needs at least an ExtSimAdapter @@ -124,8 +113,6 @@ object ExtSimSetup { * The scheduler of simona. * @param extSimAdapterData * The adapter data for the external simulation. - * @param resolution - * The resolution of the power flow. * @return * An updated [[ExtSimSetupData]]. */ @@ -137,7 +124,6 @@ object ExtSimSetup { scheduler: ActorRef[SchedulerMessage], extSimAdapterData: ExtSimAdapterData, startTime: ZonedDateTime, - resolution: FiniteDuration, ): ExtSimSetupData = { given extSimAdapter: ActorRef[ControlResponseMessageFromExt] = extSimAdapterData.getAdapter @@ -215,20 +201,26 @@ object ExtSimSetup { case extResultDataConnection: ExtResultDataConnection => val extResultProvider = context.spawn( - ExtResultProvider(scheduler), - s"ExtResultDataProvider", + ExtResultEvent + .provider(extResultDataConnection, scheduler, startTime), + s"ExtResultProvider", ) - val powerFlowResolution = resolution.toSeconds - - setupService( - extResultDataConnection, + extResultDataConnection.setActorRefs( extResultProvider, - InitExtResultData(_, powerFlowResolution, startTime), + extSimAdapter, ) extSimSetupData.update(extResultDataConnection, extResultProvider) + case extResultListener: ExtResultListener => + val extResultEventListener = context.spawn( + ExtResultEvent.listener(extResultListener), + s"ExtResultListener", + ) + + extSimSetupData.update(extResultListener, extResultEventListener) + case otherConnection => log.warn( s"There is currently no implementation for the connection: $otherConnection." diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index d680f2ab7b..0900206ad6 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -8,14 +8,10 @@ package edu.ie3.simona.sim.setup import edu.ie3.simona.api.ExtSimAdapter import edu.ie3.simona.api.data.connection.* -import edu.ie3.simona.api.ontology.DataMessageFromExt -import edu.ie3.simona.event.ResultEvent -import edu.ie3.simona.event.listener.ExtResultEventListener +import edu.ie3.simona.event.listener.ExtResultEvent import edu.ie3.simona.ontology.messages.ServiceMessage import edu.ie3.simona.service.em.ExtEmDataService import edu.ie3.simona.service.ev.ExtEvDataService -import edu.ie3.simona.service.primary.ExtPrimaryDataService -import edu.ie3.simona.service.results.ExtResultProvider import org.apache.pekko.actor.typed.ActorRef /** Case class that holds information regarding the external data connections as @@ -31,8 +27,8 @@ import org.apache.pekko.actor.typed.ActorRef * Option for an external ev data service. * @param resultListeners * Seq: external result listeners. - * @param resultServices - * Seq: external result services. + * @param resultProviders + * Seq: external result providers. */ final case class ExtSimSetupData( extSimAdapters: Iterable[ActorRef[ExtSimAdapter.Request]], @@ -41,8 +37,8 @@ final case class ExtSimSetupData( ], emDataService: Option[ActorRef[ExtEmDataService.Message]], evDataService: Option[ActorRef[ExtEvDataService.Message]], - resultListeners: Seq[ActorRef[ExtResultEventListener.Message]], - resultServices: Seq[ActorRef[ServiceMessage]], + resultListeners: Seq[ActorRef[ExtResultEvent.Message]], + resultProviders: Seq[ActorRef[ExtResultEvent.Message]], ) { private[setup] def update( @@ -53,7 +49,7 @@ final case class ExtSimSetupData( private[setup] def update( connection: ExtDataConnection, - ref: ActorRef[? >: ServiceMessage], + ref: ActorRef[?], ): ExtSimSetupData = (connection, ref) match { case ( primaryConnection: ExtPrimaryDataConnection, @@ -72,14 +68,14 @@ final case class ExtSimSetupData( copy(evDataService = Some(serviceRef)) case ( _: ExtResultDataConnection, - serviceRef: ActorRef[ServiceMessage], + providerRef: ActorRef[ExtResultEvent.Message], ) => - copy(resultServices = resultServices ++ Seq(serviceRef)) + copy(resultProviders = resultProviders ++ Seq(providerRef)) case ( _: ExtResultListener, - serviceRef: ActorRef[ExtResultEventListener.Message], + listenerRef: ActorRef[ExtResultEvent.Message], ) => - copy(resultListeners = resultListeners ++ Seq(serviceRef)) + copy(resultListeners = resultListeners ++ Seq(listenerRef)) case (_, _) => this } @@ -98,7 +94,7 @@ final case class ExtSimSetupData( extSimAdapters ++ primaryDataServices.map(_._2) ++ Seq( emDataService, evDataService, - ).flatten ++ resultListeners ++ resultServices + ).flatten ++ resultListeners ++ resultProviders } object ExtSimSetupData { diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala index 498dbf2c9c..4054abf86f 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -54,7 +54,7 @@ trait SimonaSetup { * An actor reference to the runtime event listener */ def runtimeEventListener( - context: ActorContext[_] + context: ActorContext[?] ): ActorRef[RuntimeEventListener.Request] /** Creates a sequence of result event listeners @@ -65,8 +65,7 @@ trait SimonaSetup { * A sequence of actor references to result event listeners */ def resultEventListener( - context: ActorContext[_], - extSimSetupData: ExtSimSetupData, + context: ActorContext[?] ): Seq[ActorRef[ResultEventListener.Request]] /** Creates a primary service proxy. The proxy is the first instance to ask @@ -131,7 +130,7 @@ trait SimonaSetup { * External simulations and their init data */ def extSimulations( - context: ActorContext[_], + context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], extSimPath: Option[Path], ): ExtSimSetupData diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index 495ccc1057..1e3ca50a9f 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -215,7 +215,7 @@ class SimonaStandaloneSetup( } override def extSimulations( - context: ActorContext[_], + context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], extSimPath: Option[Path], ): ExtSimSetupData = { @@ -226,7 +226,6 @@ class SimonaStandaloneSetup( context, scheduler, simonaConfig.simona.time.startTime, - simonaConfig.simona.powerflow.resolution, ) } @@ -278,17 +277,13 @@ class SimonaStandaloneSetup( ) override def resultEventListener( - context: ActorContext[_], - extSimSetupData: ExtSimSetupData, + context: ActorContext[_] ): Seq[ActorRef[ResultEventListener.Request]] = { // append ResultEventListener as well to write raw output files Seq( context .spawn( - ResultEventListener( - resultFileHierarchy, - extSimSetupData.resultServices, - ), + ResultEventListener(resultFileHierarchy), ResultEventListener.getClass.getSimpleName, ) ) diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala index 2aa5855d01..86012fe3a5 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveDataMap.scala @@ -25,6 +25,8 @@ final case class ReceiveDataMap[K, V]( def nonComplete: Boolean = expectedKeys.nonEmpty + def expects(key: K): Boolean = expectedKeys.contains(key) + def addData( key: K, value: V, diff --git a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala index b19821a23d..e593eb1c5d 100644 --- a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala @@ -310,8 +310,7 @@ class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { new MockSetup() { override def resultEventListener( - context: ActorContext[_], - extSimSetupData: ExtSimSetupData, + context: ActorContext[_] ): Seq[ActorRef[ResultEventListener.Request]] = throwTestException() } @@ -419,8 +418,7 @@ object SimonaSimSpec { ) override def resultEventListener( - context: ActorContext[_], - extSimSetupData: ExtSimSetupData, + context: ActorContext[_] ): Seq[ActorRef[ResultEventListener.Request]] = Seq( context.spawn( stoppableForwardMessage(resultEventProbe), diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala index d72c36ba16..b8fe09eef9 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala @@ -52,7 +52,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { updated.emDataService shouldBe None updated.evDataService shouldBe None updated.resultListeners shouldBe empty - updated.resultServices shouldBe empty + updated.resultProviders shouldBe empty } "be updated with multiple ExtPrimaryDataConnection correctly" in { @@ -78,7 +78,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { updated.emDataService shouldBe None updated.evDataService shouldBe None updated.resultListeners shouldBe empty - updated.resultServices shouldBe empty + updated.resultProviders shouldBe empty } "be updated with an ExtInputDataConnection correctly" in { @@ -124,7 +124,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { updated.emDataService shouldBe expected.emDataService updated.evDataService shouldBe expected.evDataService updated.resultListeners shouldBe empty - updated.resultServices shouldBe empty + updated.resultProviders shouldBe empty } } @@ -147,7 +147,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { updated.emDataService shouldBe None updated.evDataService shouldBe None updated.resultListeners shouldBe empty - updated.resultServices shouldBe Seq(resultRef) + updated.resultProviders shouldBe Seq(resultRef) } "be updated with multiple different connections correctly" in { @@ -188,7 +188,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { updated.emDataService shouldBe Some(emRef) updated.evDataService shouldBe Some(evRef) updated.resultListeners shouldBe empty - updated.resultServices shouldBe Seq(resultRef) + updated.resultProviders shouldBe Seq(resultRef) } "return evDataService correctly" in { diff --git a/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala index fb575c7de9..80b5ef11c3 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala @@ -48,8 +48,7 @@ class SimonaSetupSpec ) override def resultEventListener( - context: ActorContext[_], - extSimSetupData: ExtSimSetupData, + context: ActorContext[_] ): Seq[ActorRef[ResultEventListener.Request]] = throw new NotImplementedException("This is a dummy setup") From 23005d4c5d82669544e2977f9971138470589174 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 13 Aug 2025 09:34:05 +0200 Subject: [PATCH 070/125] Update to java21. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4477adc9c4..1cba6905dd 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ plugins { ext { // version (changing these should be considered thoroughly!) - javaVersion = JavaVersion.VERSION_17 + javaVersion = JavaVersion.VERSION_21 scalaVersion = '3' scalaBinaryVersion = '3.7.0' From 9a87d0f650c070c30f6a857f8d3309da389b9497 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 19 Aug 2025 12:52:07 +0200 Subject: [PATCH 071/125] Update result handling. --- .../ie3/simona/agent/EnvironmentRefs.scala | 4 + .../edu/ie3/simona/agent/em/EmAgent.scala | 30 +- .../edu/ie3/simona/agent/grid/GridAgent.scala | 2 - .../simona/agent/grid/GridAgentBuilder.scala | 22 +- .../ie3/simona/agent/grid/GridAgentData.scala | 8 +- .../agent/participant/ParticipantAgent.scala | 32 +-- .../participant/ParticipantAgentInit.scala | 4 +- .../ParticipantResultHandler.scala | 28 +- .../edu/ie3/simona/event/ResultEvent.scala | 25 +- .../event/listener/ExtResultEvent.scala | 267 +++--------------- .../event/listener/ResultEventListener.scala | 152 ++-------- .../messages/RequestResultMessage.scala | 18 ++ .../ontology/messages/ServiceMessage.scala | 1 + .../service/results/ResultServiceProxy.scala | 265 +++++++++++++++++ .../scala/edu/ie3/simona/sim/SimonaSim.scala | 21 +- .../ie3/simona/sim/setup/ExtSimSetup.scala | 15 +- .../ie3/simona/sim/setup/SimonaSetup.scala | 21 +- .../sim/setup/SimonaStandaloneSetup.scala | 25 +- 18 files changed, 486 insertions(+), 454 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/ontology/messages/RequestResultMessage.scala create mode 100644 src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala diff --git a/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala b/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala index c292f5238d..d3629c6142 100644 --- a/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala +++ b/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala @@ -9,6 +9,7 @@ package edu.ie3.simona.agent import edu.ie3.simona.event.RuntimeEvent import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.service.em.ExtEmDataService +import edu.ie3.simona.service.results.ResultServiceProxy import org.apache.pekko.actor.typed.ActorRef /** Container class, that gather together reference to relevant entities, that @@ -20,6 +21,8 @@ import org.apache.pekko.actor.typed.ActorRef * Reference to the runtime event listener. * @param primaryServiceProxy * Reference to the primary service proxy. + * @param resultProxy + * Reference to the result service proxy. * @param weather * Reference to the service, that provides weather information. * @param loadProfiles @@ -33,6 +36,7 @@ final case class EnvironmentRefs( scheduler: ActorRef[SchedulerMessage], runtimeEventListener: ActorRef[RuntimeEvent], primaryServiceProxy: ActorRef[ServiceMessage], + resultProxy: ActorRef[ResultServiceProxy.Message], weather: ActorRef[ServiceMessage], loadProfiles: ActorRef[ServiceMessage], emDataService: Option[ActorRef[ExtEmDataService.Message]], diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index bcaec0f789..a99d990984 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -63,7 +63,7 @@ object EmAgent { * agent is em-controlled, or a [[Left]] with a reference to the scheduler * that is activating this agent. * @param listener - * A collection of result event listeners. + * A listener for result events. * @param emDataService * An energy management service. */ @@ -74,7 +74,7 @@ object EmAgent { modelStrategy: String, simulationStartDate: ZonedDateTime, parent: Either[ActorRef[SchedulerMessage], ActorRef[FlexResponse]], - listener: Iterable[ActorRef[ResultEvent]], + listener: ActorRef[ResultEvent], emDataService: Option[ActorRef[ExtEmDataService.Message]], ): Behavior[Message] = Behaviors.setup[Message] { ctx => @@ -269,9 +269,7 @@ object EmAgent { emFlexOptions.max.toMegawatts.asMegaWatt, ) - emData.listener.foreach { - _ ! FlexOptionsResultEvent(flexResult) - } + emData.listener ! FlexOptionsResultEvent(flexResult) } emData.parent match { @@ -432,17 +430,15 @@ object EmAgent { } maybeResult.foreach { result => - emData.listener.foreach { - _ ! ParticipantResultEvent( - new EmResult( - lastActiveTick - .toDateTime(using emData.simulationStartDate), - modelShell.uuid, - result.p.toMegawatts.asMegaWatt, - result.q.toMegavars.asMegaVar, - ) + emData.listener ! ParticipantResultEvent( + new EmResult( + lastActiveTick + .toDateTime(using emData.simulationStartDate), + modelShell.uuid, + result.p.toMegawatts.asMegaWatt, + result.q.toMegavars.asMegaVar, ) - } + ) emData.parent.foreach { _ ! FlexResult(modelShell.uuid, result) @@ -473,13 +469,13 @@ object EmAgent { * agent is em-controlled, or a [[Left]] with a reference to the scheduler * that is activating this agent. * @param listener - * A collection of result event listeners. + * A listener for result events. */ private final case class EmData( outputConfig: NotifierConfig, simulationStartDate: ZonedDateTime, parent: Either[ActorRef[SchedulerMessage], ActorRef[FlexResponse]], - listener: Iterable[ActorRef[ResultEvent]], + listener: ActorRef[ResultEvent], ) /** The existence of this data object indicates that the corresponding agent diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala index a5e82a03a7..a80ac4daf3 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -69,7 +69,6 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { def apply( environmentRefs: EnvironmentRefs, simonaConfig: SimonaConfig, - listener: Iterable[ActorRef[ResultEvent]], ): Behavior[Message] = Behaviors.withStash(100) { buffer => // this determines the agents regular time bin it wants to be triggered e.g. one hour val resolution: Long = simonaConfig.simona.powerflow.resolution.toSeconds @@ -80,7 +79,6 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { val agentValues = GridAgentConstantData( environmentRefs, simonaConfig, - listener, resolution, simStartTime, TimeUtil.withDefaults diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala index f2b24936e7..9f05a6211a 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala @@ -311,9 +311,9 @@ object GridAgentBuilder { given ParticipantRefs = ParticipantRefs( gridAgentContext.self, - constantData.environmentRefs.primaryServiceProxy, + environmentRefs.primaryServiceProxy, serviceMap, - constantData.listener, + environmentRefs.resultProxy, ) given SimulationParameters = SimulationParameters( @@ -336,7 +336,7 @@ object GridAgentBuilder { constantData.outputConfigUtil.getOrDefault( NotifierIdentifier.FixedFeedIn ), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case input: LoadInput => @@ -346,7 +346,7 @@ object GridAgentBuilder { input.getUuid ), constantData.outputConfigUtil.getOrDefault(NotifierIdentifier.Load), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case input: PvInput => @@ -358,7 +358,7 @@ object GridAgentBuilder { constantData.outputConfigUtil.getOrDefault( NotifierIdentifier.PvPlant ), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case input: BmInput => @@ -370,7 +370,7 @@ object GridAgentBuilder { constantData.outputConfigUtil.getOrDefault( NotifierIdentifier.BioMassPlant ), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case input: WecInput => @@ -380,7 +380,7 @@ object GridAgentBuilder { input.getUuid ), constantData.outputConfigUtil.getOrDefault(NotifierIdentifier.Wec), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case input: EvcsInput => @@ -390,7 +390,7 @@ object GridAgentBuilder { input.getUuid ), constantData.outputConfigUtil.getOrDefault(NotifierIdentifier.Evcs), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case input: HpInput => @@ -402,7 +402,7 @@ object GridAgentBuilder { input.getUuid ), constantData.outputConfigUtil.getOrDefault(NotifierIdentifier.Hp), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case None => @@ -419,7 +419,7 @@ object GridAgentBuilder { constantData.outputConfigUtil.getOrDefault( NotifierIdentifier.Storage ), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case input: SystemParticipantInput => @@ -491,7 +491,7 @@ object GridAgentBuilder { maybeControllingEm.toRight( constantData.environmentRefs.scheduler ), - constantData.listener, + constantData.environmentRefs.resultProxy, emDataService, ), actorName(classOf[EmAgent.type], emInput.getId), diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala index a51c2b4fda..aaf451bdad 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -46,8 +46,6 @@ object GridAgentData { * environment actor refs * @param simonaConfig * config - * @param listener - * listeners * @param resolution * of the simulation * @param simStartTime @@ -56,14 +54,12 @@ object GridAgentData { final case class GridAgentConstantData( environmentRefs: EnvironmentRefs, simonaConfig: SimonaConfig, - listener: Iterable[ActorRef[ResultEvent]], resolution: Long, simStartTime: ZonedDateTime, simEndTime: ZonedDateTime, ) { - def notifyListeners(event: ResultEvent): Unit = { - listener.foreach(_ ! event) - } + def notifyListeners(event: ResultEvent): Unit = + environmentRefs.resultProxy ! event val participantConfigUtil: ParticipantConfigUtil = ConfigUtil.ParticipantConfigUtil(simonaConfig.simona.runtime.participant) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index 7d2d851837..3a10ea820c 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -7,18 +7,23 @@ package edu.ie3.simona.agent.participant import breeze.numerics.{pow, sqrt} -import edu.ie3.datamodel.models.result.system.SystemParticipantResult -import edu.ie3.simona.agent.grid.GridAgentMessages.{AssetPowerChangedMessage, AssetPowerUnchangedMessage, ProvidedPowerResponse} +import edu.ie3.simona.agent.grid.GridAgentMessages.{ + AssetPowerChangedMessage, + AssetPowerUnchangedMessage, + ProvidedPowerResponse, +} import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.model.participant.ParticipantModel.AdditionalFactoryData import edu.ie3.simona.model.participant.ParticipantModelShell import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion -import edu.ie3.simona.ontology.messages.ServiceMessage.ResultResponseMessage import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage, ServiceMessage} +import edu.ie3.simona.ontology.messages.{ + Activation, + SchedulerMessage, + ServiceMessage, +} import edu.ie3.simona.service.Data -import edu.ie3.simona.service.Data.{PrimaryData, PrimaryDataExtra} -import edu.ie3.simona.service.results.ExtResultProvider +import edu.ie3.simona.service.Data.PrimaryDataExtra import edu.ie3.util.scala.Scope import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.{ActorRef, Behavior} @@ -143,10 +148,6 @@ object ParticipantAgent { replyTo: ActorRef[ProvidedPowerResponse], ) extends Request - final case class RequestLastResult( - replyTo: ActorRef[ExtResultProvider.Message] - ) extends Request - /** Message announcing that calculations by the * [[edu.ie3.simona.agent.grid.GridAgent]] have come to an end and regular * participant activities can continue. @@ -303,17 +304,6 @@ object ParticipantAgent { updatedGridAdapter, resultHandler, ) - - case (ctx, RequestLastResult(replyTo)) => - // send last calculated results to result service - // replyTo ! ResultResponseMessage(gridAdapter.lastResults) - - ParticipantAgent( - modelShell, - inputHandler, - gridAdapter, - resultHandler, - ) } /** Starts a model calculation if all requirements have been met. A model diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala index f5895c5845..b3d5b0c887 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala @@ -60,13 +60,13 @@ object ParticipantAgentInit { * @param services * References to services by service type. * @param resultListener - * Reference to the result listeners. + * Reference to the result service proxy. */ final case class ParticipantRefs( gridAgent: ActorRef[GridAgent.Message], primaryServiceProxy: ActorRef[ServiceMessage], services: Map[ServiceType, ActorRef[ServiceMessage]], - resultListener: Iterable[ActorRef[ResultEvent]], + resultListener: ActorRef[ResultEvent], ) /** Container class that holds parameters related to the simulation. diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala index a2054b208f..4cc81e0777 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala @@ -31,7 +31,7 @@ import org.apache.pekko.actor.typed.ActorRef * The result configuration. */ final case class ParticipantResultHandler( - private val listener: Iterable[ActorRef[ResultEvent]], + private val listener: ActorRef[ResultEvent], private val config: NotifierConfig, ) { @@ -42,18 +42,16 @@ final case class ParticipantResultHandler( */ def maybeSend(result: ResultEntity): Unit = if (config.simulationResultInfo) { - listener.foreach(actor => - result match { - case thermalResult: ThermalUnitResult => - actor ! ThermalResultEvent(thermalResult) - case participantResult: SystemParticipantResult => - actor ! ParticipantResultEvent(participantResult) - case unsupported => - throw new CriticalFailureException( - s"Results of class '${unsupported.getClass.getSimpleName}' are currently not supported." - ) - } - ) + result match { + case thermalResult: ThermalUnitResult => + listener ! ThermalResultEvent(thermalResult) + case participantResult: SystemParticipantResult => + listener ! ParticipantResultEvent(participantResult) + case unsupported => + throw new CriticalFailureException( + s"Results of class '${unsupported.getClass.getSimpleName}' are currently not supported." + ) + } } /** Send the flex options result to all listeners, if enabled. @@ -63,9 +61,7 @@ final case class ParticipantResultHandler( */ def maybeSend(result: FlexOptionsResult): Unit = if (config.flexResult) { - listener.foreach( - _ ! FlexOptionsResultEvent(result) - ) + listener ! FlexOptionsResultEvent(result) } } diff --git a/src/main/scala/edu/ie3/simona/event/ResultEvent.scala b/src/main/scala/edu/ie3/simona/event/ResultEvent.scala index 49b345fa15..488df8259b 100644 --- a/src/main/scala/edu/ie3/simona/event/ResultEvent.scala +++ b/src/main/scala/edu/ie3/simona/event/ResultEvent.scala @@ -6,14 +6,15 @@ package edu.ie3.simona.event -import edu.ie3.datamodel.models.result.{CongestionResult, NodeResult} import edu.ie3.datamodel.models.result.connector.{ LineResult, SwitchResult, Transformer2WResult, } import edu.ie3.datamodel.models.result.system.{ + EmResult, FlexOptionsResult, + HpResult, SystemParticipantResult, } import edu.ie3.datamodel.models.result.thermal.{ @@ -21,7 +22,11 @@ import edu.ie3.datamodel.models.result.thermal.{ ThermalHouseResult, ThermalUnitResult, } -import edu.ie3.datamodel.models.result.system.{EmResult, HpResult} +import edu.ie3.datamodel.models.result.{ + CongestionResult, + NodeResult, + ResultEntity, +} import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult import edu.ie3.simona.event.listener.ResultEventListener import tech.units.indriya.ComparableQuantity @@ -30,7 +35,7 @@ import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.{Energy, Power, Temperature} -sealed trait ResultEvent extends Event with ResultEventListener.Request +sealed trait ResultEvent extends Event /** Calculation result events */ @@ -43,7 +48,8 @@ object ResultEvent { * the calculation result */ final case class ParticipantResultEvent( - systemParticipantResult: SystemParticipantResult + systemParticipantResult: SystemParticipantResult, + maybeNextTick: Option[Long] = None, ) extends ResultEvent object HpResult { @@ -89,7 +95,8 @@ object ResultEvent { * Result of the thermal calculation */ final case class ThermalResultEvent( - thermalResult: ThermalUnitResult + thermalResult: ThermalUnitResult, + maybeNextTick: Option[Long] = None, ) extends ResultEvent object ThermalHouseResult { @@ -156,6 +163,7 @@ object ResultEvent { transformer2wResults: Iterable[Transformer2WResult], transformer3wResults: Iterable[PartialTransformer3wResult], congestionResults: Iterable[CongestionResult] = Iterable.empty, + maybeNextTick: Option[Long] = None, ) extends ResultEvent { def +(congestionResult: Iterable[CongestionResult]): PowerFlowResultEvent = @@ -172,7 +180,12 @@ object ResultEvent { * the flex options result */ final case class FlexOptionsResultEvent( - flexOptionsResult: FlexOptionsResult + flexOptionsResult: FlexOptionsResult, + maybeNextTick: Option[Long] = None, ) extends ResultEvent + sealed trait Response + + final case class ResultResponse(results: List[ResultEntity]) extends Response + } diff --git a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala index 31bf1667b9..ce7410fc6d 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala @@ -6,9 +6,6 @@ package edu.ie3.simona.event.listener -import edu.ie3.datamodel.models.result.ResultEntity -import edu.ie3.datamodel.models.result.connector.Transformer3WResult -import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult import edu.ie3.simona.api.data.connection.{ ExtResultDataConnection, ExtResultListener, @@ -20,128 +17,85 @@ import edu.ie3.simona.api.ontology.results.{ ResultDataMessageFromExt, } import edu.ie3.simona.event.ResultEvent -import edu.ie3.simona.event.ResultEvent.* -import edu.ie3.simona.event.listener.ResultEventListener.{ - AggregatedTransformer3wResult, - Transformer3wKey, -} +import edu.ie3.simona.event.ResultEvent.ResultResponse import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, } import edu.ie3.simona.ontology.messages.ServiceMessage.ScheduleServiceActivation -import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.ontology.messages.{ + Activation, + RequestResultMessage, + SchedulerMessage, +} import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.{ActorRef, Behavior} -import org.slf4j.Logger import java.time.ZonedDateTime import java.util import java.util.UUID import scala.jdk.CollectionConverters.* -import scala.util.{Failure, Success} object ExtResultEvent { - type Message = ResultEvent | DelayedStopHelper.StoppingMsg + type Message = ResultEvent.Response | DelayedStopHelper.StoppingMsg - final case class ProviderState( + private final case class ProviderState( scheduler: ActorRef[SchedulerMessage], + resultProxy: ActorRef[RequestResultMessage], connection: ExtResultDataConnection, - resultStore: Map[UUID, ResultEntity] = Map.empty, - threeWindingResults: Map[ - Transformer3wKey, - AggregatedTransformer3wResult, - ] = Map.empty, extMessage: Option[ResultDataMessageFromExt] = None, simStartTime: ZonedDateTime, gridAssets: List[UUID] = List.empty, - ) { - def updateResultData( - updatedThreeWindingResults: Map[ - Transformer3wKey, - AggregatedTransformer3wResult, - ], - results: Seq[ResultEntity], - ): ProviderState = { - val updateStore = - resultStore ++ results.map(res => res.getInputModel -> res).toMap - - copy( - threeWindingResults = updatedThreeWindingResults, - resultStore = updateStore, - ) - } - } - - def listener( - connection: ExtResultListener, - threeWindingResults: Map[ - Transformer3wKey, - AggregatedTransformer3wResult, - ] = Map.empty, - ): Behavior[Message] = Behaviors.receivePartial[Message] { - case (ctx, resultEvent: ResultEvent) => - val (updatedThreeWinding, results) = - handleResultEvent(resultEvent, threeWindingResults)(using ctx.log) + ) - connection.queueExtResponseMsg( - new ProvideResultEntities(results.toList.asJava) - ) + def listener(connection: ExtResultListener): Behavior[Message] = + Behaviors.receivePartial[Message] { + case (_, ResultResponse(results)) => + connection.queueExtResponseMsg( + new ProvideResultEntities(results.asJava) + ) - listener(connection, updatedThreeWinding) + Behaviors.same - case (ctx, msg: DelayedStopHelper.StoppingMsg) => - DelayedStopHelper.handleMsg((ctx, msg)) - } + case (ctx, msg: DelayedStopHelper.StoppingMsg) => + DelayedStopHelper.handleMsg((ctx, msg)) + } def provider( connection: ExtResultDataConnection, scheduler: ActorRef[SchedulerMessage], + resultProxy: ActorRef[RequestResultMessage], simStartTime: ZonedDateTime, ): Behavior[Message | DataMessageFromExt | Activation] = { val gridResults = connection.getGridResultDataAssets.asScala - val participantResults = connection.getParticipantResultDataAssets.asScala - val flexOptionResults = connection.getFlexOptionAssets.asScala - - val assets = (gridResults ++ participantResults ++ flexOptionResults).toSet val stateData = ProviderState( scheduler, + resultProxy, connection, simStartTime = simStartTime, gridAssets = gridResults.toList, ) - val resultFilter: ResultEntity => Boolean = - assets.contains.compose(_.getInputModel) - - provider(stateData)(using resultFilter) + provider(stateData) } - private def provider(stateData: ProviderState)(using - resultFilter: ResultEntity => Boolean + private def provider( + stateData: ProviderState ): Behavior[Message | DataMessageFromExt | Activation] = Behaviors.receivePartial[Message | DataMessageFromExt | Activation] { - case (ctx, resultEvent: ResultEvent) => - val (updatedThreeWinding, results) = - handleResultEvent( - resultEvent, - stateData.threeWindingResults, - resultFilter, - )(using ctx.log) - - val updatedState = - stateData.updateResultData(updatedThreeWinding, results) + case (ctx, ResultResponse(results)) => + // send result to external simulation + stateData.connection.queueExtResponseMsg( + new ProvideResultEntities(results.asJava) + ) - // reactivate this service, if we have an unanswered request - stateData.extMessage.foreach { case extMsg: RequestResultEntities => - ctx.self ! Activation(extMsg.tick) - } + stateData.scheduler ! Completion(ctx.self) - provider(updatedState) + Behaviors.same case (_, messageFromExt: ResultDataMessageFromExt) => // save ext message @@ -168,71 +122,22 @@ object ExtResultEvent { extMsg match { case requestResultEntities: RequestResultEntities => - val uuids = - new util.ArrayList[UUID](requestResultEntities.requestedResults) - val receivedTick = requestResultEntities.tick + val requestedResults = + new util.ArrayList(requestResultEntities.requestedResults) - // TODO: Check received tick - // receivedTick == tick - - if (receivedTick == 0) { - // we can't send grid results for the tick 0 - // therefore, we remove them - uuids.removeAll(stateData.gridAssets.asJava) + if requestResultEntities.tick == 0 then { + // removing the grid assets for tick 0, since SIMONA will produce no output + requestedResults.removeAll(stateData.gridAssets.asJava) } - val results = stateData.resultStore - - if (results.nonEmpty) { - val foundResults = uuids.asScala - .flatMap(uuid => results.get(uuid).map(data => uuid -> data)) - .toMap - - val updatedData = results.removedAll(foundResults.keys) - - // check if there are unanswered requests - uuids.removeAll(foundResults.keySet.asJava) - - val updatedStateData = if (uuids.isEmpty) { - // send results to ext - stateData.connection.queueExtResponseMsg( - new ProvideResultEntities(updatedData.values.toList.asJava) - ) - - // tell the scheduler that we are finished - stateData.scheduler ! Completion(ctx.self) - - stateData.copy( - resultStore = updatedData, - extMessage = None, - ) - } else { - val updatedRequest = - new RequestResultEntities(receivedTick, uuids) - - stateData.copy( - resultStore = updatedData, - extMessage = Some(updatedRequest), - ) - } - - provider(updatedStateData) - - } else if (results.isEmpty && uuids.isEmpty) { - // send no results to ext - stateData.connection.queueExtResponseMsg( - ProvideResultEntities.empty() - ) - - // tell the scheduler that we are finished - stateData.scheduler ! Completion(ctx.self) - - provider(stateData.copy(extMessage = None)) - } else { - ctx.log.warn(s"Could not find results! Waiting ...") - Behaviors.same - } + // request results from result proxy + stateData.resultProxy ! RequestResultMessage( + requestedResults.asScala.toSeq, + tick, + ctx.self, + ) + Behaviors.same case other => ctx.log.warn(s"Cannot handle external result message: $other") Behaviors.same @@ -242,88 +147,4 @@ object ExtResultEvent { DelayedStopHelper.handleMsg((ctx, msg)) } - - private def handleResultEvent( - resultEvent: ResultEvent, - threeWindingResults: Map[Transformer3wKey, AggregatedTransformer3wResult], - resultFilter: ResultEntity => Boolean = _ => true, - )(using - log: Logger - ): (Map[Transformer3wKey, AggregatedTransformer3wResult], Seq[ResultEntity]) = - resultEvent match { - case ParticipantResultEvent(systemParticipantResult) => - (threeWindingResults, Seq(systemParticipantResult).filter(resultFilter)) - - case PowerFlowResultEvent( - nodeResults, - switchResults, - lineResults, - transformer2wResults, - partialTransformer3wResults, - congestionResults, - ) => - // handling of three winding transformers - val (updatedResults, transformer3wResults) = - handleThreeWindingTransformers( - partialTransformer3wResults, - threeWindingResults, - ) - - val results: Seq[ResultEntity] = - transformer3wResults ++ nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ congestionResults - - (updatedResults, results.filter(resultFilter)) - - case ThermalResultEvent(thermalResult) => - (threeWindingResults, Seq(thermalResult).filter(resultFilter)) - - case FlexOptionsResultEvent(flexOptionsResult) => - (threeWindingResults, Seq(flexOptionsResult).filter(resultFilter)) - } - - private def handleThreeWindingTransformers( - transformer3wResults: Iterable[PartialTransformer3wResult], - threeWindingResults: Map[Transformer3wKey, AggregatedTransformer3wResult], - )(using log: Logger) = transformer3wResults.foldLeft( - threeWindingResults, - Seq.empty[Transformer3WResult], - ) { case ((allPartialResults, allResults), result) => - val key = Transformer3wKey(result.input, result.time) - // retrieve existing partial result or use empty one - val partialResult = - allPartialResults.getOrElse( - key, - AggregatedTransformer3wResult.EMPTY, - ) - // add partial result - partialResult.add(result).map { updatedResult => - if (updatedResult.ready) { - // if result is complete, we can write it out - updatedResult.consolidate match { - case Failure(exception) => - log.warn( - "Failure when handling partial Transformer3w result", - exception, - ) - // on failure, we just continue with previous results - (allPartialResults, allResults) - case Success(res) => - (allPartialResults.removed(key), allResults.appended(res)) - } - - } else { - // if result is not complete yet, just update it - (allPartialResults + (key -> updatedResult), allResults) - } - } match { - case Success(results) => results - case Failure(exception) => - log.warn( - "Failure when handling partial Transformer3w result", - exception, - ) - // on failure, we just continue with previous results - (allPartialResults, allResults) - } - } } diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index 6af3e36789..fd5c3fc1f8 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -8,23 +8,16 @@ package edu.ie3.simona.event.listener import edu.ie3.datamodel.io.processor.result.ResultEntityProcessor import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} -import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult -import edu.ie3.simona.event.ResultEvent.{ - FlexOptionsResultEvent, - ParticipantResultEvent, - PowerFlowResultEvent, - ThermalResultEvent, -} +import edu.ie3.simona.event.ResultEvent +import edu.ie3.simona.event.ResultEvent.ResultResponse import edu.ie3.simona.exceptions.{ FileHierarchyException, ProcessResultEventException, } import edu.ie3.simona.io.result.* -import edu.ie3.simona.ontology.messages.ServiceMessage -import edu.ie3.simona.ontology.messages.ServiceMessage.ResultResponseMessage import edu.ie3.simona.util.ResultFileHierarchy import org.apache.pekko.actor.typed.scaladsl.Behaviors -import org.apache.pekko.actor.typed.{ActorRef, Behavior, PostStop} +import org.apache.pekko.actor.typed.{Behavior, PostStop} import org.slf4j.Logger import scala.concurrent.ExecutionContext.Implicits.global @@ -36,6 +29,8 @@ object ResultEventListener extends Transformer3wResultSupport { trait Request + type Message = Request | ResultEvent.Response + private final case class SinkResponse( response: Map[Class[_], ResultEntitySink] ) extends Request @@ -50,11 +45,7 @@ object ResultEventListener extends Transformer3wResultSupport { * listener */ private final case class BaseData( - classToSink: Map[Class[_], ResultEntitySink], - threeWindingResults: Map[ - Transformer3wKey, - AggregatedTransformer3wResult, - ] = Map.empty, + classToSink: Map[Class[_], ResultEntitySink] ) /** Initialize the sinks for this listener based on the provided collection @@ -149,73 +140,19 @@ object ResultEventListener extends Transformer3wResultSupport { } } - /** Handle the given result and possibly update the state data - * - * @param resultEntity - * Result entity to handle - * @param baseData - * Base data - * @return - * The possibly update base data - */ - private def handleResult( - resultEntity: ResultEntity, - baseData: BaseData, - log: Logger, - ): BaseData = { - handOverToSink(resultEntity, baseData.classToSink, log) - baseData - } - - /** Handle a partial three winding result properly by adding it to an - * [[AggregatedTransformer3wResult]] and flushing then possibly completed - * results. Finally, the base data are updated. + /** Handle the given results. * - * @param result - * Result entity to handle + * @param resultEntities + * Results entity to handle. * @param baseData - * Base data - * @return - * The possibly update base data + * Base data. */ - private def handlePartialTransformer3wResult( - result: PartialTransformer3wResult, + private def handleResults( + resultEntities: Iterable[ResultEntity], baseData: BaseData, log: Logger, - ): BaseData = { - val key = Transformer3wKey(result.input, result.time) - // retrieve existing partial result or use empty one - val partialResult = - baseData.threeWindingResults.getOrElse( - key, - AggregatedTransformer3wResult.EMPTY, - ) - // add partial result - val updatedResults = partialResult.add(result).map { updatedResult => - if (updatedResult.ready) { - // if result is complete, we can write it out - updatedResult.consolidate.foreach { - handOverToSink(_, baseData.classToSink, log) - } - // also remove partial result from map - baseData.threeWindingResults.removed(key) - } else { - // if result is not complete yet, just update it - baseData.threeWindingResults + (key -> updatedResult) - } - } match { - case Success(results) => results - case Failure(exception) => - log.warn( - "Failure when handling partial Transformer3w result", - exception, - ) - // on failure, we just continue with previous results - baseData.threeWindingResults - } - - baseData.copy(threeWindingResults = updatedResults) - } + ): Unit = + resultEntities.foreach(handOverToSink(_, baseData.classToSink, log)) /** Handing over the given result entity to the sink, that might be apparent * in the map @@ -240,7 +177,7 @@ object ResultEventListener extends Transformer3wResultSupport { def apply( resultFileHierarchy: ResultFileHierarchy - ): Behavior[Request] = Behaviors.setup[Request] { ctx => + ): Behavior[Message] = Behaviors.setup[Message] { ctx => ctx.log.debug("Starting initialization!") resultFileHierarchy.resultSinkType match { case _: ResultSinkType.Kafka => @@ -266,8 +203,8 @@ object ResultEventListener extends Transformer3wResultSupport { init } - private def init: Behavior[Request] = Behaviors.withStash(200) { buffer => - Behaviors.receive[Request] { + private def init: Behavior[Message] = Behaviors.withStash(200) { buffer => + Behaviors.receive[Message] { case (ctx, SinkResponse(response)) => ctx.log.debug("Initialization complete!") buffer.unstashAll(idle(BaseData(response))) @@ -283,47 +220,12 @@ object ResultEventListener extends Transformer3wResultSupport { } } - private def idle(baseData: BaseData): Behavior[Request] = Behaviors - .receivePartial[Request] { - case (ctx, ParticipantResultEvent(participantResult)) => - val updatedBaseData = handleResult(participantResult, baseData, ctx.log) - idle(updatedBaseData) - - case (ctx, ThermalResultEvent(thermalResult)) => - val updatedBaseData = handleResult(thermalResult, baseData, ctx.log) - idle(updatedBaseData) - - case ( - ctx, - PowerFlowResultEvent( - nodeResults, - switchResults, - lineResults, - transformer2wResults, - transformer3wResults, - congestionResults, - ), - ) => - val updatedBaseData = - (nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ transformer3wResults ++ congestionResults) - .foldLeft(baseData) { - case (currentBaseData, resultEntity: ResultEntity) => - handleResult(resultEntity, currentBaseData, ctx.log) - case ( - currentBaseData, - partialTransformerResult: PartialTransformer3wResult, - ) => - handlePartialTransformer3wResult( - partialTransformerResult, - currentBaseData, - ctx.log, - ) - } - idle(updatedBaseData) + private def idle(baseData: BaseData): Behavior[Message] = Behaviors + .receivePartial[Message] { + case (ctx, ResultResponse(results)) => + handleResults(results, baseData, ctx.log) - case (ctx, FlexOptionsResultEvent(flexOptionsResult)) => - val updatedBaseData = handleResult(flexOptionsResult, baseData, ctx.log) - idle(updatedBaseData) + Behaviors.same case (ctx, msg: DelayedStopHelper.StoppingMsg) => DelayedStopHelper.handleMsg((ctx, msg)) @@ -331,15 +233,7 @@ object ResultEventListener extends Transformer3wResultSupport { } .receiveSignal { case (ctx, PostStop) => // wait until all I/O has finished - ctx.log.debug( - "Shutdown initiated.\n\tThe following three winding results are not comprehensive and are not " + - "handled in sinks:{}\n\tWaiting until writing result data is completed ...", - baseData.threeWindingResults.keys - .map { case Transformer3wKey(model, zdt) => - s"model '$model' at $zdt" - } - .mkString("\n\t\t"), - ) + ctx.log.debug("Shutdown initiated.") // close sinks concurrently to speed up closing (closing calls might be blocking) Await.ready( diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/RequestResultMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/RequestResultMessage.scala new file mode 100644 index 0000000000..badba99fdc --- /dev/null +++ b/src/main/scala/edu/ie3/simona/ontology/messages/RequestResultMessage.scala @@ -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.ontology.messages + +import edu.ie3.simona.event.ResultEvent +import org.apache.pekko.actor.typed.ActorRef + +import java.util.UUID + +final case class RequestResultMessage( + requestedResults: Seq[UUID], + tick: Long, + replyTo: ActorRef[ResultEvent.Response], +) 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 ad74c9dba6..5536a6a22a 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/ServiceMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/ServiceMessage.scala @@ -161,6 +161,7 @@ object ServiceMessage { receiver: UUID | ActorRef[FlexResponse] | ActorRef[EmAgent.Message], ) extends ServiceResponseMessage + @deprecated final case class ResultResponseMessage(results: Iterable[ResultEntity]) extends ServiceMessage with ServiceResponseMessage { diff --git a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala new file mode 100644 index 0000000000..f2c0dc8783 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala @@ -0,0 +1,265 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.results + +import edu.ie3.datamodel.models.result.ResultEntity +import edu.ie3.datamodel.models.result.connector.Transformer3WResult +import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult +import edu.ie3.simona.event.ResultEvent +import edu.ie3.simona.event.ResultEvent.* +import edu.ie3.simona.event.listener.DelayedStopHelper +import edu.ie3.simona.event.listener.ResultEventListener.{ + AggregatedTransformer3wResult, + Transformer3wKey, +} +import edu.ie3.simona.ontology.messages.RequestResultMessage +import edu.ie3.simona.service.ServiceStateData.ServiceBaseStateData +import org.apache.pekko.actor.typed.scaladsl.{Behaviors, StashBuffer} +import org.apache.pekko.actor.typed.{ActorRef, Behavior, PostStop} +import org.slf4j.Logger + +import java.util.UUID +import scala.util.{Failure, Success} + +object ResultServiceProxy { + + type Message = ResultEvent | RequestResultMessage | + DelayedStopHelper.StoppingMsg + + private final case class ResultServiceStateData( + listeners: Seq[ActorRef[ResultEvent.ResultResponse]], + nextTicks: Map[UUID, Long] = Map.empty, + resultMapping: Map[UUID, ResultEntity] = Map.empty, + threeWindingResults: Map[ + Transformer3wKey, + AggregatedTransformer3wResult, + ] = Map.empty, + ) extends ServiceBaseStateData { + def notifyListener(results: List[ResultEntity]): Unit = + listeners.foreach(_ ! ResultResponse(results)) + } + + def apply( + listeners: Seq[ActorRef[ResultEvent.ResultResponse]], + bufferSize: Int = 10000, + ): Behavior[Message] = Behaviors.withStash(bufferSize) { buffer => + idle(ResultServiceStateData(listeners))(using buffer) + } + + private def idle( + stateData: ResultServiceStateData + )(using + buffer: StashBuffer[Message] + ): Behavior[Message] = Behaviors + .receivePartial[Message] { + case (ctx, resultEvent: ResultEvent) => + // handles the event and updates the state data + val updatedStateData = + handleResultEvent(resultEvent, stateData)(using ctx.log) + + // un-stash received requests + buffer.unstashAll(idle(updatedStateData)) + case (ctx, requestResultMessage: RequestResultMessage) => + val requestedResults = requestResultMessage.requestedResults + val tick = requestResultMessage.tick + + val nextTicks = stateData.nextTicks + val results = stateData.resultMapping + + val allResultsPresent = requestedResults.forall(results.contains) + val allResultsUpToDate = requestedResults.forall { uuid => + nextTicks.get(uuid) match { + case Some(value) => value < tick + case None => true + } + } + + if allResultsPresent && allResultsUpToDate then { + val res = requestedResults.map(results).toList + ctx.log.debug(s"Answering message: $requestResultMessage") + + requestResultMessage.replyTo ! ResultResponse(res) + } else { + buffer.stash(requestResultMessage) + } + + Behaviors.same + + case (ctx, msg: DelayedStopHelper.StoppingMsg) => + DelayedStopHelper.handleMsg((ctx, msg)) + } + .receiveSignal { case (ctx, PostStop) => + ctx.log.debug( + "Shutdown initiated.\n\tThe following three winding results are not comprehensive and are not " + + "handled in sinks:{}\n\tWaiting until writing result data is completed ...", + stateData.threeWindingResults.keys + .map { case Transformer3wKey(model, zdt) => + s"model '$model' at $zdt" + } + .mkString("\n\t\t"), + ) + + Behaviors.same + } + + private def handleResultEvent( + resultEvent: ResultEvent, + stateData: ResultServiceStateData, + )(using log: Logger): ResultServiceStateData = resultEvent match { + case ParticipantResultEvent(systemParticipantResult, maybeNextTick) => + // notify listener + stateData.notifyListener(List(systemParticipantResult)) + + val uuid = systemParticipantResult.getInputModel + + val nextTicks = stateData.nextTicks + + val updatedNextTicks = maybeNextTick match { + case Some(value) => + nextTicks.updated(uuid, value) + case None => + nextTicks + } + + stateData.copy( + nextTicks = updatedNextTicks, + resultMapping = stateData.resultMapping.updated( + uuid, + systemParticipantResult, + ), + ) + + case PowerFlowResultEvent( + nodeResults, + switchResults, + lineResults, + transformer2wResults, + partialTransformer3wResults, + congestionResults, + maybeNextTick, + ) => + // handling of three winding transformers + val (updatedResults, transformer3wResults) = + handleThreeWindingTransformers( + partialTransformer3wResults, + stateData.threeWindingResults, + ) + + val results = + (transformer3wResults ++ nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ congestionResults).map { + res => res.getInputModel -> res + }.toMap + + // notify listener + stateData.notifyListener(results.values.toList) + + val nextTicks = stateData.nextTicks + + val updatedNextTicks = maybeNextTick match { + case Some(value) => + nextTicks ++ results.keys.map(key => key -> value).toMap + + case None => + nextTicks + } + + stateData.copy( + nextTicks = updatedNextTicks, + resultMapping = stateData.resultMapping ++ results, + threeWindingResults = updatedResults, + ) + + case ThermalResultEvent(thermalResult, maybeNextTick) => + // notify listener + stateData.notifyListener(List(thermalResult)) + + val uuid = thermalResult.getInputModel + + val nextTicks = stateData.nextTicks + + val updatedNextTicks = maybeNextTick match { + case Some(value) => + nextTicks.updated(uuid, value) + case None => + nextTicks + } + + stateData.copy( + nextTicks = updatedNextTicks, + resultMapping = stateData.resultMapping + .updated(uuid, thermalResult), + ) + + case FlexOptionsResultEvent(flexOptionsResult, maybeNextTick) => + // notify listener + stateData.notifyListener(List(flexOptionsResult)) + + val uuid = flexOptionsResult.getInputModel + + val nextTicks = stateData.nextTicks + + val updatedNextTicks = maybeNextTick match { + case Some(value) => + nextTicks.updated(uuid, value) + case None => + nextTicks + } + + stateData.copy( + nextTicks = updatedNextTicks, + resultMapping = stateData.resultMapping + .updated(uuid, flexOptionsResult), + ) + } + + private def handleThreeWindingTransformers( + transformer3wResults: Iterable[PartialTransformer3wResult], + threeWindingResults: Map[Transformer3wKey, AggregatedTransformer3wResult], + )(using log: Logger) = transformer3wResults.foldLeft( + threeWindingResults, + Seq.empty[Transformer3WResult], + ) { case ((allPartialResults, allResults), result) => + val key = Transformer3wKey(result.input, result.time) + // retrieve existing partial result or use empty one + val partialResult = + allPartialResults.getOrElse( + key, + AggregatedTransformer3wResult.EMPTY, + ) + // add partial result + partialResult.add(result).map { updatedResult => + if (updatedResult.ready) { + // if result is complete, we can write it out + updatedResult.consolidate match { + case Failure(exception) => + log.warn( + "Failure when handling partial Transformer3w result", + exception, + ) + // on failure, we just continue with previous results + (allPartialResults, allResults) + case Success(res) => + (allPartialResults.removed(key), allResults.appended(res)) + } + + } else { + // if result is not complete yet, just update it + (allPartialResults + (key -> updatedResult), allResults) + } + } match { + case Success(results) => results + case Failure(exception) => + log.warn( + "Failure when handling partial Transformer3w result", + exception, + ) + // on failure, we just continue with previous results + (allPartialResults, allResults) + } + } + +} diff --git a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala index 189e4edcd0..316f825cfc 100644 --- a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala +++ b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala @@ -73,6 +73,10 @@ object SimonaSim { val runtimeEventListener = simonaSetup.runtimeEventListener(ctx) val resultEventListeners = simonaSetup.resultEventListener(ctx) + // result proxy + val resultProxy = + simonaSetup.resultServiceProxy(ctx, resultEventListeners) + val timeAdvancer = simonaSetup.timeAdvancer(ctx, ctx.self, runtimeEventListener) val scheduler = simonaSetup.scheduler(ctx, timeAdvancer) @@ -86,10 +90,11 @@ object SimonaSim { simonaSetup.simonaConfig.simona.input.extSimDir.map(Path.of(_)) val extSimulationData = - simonaSetup.extSimulations(ctx, scheduler, extSimDir) + simonaSetup.extSimulations(ctx, scheduler, resultProxy, extSimDir) val allResultEventListeners = - resultEventListeners ++ extSimulationData.resultListeners ++ extSimulationData.resultProviders + resultEventListeners ++ extSimulationData.resultListeners + val resultProviders = extSimulationData.resultProviders /* start services */ // primary service proxy @@ -107,6 +112,7 @@ object SimonaSim { scheduler, runtimeEventListener, primaryServiceProxy, + resultProxy, weatherService, loadProfileService, extSimulationData.emDataService, @@ -114,11 +120,7 @@ object SimonaSim { ) /* start grid agents */ - val gridAgents = simonaSetup.gridAgents( - ctx, - environmentRefs, - allResultEventListeners, - ) + val gridAgents = simonaSetup.gridAgents(ctx, environmentRefs) val otherActors = Iterable[ActorRef[?]]( timeAdvancer, @@ -131,6 +133,7 @@ object SimonaSim { /* watch all actors */ allResultEventListeners.foreach(ctx.watch) + resultProviders.foreach(ctx.watch) ctx.watch(runtimeEventListener) otherActors.foreach(ctx.watch) @@ -141,7 +144,9 @@ object SimonaSim { timeAdvancer ! TimeAdvancer.Start val delayedActors = - allResultEventListeners.appended(runtimeEventListener) + allResultEventListeners + .appendedAll(resultProviders) + .appended(runtimeEventListener) idle( ActorData( diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index 487a20dbbf..c7168ad873 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -13,7 +13,11 @@ import edu.ie3.simona.api.simulation.{ExtSimAdapterData, ExtSimulation} import edu.ie3.simona.api.{ExtLinkInterface, ExtSimAdapter} import edu.ie3.simona.event.listener.ExtResultEvent import edu.ie3.simona.exceptions.ServiceException -import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} +import edu.ie3.simona.ontology.messages.{ + RequestResultMessage, + SchedulerMessage, + ServiceMessage, +} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceStateData.InitializeServiceStateData import edu.ie3.simona.service.em.ExtEmDataService @@ -57,6 +61,7 @@ object ExtSimSetup { )(using context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], + resultProxy: ActorRef[RequestResultMessage], startTime: ZonedDateTime, ): ExtSimSetupData = extLinks.zipWithIndex.foldLeft(ExtSimSetupData.apply) { case (extSimSetupData, (extLink, index)) => @@ -123,6 +128,7 @@ object ExtSimSetup { context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], extSimAdapterData: ExtSimAdapterData, + resultProxy: ActorRef[RequestResultMessage], startTime: ZonedDateTime, ): ExtSimSetupData = { given extSimAdapter: ActorRef[ControlResponseMessageFromExt] = @@ -202,7 +208,12 @@ object ExtSimSetup { case extResultDataConnection: ExtResultDataConnection => val extResultProvider = context.spawn( ExtResultEvent - .provider(extResultDataConnection, scheduler, startTime), + .provider( + extResultDataConnection, + scheduler, + resultProxy, + startTime, + ), s"ExtResultProvider", ) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala index 4054abf86f..70e075a341 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -11,12 +11,18 @@ import edu.ie3.datamodel.models.input.connector.Transformer3WInput import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.config.SimonaConfig +import edu.ie3.simona.event.ResultEvent.ResultResponse import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} -import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} +import edu.ie3.simona.ontology.messages.{ + RequestResultMessage, + SchedulerMessage, + ServiceMessage, +} import edu.ie3.simona.scheduler.TimeAdvancer import edu.ie3.simona.scheduler.core.Core.CoreFactory import edu.ie3.simona.scheduler.core.RegularSchedulerCore +import edu.ie3.simona.service.results.ResultServiceProxy import edu.ie3.simona.sim.SimonaSim import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext @@ -66,7 +72,7 @@ trait SimonaSetup { */ def resultEventListener( context: ActorContext[?] - ): Seq[ActorRef[ResultEventListener.Request]] + ): Seq[ActorRef[ResultEventListener.Message]] /** Creates a primary service proxy. The proxy is the first instance to ask * for primary data. If necessary, it delegates the registration request to @@ -88,6 +94,11 @@ trait SimonaSetup { extSimSetupData: ExtSimSetupData, ): ActorRef[ServiceMessage] + def resultServiceProxy( + context: ActorContext[?], + listeners: Seq[ActorRef[ResultResponse]], + ): ActorRef[ResultServiceProxy.Message] + /** Creates a weather service * * @param context @@ -124,6 +135,8 @@ trait SimonaSetup { * Actor context to use * @param scheduler * Actor reference to the scheduler to use + * @param resultProxy + * Actor reference to the result provider. * @param extSimPath * option for a directory with external simulations * @return @@ -132,6 +145,7 @@ trait SimonaSetup { def extSimulations( context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], + resultProxy: ActorRef[RequestResultMessage], extSimPath: Option[Path], ): ExtSimSetupData @@ -176,8 +190,6 @@ trait SimonaSetup { * Actor context to use * @param environmentRefs * EnvironmentRefs to use - * @param resultEventListeners - * Listeners that await events from system participants * @return * A mapping from actor reference to it's according initialization data to * be used when setting up the agents @@ -185,7 +197,6 @@ trait SimonaSetup { def gridAgents( context: ActorContext[_], environmentRefs: EnvironmentRefs, - resultEventListeners: Seq[ActorRef[ResultEvent]], ): Iterable[ActorRef[GridAgent.Message]] /** SIMONA links sub grids connected by a three winding transformer a bit diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index 1e3ca50a9f..b1fd20e613 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -19,7 +19,11 @@ import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.exceptions.agent.GridAgentInitializationException import edu.ie3.simona.io.grid.GridProvider -import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} +import edu.ie3.simona.ontology.messages.{ + RequestResultMessage, + SchedulerMessage, + ServiceMessage, +} import edu.ie3.simona.scheduler.core.Core.CoreFactory import edu.ie3.simona.scheduler.core.RegularSchedulerCore import edu.ie3.simona.scheduler.{ScheduleLock, Scheduler, TimeAdvancer} @@ -27,6 +31,8 @@ import edu.ie3.simona.service.load.LoadProfileService import edu.ie3.simona.service.load.LoadProfileService.InitLoadProfileServiceStateData import edu.ie3.simona.service.primary.PrimaryServiceProxy import edu.ie3.simona.service.primary.PrimaryServiceProxy.InitPrimaryServiceProxyStateData +import edu.ie3.simona.service.results.ResultServiceProxy +import edu.ie3.simona.service.results.ResultServiceProxy.Message import edu.ie3.simona.service.weather.WeatherService import edu.ie3.simona.service.weather.WeatherService.InitWeatherServiceStateData import edu.ie3.simona.sim.SimonaSim @@ -62,7 +68,6 @@ class SimonaStandaloneSetup( override def gridAgents( context: ActorContext[_], environmentRefs: EnvironmentRefs, - resultEventListeners: Seq[ActorRef[ResultEvent]], ): Iterable[ActorRef[GridAgent.Message]] = { /* get the grid */ @@ -85,7 +90,6 @@ class SimonaStandaloneSetup( subGridTopologyGraph, context, environmentRefs, - resultEventListeners, ) val keys = ScheduleLock.multiKey( @@ -166,6 +170,15 @@ class SimonaStandaloneSetup( primaryServiceProxy } + override def resultServiceProxy( + context: ActorContext[_], + listeners: Seq[ActorRef[ResultEvent.ResultResponse]], + ): ActorRef[ResultServiceProxy.Message] = + context.spawn( + ResultServiceProxy(listeners), + "resultEventProxyAgent", + ) + override def weatherService( context: ActorContext[_], scheduler: ActorRef[SchedulerMessage], @@ -217,6 +230,7 @@ class SimonaStandaloneSetup( override def extSimulations( context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], + resultProxy: ActorRef[RequestResultMessage], extSimPath: Option[Path], ): ExtSimSetupData = { val jars = ExtSimLoader.scanInputFolder(extSimPath) @@ -225,6 +239,7 @@ class SimonaStandaloneSetup( setupExtSim(extLinks, args)(using context, scheduler, + resultProxy, simonaConfig.simona.time.startTime, ) } @@ -278,7 +293,7 @@ class SimonaStandaloneSetup( override def resultEventListener( context: ActorContext[_] - ): Seq[ActorRef[ResultEventListener.Request]] = { + ): Seq[ActorRef[ResultEventListener.Message]] = { // append ResultEventListener as well to write raw output files Seq( context @@ -293,7 +308,6 @@ class SimonaStandaloneSetup( subGridTopologyGraph: SubGridTopologyGraph, context: ActorContext[_], environmentRefs: EnvironmentRefs, - resultEventListeners: Seq[ActorRef[ResultEvent]], ): Map[Int, ActorRef[GridAgent.Message]] = { subGridTopologyGraph .vertexSet() @@ -304,7 +318,6 @@ class SimonaStandaloneSetup( GridAgent( environmentRefs, simonaConfig, - resultEventListeners, ), subGridContainer.getSubnet.toString, ) From ed072a4b75da23dec0573dc52e26bbcfdbed0a83 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 20 Aug 2025 11:16:44 +0200 Subject: [PATCH 072/125] Adapting code. Fixing bugs. --- .../scala/edu/ie3/simona/service/load/LoadProfileService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simona/service/load/LoadProfileService.scala b/src/main/scala/edu/ie3/simona/service/load/LoadProfileService.scala index 283ae5136d..43002fe5cb 100644 --- a/src/main/scala/edu/ie3/simona/service/load/LoadProfileService.scala +++ b/src/main/scala/edu/ie3/simona/service/load/LoadProfileService.scala @@ -216,7 +216,7 @@ object LoadProfileService extends SimonaService { ): (LoadProfileInitializedStateData, Option[Long]) = { /* Pop the next activation tick and update the state data */ - val nextTick = tick + serviceStateData.resolution.toSeconds + val nextTick = tick + 15.minutes.toSeconds val updatedStateData: LoadProfileInitializedStateData = serviceStateData.copy(nextActivationTick = nextTick) From 15f31de63cd8084fd08256e6fbb9ff0fa0f9d0cb Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 20 Aug 2025 16:45:18 +0200 Subject: [PATCH 073/125] Saving changes. --- .../edu/ie3/simona/agent/grid/GridAgent.scala | 7 +- .../agent/grid/GridResultsSupport.scala | 9 +-- .../agent/grid/congestion/DCMAlgorithm.scala | 3 +- .../data/CongestionManagementData.scala | 2 + .../agent/participant/ParticipantAgent.scala | 21 +++++- .../participant/ParticipantGridAdapter.scala | 4 +- .../edu/ie3/simona/event/ResultEvent.scala | 13 ++-- .../event/listener/ExtResultEvent.scala | 22 +++--- ...esultMessage.scala => RequestResult.scala} | 2 +- .../service/results/ExtResultProvider.scala | 6 +- .../service/results/ResultServiceProxy.scala | 70 +++++-------------- .../ie3/simona/sim/setup/ExtSimSetup.scala | 7 +- .../ie3/simona/sim/setup/SimonaSetup.scala | 4 +- .../sim/setup/SimonaStandaloneSetup.scala | 4 +- 14 files changed, 82 insertions(+), 92 deletions(-) rename src/main/scala/edu/ie3/simona/ontology/messages/{RequestResultMessage.scala => RequestResult.scala} (91%) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala index a80ac4daf3..b24ea84cee 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -256,6 +256,7 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { createResultModels( gridAgentBaseData.gridEnv.gridModel, valueStore, + nextTick )(using currentTick.toDateTime(using constantData.simStartTime), ctx.log, @@ -264,7 +265,7 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { // check if congestion management is enabled if (gridAgentBaseData.congestionManagementParams.detectionEnabled) { - startCongestionManagement(gridAgentBaseData, currentTick, results, ctx) + startCongestionManagement(gridAgentBaseData, currentTick, nextTick, results, ctx) } else { // clean up agent and go back to idle gotoIdle(gridAgentBaseData, nextTick, results, ctx) @@ -300,7 +301,9 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { ): Behavior[Message] = { // notify listener about the results - results.foreach(constantData.notifyListeners) + results + .map(_.copy(nextTick = nextTick)) + .foreach(constantData.notifyListeners) // do my cleanup stuff ctx.log.debug("Doing my cleanup stuff") diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala index 4a099abe06..d463f7aa5e 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala @@ -59,17 +59,17 @@ private[grid] trait GridResultsSupport { def createResultModels( grid: GridModel, sweepValueStore: SweepValueStore, - )(implicit timestamp: ZonedDateTime, log: Logger): PowerFlowResultEvent = { + nextTick: Long, + )(using timestamp: ZonedDateTime, log: Logger): PowerFlowResultEvent = { // no sanity check for duplicated uuid result data as we expect valid data at this point - implicit val sweepValueStoreData: Map[UUID, SweepValueStoreData] = + given sweepValueStoreData: Map[UUID, SweepValueStoreData] = sweepValueStore.sweepData .map(sweepValueStoreData => sweepValueStoreData.nodeUuid -> sweepValueStoreData ) .toMap - implicit val iNominal: ElectricCurrent = - grid.mainRefSystem.nominalCurrent + given ElectricCurrent = grid.mainRefSystem.nominalCurrent /* When creating node results, we have to consider two things: * 1) The result of a two winding transformer's hv node is calculated twice. If this grid contains the @@ -89,6 +89,7 @@ private[grid] trait GridResultsSupport { buildLineResults(grid.gridComponents.lines), buildTransformer2wResults(grid.gridComponents.transformers), buildTransformer3wResults(grid.gridComponents.transformers3w), + nextTick = nextTick, ) } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/congestion/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/congestion/DCMAlgorithm.scala index f57ed196db..59af0398b3 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/congestion/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/congestion/DCMAlgorithm.scala @@ -48,6 +48,7 @@ trait DCMAlgorithm extends CongestionDetection { private[grid] def startCongestionManagement( gridAgentBaseData: GridAgentBaseData, currentTick: Long, + nextTick: Long, results: Option[PowerFlowResultEvent], ctx: ActorContext[Message], )(using @@ -59,7 +60,7 @@ trait DCMAlgorithm extends CongestionDetection { val congestionManagementData = results .map(res => CongestionManagementData(gridAgentBaseData, currentTick, res)) .getOrElse( - CongestionManagementData.empty(gridAgentBaseData, currentTick) + CongestionManagementData.empty(gridAgentBaseData, currentTick, nextTick) ) ctx.self ! StartStep diff --git a/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala b/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala index 8811557bbc..19fa3f2f3a 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala @@ -169,6 +169,7 @@ object CongestionManagementData { def empty( gridAgentBaseData: GridAgentBaseData, currentTick: Long, + nextTick: Long, ): CongestionManagementData = apply( gridAgentBaseData, currentTick, @@ -178,6 +179,7 @@ object CongestionManagementData { Seq.empty, Seq.empty, Seq.empty, + nextTick = nextTick, ), ) } diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index 3a10ea820c..5bc6363837 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -7,11 +7,16 @@ package edu.ie3.simona.agent.participant import breeze.numerics.{pow, sqrt} +import edu.ie3.datamodel.models.result.system.{ + FlexOptionsResult, + SystemParticipantResult, +} import edu.ie3.simona.agent.grid.GridAgentMessages.{ AssetPowerChangedMessage, AssetPowerUnchangedMessage, ProvidedPowerResponse, } +import edu.ie3.simona.event.ResultEvent.ResultResponse import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.model.participant.ParticipantModel.AdditionalFactoryData import edu.ie3.simona.model.participant.ParticipantModelShell @@ -19,6 +24,7 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.{ Activation, + RequestResult, SchedulerMessage, ServiceMessage, } @@ -35,7 +41,7 @@ import squants.{Dimensionless, Each} */ object ParticipantAgent { - type Message = Request | ActivationRequest + type Message = Request | ActivationRequest | RequestResult type ActivationRequest = Activation | FlexRequest @@ -304,6 +310,17 @@ object ParticipantAgent { updatedGridAdapter, resultHandler, ) + + case (ctx, request: RequestResult) => + val tick = request.tick + + if inputHandler.activation.exists(_.tick <= tick) then { + ctx.self ! request + } else { + request.replyTo ! ResultResponse(gridAdapter.lastResults.toList) + } + + Behaviors.same } /** Starts a model calculation if all requirements have been met. A model @@ -460,7 +477,7 @@ object ParticipantAgent { (updatedShell, inputHandler.completeActivation(), updatedGridAdapter) } else - (modelShell, inputHandler, gridAdapter) + (modelShell, inputHandler, gridAdapter.clearLastResults) } /** Checks if all required messages needed for calculation have been received. diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantGridAdapter.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantGridAdapter.scala index d258df0a18..499cba9ed0 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantGridAdapter.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantGridAdapter.scala @@ -94,6 +94,8 @@ final case class ParticipantGridAdapter( lastResults = result.modelResults, ) + def clearLastResults: ParticipantGridAdapter = copy(lastResults = Seq.empty) + /** Handles a power request by making sure an average power value has been * calculated, taking into account the new voltage value. * @@ -217,7 +219,7 @@ object ParticipantGridAdapter { expectedRequestTick = expectedRequestTick, tickToPower = SortedMap.empty, avgPowerResult = None, - )( + )(using requestVoltageDeviationTolerance = requestVoltageDeviationTolerance ) diff --git a/src/main/scala/edu/ie3/simona/event/ResultEvent.scala b/src/main/scala/edu/ie3/simona/event/ResultEvent.scala index 488df8259b..2700a884b2 100644 --- a/src/main/scala/edu/ie3/simona/event/ResultEvent.scala +++ b/src/main/scala/edu/ie3/simona/event/ResultEvent.scala @@ -48,8 +48,7 @@ object ResultEvent { * the calculation result */ final case class ParticipantResultEvent( - systemParticipantResult: SystemParticipantResult, - maybeNextTick: Option[Long] = None, + systemParticipantResult: SystemParticipantResult ) extends ResultEvent object HpResult { @@ -95,8 +94,7 @@ object ResultEvent { * Result of the thermal calculation */ final case class ThermalResultEvent( - thermalResult: ThermalUnitResult, - maybeNextTick: Option[Long] = None, + thermalResult: ThermalUnitResult ) extends ResultEvent object ThermalHouseResult { @@ -155,6 +153,8 @@ object ResultEvent { * the partial power flow results for three winding transformers * @param congestionResults * the congestion found by the congestion managements (default: empty) + * @param nextTick + * The next tick, for which new result will be sent. */ final case class PowerFlowResultEvent( nodeResults: Iterable[NodeResult], @@ -163,7 +163,7 @@ object ResultEvent { transformer2wResults: Iterable[Transformer2WResult], transformer3wResults: Iterable[PartialTransformer3wResult], congestionResults: Iterable[CongestionResult] = Iterable.empty, - maybeNextTick: Option[Long] = None, + nextTick: Long, ) extends ResultEvent { def +(congestionResult: Iterable[CongestionResult]): PowerFlowResultEvent = @@ -180,8 +180,7 @@ object ResultEvent { * the flex options result */ final case class FlexOptionsResultEvent( - flexOptionsResult: FlexOptionsResult, - maybeNextTick: Option[Long] = None, + flexOptionsResult: FlexOptionsResult ) extends ResultEvent sealed trait Response diff --git a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala index ce7410fc6d..b7c13027f2 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala @@ -26,7 +26,7 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ import edu.ie3.simona.ontology.messages.ServiceMessage.ScheduleServiceActivation import edu.ie3.simona.ontology.messages.{ Activation, - RequestResultMessage, + RequestResult, SchedulerMessage, } import org.apache.pekko.actor.typed.scaladsl.Behaviors @@ -43,11 +43,11 @@ object ExtResultEvent { private final case class ProviderState( scheduler: ActorRef[SchedulerMessage], - resultProxy: ActorRef[RequestResultMessage], + resultProxy: ActorRef[RequestResult], connection: ExtResultDataConnection, extMessage: Option[ResultDataMessageFromExt] = None, - simStartTime: ZonedDateTime, - gridAssets: List[UUID] = List.empty, + gridAssets: Seq[UUID], + flexAssets: Seq[UUID], ) def listener(connection: ExtResultListener): Behavior[Message] = @@ -66,18 +66,15 @@ object ExtResultEvent { def provider( connection: ExtResultDataConnection, scheduler: ActorRef[SchedulerMessage], - resultProxy: ActorRef[RequestResultMessage], - simStartTime: ZonedDateTime, + resultProxy: ActorRef[RequestResult], ): Behavior[Message | DataMessageFromExt | Activation] = { - val gridResults = connection.getGridResultDataAssets.asScala - val stateData = ProviderState( scheduler, resultProxy, connection, - simStartTime = simStartTime, - gridAssets = gridResults.toList, + gridAssets = connection.getGridResultDataAssets.asScala.toSeq, + flexAssets = connection.getFlexOptionAssets.asScala.toSeq, ) provider(stateData) @@ -125,13 +122,16 @@ object ExtResultEvent { val requestedResults = new util.ArrayList(requestResultEntities.requestedResults) + // TODO: flex result are currently not supported by the result provider + requestedResults.removeAll(stateData.flexAssets.asJava) + if requestResultEntities.tick == 0 then { // removing the grid assets for tick 0, since SIMONA will produce no output requestedResults.removeAll(stateData.gridAssets.asJava) } // request results from result proxy - stateData.resultProxy ! RequestResultMessage( + stateData.resultProxy ! RequestResult( requestedResults.asScala.toSeq, tick, ctx.self, diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/RequestResultMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/RequestResult.scala similarity index 91% rename from src/main/scala/edu/ie3/simona/ontology/messages/RequestResultMessage.scala rename to src/main/scala/edu/ie3/simona/ontology/messages/RequestResult.scala index badba99fdc..efa33d355f 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/RequestResultMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/RequestResult.scala @@ -11,7 +11,7 @@ import org.apache.pekko.actor.typed.ActorRef import java.util.UUID -final case class RequestResultMessage( +final case class RequestResult( requestedResults: Seq[UUID], tick: Long, replyTo: ActorRef[ResultEvent.Response], diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala index 50537d2700..9202cd9c5d 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala @@ -32,7 +32,7 @@ import org.apache.pekko.actor.typed.scaladsl.ActorContext import java.time.ZonedDateTime import java.util.UUID -import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava} +import scala.jdk.CollectionConverters.{ListHasAsScala, SeqHasAsJava} import scala.util.{Failure, Success, Try} @deprecated @@ -164,7 +164,7 @@ object ExtResultProvider extends SimonaService with ExtDataSupport { if (updated.isComplete) { serviceStateData.extResultDataConnection.queueExtResponseMsg( - new ProvideResultEntities(updated.receivedData.asJava) + new ProvideResultEntities(updated.receivedData.values.toList.asJava) ) serviceStateData.copy( @@ -246,7 +246,7 @@ object ExtResultProvider extends SimonaService with ExtDataSupport { if (updated.isComplete) { serviceStateData.extResultDataConnection.queueExtResponseMsg( - new ProvideResultEntities(updated.receivedData.asJava) + new ProvideResultEntities(updated.receivedData.values.toList.asJava) ) serviceStateData.copy( diff --git a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala index f2c0dc8783..307e3f45db 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala @@ -16,7 +16,7 @@ import edu.ie3.simona.event.listener.ResultEventListener.{ AggregatedTransformer3wResult, Transformer3wKey, } -import edu.ie3.simona.ontology.messages.RequestResultMessage +import edu.ie3.simona.ontology.messages.RequestResult import edu.ie3.simona.service.ServiceStateData.ServiceBaseStateData import org.apache.pekko.actor.typed.scaladsl.{Behaviors, StashBuffer} import org.apache.pekko.actor.typed.{ActorRef, Behavior, PostStop} @@ -27,8 +27,7 @@ import scala.util.{Failure, Success} object ResultServiceProxy { - type Message = ResultEvent | RequestResultMessage | - DelayedStopHelper.StoppingMsg + type Message = ResultEvent | RequestResult | DelayedStopHelper.StoppingMsg private final case class ResultServiceStateData( listeners: Seq[ActorRef[ResultEvent.ResultResponse]], @@ -41,6 +40,9 @@ object ResultServiceProxy { ) extends ServiceBaseStateData { def notifyListener(results: List[ResultEntity]): Unit = listeners.foreach(_ ! ResultResponse(results)) + + def notifyListener(result: ResultEntity): Unit = + listeners.foreach(_ ! ResultResponse(List(result))) } def apply( @@ -63,7 +65,7 @@ object ResultServiceProxy { // un-stash received requests buffer.unstashAll(idle(updatedStateData)) - case (ctx, requestResultMessage: RequestResultMessage) => + case (ctx, requestResultMessage: RequestResult) => val requestedResults = requestResultMessage.requestedResults val tick = requestResultMessage.tick @@ -110,27 +112,17 @@ object ResultServiceProxy { resultEvent: ResultEvent, stateData: ResultServiceStateData, )(using log: Logger): ResultServiceStateData = resultEvent match { - case ParticipantResultEvent(systemParticipantResult, maybeNextTick) => + case ParticipantResultEvent(systemParticipantResult) => // notify listener - stateData.notifyListener(List(systemParticipantResult)) + stateData.notifyListener(systemParticipantResult) val uuid = systemParticipantResult.getInputModel - val nextTicks = stateData.nextTicks - - val updatedNextTicks = maybeNextTick match { - case Some(value) => - nextTicks.updated(uuid, value) - case None => - nextTicks - } - stateData.copy( - nextTicks = updatedNextTicks, resultMapping = stateData.resultMapping.updated( uuid, systemParticipantResult, - ), + ) ) case PowerFlowResultEvent( @@ -140,7 +132,7 @@ object ResultServiceProxy { transformer2wResults, partialTransformer3wResults, congestionResults, - maybeNextTick, + nextTick, ) => // handling of three winding transformers val (updatedResults, transformer3wResults) = @@ -159,13 +151,8 @@ object ResultServiceProxy { val nextTicks = stateData.nextTicks - val updatedNextTicks = maybeNextTick match { - case Some(value) => - nextTicks ++ results.keys.map(key => key -> value).toMap - - case None => - nextTicks - } + val updatedNextTicks = + nextTicks ++ results.keys.map(key => key -> nextTick).toMap stateData.copy( nextTicks = updatedNextTicks, @@ -173,46 +160,25 @@ object ResultServiceProxy { threeWindingResults = updatedResults, ) - case ThermalResultEvent(thermalResult, maybeNextTick) => + case ThermalResultEvent(thermalResult) => // notify listener - stateData.notifyListener(List(thermalResult)) - + stateData.notifyListener(thermalResult) val uuid = thermalResult.getInputModel - val nextTicks = stateData.nextTicks - - val updatedNextTicks = maybeNextTick match { - case Some(value) => - nextTicks.updated(uuid, value) - case None => - nextTicks - } - stateData.copy( - nextTicks = updatedNextTicks, resultMapping = stateData.resultMapping - .updated(uuid, thermalResult), + .updated(uuid, thermalResult) ) - case FlexOptionsResultEvent(flexOptionsResult, maybeNextTick) => + case FlexOptionsResultEvent(flexOptionsResult) => // notify listener - stateData.notifyListener(List(flexOptionsResult)) + stateData.notifyListener(flexOptionsResult) val uuid = flexOptionsResult.getInputModel - val nextTicks = stateData.nextTicks - - val updatedNextTicks = maybeNextTick match { - case Some(value) => - nextTicks.updated(uuid, value) - case None => - nextTicks - } - stateData.copy( - nextTicks = updatedNextTicks, resultMapping = stateData.resultMapping - .updated(uuid, flexOptionsResult), + .updated(uuid, flexOptionsResult) ) } diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index c7168ad873..d8435ca243 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -14,7 +14,7 @@ import edu.ie3.simona.api.{ExtLinkInterface, ExtSimAdapter} import edu.ie3.simona.event.listener.ExtResultEvent import edu.ie3.simona.exceptions.ServiceException import edu.ie3.simona.ontology.messages.{ - RequestResultMessage, + RequestResult, SchedulerMessage, ServiceMessage, } @@ -61,7 +61,7 @@ object ExtSimSetup { )(using context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], - resultProxy: ActorRef[RequestResultMessage], + resultProxy: ActorRef[RequestResult], startTime: ZonedDateTime, ): ExtSimSetupData = extLinks.zipWithIndex.foldLeft(ExtSimSetupData.apply) { case (extSimSetupData, (extLink, index)) => @@ -128,7 +128,7 @@ object ExtSimSetup { context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], extSimAdapterData: ExtSimAdapterData, - resultProxy: ActorRef[RequestResultMessage], + resultProxy: ActorRef[RequestResult], startTime: ZonedDateTime, ): ExtSimSetupData = { given extSimAdapter: ActorRef[ControlResponseMessageFromExt] = @@ -212,7 +212,6 @@ object ExtSimSetup { extResultDataConnection, scheduler, resultProxy, - startTime, ), s"ExtResultProvider", ) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala index 70e075a341..5e8eea6d63 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -15,7 +15,7 @@ import edu.ie3.simona.event.ResultEvent.ResultResponse import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.ontology.messages.{ - RequestResultMessage, + RequestResult, SchedulerMessage, ServiceMessage, } @@ -145,7 +145,7 @@ trait SimonaSetup { def extSimulations( context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], - resultProxy: ActorRef[RequestResultMessage], + resultProxy: ActorRef[RequestResult], extSimPath: Option[Path], ): ExtSimSetupData diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index b1fd20e613..bc622c107a 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -20,7 +20,7 @@ import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.exceptions.agent.GridAgentInitializationException import edu.ie3.simona.io.grid.GridProvider import edu.ie3.simona.ontology.messages.{ - RequestResultMessage, + RequestResult, SchedulerMessage, ServiceMessage, } @@ -230,7 +230,7 @@ class SimonaStandaloneSetup( override def extSimulations( context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], - resultProxy: ActorRef[RequestResultMessage], + resultProxy: ActorRef[RequestResult], extSimPath: Option[Path], ): ExtSimSetupData = { val jars = ExtSimLoader.scanInputFolder(extSimPath) From aaa8b033edba146f6f111a18a2e12b22daf3e6fe Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 25 Aug 2025 09:22:16 +0200 Subject: [PATCH 074/125] Refactor result handling. --- .../edu/ie3/simona/agent/grid/GridAgent.scala | 18 +- .../simona/agent/grid/GridAgentBuilder.scala | 2 +- .../ie3/simona/agent/grid/GridAgentData.scala | 9 + .../agent/participant/ParticipantAgent.scala | 19 +- .../participant/ParticipantAgentInit.scala | 9 +- .../ParticipantResultHandler.scala | 20 ++- .../edu/ie3/simona/event/ResultEvent.scala | 3 +- .../event/listener/ExtResultEvent.scala | 6 +- .../event/listener/ResultEventListener.scala | 2 +- .../service/results/ExtResultProvider.scala | 8 +- .../service/results/ResultServiceProxy.scala | 170 ++++++++++++------ .../scala/edu/ie3/simona/sim/SimonaSim.scala | 7 +- .../ie3/simona/sim/setup/ExtSimSetup.scala | 7 +- .../ie3/simona/sim/setup/SimonaSetup.scala | 4 +- .../sim/setup/SimonaStandaloneSetup.scala | 4 +- .../edu/ie3/simona/util/CollectionUtils.scala | 9 + 16 files changed, 201 insertions(+), 96 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala index b24ea84cee..31682d7969 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -32,6 +32,7 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, } +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.util.TimeUtil import org.apache.pekko.actor.typed.scaladsl.AskPattern.Askable @@ -214,6 +215,13 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { ctx.self, Some(activation.tick), ) + + // inform the result proxy that this grid agent will send new results + constantData.environmentRefs.resultProxy ! ExpectResult( + gridAgentBaseData.assets, + activation.tick, + ) + buffer.unstashAll(simulateGrid(gridAgentBaseData, activation.tick)) case (_, msg: Message) => @@ -256,7 +264,7 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { createResultModels( gridAgentBaseData.gridEnv.gridModel, valueStore, - nextTick + nextTick, )(using currentTick.toDateTime(using constantData.simStartTime), ctx.log, @@ -265,7 +273,13 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { // check if congestion management is enabled if (gridAgentBaseData.congestionManagementParams.detectionEnabled) { - startCongestionManagement(gridAgentBaseData, currentTick, nextTick, results, ctx) + startCongestionManagement( + gridAgentBaseData, + currentTick, + nextTick, + results, + ctx, + ) } else { // clean up agent and go back to idle gotoIdle(gridAgentBaseData, nextTick, results, ctx) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala index 9f05a6211a..4d691e9bd4 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala @@ -312,8 +312,8 @@ object GridAgentBuilder { given ParticipantRefs = ParticipantRefs( gridAgentContext.self, environmentRefs.primaryServiceProxy, - serviceMap, environmentRefs.resultProxy, + serviceMap, ) given SimulationParameters = SimulationParameters( diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala index aaf451bdad..9c6e14caa5 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -315,6 +315,15 @@ object GridAgentData { ) extends GridAgentData with GridAgentDataHelper { + val assets: Seq[UUID] = { + val components = gridEnv.gridModel.gridComponents + components.nodes.map(_.uuid) ++ components.lines.map( + _.uuid + ) ++ components.switches.map(_.uuid) ++ components.transformers.map( + _.uuid + ) ++ components.transformers3w.map(_.uuid) + } + override protected val subgridGates: Vector[SubGridGate] = gridEnv.subgridGateToActorRef.keys.toVector override protected val subgridId: Int = gridEnv.gridModel.subnetNo diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index 5bc6363837..08f457fdae 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -41,7 +41,7 @@ import squants.{Dimensionless, Each} */ object ParticipantAgent { - type Message = Request | ActivationRequest | RequestResult + type Message = Request | ActivationRequest type ActivationRequest = Activation | FlexRequest @@ -203,6 +203,9 @@ object ParticipantAgent { case (ctx, activation: ActivationRequest) => given ActorRef[Message] = ctx.self + // inform the result proxy that this grid agent will send new results + resultHandler.informProxy(modelShell.uuid, activation.tick) + val coreWithActivation = inputHandler.handleActivation(activation) val (updatedShell, updatedInputHandler, updatedGridAdapter) = @@ -223,6 +226,9 @@ object ParticipantAgent { case (ctx, msg: DataInputMessage) => given ActorRef[Message] = ctx.self + // inform the result proxy that this grid agent will send new results + resultHandler.informProxy(modelShell.uuid, msg.tick) + val inputHandlerWithData = inputHandler.handleDataInputMessage(msg) val (updatedShell, updatedInputHandler, updatedGridAdapter) = @@ -310,17 +316,6 @@ object ParticipantAgent { updatedGridAdapter, resultHandler, ) - - case (ctx, request: RequestResult) => - val tick = request.tick - - if inputHandler.activation.exists(_.tick <= tick) then { - ctx.self ! request - } else { - request.replyTo ! ResultResponse(gridAdapter.lastResults.toList) - } - - Behaviors.same } /** Starts a model calculation if all requirements have been met. A model diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala index b3d5b0c887..e474445574 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala @@ -35,6 +35,7 @@ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.simona.service.ServiceType +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import edu.ie3.simona.service.weather.WeatherService.Coordinate import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.typed.scaladsl.Behaviors @@ -57,16 +58,16 @@ object ParticipantAgentInit { * Reference to the grid agent. * @param primaryServiceProxy * Reference to the primary service proxy. + * @param resultServiceProxy + * Reference to the result service proxy. * @param services * References to services by service type. - * @param resultListener - * Reference to the result service proxy. */ final case class ParticipantRefs( gridAgent: ActorRef[GridAgent.Message], primaryServiceProxy: ActorRef[ServiceMessage], + resultServiceProxy: ActorRef[ResultEvent | ExpectResult], services: Map[ServiceType, ActorRef[ServiceMessage]], - resultListener: ActorRef[ResultEvent], ) /** Container class that holds parameters related to the simulation. @@ -416,7 +417,7 @@ object ParticipantAgentInit { simulationParams.requestVoltageDeviationTolerance, ), ParticipantResultHandler( - participantRefs.resultListener, + participantRefs.resultServiceProxy, notifierConfig, ), ) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala index 4cc81e0777..981bc5c350 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala @@ -20,18 +20,21 @@ import edu.ie3.simona.event.ResultEvent.{ } import edu.ie3.simona.event.notifier.NotifierConfig import edu.ie3.simona.exceptions.CriticalFailureException +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import org.apache.pekko.actor.typed.ActorRef +import java.util.UUID + /** Handles all kind of results stemming from the participant by sending them to - * the result listener, if applicable. + * the result proxy, if applicable. * - * @param listener - * The actor reference to the result listener. + * @param resultProxy + * The actor reference to the result resultProxy. * @param config * The result configuration. */ final case class ParticipantResultHandler( - private val listener: ActorRef[ResultEvent], + private val resultProxy: ActorRef[ResultEvent | ExpectResult], private val config: NotifierConfig, ) { @@ -44,9 +47,9 @@ final case class ParticipantResultHandler( if (config.simulationResultInfo) { result match { case thermalResult: ThermalUnitResult => - listener ! ThermalResultEvent(thermalResult) + resultProxy ! ThermalResultEvent(thermalResult) case participantResult: SystemParticipantResult => - listener ! ParticipantResultEvent(participantResult) + resultProxy ! ParticipantResultEvent(participantResult) case unsupported => throw new CriticalFailureException( s"Results of class '${unsupported.getClass.getSimpleName}' are currently not supported." @@ -61,7 +64,10 @@ final case class ParticipantResultHandler( */ def maybeSend(result: FlexOptionsResult): Unit = if (config.flexResult) { - listener ! FlexOptionsResultEvent(result) + resultProxy ! FlexOptionsResultEvent(result) } + def informProxy(uuid: UUID, tick: Long): Unit = + resultProxy ! ExpectResult(uuid, tick) + } diff --git a/src/main/scala/edu/ie3/simona/event/ResultEvent.scala b/src/main/scala/edu/ie3/simona/event/ResultEvent.scala index 2700a884b2..dd235bfc50 100644 --- a/src/main/scala/edu/ie3/simona/event/ResultEvent.scala +++ b/src/main/scala/edu/ie3/simona/event/ResultEvent.scala @@ -185,6 +185,7 @@ object ResultEvent { sealed trait Response - final case class ResultResponse(results: List[ResultEntity]) extends Response + final case class ResultResponse(results: Map[UUID, Iterable[ResultEntity]]) + extends Response } diff --git a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala index b7c13027f2..a35557ccc4 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala @@ -32,9 +32,9 @@ import edu.ie3.simona.ontology.messages.{ import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.{ActorRef, Behavior} -import java.time.ZonedDateTime import java.util import java.util.UUID +import edu.ie3.simona.util.CollectionUtils.asJava import scala.jdk.CollectionConverters.* object ExtResultEvent { @@ -85,6 +85,8 @@ object ExtResultEvent { ): Behavior[Message | DataMessageFromExt | Activation] = Behaviors.receivePartial[Message | DataMessageFromExt | Activation] { case (ctx, ResultResponse(results)) => + ctx.log.warn(s"Sending results to ext. Results: $results") + // send result to external simulation stateData.connection.queueExtResponseMsg( new ProvideResultEntities(results.asJava) @@ -124,7 +126,7 @@ object ExtResultEvent { // TODO: flex result are currently not supported by the result provider requestedResults.removeAll(stateData.flexAssets.asJava) - + if requestResultEntities.tick == 0 then { // removing the grid assets for tick 0, since SIMONA will produce no output requestedResults.removeAll(stateData.gridAssets.asJava) diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index fd5c3fc1f8..7f0d2dd211 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -223,7 +223,7 @@ object ResultEventListener extends Transformer3wResultSupport { private def idle(baseData: BaseData): Behavior[Message] = Behaviors .receivePartial[Message] { case (ctx, ResultResponse(results)) => - handleResults(results, baseData, ctx.log) + handleResults(results.values.flatten, baseData, ctx.log) Behaviors.same diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala index 9202cd9c5d..3a252b451c 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala @@ -164,7 +164,9 @@ object ExtResultProvider extends SimonaService with ExtDataSupport { if (updated.isComplete) { serviceStateData.extResultDataConnection.queueExtResponseMsg( - new ProvideResultEntities(updated.receivedData.values.toList.asJava) + new ProvideResultEntities( + updated.receivedData.values.toList.asJava + ) ) serviceStateData.copy( @@ -246,7 +248,9 @@ object ExtResultProvider extends SimonaService with ExtDataSupport { if (updated.isComplete) { serviceStateData.extResultDataConnection.queueExtResponseMsg( - new ProvideResultEntities(updated.receivedData.values.toList.asJava) + new ProvideResultEntities( + updated.receivedData.values.toList.asJava + ) ) serviceStateData.copy( diff --git a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala index 307e3f45db..cf93ca01ef 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala @@ -18,38 +18,117 @@ import edu.ie3.simona.event.listener.ResultEventListener.{ } import edu.ie3.simona.ontology.messages.RequestResult import edu.ie3.simona.service.ServiceStateData.ServiceBaseStateData +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import edu.ie3.simona.util.TickUtil.RichZonedDateTime import org.apache.pekko.actor.typed.scaladsl.{Behaviors, StashBuffer} import org.apache.pekko.actor.typed.{ActorRef, Behavior, PostStop} import org.slf4j.Logger +import java.time.ZonedDateTime import java.util.UUID import scala.util.{Failure, Success} object ResultServiceProxy { - type Message = ResultEvent | RequestResult | DelayedStopHelper.StoppingMsg + type Message = ResultEvent | RequestResult | ExpectResult | + DelayedStopHelper.StoppingMsg + + final case class ExpectResult(assets: UUID | Seq[UUID], tick: Long) private final case class ResultServiceStateData( listeners: Seq[ActorRef[ResultEvent.ResultResponse]], - nextTicks: Map[UUID, Long] = Map.empty, - resultMapping: Map[UUID, ResultEntity] = Map.empty, + simStartTime: ZonedDateTime, + currentTick: Long = INIT_SIM_TICK, threeWindingResults: Map[ Transformer3wKey, AggregatedTransformer3wResult, ] = Map.empty, + gridResults: Map[UUID, Iterable[ResultEntity]] = Map.empty, + results: Map[UUID, Iterable[ResultEntity]] = Map.empty, + waitingForResults: Map[UUID, Long] = Map.empty, ) extends ServiceBaseStateData { - def notifyListener(results: List[ResultEntity]): Unit = + def notifyListener(results: Map[UUID, Iterable[ResultEntity]]): Unit = listeners.foreach(_ ! ResultResponse(results)) def notifyListener(result: ResultEntity): Unit = - listeners.foreach(_ ! ResultResponse(List(result))) + listeners.foreach( + _ ! ResultResponse(Map(result.getInputModel -> List(result))) + ) + + def isWaiting(uuids: Iterable[UUID], tick: Long): Boolean = { + uuids.exists { uuid => + waitingForResults.get(uuid) match { + case Some(nextTick) if nextTick <= tick => true + case _ => false + } + } + } + + def updateTick(tick: Long): ResultServiceStateData = + copy(currentTick = tick) + + def waitForResult(expectResult: ExpectResult): ResultServiceStateData = + expectResult.assets match { + case uuid: UUID => + copy(waitingForResults = + waitingForResults.updated(uuid, expectResult.tick) + ) + case uuids: Seq[UUID] => + val tick = expectResult.tick + + copy(waitingForResults = + waitingForResults ++ uuids.map(uuid => uuid -> tick).toMap + ) + } + + def addResult(result: ResultEntity): ResultServiceStateData = { + val uuid = result.getInputModel + val tick = result.getTime.toTick(using simStartTime) + + val updatedWaitingForResults = + if waitingForResults.get(uuid).contains(tick) then { + waitingForResults.removed(uuid) + } else waitingForResults + + val updatedResults = results.get(uuid) match { + case Some(values) => + val updatedValues = values + .map { value => value.getClass -> value } + .toMap + .updated(result.getClass, result) + .values + + results.updated(uuid, updatedValues) + + case None => + results.updated(uuid, Iterable(result)) + } + + copy( + results = updatedResults, + waitingForResults = updatedWaitingForResults, + ) + } + + def getResults(uuids: Seq[UUID]): Map[UUID, Iterable[ResultEntity]] = { + uuids.flatMap { uuid => + gridResults.get(uuid) match { + case Some(values) => + Some(uuid -> values) + case None => + results.get(uuid).map { res => uuid -> res } + } + }.toMap + } + } def apply( listeners: Seq[ActorRef[ResultEvent.ResultResponse]], + simStartTime: ZonedDateTime, bufferSize: Int = 10000, ): Behavior[Message] = Behaviors.withStash(bufferSize) { buffer => - idle(ResultServiceStateData(listeners))(using buffer) + idle(ResultServiceStateData(listeners, simStartTime))(using buffer) } private def idle( @@ -58,7 +137,12 @@ object ResultServiceProxy { buffer: StashBuffer[Message] ): Behavior[Message] = Behaviors .receivePartial[Message] { + case (_, expectResult: ExpectResult) => + idle(stateData.waitForResult(expectResult)) + case (ctx, resultEvent: ResultEvent) => + ctx.log.warn(s"Received results: $resultEvent") + // handles the event and updates the state data val updatedStateData = handleResultEvent(resultEvent, stateData)(using ctx.log) @@ -69,24 +153,15 @@ object ResultServiceProxy { val requestedResults = requestResultMessage.requestedResults val tick = requestResultMessage.tick - val nextTicks = stateData.nextTicks - val results = stateData.resultMapping - - val allResultsPresent = requestedResults.forall(results.contains) - val allResultsUpToDate = requestedResults.forall { uuid => - nextTicks.get(uuid) match { - case Some(value) => value < tick - case None => true - } - } + if stateData.isWaiting(requestedResults, tick) then { + ctx.log.warn(s"Cannot answer request: $requestedResults") - if allResultsPresent && allResultsUpToDate then { - val res = requestedResults.map(results).toList - ctx.log.debug(s"Answering message: $requestResultMessage") - - requestResultMessage.replyTo ! ResultResponse(res) - } else { buffer.stash(requestResultMessage) + } else { + + requestResultMessage.replyTo ! ResultResponse( + stateData.getResults(requestedResults) + ) } Behaviors.same @@ -112,19 +187,6 @@ object ResultServiceProxy { resultEvent: ResultEvent, stateData: ResultServiceStateData, )(using log: Logger): ResultServiceStateData = resultEvent match { - case ParticipantResultEvent(systemParticipantResult) => - // notify listener - stateData.notifyListener(systemParticipantResult) - - val uuid = systemParticipantResult.getInputModel - - stateData.copy( - resultMapping = stateData.resultMapping.updated( - uuid, - systemParticipantResult, - ) - ) - case PowerFlowResultEvent( nodeResults, switchResults, @@ -141,45 +203,37 @@ object ResultServiceProxy { stateData.threeWindingResults, ) - val results = - (transformer3wResults ++ nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ congestionResults).map { - res => res.getInputModel -> res - }.toMap + val gridResults = + (transformer3wResults ++ nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ congestionResults) + .groupBy(_.getInputModel) // notify listener - stateData.notifyListener(results.values.toList) - - val nextTicks = stateData.nextTicks - - val updatedNextTicks = - nextTicks ++ results.keys.map(key => key -> nextTick).toMap + stateData.notifyListener(gridResults) stateData.copy( - nextTicks = updatedNextTicks, - resultMapping = stateData.resultMapping ++ results, + gridResults = stateData.gridResults ++ gridResults, threeWindingResults = updatedResults, + waitingForResults = + stateData.waitingForResults.removedAll(gridResults.keys), ) + case ParticipantResultEvent(systemParticipantResult) => + // notify listener + stateData.notifyListener(systemParticipantResult) + + stateData.addResult(systemParticipantResult) + case ThermalResultEvent(thermalResult) => // notify listener stateData.notifyListener(thermalResult) - val uuid = thermalResult.getInputModel - stateData.copy( - resultMapping = stateData.resultMapping - .updated(uuid, thermalResult) - ) + stateData.addResult(thermalResult) case FlexOptionsResultEvent(flexOptionsResult) => // notify listener stateData.notifyListener(flexOptionsResult) - val uuid = flexOptionsResult.getInputModel - - stateData.copy( - resultMapping = stateData.resultMapping - .updated(uuid, flexOptionsResult) - ) + stateData.addResult(flexOptionsResult) } private def handleThreeWindingTransformers( diff --git a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala index 316f825cfc..1ec9aad80f 100644 --- a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala +++ b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala @@ -74,8 +74,11 @@ object SimonaSim { val resultEventListeners = simonaSetup.resultEventListener(ctx) // result proxy - val resultProxy = - simonaSetup.resultServiceProxy(ctx, resultEventListeners) + val resultProxy = simonaSetup.resultServiceProxy( + ctx, + resultEventListeners, + simonaSetup.simonaConfig.simona.time.startTime, + ) val timeAdvancer = simonaSetup.timeAdvancer(ctx, ctx.self, runtimeEventListener) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index d8435ca243..6223e1a019 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -87,7 +87,7 @@ object ExtSimSetup { ) // setup data services that belong to this external simulation - val updatedSetupData = connect(extSimulation, extSimSetupData) + val updatedSetupData = connect(extSimulation, extSimSetupData, index) // starting external simulation new Thread(extSimulation, s"External simulation $index") @@ -112,6 +112,8 @@ object ExtSimSetup { * To connect. * @param extSimSetupData * That contains information about all external simulations. + * @param index + * Index of the external link interface. * @param context * The actor context of this actor system. * @param scheduler @@ -124,6 +126,7 @@ object ExtSimSetup { private[setup] def connect( extSimulation: ExtSimulation, extSimSetupData: ExtSimSetupData, + index: Int, )(using context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], @@ -226,7 +229,7 @@ object ExtSimSetup { case extResultListener: ExtResultListener => val extResultEventListener = context.spawn( ExtResultEvent.listener(extResultListener), - s"ExtResultListener", + s"ExtResultListener_$index", ) extSimSetupData.update(extResultListener, extResultEventListener) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala index 5e8eea6d63..8352a565a7 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -28,6 +28,7 @@ import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext import java.nio.file.Path +import java.time.ZonedDateTime /** Trait that can be used to set up a customized simona simulation by providing * implementations for all setup information required by a @@ -96,7 +97,8 @@ trait SimonaSetup { def resultServiceProxy( context: ActorContext[?], - listeners: Seq[ActorRef[ResultResponse]], + listeners: Seq[ActorRef[ResultEvent.ResultResponse]], + simStartTime: ZonedDateTime, ): ActorRef[ResultServiceProxy.Message] /** Creates a weather service diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index bc622c107a..ed29cbaa90 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -45,6 +45,7 @@ import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext import java.nio.file.Path +import java.time.ZonedDateTime import java.util.UUID import java.util.concurrent.LinkedBlockingQueue import scala.jdk.CollectionConverters.* @@ -173,9 +174,10 @@ class SimonaStandaloneSetup( override def resultServiceProxy( context: ActorContext[_], listeners: Seq[ActorRef[ResultEvent.ResultResponse]], + simStartTime: ZonedDateTime, ): ActorRef[ResultServiceProxy.Message] = context.spawn( - ResultServiceProxy(listeners), + ResultServiceProxy(listeners, simStartTime), "resultEventProxyAgent", ) diff --git a/src/main/scala/edu/ie3/simona/util/CollectionUtils.scala b/src/main/scala/edu/ie3/simona/util/CollectionUtils.scala index 6f737a0d1b..a997f4aaaa 100644 --- a/src/main/scala/edu/ie3/simona/util/CollectionUtils.scala +++ b/src/main/scala/edu/ie3/simona/util/CollectionUtils.scala @@ -11,9 +11,18 @@ 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 [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 From 7de8b79aedc8597574965aaed6e07c1614de9eac Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 25 Aug 2025 10:47:09 +0200 Subject: [PATCH 075/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 30 ++++++++----------- .../simona/service/em/EmServiceBaseCore.scala | 15 ++++------ .../simona/service/em/ExtEmDataService.scala | 8 ++--- .../scala/edu/ie3/simona/sim/SimonaSim.scala | 2 +- .../sim/setup/SimonaStandaloneSetup.scala | 12 +++----- 5 files changed, 27 insertions(+), 40 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 050b61eb2e..1fc701520c 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -8,15 +8,12 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.model.em.{ - EmSetPointResult, - ExtendedFlexOptionsResult, - FlexOptionRequestResult, -} +import edu.ie3.simona.api.data.model.em.{EmSetPointResult, ExtendedFlexOptionsResult, FlexOptionRequestResult} import edu.ie3.simona.api.ontology.em.* 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.MinMaxFlexOptions +import edu.ie3.simona.ontology.messages.flex.{FlexType, PowerLimitFlexOptions} import edu.ie3.simona.service.em.EmCommunicationCore.DataMap import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.TickLong @@ -30,12 +27,7 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.Power -import scala.jdk.CollectionConverters.{ - IterableHasAsScala, - MapHasAsJava, - MapHasAsScala, - SetHasAsJava, -} +import scala.jdk.CollectionConverters.{IterableHasAsScala, MapHasAsJava, MapHasAsScala, SetHasAsJava} import scala.jdk.OptionConverters.RichOption final case class EmCommunicationCore( @@ -107,11 +99,15 @@ final case class EmCommunicationCore( } else { log.info(s"Receive a request for completion for tick '$tick'.") + /* uuidToAgent.foreach { case (_, emAgent) => emAgent ! IssueNoControl(tick) } (this, Some(new EmCompletion(getMaybeNextTick.toJava))) + */ + + (this, None) } case provideFlexRequests: ProvideFlexRequestData => @@ -126,7 +122,7 @@ final case class EmCommunicationCore( val agents = emEntities.map(uuidToAgent) - agents.foreach(_ ! FlexActivation(tick)) + agents.foreach(_ ! FlexActivation(tick, FlexType.PowerLimit)) ( copy( @@ -153,7 +149,7 @@ final case class EmCommunicationCore( flexOptions.asScala.foreach { option => receiver ! ProvideFlexOptions( option.sender, - MinMaxFlexOptions( + PowerLimitFlexOptions( option.pRef.toSquants, option.pMin.toSquants, option.pMax.toSquants, @@ -192,7 +188,7 @@ final case class EmCommunicationCore( ref ! scheduleFlexActivation case Left(uuid) => - uuidToAgent(uuid) ! FlexActivation(INIT_SIM_TICK) + uuidToAgent(uuid) ! FlexActivation(INIT_SIM_TICK, PowerLimit) } } else { log.warn(s"$scheduleFlexActivation not handled!") @@ -226,7 +222,7 @@ final case class EmCommunicationCore( val (updated, updatedAdditional) = provideFlexOptions match { case ProvideFlexOptions( modelUuid, - MinMaxFlexOptions(ref, min, max), + PowerLimitFlexOptions(ref, min, max), ) => val result = new ExtendedFlexOptionsResult( tick.toDateTime(using startTime), @@ -328,7 +324,7 @@ final case class EmCommunicationCore( startTime: ZonedDateTime, log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = flexRequest match { - case flexActivation @ FlexActivation(tick) => + case flexActivation @ FlexActivation(tick, _) => if (tick == INIT_SIM_TICK) { receiver ! flexActivation 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 c54a080766..af1381dd86 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -11,8 +11,9 @@ import edu.ie3.simona.api.data.model.em.ExtendedFlexOptionsResult 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.MinMaxFlexOptions +import edu.ie3.simona.ontology.messages.flex.PowerLimitFlexOptions import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.TickLong @@ -21,11 +22,7 @@ import org.slf4j.Logger import java.time.ZonedDateTime import java.util.UUID -import scala.jdk.CollectionConverters.{ - ListHasAsScala, - MapHasAsJava, - SetHasAsScala, -} +import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, SetHasAsScala} import scala.jdk.OptionConverters.RichOption final case class EmServiceBaseCore( @@ -90,7 +87,7 @@ final case class EmServiceBaseCore( } emEntities.map(uuidToAgent).foreach { ref => - ref ! FlexActivation(tick) + ref ! FlexActivation(tick, PowerLimit) } ( @@ -112,7 +109,7 @@ final case class EmServiceBaseCore( val emEntities = provideEmSetPoints.emSetPoints.keySet.asScala emEntities.map(uuidToAgent).foreach { ref => - ref ! FlexActivation(tick) + ref ! FlexActivation(tick, PowerLimit) } ( @@ -145,7 +142,7 @@ final case class EmServiceBaseCore( val (updated, updatedAdditional) = provideFlexOptions match { case ProvideFlexOptions( modelUuid, - MinMaxFlexOptions(ref, min, max), + PowerLimitFlexOptions(ref, min, max), ) => val result = new ExtendedFlexOptionsResult( tick.toDateTime, 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 a0180ee3df..f9e42425db 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -15,11 +15,9 @@ import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequ import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.ServiceMessage import edu.ie3.simona.ontology.messages.ServiceMessage.* +import edu.ie3.simona.ontology.messages.flex.FlexType.PowerLimit import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* -import edu.ie3.simona.service.ServiceStateData.{ - InitializeServiceStateData, - ServiceBaseStateData, -} +import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} import edu.ie3.simona.service.{ExtDataSupport, SimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.typed.ActorRef @@ -147,7 +145,7 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { serviceStateData.serviceCore.handleRegistration(emServiceRegistration) if (emServiceRegistration.parentEm.isEmpty) { - emServiceRegistration.requestingActor ! FlexActivation(INIT_SIM_TICK) + emServiceRegistration.requestingActor ! FlexActivation(INIT_SIM_TICK, PowerLimit) } Success(serviceStateData.copy(serviceCore = updatedCore)) diff --git a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala index 1ec9aad80f..866013e0c6 100644 --- a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala +++ b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala @@ -77,7 +77,7 @@ object SimonaSim { val resultProxy = simonaSetup.resultServiceProxy( ctx, resultEventListeners, - simonaSetup.simonaConfig.simona.time.startTime, + simonaSetup.simonaConfig.simona.time.simStartTime, ) val timeAdvancer = diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index f5052708d9..aa7a3a4e1f 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -237,7 +237,7 @@ class SimonaStandaloneSetup( context, scheduler, resultProxy, - simonaConfig.simona.time.startTime, + simonaConfig.simona.time.simStartTime, ) } @@ -246,19 +246,15 @@ class SimonaStandaloneSetup( simulation: ActorRef[SimonaSim.SimulationEnded.type], runtimeEventListener: ActorRef[RuntimeEvent], ): ActorRef[TimeAdvancer.Request] = { - val startDateTime = TimeUtil.withDefaults.toZonedDateTime( - simonaConfig.simona.time.startDateTime - ) - val endDateTime = TimeUtil.withDefaults.toZonedDateTime( - simonaConfig.simona.time.endDateTime - ) + val startDateTime = simonaConfig.simona.time.simStartTime + val endDateTime = simonaConfig.simona.time.simEndTime context.spawn( TimeAdvancer( simulation, Some(runtimeEventListener), simonaConfig.simona.time.schedulerReadyCheckWindow, - endDateTime.toTick(startDateTime), + endDateTime.toTick(using startDateTime), ), TimeAdvancer.getClass.getSimpleName, ) From 5c42c0f196d4c07ad0024018ce3b0ac7d6fe0358 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 26 Aug 2025 17:06:34 +0200 Subject: [PATCH 076/125] Saving changes. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 4 - .../flex/MinMaxFixFlexibilityMessage.scala | 2 +- .../service/em/EmCommunicationCore.scala | 123 +++--- .../service/em/EmCommunicationCore2.scala | 415 ++++++++++++++++++ .../simona/service/em/EmServiceBaseCore.scala | 6 +- .../ie3/simona/service/em/EmServiceCore.scala | 16 +- .../simona/service/em/ExtEmDataService.scala | 12 +- .../service/results/ResultServiceProxy.scala | 4 +- .../sim/setup/SimonaStandaloneSetup.scala | 2 +- .../ie3/simona/util/ReceiveMultiDataMap.scala | 99 +++-- 10 files changed, 574 insertions(+), 109 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 2e3390401d..93116dc2b4 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -24,7 +24,6 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration import edu.ie3.simona.ontology.messages.flex.FlexOptions import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.service.Data.PrimaryData.ComplexPower import edu.ie3.simona.service.em.ExtEmDataService @@ -32,9 +31,6 @@ import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.util.quantities.QuantityUtils.* import edu.ie3.util.scala.quantities.DefaultQuantities.* import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} -import edu.ie3.util.quantities.QuantityUtils.* -import edu.ie3.util.scala.quantities.DefaultQuantities.* -import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.{ActorRef, Behavior} import java.time.ZonedDateTime diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala index e208f91206..25c751520c 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala @@ -37,7 +37,7 @@ object MinMaxFixFlexibilityMessage { min: Power, max: Power, fix: Power, - ) extends FlexOptions + ) object MinMaxFixFlexOptions { diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 1fc701520c..5ed5a74b4e 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -8,7 +8,11 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.model.em.{EmSetPointResult, ExtendedFlexOptionsResult, FlexOptionRequestResult} +import edu.ie3.simona.api.data.model.em.{ + EmSetPointResult, + ExtendedFlexOptionsResult, + FlexOptionRequestResult, +} import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration import edu.ie3.simona.ontology.messages.flex.FlexType.PowerLimit @@ -27,18 +31,23 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.Power -import scala.jdk.CollectionConverters.{IterableHasAsScala, MapHasAsJava, MapHasAsScala, SetHasAsJava} +import scala.jdk.CollectionConverters.{ + IterableHasAsScala, + MapHasAsJava, + MapHasAsScala, + SetHasAsJava, +} import scala.jdk.OptionConverters.RichOption final case class EmCommunicationCore( override val lastFinishedTick: Long = PRE_INIT_TICK, override val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, - agentToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, - uuidToFlexResponse: Map[UUID, ActorRef[FlexResponse]] = Map.empty, - flexResponseToUuid: Map[ActorRef[FlexResponse], UUID] = Map.empty, + agentToUuid: Map[ActorRef[FlexResponse] | ActorRef[FlexRequest], UUID] = + Map.empty, + uuidToInferior: Map[UUID, Seq[UUID]] = Map.empty, + activatedAgents: Set[UUID] = Set.empty, override val completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, - flexAdapterToUuid: Map[ActorRef[FlexRequest], UUID] = Map.empty, uuidToPRef: Map[UUID, ComparableQuantity[Power]] = Map.empty, toSchedule: Map[UUID, ScheduleFlexActivation] = Map.empty, flexRequestReceived: DataMap[UUID, Boolean] = @@ -58,23 +67,24 @@ final case class EmCommunicationCore( val parentEm = emServiceRegistration.parentEm val parentUuid = emServiceRegistration.parentUuid - val (updatedResponseToUuid, updatedUuidToResponse) = - parentEm.zip(parentUuid) match { - case Some((parent, uuid)) => - ( - flexResponseToUuid + (parent -> uuid), - uuidToFlexResponse + (uuid -> parent), - ) + val updatedInferior = emServiceRegistration.parentUuid match { + case Some(parent) => + val inferior = uuidToInferior.get(parent) match { + case Some(inferiorUuids) => + inferiorUuids.appended(uuid) + case None => + Seq(uuid) + } - case None => - (flexResponseToUuid, uuidToFlexResponse) - } + uuidToInferior.updated(parent, inferior) + case None => + uuidToInferior + } copy( - uuidToAgent = uuidToAgent + (uuid -> ref), - agentToUuid = agentToUuid + (ref -> uuid), - uuidToFlexResponse = updatedUuidToResponse, - flexResponseToUuid = updatedResponseToUuid, + uuidToAgent = uuidToAgent.updated(uuid, ref), + agentToUuid = agentToUuid.updated(ref, uuid), + uuidToInferior = updatedInferior, flexRequestReceived = flexRequestReceived.updateStructure(parentUuid, uuid), flexOptionResponse = flexOptionResponse.updateStructure(parentUuid, uuid), @@ -90,7 +100,9 @@ final case class EmCommunicationCore( log: Logger ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMSg match { case requestEmCompletion: RequestEmCompletion => - if (requestEmCompletion.tick != tick) { + val extTick = requestEmCompletion.tick + + if (extTick != tick) { log.warn( s"Received completion request for tick '${requestEmCompletion.tick}' in tick '$tick'." ) @@ -99,45 +111,47 @@ final case class EmCommunicationCore( } else { log.info(s"Receive a request for completion for tick '$tick'.") - /* - uuidToAgent.foreach { case (_, emAgent) => - emAgent ! IssueNoControl(tick) - } - - (this, Some(new EmCompletion(getMaybeNextTick.toJava))) - */ + val nextTick: Option[java.lang.Long] = + if activatedAgents.nonEmpty then { + Some(extTick + 1) + } else getMaybeNextTick - (this, None) + ( + copy(lastFinishedTick = tick), + Some(new EmCompletion(nextTick.toJava)), + ) } case provideFlexRequests: ProvideFlexRequestData => - log.warn(s"Received message: $provideFlexRequests") + // TODO: Check tick? - // entities for which flex options are requested - val emEntities: Set[UUID] = provideFlexRequests - .flexRequests() - .asScala - .map { case (agent, _) => agent } - .toSet - - val agents = emEntities.map(uuidToAgent) + log.warn(s"Received message: $provideFlexRequests") - agents.foreach(_ ! FlexActivation(tick, FlexType.PowerLimit)) + val activated = provideFlexRequests.flexRequests.asScala.flatMap { + case (uuid, _) => + uuidToAgent.get(uuid).map { agent => + // TODO: Support more FlexTypes + agent ! FlexActivation(tick, FlexType.PowerLimit) + uuid + } + }.toSet ( copy( + activatedAgents = activatedAgents ++ activated, flexRequestReceived = - flexRequestReceived.addSubKeysToExpectedKeys(emEntities), + flexRequestReceived.addSubKeysToExpectedKeys(activated), flexOptionResponse = - flexOptionResponse.addSubKeysToExpectedKeys(emEntities), + flexOptionResponse.addSubKeysToExpectedKeys(activated), setPointResponse = - setPointResponse.addSubKeysToExpectedKeys(emEntities), - completions = completions.addExpectedKeys(emEntities), + setPointResponse.addSubKeysToExpectedKeys(activated), + completions = completions.addExpectedKeys(activated), ), None, ) case provideFlexOptions: ProvideEmFlexOptionData => + // TODO: Check tick? log.warn(s"Received message: $provideFlexOptions") provideFlexOptions @@ -165,10 +179,19 @@ final case class EmCommunicationCore( (this, None) case providedSetPoints: ProvideEmSetPointData => + // TODO: Check tick? log.warn(s"Received message: $providedSetPoints") handleSetPoint(tick, providedSetPoints, log) + (this, None) + + case _: RequestEmFlexResults => + // should not happen, this should be done by ProvideFlexRequestData + log.warn( + s"Received request for flex results. This is not supported by ${this.getClass}!" + ) + (this, None) } @@ -214,7 +237,7 @@ final case class EmCommunicationCore( val receiverUuid = receiver match { case Right(otherRef) => - flexResponseToUuid(otherRef) + agentToUuid(otherRef) case Left(self: UUID) => self } @@ -222,7 +245,7 @@ final case class EmCommunicationCore( val (updated, updatedAdditional) = provideFlexOptions match { case ProvideFlexOptions( modelUuid, - PowerLimitFlexOptions(ref, min, max), + PowerLimitFlexOptions(ref, min, max), ) => val result = new ExtendedFlexOptionsResult( tick.toDateTime(using startTime), @@ -238,7 +261,8 @@ final case class EmCommunicationCore( additionalFlexOptions.updated(modelUuid, result), ) - case _ => + case other => + log.warn(s"Flex option type '$other' is currently not supported!") (flexOptionResponse, additionalFlexOptions) } @@ -287,9 +311,6 @@ final case class EmCommunicationCore( } } - case _: FlexResult => - (this, None) - case completion: FlexCompletion => receiver.map(_ ! completion) @@ -315,6 +336,10 @@ final case class EmCommunicationCore( } else (copy(completions = updated), None) + case other => + log.warn(s"Message $other is currently not supported.") + (this, None) + } override def handleFlexRequest( diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala new file mode 100644 index 0000000000..fa700a3ce8 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala @@ -0,0 +1,415 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.em + +import edu.ie3.datamodel.models.result.ResultEntity +import edu.ie3.datamodel.models.result.system.FlexOptionsResult +import edu.ie3.datamodel.models.value.PValue +import edu.ie3.simona.agent.em.EmAgent.Message +import edu.ie3.simona.api.data.model.em.{EmSetPointResult, ExtendedFlexOptionsResult, FlexOptionRequestResult} +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.FlexibilityMessage.* +import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} +import edu.ie3.simona.util.{ReceiveDataMap, ReceiveMultiDataMap} +import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} +import edu.ie3.simona.util.TickUtil.* +import edu.ie3.util.scala.quantities.QuantityConversionUtils.* +import org.apache.pekko.actor.typed.ActorRef +import org.slf4j.Logger +import squants.Power + +import java.time.ZonedDateTime +import java.util.UUID +import scala.jdk.CollectionConverters.* +import scala.jdk.OptionConverters.* +import edu.ie3.simona.util.CollectionUtils.asJava + +import scala.util.Try + +case class EmCommunicationCore2( + disaggregated: Boolean = false, + override val lastFinishedTick: Long = PRE_INIT_TICK, + override val uuidToAgent: Map[UUID, ActorRef[Message]] = Map.empty, + refToUuid: Map[ActorRef[FlexResponse] | ActorRef[FlexRequest], UUID] = + Map.empty, + uuidToInferior: Map[UUID, Seq[UUID]] = Map.empty, + uuidToParent: Map[UUID, UUID] = Map.empty, + override val completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, + requestedFlexType: Map[UUID, FlexType] = Map.empty, + allFlexOptions: Map[UUID, FlexOptionsResult] = Map.empty, + currentSetPoint: Map[UUID, Power] = Map.empty, + activatedAgents: Set[UUID] = Set.empty, + expectDataFrom: ReceiveMultiDataMap[UUID, ResultEntity] = ReceiveMultiDataMap.empty, +) extends EmServiceCore { + + override def handleRegistration( + emServiceRegistration: EmServiceRegistration + ): EmServiceCore = { + val uuid = emServiceRegistration.inputUuid + val ref = emServiceRegistration.requestingActor + + val (updatedInferior, updatedUuidToParent) = + emServiceRegistration.parentUuid match { + case Some(parent) => + val inferior = uuidToInferior.get(parent) match { + case Some(inferiorUuids) => + inferiorUuids.appended(uuid) + case None => + Seq(uuid) + } + + ( + uuidToInferior.updated(parent, inferior), + uuidToParent.updated(uuid, parent), + ) + case None => + (uuidToInferior, uuidToParent) + } + + copy( + uuidToAgent = uuidToAgent.updated(uuid, ref), + refToUuid = refToUuid.updated(ref, uuid), + uuidToInferior = updatedInferior, + uuidToParent = updatedUuidToParent, + ) + } + + override def handleExtMessage(tick: Long, extMSg: EmDataMessageFromExt)(using + log: Logger + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMSg match { + 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: Option[java.lang.Long] = + if activatedAgents.nonEmpty then { + Some(extTick + 1) + } else getMaybeNextTick + + ( + copy(lastFinishedTick = tick), + Some(new EmCompletion(nextTick.toJava)), + ) + } + case requestFlexOptions: ProvideFlexRequestData => + // request flex option from agents + val extTick = requestFlexOptions.tick + + if extTick != tick then { + throw new CriticalFailureException( + s"Received request for tick '$extTick', while being in tick '$tick'." + ) + + } else { + val activated = requestFlexOptions.flexRequests.asScala.flatMap { + case (uuid, _) if !activatedAgents.contains(uuid) => + uuidToAgent.get(uuid).map { agent => + agent ! FlexActivation( + tick, + requestedFlexType.getOrElse(uuid, FlexType.PowerLimit), + ) + uuid -> Try(uuidToInferior(uuid).size).getOrElse(0) + } + case _ => + None + }.toMap + + val keys = activated.keySet + + // add activated agents + ( + copy( + activatedAgents = activatedAgents ++ keys, + expectDataFrom = expectDataFrom.addExpectedKeys(activated), + completions = completions.addExpectedKeys(keys), + ), + None, + ) + } + + case provideFlexOptions: ProvideEmFlexOptionData => + // provides flex options for agents + val extTick = provideFlexOptions.tick + + if extTick != tick then { + throw new CriticalFailureException( + s"Received flex option for tick '$extTick', while being in tick '$tick'." + ) + + } else { + val expectMoreData = provideFlexOptions.flexOptions.asScala.map { + case (uuid, options) => + val agent = uuidToAgent(uuid) + + // send flex options to agent + options.asScala.foreach { option => + agent ! ProvideFlexOptions( + option.sender, + PowerLimitFlexOptions( + option.pRef.toSquants, + option.pMin.toSquants, + option.pMax.toSquants, + ), + ) + } + + uuid -> Try(uuidToInferior(uuid).size).getOrElse(0) + }.toMap + + ( + copy(expectDataFrom = expectDataFrom.addExpectedKeys(expectMoreData)), + None, + ) + } + + case provideSetPoints: ProvideEmSetPointData => + // provides set points for agents + val extTick = provideSetPoints.tick + + if extTick != tick then { + throw new CriticalFailureException( + s"Received set points for tick '$extTick', while being in tick '$tick'." + ) + + } else { + val expectMoreData = provideSetPoints.emSetPoints.asScala.map { + case (uuid, setPoint) => + val agent = uuidToAgent(uuid) + + setPoint.power.toScala.flatMap( + _.getP.toScala.map(_.toSquants) + ) match { + case Some(power) => + agent ! IssuePowerControl(extTick, power) + + case None => + agent ! IssueNoControl(extTick) + } + + uuid -> Try(uuidToInferior(uuid).size).getOrElse(0) + }.toMap + + ( + copy(expectDataFrom = expectDataFrom.addExpectedKeys(expectMoreData)), + None, + ) + } + + case _: RequestEmFlexResults => + // should not happen, this should be done by ProvideFlexRequestData + log.warn( + s"Received request for flex results. This is not supported by ${this.getClass}!" + ) + + (this, None) + } + + override def handleFlexResponse( + tick: Long, + flexResponse: FlexResponse, + receiver: Either[UUID, ActorRef[FlexResponse]], + )(using + startTime: ZonedDateTime, + log: Logger, + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { + val receiverUuid = receiver match { + case Left(value) => + value + case Right(ref) => + refToUuid(ref) + } + + flexResponse match { + case scheduleFlexActivation @ ScheduleFlexActivation( + modelUuid, + _, + scheduleKey, + ) => + if (tick == INIT_SIM_TICK) { + scheduleKey.foreach(_.unlock()) + + uuidToAgent(receiverUuid) ! FlexActivation( + INIT_SIM_TICK, + FlexType.PowerLimit, + ) + + } else { + log.warn(s"$scheduleFlexActivation not handled!") + } + + (this, None) + + case provideFlexOptions @ ProvideFlexOptions(sender, flexOptions) => + if tick == INIT_SIM_TICK then { + uuidToAgent(receiverUuid) ! provideFlexOptions + + (this, None) + } else { + // flex option to ext + val resultToExt = flexOptions match { + case PowerLimitFlexOptions(ref, min, max) => + val flexOptionResult = new ExtendedFlexOptionsResult( + tick.toDateTime(using startTime), + sender, + receiverUuid, + ref.toQuantity, + min.toQuantity, + max.toQuantity, + ) + + if disaggregated then { + uuidToInferior(receiverUuid) + .flatMap(allFlexOptions.get) + .foreach { result => + flexOptionResult + .addDisaggregated(result.getInputModel, result) + } + } + + flexOptionResult + + case other => + throw CriticalFailureException( + s"Flex option type '$other' is currently not supported!" + ) + } + + val updated = expectDataFrom.addData(sender, resultToExt) + + if updated.isComplete then { + ( + copy( + allFlexOptions = allFlexOptions.updated(sender, resultToExt), + expectDataFrom = ReceiveMultiDataMap.empty, + ), + Some(new EmResults(updated.receivedData.asJava)), + ) + } else { + ( + copy( + allFlexOptions = allFlexOptions.updated(sender, resultToExt), + expectDataFrom = updated, + ), + None, + ) + } + } + + case completion @ FlexCompletion( + sender, + requestAtNextActivation, + requestAtTick, + ) => + if tick == INIT_SIM_TICK then { + receiver match { + case Left(value) => + (copy(lastFinishedTick = tick), None) + + case Right(ref) => + ref ! completion + (this, None) + } + } else { + val updatedData = completions.addData(sender, completion) + + if updatedData.isComplete then { + ( + copy( + lastFinishedTick = tick, + completions = ReceiveDataMap.empty, + requestedFlexType = Map.empty, + allFlexOptions = Map.empty, + currentSetPoint = Map.empty, + activatedAgents = Set.empty, + expectDataFrom = ReceiveMultiDataMap.empty, + ), + Some(new EmCompletion(getMaybeNextTick.toJava)), + ) + } else { + (copy(completions = updatedData), None) + } + } + + // not supported + case other => + throw new CriticalFailureException( + s"Flex response $other is not supported!" + ) + } + } + + override def handleFlexRequest( + flexRequest: FlexRequest, + receiver: ActorRef[FlexRequest], + )(using + startTime: ZonedDateTime, + log: Logger, + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { + val receiverUuid = refToUuid(receiver) + val sender = uuidToParent(receiverUuid) + + val updated = flexRequest match { + case FlexActivation(tick, flexType) => + // send request to ext + expectDataFrom.addData( + sender, + new FlexOptionRequestResult( + tick.toDateTime(using startTime), + sender, + Seq(receiverUuid).asJava, + ), + ) + + case control: IssueFlexControl => + // send set point to ext + + val (time, power) = control match { + case IssueNoControl(tick) => + (tick.toDateTime, new PValue(null)) + + case IssuePowerControl(tick, setPower) => + (tick.toDateTime, new PValue(setPower.toQuantity)) + + case other => + throw new CriticalFailureException( + s"Flex control $other is not supported!" + ) + } + + expectDataFrom.addData( + sender, + new EmSetPointResult( + time, + sender, + java.util.Map.of(receiverUuid, power), + ), + ) + } + + if updated.isComplete then { + ( + copy(expectDataFrom = ReceiveMultiDataMap.empty), + Some(new EmResults(updated.receivedData.asJava)), + ) + } else { + (copy(expectDataFrom = updated), None) + } + } + +} 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 af1381dd86..d4a6938968 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -22,7 +22,11 @@ import org.slf4j.Logger import java.time.ZonedDateTime import java.util.UUID -import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, SetHasAsScala} +import scala.jdk.CollectionConverters.{ + ListHasAsScala, + MapHasAsJava, + SetHasAsScala, +} import scala.jdk.OptionConverters.RichOption final case class EmServiceBaseCore( 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 4cf94d933e..bb495a3b17 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -16,6 +16,7 @@ import edu.ie3.simona.ontology.messages.ServiceMessage.{ } import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.util.ReceiveDataMap +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.quantities.QuantityUtils.asMegaWatt import edu.ie3.util.scala.quantities.QuantityConversionUtils.PowerConversionSimona import org.apache.pekko.actor.typed.ActorRef @@ -63,7 +64,13 @@ trait EmServiceCore { receiver match { case ref: ActorRef[FlexRequest] => - handleFlexRequest(flexRequest, ref) + if tick == INIT_SIM_TICK then { + ref ! flexRequest + + (this, None) + } else { + handleFlexRequest(flexRequest, ref) + } case _ => // should not happen @@ -79,7 +86,12 @@ trait EmServiceCore { handleFlexResponse(tick, flexResponse, Left(uuid)) case ref: ActorRef[FlexResponse] => - handleFlexResponse(tick, flexResponse, Right(ref)) + if tick == INIT_SIM_TICK then { + ref ! flexResponse + (this, None) + } else { + handleFlexResponse(tick, flexResponse, Right(ref)) + } } } 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 f9e42425db..ee3ff0c645 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -17,7 +17,10 @@ import edu.ie3.simona.ontology.messages.ServiceMessage import edu.ie3.simona.ontology.messages.ServiceMessage.* import edu.ie3.simona.ontology.messages.flex.FlexType.PowerLimit import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* -import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, +} import edu.ie3.simona.service.{ExtDataSupport, SimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.typed.ActorRef @@ -114,7 +117,7 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { case EmMode.BASE => EmServiceBaseCore.empty case EmMode.EM_COMMUNICATION => - EmCommunicationCore.empty + EmCommunicationCore2() } val emDataInitializedStateData = @@ -145,7 +148,10 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { serviceStateData.serviceCore.handleRegistration(emServiceRegistration) if (emServiceRegistration.parentEm.isEmpty) { - emServiceRegistration.requestingActor ! FlexActivation(INIT_SIM_TICK, PowerLimit) + emServiceRegistration.requestingActor ! FlexActivation( + INIT_SIM_TICK, + PowerLimit, + ) } Success(serviceStateData.copy(serviceCore = updatedCore)) diff --git a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala index cf93ca01ef..2120231c1b 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala @@ -141,7 +141,7 @@ object ResultServiceProxy { idle(stateData.waitForResult(expectResult)) case (ctx, resultEvent: ResultEvent) => - ctx.log.warn(s"Received results: $resultEvent") + // ctx.log.warn(s"Received results: $resultEvent") // handles the event and updates the state data val updatedStateData = @@ -154,7 +154,7 @@ object ResultServiceProxy { val tick = requestResultMessage.tick if stateData.isWaiting(requestedResults, tick) then { - ctx.log.warn(s"Cannot answer request: $requestedResults") + // ctx.log.warn(s"Cannot answer request: $requestedResults") buffer.stash(requestResultMessage) } else { diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index aa7a3a4e1f..a76fd5becf 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -247,7 +247,7 @@ class SimonaStandaloneSetup( runtimeEventListener: ActorRef[RuntimeEvent], ): ActorRef[TimeAdvancer.Request] = { val startDateTime = simonaConfig.simona.time.simStartTime - val endDateTime = simonaConfig.simona.time.simEndTime + val endDateTime = simonaConfig.simona.time.simEndTime context.spawn( TimeAdvancer( diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala index 7029457cf0..c5444ef889 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala @@ -1,61 +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.util -/** Map that holds data that an actor is expected to receive over time and - * efficiently determines if all expected data has been received. - * - * @tparam K - * The type of the key - * @tparam V - * The type of the value - */ final case class ReceiveMultiDataMap[K, V]( - private val maxTime: Int, - private val keyToTime: Map[K, Int], - private val values: Map[Int, ReceiveDataMap[K, V]], -) { - def isComplete: Boolean = values(maxTime).isComplete - - def nonComplete: Boolean = values(maxTime).nonComplete + private val expectedKeys: Map[K, Int], + receivedData: Map[K, Seq[V]], + ) { + def isComplete: Boolean = expectedKeys.isEmpty - def addKeys(keys: Set[K]): ReceiveMultiDataMap[K, V] = { - val time = maxTime + 1 + def nonComplete: Boolean = expectedKeys.nonEmpty - copy( - maxTime = time, - keyToTime = keyToTime ++ keys.map(_ -> time), - values = values + (time -> ReceiveDataMap(keys)), - ) + def expects(key: K): Boolean = expectedKeys.contains(key) + + def addData[A]( + key: K, + value: V, + ): ReceiveMultiDataMap[K, V] = { + if (!expectedKeys.contains(key)) { + if !receivedData.contains(key) then { + throw new RuntimeException( + s"Received value $value for key $key, but no data has been expected or received for this key." + ) + } else { + val newValue = receivedData(key).appended(value) + + copy(receivedData = receivedData.updated(key, newValue)) + } + + } else { + val count = expectedKeys(key) - 1 + + if count == 0 then { + copy( + expectedKeys = expectedKeys.removed(key), + receivedData.updated(key, Seq(value)), + ) + } else { + copy( + expectedKeys = expectedKeys.updated(key, count), + receivedData.updated(key, Seq(value)), + ) + } + } } - def addData( - key: K, - value: V, - ): ReceiveMultiDataMap[K, V] = { - val time = keyToTime(key) - val updated = values(time).addData(key, value) - - copy(values = values + (time -> updated)) - } + def addExpectedKeys(keys: Map[K, Int]): ReceiveMultiDataMap[K, V] = + copy(expectedKeys = expectedKeys ++ keys.filter(_._2 > 0)) - def removeLastFinished(): (Map[K, V], ReceiveMultiDataMap[K, V]) = { - val time = maxTime - 1 - val map = values(maxTime).receivedData + def getExpectedKeys: Set[K] = expectedKeys.keySet - (map, copy(maxTime = time, values = values.removed(maxTime))) - } } object ReceiveMultiDataMap { - def apply[K, V](keys: Set[K]): ReceiveMultiDataMap[K, V] = - empty.addKeys(keys) + + def apply[K, V]( + expectedKeys: Map[K, Int] + ): ReceiveMultiDataMap[K, V] = + ReceiveMultiDataMap( + expectedKeys = expectedKeys, + receivedData = Map.empty, + ) def empty[K, V]: ReceiveMultiDataMap[K, V] = - ReceiveMultiDataMap(-1, Map.empty, Map.empty) + ReceiveMultiDataMap( + expectedKeys = Map.empty, + receivedData = Map.empty, + ) -} +} \ No newline at end of file From a95a8086c129cffa32ccc31af76ca84be160b967 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 27 Aug 2025 15:17:25 +0200 Subject: [PATCH 077/125] Saving changes. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 7 +++ .../edu/ie3/simona/agent/em/EmDataCore.scala | 12 +++++ .../agent/participant/ParticipantAgent.scala | 24 +++++---- .../participant/ParticipantModelShell.scala | 2 + .../messages/flex/FlexibilityMessage.scala | 7 +++ .../service/em/EmCommunicationCore2.scala | 50 ++++++++++++++----- .../ie3/simona/util/ReceiveMultiDataMap.scala | 32 +++++++----- .../mutable/PriorityMultiBiSet.scala | 3 ++ 8 files changed, 100 insertions(+), 37 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 93116dc2b4..1b5c227799 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -204,6 +204,13 @@ object EmAgent { case (_, msg: FlexActivation) => activate(emData, modelShell, core, msg.tick) + case (ctx, msg: FlexShiftActivation) => + val tick = msg.tick + ctx.log.info( + s"EmAgent (${modelShell.uuid}) activated by service for tick $tick" + ) + activate(emData, modelShell, core.gotoTick(tick), msg.tick) + case (ctx, msg: IssueFlexControl) => val flexOptionsCore = core.activate(msg.tick) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmDataCore.scala b/src/main/scala/edu/ie3/simona/agent/em/EmDataCore.scala index 9ca54467d7..c486996921 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmDataCore.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmDataCore.scala @@ -81,6 +81,18 @@ object EmDataCore { modelToActor = modelToActor.updated(asset, actor) ) + def gotoTick(newTick: Long): Inactive = { + // remove the activations + activationQueue.headKeyOption.foreach { nextScheduledTick => + if newTick > nextScheduledTick then { + val toActivate = activationQueue.getAndRemoveSet(nextScheduledTick) + activationQueue.set(newTick, toActivate) + } + } + + this + } + /** Tries to handle an activation of the EmAgent for given tick. If the * activation for the tick is not valid, a [[CriticalFailureException]] is * thrown. If successful, an [[AwaitingFlexOptions]] data core is returned diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index 4ceb7faaca..27fe8e6bb3 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -406,14 +406,17 @@ object ParticipantAgent { case FlexActivation(tick, flexType) => val shellWithFlex = - if (isCalculationRequired(shell, inputHandler)) { + if isCalculationRequired( + shell, + inputHandler, + ) || !modelShell.hasFlexOptions + then { val newShell = shell.updateFlexOptions(tick, flexType) resultHandler.maybeSend( newShell.determineFlexOptionsResult(tick, flexType) ) newShell - } else - shell + } else shell parent.fold( _ => @@ -515,13 +518,12 @@ object ParticipantAgent { private def isCalculationRequired( modelShell: ParticipantModelShell[?, ?], inputHandler: ParticipantInputHandler, - ): Boolean = - inputHandler.hasNewData || - inputHandler.activation.exists(activation => - modelShell - .getChangeIndicator(activation.tick - 1, None) - .changesAtTick - .contains(activation.tick) - ) + ): Boolean = inputHandler.hasNewData || + inputHandler.activation.exists(activation => + modelShell + .getChangeIndicator(activation.tick - 1, None) + .changesAtTick + .contains(activation.tick) + ) } diff --git a/src/main/scala/edu/ie3/simona/model/participant/ParticipantModelShell.scala b/src/main/scala/edu/ie3/simona/model/participant/ParticipantModelShell.scala index ecf9af7373..1a5ccef048 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/ParticipantModelShell.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/ParticipantModelShell.scala @@ -115,6 +115,8 @@ final case class ParticipantModelShell[ */ def operationStart: Long = operationInterval.start + def hasFlexOptions: Boolean = flexOptions.isDefined + /** Returns the current flex options, if present, or throws a * [[CriticalFailureException]]. Only call this if you are certain the flex * options have been set. diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala index 62d7321935..fe4a48090f 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala @@ -9,6 +9,7 @@ package edu.ie3.simona.ontology.messages.flex import edu.ie3.datamodel.models.input.AssetInput import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.simona.service.Data.PrimaryData.ComplexPower +import edu.ie3.simona.service.em.{EmServiceCore, ExtEmDataService} import org.apache.pekko.actor.typed.ActorRef import squants.Power @@ -102,6 +103,12 @@ object FlexibilityMessage { FlexActivation(tick, flexType) } + // shifts the activation for controlled asset agent to the given tick + final case class FlexShiftActivation( + override val tick: Long, + flexType: FlexType, + ) extends FlexRequest + /** Message that provides [[FlexOptions]] to an * [[edu.ie3.simona.agent.em.EmAgent]] after they have been requested via * [[FlexActivation]]. diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala index fa700a3ce8..98b876db15 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala @@ -10,15 +10,24 @@ import edu.ie3.datamodel.models.result.ResultEntity import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent.Message -import edu.ie3.simona.api.data.model.em.{EmSetPointResult, ExtendedFlexOptionsResult, FlexOptionRequestResult} +import edu.ie3.simona.api.data.model.em.{ + EmSetPointResult, + ExtendedFlexOptionsResult, + FlexOptionRequestResult, +} 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.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} -import edu.ie3.simona.util.{ReceiveDataMap, ReceiveMultiDataMap} +import edu.ie3.simona.ontology.messages.flex.{ + FlexType, + FlexibilityMessage, + PowerLimitFlexOptions, +} +import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.* +import edu.ie3.simona.util.{ReceiveDataMap, ReceiveMultiDataMap} import edu.ie3.util.scala.quantities.QuantityConversionUtils.* import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger @@ -28,8 +37,6 @@ import java.time.ZonedDateTime import java.util.UUID import scala.jdk.CollectionConverters.* import scala.jdk.OptionConverters.* -import edu.ie3.simona.util.CollectionUtils.asJava - import scala.util.Try case class EmCommunicationCore2( @@ -40,12 +47,14 @@ case class EmCommunicationCore2( Map.empty, uuidToInferior: Map[UUID, Seq[UUID]] = Map.empty, uuidToParent: Map[UUID, UUID] = Map.empty, - override val completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, + override val completions: ReceiveDataMap[UUID, FlexCompletion] = + ReceiveDataMap.empty, requestedFlexType: Map[UUID, FlexType] = Map.empty, allFlexOptions: Map[UUID, FlexOptionsResult] = Map.empty, currentSetPoint: Map[UUID, Power] = Map.empty, activatedAgents: Set[UUID] = Set.empty, - expectDataFrom: ReceiveMultiDataMap[UUID, ResultEntity] = ReceiveMultiDataMap.empty, + expectDataFrom: ReceiveMultiDataMap[UUID, ResultEntity] = + ReceiveMultiDataMap.empty, ) extends EmServiceCore { override def handleRegistration( @@ -120,11 +129,11 @@ case class EmCommunicationCore2( val activated = requestFlexOptions.flexRequests.asScala.flatMap { case (uuid, _) if !activatedAgents.contains(uuid) => uuidToAgent.get(uuid).map { agent => - agent ! FlexActivation( + agent ! FlexShiftActivation( tick, requestedFlexType.getOrElse(uuid, FlexType.PowerLimit), ) - uuid -> Try(uuidToInferior(uuid).size).getOrElse(0) + uuid -> Try(uuidToInferior(uuid).size).getOrElse(1) } case _ => None @@ -132,11 +141,14 @@ case class EmCommunicationCore2( val keys = activated.keySet + val updatedExpectDataFrom = expectDataFrom.addExpectedKeys(activated) + log.warn(s"ExpectDataFrom: $updatedExpectDataFrom, Request: $activated") + // add activated agents ( copy( activatedAgents = activatedAgents ++ keys, - expectDataFrom = expectDataFrom.addExpectedKeys(activated), + expectDataFrom = updatedExpectDataFrom, completions = completions.addExpectedKeys(keys), ), None, @@ -169,11 +181,17 @@ case class EmCommunicationCore2( ) } - uuid -> Try(uuidToInferior(uuid).size).getOrElse(0) + uuid -> 1 }.toMap + val updatedExpectDataFrom = + expectDataFrom.addExpectedKeys(expectMoreData) + log.warn( + s"ExpectDataFrom: $updatedExpectDataFrom, FlexOption: $expectMoreData" + ) + ( - copy(expectDataFrom = expectDataFrom.addExpectedKeys(expectMoreData)), + copy(expectDataFrom = updatedExpectDataFrom), None, ) } @@ -205,8 +223,14 @@ case class EmCommunicationCore2( uuid -> Try(uuidToInferior(uuid).size).getOrElse(0) }.toMap + val updatedExpectDataFrom = + expectDataFrom.addExpectedKeys(expectMoreData) + log.warn( + s"ExpectDataFrom: $updatedExpectDataFrom, SetPoint: $expectMoreData" + ) + ( - copy(expectDataFrom = expectDataFrom.addExpectedKeys(expectMoreData)), + copy(expectDataFrom = updatedExpectDataFrom), None, ) } diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala index c5444ef889..187fdfa2cd 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala @@ -1,19 +1,25 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + package edu.ie3.simona.util final case class ReceiveMultiDataMap[K, V]( - private val expectedKeys: Map[K, Int], - receivedData: Map[K, Seq[V]], - ) { + private val expectedKeys: Map[K, Int], + receivedData: Map[K, Seq[V]], +) { def isComplete: Boolean = expectedKeys.isEmpty def nonComplete: Boolean = expectedKeys.nonEmpty def expects(key: K): Boolean = expectedKeys.contains(key) - + def addData[A]( - key: K, - value: V, - ): ReceiveMultiDataMap[K, V] = { + key: K, + value: V, + ): ReceiveMultiDataMap[K, V] = { if (!expectedKeys.contains(key)) { if !receivedData.contains(key) then { throw new RuntimeException( @@ -21,13 +27,13 @@ final case class ReceiveMultiDataMap[K, V]( ) } else { val newValue = receivedData(key).appended(value) - + copy(receivedData = receivedData.updated(key, newValue)) } - + } else { val count = expectedKeys(key) - 1 - + if count == 0 then { copy( expectedKeys = expectedKeys.removed(key), @@ -52,8 +58,8 @@ final case class ReceiveMultiDataMap[K, V]( object ReceiveMultiDataMap { def apply[K, V]( - expectedKeys: Map[K, Int] - ): ReceiveMultiDataMap[K, V] = + expectedKeys: Map[K, Int] + ): ReceiveMultiDataMap[K, V] = ReceiveMultiDataMap( expectedKeys = expectedKeys, receivedData = Map.empty, @@ -65,4 +71,4 @@ object ReceiveMultiDataMap { receivedData = Map.empty, ) -} \ No newline at end of file +} diff --git a/src/main/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiBiSet.scala b/src/main/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiBiSet.scala index fca587b202..504682d893 100644 --- a/src/main/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiBiSet.scala +++ b/src/main/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiBiSet.scala @@ -83,6 +83,9 @@ final case class PriorityMultiBiSet[K, V]( } } + def set(key: K, values: Set[V]): Unit = + values.foreach(set(key, _)) + /** Removes the given value, if it exists. * * @param value From 9455d8e0120a61ed106634eae9839f5263ab9a87 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 1 Sep 2025 09:39:42 +0200 Subject: [PATCH 078/125] Saving changes. --- .../ie3/simona/service/SimonaService.scala | 2 + .../service/em/EmCommunicationCore2.scala | 139 ++++++++---------- .../simona/service/em/ExtEmDataService.scala | 8 +- 3 files changed, 67 insertions(+), 82 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/SimonaService.scala b/src/main/scala/edu/ie3/simona/service/SimonaService.scala index 2529366f56..6156874571 100644 --- a/src/main/scala/edu/ie3/simona/service/SimonaService.scala +++ b/src/main/scala/edu/ie3/simona/service/SimonaService.scala @@ -192,6 +192,8 @@ abstract class SimonaService { // we need to do an additional activation of this service ctx.self ! Activation(tick) + case Some(nextTick) if nextTick == -1 => + // this indicated that no completion should be sent case _ => scheduler ! Completion( ctx.self, diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala index 98b876db15..017f04d803 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala @@ -53,6 +53,8 @@ case class EmCommunicationCore2( allFlexOptions: Map[UUID, FlexOptionsResult] = Map.empty, currentSetPoint: Map[UUID, Power] = Map.empty, activatedAgents: Set[UUID] = Set.empty, + waitingForFlexOptions: Set[UUID] = Set.empty, + waitingForSetPoint: Set[UUID] = Set.empty, expectDataFrom: ReceiveMultiDataMap[UUID, ResultEntity] = ReceiveMultiDataMap.empty, ) extends EmServiceCore { @@ -108,7 +110,7 @@ case class EmCommunicationCore2( val nextTick: Option[java.lang.Long] = if activatedAgents.nonEmpty then { - Some(extTick + 1) + requestEmCompletion.maybeNextTick.toScala } else getMaybeNextTick ( @@ -116,17 +118,18 @@ case class EmCommunicationCore2( Some(new EmCompletion(nextTick.toJava)), ) } - case requestFlexOptions: ProvideFlexRequestData => - // request flex option from agents - val extTick = requestFlexOptions.tick + + case provideEmData: ProvideEmData => + // provide em data + val extTick = provideEmData.tick if extTick != tick then { throw new CriticalFailureException( s"Received request for tick '$extTick', while being in tick '$tick'." ) - } else { - val activated = requestFlexOptions.flexRequests.asScala.flatMap { + // handle flex requests + val activated = provideEmData.flexRequests.asScala.flatMap { case (uuid, _) if !activatedAgents.contains(uuid) => uuidToAgent.get(uuid).map { agent => agent ! FlexShiftActivation( @@ -139,34 +142,9 @@ case class EmCommunicationCore2( None }.toMap - val keys = activated.keySet - - val updatedExpectDataFrom = expectDataFrom.addExpectedKeys(activated) - log.warn(s"ExpectDataFrom: $updatedExpectDataFrom, Request: $activated") - - // add activated agents - ( - copy( - activatedAgents = activatedAgents ++ keys, - expectDataFrom = updatedExpectDataFrom, - completions = completions.addExpectedKeys(keys), - ), - None, - ) - } - - case provideFlexOptions: ProvideEmFlexOptionData => - // provides flex options for agents - val extTick = provideFlexOptions.tick - - if extTick != tick then { - throw new CriticalFailureException( - s"Received flex option for tick '$extTick', while being in tick '$tick'." - ) - - } else { - val expectMoreData = provideFlexOptions.flexOptions.asScala.map { - case (uuid, options) => + // handle flex options + val expectFlexOptions = provideEmData.flexOptions.asScala.flatMap { + case (uuid, options) if waitingForFlexOptions.contains(uuid) => val agent = uuidToAgent(uuid) // send flex options to agent @@ -181,33 +159,15 @@ case class EmCommunicationCore2( ) } - uuid -> 1 + Some(uuid -> 1) + case _ => None }.toMap - val updatedExpectDataFrom = - expectDataFrom.addExpectedKeys(expectMoreData) - log.warn( - s"ExpectDataFrom: $updatedExpectDataFrom, FlexOption: $expectMoreData" - ) - - ( - copy(expectDataFrom = updatedExpectDataFrom), - None, - ) - } - - case provideSetPoints: ProvideEmSetPointData => - // provides set points for agents - val extTick = provideSetPoints.tick - - if extTick != tick then { - throw new CriticalFailureException( - s"Received set points for tick '$extTick', while being in tick '$tick'." - ) - - } else { - val expectMoreData = provideSetPoints.emSetPoints.asScala.map { - case (uuid, setPoint) => + // handle set points + val expectedSetPoints = provideEmData.setPoints.asScala.flatMap { + case (uuid, setPoint) + if waitingForSetPoint.contains(uuid) | waitingForFlexOptions + .contains(uuid) => val agent = uuidToAgent(uuid) setPoint.power.toScala.flatMap( @@ -220,17 +180,28 @@ case class EmCommunicationCore2( agent ! IssueNoControl(extTick) } - uuid -> Try(uuidToInferior(uuid).size).getOrElse(0) + Some(uuid -> Try(uuidToInferior(uuid).size).getOrElse(0)) + case _ => None }.toMap - val updatedExpectDataFrom = - expectDataFrom.addExpectedKeys(expectMoreData) - log.warn( - s"ExpectDataFrom: $updatedExpectDataFrom, SetPoint: $expectMoreData" - ) + val activatedKeys = activated.keySet + val flexOptionKeys = expectFlexOptions.keys + val setPointKeys = expectedSetPoints.keys + + val updatedExpectDataFrom = expectDataFrom.addExpectedKeys(activated).addExpectedKeys(expectFlexOptions).addExpectedKeys(expectedSetPoints) + log.warn(s"ExpectDataFrom: $updatedExpectDataFrom, Request: $activated, FlexOption: $expectFlexOptions, SetPoint: $expectedSetPoints") + + + // add activated agents ( - copy(expectDataFrom = updatedExpectDataFrom), + copy( + activatedAgents = activatedAgents ++ activatedKeys, + waitingForFlexOptions = waitingForFlexOptions ++ activatedKeys -- flexOptionKeys -- setPointKeys, + waitingForSetPoint = waitingForSetPoint ++ flexOptionKeys -- setPointKeys, + expectDataFrom = updatedExpectDataFrom, + completions = completions.addExpectedKeys(activatedKeys), + ), None, ) } @@ -241,6 +212,11 @@ case class EmCommunicationCore2( s"Received request for flex results. This is not supported by ${this.getClass}!" ) + (this, None) + + case other => + log.warn(s"Deprecated message received! Message: $other") + (this, None) } @@ -286,7 +262,7 @@ case class EmCommunicationCore2( (this, None) } else { // flex option to ext - val resultToExt = flexOptions match { + val (resultToExt, pRef) = flexOptions match { case PowerLimitFlexOptions(ref, min, max) => val flexOptionResult = new ExtendedFlexOptionsResult( tick.toDateTime(using startTime), @@ -306,7 +282,7 @@ case class EmCommunicationCore2( } } - flexOptionResult + (flexOptionResult, ref) case other => throw CriticalFailureException( @@ -320,6 +296,7 @@ case class EmCommunicationCore2( ( copy( allFlexOptions = allFlexOptions.updated(sender, resultToExt), + currentSetPoint = currentSetPoint.updated(sender, pRef), expectDataFrom = ReceiveMultiDataMap.empty, ), Some(new EmResults(updated.receivedData.asJava)), @@ -328,6 +305,7 @@ case class EmCommunicationCore2( ( copy( allFlexOptions = allFlexOptions.updated(sender, resultToExt), + currentSetPoint = currentSetPoint.updated(sender, pRef), expectDataFrom = updated, ), None, @@ -340,13 +318,14 @@ case class EmCommunicationCore2( requestAtNextActivation, requestAtTick, ) => + // the completion can be sent directly to the receiver, since it's not used by the external communication + uuidToAgent(receiverUuid) ! completion + if tick == INIT_SIM_TICK then { receiver match { case Left(value) => (copy(lastFinishedTick = tick), None) - - case Right(ref) => - ref ! completion + case Right(_) => (this, None) } } else { @@ -372,9 +351,9 @@ case class EmCommunicationCore2( // not supported case other => - throw new CriticalFailureException( - s"Flex response $other is not supported!" - ) + log.warn(s"Flex response $other is not supported!") + + (this, None) } } @@ -385,8 +364,8 @@ case class EmCommunicationCore2( startTime: ZonedDateTime, log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { - val receiverUuid = refToUuid(receiver) - val sender = uuidToParent(receiverUuid) + val receiverUuid = refToUuid(receiver) // the controlled em + val sender = uuidToParent(receiverUuid) // the controlling em val updated = flexRequest match { case FlexActivation(tick, flexType) => @@ -402,10 +381,16 @@ case class EmCommunicationCore2( case control: IssueFlexControl => // send set point to ext + log.warn(s"Receiver $receiverUuid got flex request from $sender") val (time, power) = control match { case IssueNoControl(tick) => - (tick.toDateTime, new PValue(null)) + log.warn(s"Set points: $currentSetPoint") + + ( + tick.toDateTime, + new PValue(currentSetPoint(receiverUuid).toQuantity), + ) case IssuePowerControl(tick, setPower) => (tick.toDateTime, new PValue(setPower.toQuantity)) 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 ee3ff0c645..931abea979 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -7,20 +7,17 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.ontology.em.* 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 import edu.ie3.simona.exceptions.{InitializationException, ServiceException} import edu.ie3.simona.ontology.messages.ServiceMessage import edu.ie3.simona.ontology.messages.ServiceMessage.* import edu.ie3.simona.ontology.messages.flex.FlexType.PowerLimit import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* -import edu.ie3.simona.service.ServiceStateData.{ - InitializeServiceStateData, - ServiceBaseStateData, -} +import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} import edu.ie3.simona.service.{ExtDataSupport, SimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.typed.ActorRef @@ -167,6 +164,7 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { serviceStateData: ExtEmDataStateData, ctx: ActorContext[Message], ): (ExtEmDataStateData, Option[Long]) = { + given Logger = ctx.log val stateTick = serviceStateData.tick if (tick != stateTick) { From 90015ddb97702594e780ea9a149a083ba0f163aa Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 1 Sep 2025 09:44:00 +0200 Subject: [PATCH 079/125] Saving changes. --- .../ie3/simona/service/SimonaService.scala | 2 +- .../service/em/EmCommunicationCore2.scala | 21 ++++++++++++------- .../simona/service/em/ExtEmDataService.scala | 5 ++++- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/SimonaService.scala b/src/main/scala/edu/ie3/simona/service/SimonaService.scala index 6156874571..630dd4a0c7 100644 --- a/src/main/scala/edu/ie3/simona/service/SimonaService.scala +++ b/src/main/scala/edu/ie3/simona/service/SimonaService.scala @@ -193,7 +193,7 @@ abstract class SimonaService { ctx.self ! Activation(tick) case Some(nextTick) if nextTick == -1 => - // this indicated that no completion should be sent + // this indicated that no completion should be sent case _ => scheduler ! Completion( ctx.self, diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala index 017f04d803..b74990f23c 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala @@ -166,8 +166,8 @@ case class EmCommunicationCore2( // handle set points val expectedSetPoints = provideEmData.setPoints.asScala.flatMap { case (uuid, setPoint) - if waitingForSetPoint.contains(uuid) | waitingForFlexOptions - .contains(uuid) => + if waitingForSetPoint.contains(uuid) | waitingForFlexOptions + .contains(uuid) => val agent = uuidToAgent(uuid) setPoint.power.toScala.flatMap( @@ -188,17 +188,22 @@ case class EmCommunicationCore2( val flexOptionKeys = expectFlexOptions.keys val setPointKeys = expectedSetPoints.keys - - val updatedExpectDataFrom = expectDataFrom.addExpectedKeys(activated).addExpectedKeys(expectFlexOptions).addExpectedKeys(expectedSetPoints) - log.warn(s"ExpectDataFrom: $updatedExpectDataFrom, Request: $activated, FlexOption: $expectFlexOptions, SetPoint: $expectedSetPoints") - + val updatedExpectDataFrom = expectDataFrom + .addExpectedKeys(activated) + .addExpectedKeys(expectFlexOptions) + .addExpectedKeys(expectedSetPoints) + log.warn( + s"ExpectDataFrom: $updatedExpectDataFrom, Request: $activated, FlexOption: $expectFlexOptions, SetPoint: $expectedSetPoints" + ) // add activated agents ( copy( activatedAgents = activatedAgents ++ activatedKeys, - waitingForFlexOptions = waitingForFlexOptions ++ activatedKeys -- flexOptionKeys -- setPointKeys, - waitingForSetPoint = waitingForSetPoint ++ flexOptionKeys -- setPointKeys, + waitingForFlexOptions = + waitingForFlexOptions ++ activatedKeys -- flexOptionKeys -- setPointKeys, + waitingForSetPoint = + waitingForSetPoint ++ flexOptionKeys -- setPointKeys, expectDataFrom = updatedExpectDataFrom, completions = completions.addExpectedKeys(activatedKeys), ), 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 931abea979..7c53e19181 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -17,7 +17,10 @@ import edu.ie3.simona.ontology.messages.ServiceMessage import edu.ie3.simona.ontology.messages.ServiceMessage.* import edu.ie3.simona.ontology.messages.flex.FlexType.PowerLimit import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* -import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, +} import edu.ie3.simona.service.{ExtDataSupport, SimonaService} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.typed.ActorRef From 3a234e057b7575c2f0e240d761401ed2709597dd Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 1 Sep 2025 13:20:02 +0200 Subject: [PATCH 080/125] fmt --- .../edu/ie3/simona/agent/grid/GridAgent.scala | 2 +- .../agent/participant/ParticipantAgent.scala | 3 +-- .../ParticipantResultHandler.scala | 4 ++-- .../edu/ie3/simona/config/ArgsParser.scala | 4 ++-- .../scala/edu/ie3/simona/main/EmBuilder.scala | 2 +- .../PrimaryDataParticipantModel.scala | 2 +- .../flex/MinMaxFixFlexibilityMessage.scala | 6 ++--- .../service/em/EmCommunicationCore.scala | 22 +++++++++---------- .../service/em/EmCommunicationCore2.scala | 6 ++++- .../simona/service/em/EmServiceBaseCore.scala | 16 +++++++------- .../simona/service/em/ExtEmDataService.scala | 6 ++--- .../primary/ExtPrimaryDataService.scala | 2 +- .../service/primary/PrimaryServiceProxy.scala | 2 +- .../service/results/ExtResultProvider.scala | 8 +++---- .../service/results/ExtResultSchedule.scala | 2 +- .../service/results/ResultServiceProxy.scala | 2 +- .../ie3/simona/sim/setup/ExtSimSetup.scala | 6 ++--- .../sim/setup/SimonaStandaloneSetup.scala | 4 ++-- .../util/ReceiveHierarchicalDataMap.scala | 4 ++-- .../ie3/simona/util/ReceiveMultiDataMap.scala | 2 +- .../service/em/ExtEmCommunicationIT.scala | 4 ++-- .../primary/PrimaryServiceProxySpec.scala | 4 ++-- .../simona/sim/setup/ExtSimSetupSpec.scala | 2 +- 23 files changed, 59 insertions(+), 56 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala index 75aafe0ce2..357598b5e6 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -287,7 +287,7 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { } // check if congestion management is enabled - if (gridAgentBaseData.congestionManagementParams.detectionEnabled) { + if gridAgentBaseData.congestionManagementParams.detectionEnabled then { startCongestionManagement( gridAgentBaseData, currentTick, diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index d107522d1b..0613ed6248 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -473,8 +473,7 @@ object ParticipantAgent { .get (updatedShell, inputHandler.completeActivation(), updatedGridAdapter) - } else - (modelShell, inputHandler, gridAdapter.clearLastResults) + } else (modelShell, inputHandler, gridAdapter.clearLastResults) } /** Checks if all required messages needed for calculation have been received. diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala index 981bc5c350..f3803131e9 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala @@ -44,7 +44,7 @@ final case class ParticipantResultHandler( * The [[SystemParticipantResult]]. */ def maybeSend(result: ResultEntity): Unit = - if (config.simulationResultInfo) { + if config.simulationResultInfo then { result match { case thermalResult: ThermalUnitResult => resultProxy ! ThermalResultEvent(thermalResult) @@ -63,7 +63,7 @@ final case class ParticipantResultHandler( * The [[FlexOptionsResult]]. */ def maybeSend(result: FlexOptionsResult): Unit = - if (config.flexResult) { + if config.flexResult then { resultProxy ! FlexOptionsResultEvent(result) } diff --git a/src/main/scala/edu/ie3/simona/config/ArgsParser.scala b/src/main/scala/edu/ie3/simona/config/ArgsParser.scala index ff8951bbe4..12e5dd200c 100644 --- a/src/main/scala/edu/ie3/simona/config/ArgsParser.scala +++ b/src/main/scala/edu/ie3/simona/config/ArgsParser.scala @@ -8,7 +8,7 @@ package edu.ie3.simona.config import com.typesafe.config.{ConfigFactory, Config as TypesafeConfig} import com.typesafe.scalalogging.LazyLogging -import scopt.{OptionParser as scoptOptionParser} +import scopt.OptionParser as scoptOptionParser import java.io.File import java.nio.file.Paths @@ -43,7 +43,7 @@ object ArgsParser extends LazyLogging { opt[String]("ext-address") .action((value, args) => args.copy(extAddress = Option(value))) .validate(value => - if (value.trim.isEmpty) failure("ext-address cannot be empty") + if value.trim.isEmpty then failure("ext-address cannot be empty") else success ) .text( diff --git a/src/main/scala/edu/ie3/simona/main/EmBuilder.scala b/src/main/scala/edu/ie3/simona/main/EmBuilder.scala index 9eab6f090e..86775f089a 100644 --- a/src/main/scala/edu/ie3/simona/main/EmBuilder.scala +++ b/src/main/scala/edu/ie3/simona/main/EmBuilder.scala @@ -9,7 +9,7 @@ package edu.ie3.simona.main import edu.ie3.datamodel.io.naming.FileNamingStrategy import edu.ie3.datamodel.io.sink.CsvFileSink import edu.ie3.datamodel.io.source.csv.CsvDataSource -import edu.ie3.datamodel.io.source._ +import edu.ie3.datamodel.io.source.* import edu.ie3.datamodel.models.OperationTime import edu.ie3.datamodel.models.input.{EmInput, OperatorInput} diff --git a/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala index 8271854fc3..3e1414d863 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PrimaryDataParticipantModel.scala @@ -129,7 +129,7 @@ final case class PrimaryDataParticipantModel[PD <: PrimaryData: ClassTag]( ): (PrimaryOperatingPoint[PD], OperationChangeIndicator) = { // scale the whole primary data by the same factor that // the active power set point was scaled by - val factor = if (setPower.value != 0.0) { + val factor = if setPower.value != 0.0 then { state.data.p / setPower } else 1.0 diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala index 25c751520c..f3b005f5a8 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFixFlexibilityMessage.scala @@ -7,7 +7,7 @@ package edu.ie3.simona.ontology.messages.flex import edu.ie3.simona.exceptions.CriticalFailureException -import edu.ie3.util.scala.quantities.DefaultQuantities._ +import edu.ie3.util.scala.quantities.DefaultQuantities.* import squants.Power import java.util.UUID @@ -87,12 +87,12 @@ object MinMaxFixFlexibilityMessage { max: Power, fix: Power, ): MinMaxFixFlexOptions = { - if (min > ref) + if min > ref then throw new CriticalFailureException( s"Minimum power $min is greater than reference power $ref" ) - if (ref > max) + if ref > max then throw new CriticalFailureException( s"Reference power $ref is greater than maximum power $max" ) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 5ed5a74b4e..376ae80e5c 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -102,7 +102,7 @@ final case class EmCommunicationCore( case requestEmCompletion: RequestEmCompletion => val extTick = requestEmCompletion.tick - if (extTick != tick) { + if extTick != tick then { log.warn( s"Received completion request for tick '${requestEmCompletion.tick}' in tick '$tick'." ) @@ -204,7 +204,7 @@ final case class EmCommunicationCore( log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = flexResponse match { case scheduleFlexActivation: ScheduleFlexActivation => - if (scheduleFlexActivation.tick == INIT_SIM_TICK) { + if scheduleFlexActivation.tick == INIT_SIM_TICK then { receiver match { case Right(ref) => log.warn(s"$ref: $scheduleFlexActivation") @@ -220,7 +220,7 @@ final case class EmCommunicationCore( (this, None) case provideFlexOptions: ProvideFlexOptions => - if (tick == INIT_SIM_TICK) { + if tick == INIT_SIM_TICK then { receiver match { case Right(otherRef) => otherRef ! provideFlexOptions @@ -266,7 +266,7 @@ final case class EmCommunicationCore( (flexOptionResponse, additionalFlexOptions) } - if (updated.hasCompletedKeys) { + if updated.hasCompletedKeys then { // all responses received, forward them to external simulation in a bundle val (data, updatedFlexOptionResponse) = updated.getFinishedData @@ -279,7 +279,7 @@ final case class EmCommunicationCore( entity -> value } - if (disaggregatedFlex) { + if disaggregatedFlex then { processedData.foreach { case (key, value) => updatedFlexOptionResponse.structure(key).foreach { inferior => value.addDisaggregated(inferior, updatedAdditional(inferior)) @@ -316,10 +316,10 @@ final case class EmCommunicationCore( val updated = completions.addData(completion.modelUuid, completion) - if (updated.isComplete) { + if updated.isComplete then { val allKeys = updated.receivedData.keySet - val extMsgOption = if (tick != INIT_SIM_TICK) { + val extMsgOption = 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.toJava)) } else None @@ -350,7 +350,7 @@ final case class EmCommunicationCore( log: Logger, ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = flexRequest match { case flexActivation @ FlexActivation(tick, _) => - if (tick == INIT_SIM_TICK) { + if tick == INIT_SIM_TICK then { receiver ! flexActivation (this, None) @@ -362,7 +362,7 @@ final case class EmCommunicationCore( true, ) - if (updated.hasCompletedKeys) { + if updated.hasCompletedKeys then { val (dataMap, _, updatedFlexRequest) = updated.getFinishedDataHierarchical @@ -386,7 +386,7 @@ final case class EmCommunicationCore( } case issueFlexControl: IssueFlexControl => - if (issueFlexControl.tick == INIT_SIM_TICK) { + if issueFlexControl.tick == INIT_SIM_TICK then { receiver ! issueFlexControl @@ -405,7 +405,7 @@ final case class EmCommunicationCore( val updated = setPointResponse.addData(uuid, power) - if (updated.hasCompletedKeys) { + if updated.hasCompletedKeys then { val (structureMap, dataMap, updatedSetPointResponse) = updated.getFinishedDataHierarchical diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala index b74990f23c..e29dbee1b6 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala @@ -246,7 +246,7 @@ case class EmCommunicationCore2( _, scheduleKey, ) => - if (tick == INIT_SIM_TICK) { + if tick == INIT_SIM_TICK then { scheduleKey.foreach(_.unlock()) uuidToAgent(receiverUuid) ! FlexActivation( @@ -414,6 +414,10 @@ case class EmCommunicationCore2( java.util.Map.of(receiverUuid, power), ), ) + + case other => + log.warn(s"$other is not supported!") + expectDataFrom } if updated.isComplete then { 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 d4a6938968..977afb3c05 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -86,7 +86,7 @@ final case class EmServiceBaseCore( val emEntities = requestEmFlexResults.emEntities.asScala val disaggregated = requestEmFlexResults.disaggregated - if (disaggregated) { + if disaggregated then { log.warn(s"Disaggregated flex options are currently not supported!") } @@ -104,7 +104,7 @@ final case class EmServiceBaseCore( ) case provideEmSetPoints: ProvideEmSetPointData => - if (canHandleSetPoints) { + if canHandleSetPoints then { handleSetPoint(tick, provideEmSetPoints, log) (this, None) @@ -157,7 +157,7 @@ final case class EmServiceBaseCore( max.toQuantity, ) - if (flexOptions.getExpectedKeys.contains(modelUuid)) { + if flexOptions.getExpectedKeys.contains(modelUuid) then { ( flexOptions.addData(modelUuid, result), additionalFlexOptions, @@ -173,12 +173,12 @@ final case class EmServiceBaseCore( (flexOptions, additionalFlexOptions) } - if (updated.isComplete) { + if updated.isComplete then { // we received all flex options val data = updated.receivedData - if (disaggregatedFlex) { + if disaggregatedFlex then { // we add the disaggregated flex options data.foreach { case (key, value) => @@ -194,7 +194,7 @@ final case class EmServiceBaseCore( canHandleSetPoints = true, ) - if (sendOptionsToExt) { + if sendOptionsToExt then { // we have received an option request, that will now be answered (updatedCore, Some(new FlexOptionsResponse(data.asJava))) @@ -224,10 +224,10 @@ final case class EmServiceBaseCore( case completion: FlexCompletion => val updated = completions.addData(completion.modelUuid, completion) - if (updated.isComplete) { + if updated.isComplete then { val allKeys = updated.receivedData.keySet - val extMsgOption = if (tick != INIT_SIM_TICK) { + val extMsgOption = 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.toJava)) } else None 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 7c53e19181..f697e3de4e 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -147,7 +147,7 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { val updatedCore = serviceStateData.serviceCore.handleRegistration(emServiceRegistration) - if (emServiceRegistration.parentEm.isEmpty) { + if emServiceRegistration.parentEm.isEmpty then { emServiceRegistration.requestingActor ! FlexActivation( INIT_SIM_TICK, PowerLimit, @@ -170,13 +170,13 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { given Logger = ctx.log val stateTick = serviceStateData.tick - if (tick != stateTick) { + if tick != stateTick then { // we received an activation for the next tick // check the last finished tick of the core val lastFinishedTick = serviceStateData.serviceCore.lastFinishedTick - val updatedStateData = if (lastFinishedTick == stateTick) { + val updatedStateData = if lastFinishedTick == stateTick then { // we finished the last tick and update the core with the requested tick serviceStateData.copy(tick = tick) diff --git a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala index 090d2c41d4..8d7e8af703 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala @@ -194,7 +194,7 @@ object ExtPrimaryDataService extends SimonaService with ExtDataSupport { val maybeNextTick = primaryDataMessage.maybeNextTick.toScala.map(Long2long) // Distribute Primary Data - if (actorToPrimaryData.nonEmpty) { + if actorToPrimaryData.nonEmpty then { actorToPrimaryData.foreach { case (actor, value) => value.toPrimaryData match { case Success(primaryData) => diff --git a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala index 123f92e0fc..8c7a50c9b1 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala @@ -246,7 +246,7 @@ object PrimaryServiceProxy { } } .toMap - if (extSimulationData.nonEmpty) { + if extSimulationData.nonEmpty then { val extSubscribersToService = extSimulationData.flatMap { case (connection, ref) => connection.getPrimaryDataAssets.asScala.map(id => id -> ref) diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala index 3a252b451c..570ce34896 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala @@ -123,7 +123,7 @@ object ExtResultProvider extends SimonaService with ExtDataSupport { val updatedStateData = extMsg match { case request: RequestResultEntities => - if (request.tick != tick) { + if request.tick != tick then { ctx.log.warn( s"Received result request for tick '${request.tick}', but current simulation tick is '$tick'!" ) @@ -161,7 +161,7 @@ object ExtResultProvider extends SimonaService with ExtDataSupport { dataMap.addData(key, currentStorage(key)) } - if (updated.isComplete) { + if updated.isComplete then { serviceStateData.extResultDataConnection.queueExtResponseMsg( new ProvideResultEntities( @@ -219,7 +219,7 @@ object ExtResultProvider extends SimonaService with ExtDataSupport { res.getInputModel -> res }.toMap - if (receiveDataMap.getExpectedKeys.isEmpty) { + if receiveDataMap.getExpectedKeys.isEmpty then { // we currently expect no result, // save received result in storage @@ -245,7 +245,7 @@ object ExtResultProvider extends SimonaService with ExtDataSupport { dataMap.addData(model, participantResults(model)) } - if (updated.isComplete) { + if updated.isComplete then { serviceStateData.extResultDataConnection.queueExtResponseMsg( new ProvideResultEntities( diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala index bbed15fdd6..898b60652e 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala @@ -61,7 +61,7 @@ final case class ExtResultSchedule( ): ExtResultSchedule = { val remainingKeys = scheduleMap.get(tick).map(_.diff(keys.toSet)).getOrElse(Set.empty) - if (remainingKeys.isEmpty) { + if remainingKeys.isEmpty then { copy( scheduleMap = scheduleMap.-(tick) ) diff --git a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala index 2120231c1b..c1e92aa588 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala @@ -252,7 +252,7 @@ object ResultServiceProxy { ) // add partial result partialResult.add(result).map { updatedResult => - if (updatedResult.ready) { + if updatedResult.ready then { // if result is complete, we can write it out updatedResult.consolidate match { case Failure(exception) => diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index 674528057e..6b1371decb 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -162,13 +162,13 @@ object ExtSimSetup { extSimSetupData.update(extPrimaryDataConnection, serviceRef) case extEmDataConnection: ExtEmDataConnection => - if (setupData.emDataService.nonEmpty) { + if setupData.emDataService.nonEmpty then { throw ServiceException( s"Trying to connect another EmDataConnection. Currently only one is allowed." ) } - if (extEmDataConnection.getControlledEms.isEmpty) { + if extEmDataConnection.getControlledEms.isEmpty then { log.warn( s"External em connection $extEmDataConnection is not used, because there are no controlled ems present!" ) @@ -189,7 +189,7 @@ object ExtSimSetup { } case extEvDataConnection: ExtEvDataConnection => - if (setupData.evDataService.nonEmpty) { + if setupData.evDataService.nonEmpty then { throw ServiceException( s"Trying to connect another EvDataConnection. Currently only one is allowed." ) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index d0ba033713..99b80098b2 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -171,7 +171,7 @@ class SimonaStandaloneSetup( } override def resultServiceProxy( - context: ActorContext[_], + context: ActorContext[?], listeners: Seq[ActorRef[ResultEvent.ResultResponse]], simStartTime: ZonedDateTime, ): ActorRef[ResultServiceProxy.Message] = @@ -285,7 +285,7 @@ class SimonaStandaloneSetup( ) override def resultEventListener( - context: ActorContext[_] + context: ActorContext[?] ): Seq[ActorRef[ResultEventListener.Message]] = { // append ResultEventListener as well to write raw output files Seq( diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala index 7d338210c2..76ab01c9b1 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveHierarchicalDataMap.scala @@ -68,7 +68,7 @@ final case class ReceiveHierarchicalDataMap[K, V]( value: V, ): ReceiveHierarchicalDataMap[K, V] = { - if (!allKeys.contains(key)) + if !allKeys.contains(key) then throw new RuntimeException( s"Received value $value for key $key, but no data has been expected for this key." ) @@ -82,7 +82,7 @@ final case class ReceiveHierarchicalDataMap[K, V]( def getExpectedKeys: Set[K] = expectedKeys def getFinishedData: (Map[K, V], ReceiveHierarchicalDataMap[K, V]) = { - val dataMap = if (expectedKeys.nonEmpty) { + val dataMap = if expectedKeys.nonEmpty then { structure.keySet .filter(isComplete) .flatMap(key => structure(key)) diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala index 187fdfa2cd..3f7ae62629 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala @@ -20,7 +20,7 @@ final case class ReceiveMultiDataMap[K, V]( key: K, value: V, ): ReceiveMultiDataMap[K, V] = { - if (!expectedKeys.contains(key)) { + if !expectedKeys.contains(key) then { if !receivedData.contains(key) then { throw new RuntimeException( s"Received value $value for key $key, but no data has been expected or received for this key." diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index 20435d0f23..ddf168de9d 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -461,7 +461,7 @@ class ExtEmCommunicationIT .receiverToFlexOptions() .asScala - if (flexOptionResponseInferior.size != 2) { + if flexOptionResponseInferior.size != 2 then { flexOptionResponseInferior.addAll( connection .receiveWithType(classOf[FlexOptionsResponse]) @@ -531,7 +531,7 @@ class ExtEmCommunicationIT .asScala .flatMap(_._2.getReceiverToSetPoint.asScala) - if (inferiorSetPoints.size != 2) { + if inferiorSetPoints.size != 2 then { inferiorSetPoints.addAll( connection .receiveWithType(classOf[EmSetPointDataResponse]) diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala index 6fda2a740c..94ceacd42c 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala @@ -68,7 +68,7 @@ import org.slf4j.{Logger, LoggerFactory} import java.nio.file.{Path, Paths} import java.time.ZonedDateTime import java.util.UUID -import scala.jdk.CollectionConverters._ +import scala.jdk.CollectionConverters.* import scala.language.implicitConversions import scala.util.{Failure, Success} @@ -148,7 +148,7 @@ class PrimaryServiceProxySpec private val extEntityId = UUID.fromString("07bbe1aa-1f39-4dfb-b41b-339dec816ec4") - private val valueMap: Map[UUID, Class[_ <: Value]] = Map( + private val valueMap: Map[UUID, Class[? <: Value]] = Map( extEntityId -> classOf[PValue] ) diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala index 7e7a3c47ba..450f562235 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala @@ -25,7 +25,7 @@ class ExtSimSetupSpec extends UnitSpec { val uuid5 = UUID.fromString("ebcefed4-a3e6-4a2a-b4a5-74226d548546") val uuid6 = UUID.fromString("4a9c8e14-c0ee-425b-af40-9552b9075414") - def toMap(uuids: Set[UUID]): Map[UUID, Class[_ <: Value]] = uuids + def toMap(uuids: Set[UUID]): Map[UUID, Class[? <: Value]] = uuids .map(uuid => uuid -> classOf[PValue]) .toMap From b071c2469cac2ebf186bd75f11d39b23682fd366 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 1 Sep 2025 14:03:30 +0200 Subject: [PATCH 081/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 4 +- .../service/em/EmCommunicationCore2.scala | 4 +- .../service/em/ExtEmCommunicationIT.scala | 86 +++++++------------ 3 files changed, 34 insertions(+), 60 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 376ae80e5c..04a52efe3f 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -377,7 +377,7 @@ final case class EmCommunicationCore( ( copy(flexRequestReceived = updatedFlexRequest), - Some(new FlexRequestResponse(map.asJava)), + None//Some(new FlexRequestResponse(map.asJava)), ) } else { @@ -423,7 +423,7 @@ final case class EmCommunicationCore( ( copy(setPointResponse = updatedSetPointResponse), - Some(new EmSetPointDataResponse(setPointResults.asJava)), + None//Some(new EmSetPointDataResponse(setPointResults.asJava)), ) } else { diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala index e29dbee1b6..163b12414c 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala @@ -304,7 +304,7 @@ case class EmCommunicationCore2( currentSetPoint = currentSetPoint.updated(sender, pRef), expectDataFrom = ReceiveMultiDataMap.empty, ), - Some(new EmResults(updated.receivedData.asJava)), + Some(new EmResultResponse(updated.receivedData.asJava)), ) } else { ( @@ -423,7 +423,7 @@ case class EmCommunicationCore2( if updated.isComplete then { ( copy(expectDataFrom = ReceiveMultiDataMap.empty), - Some(new EmResults(updated.receivedData.asJava)), + Some(new EmResultResponse(updated.receivedData.asJava)), ) } else { (copy(expectDataFrom = updated), None) diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index ddf168de9d..962c14a1eb 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -8,51 +8,27 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.grid.GridAgent -import edu.ie3.simona.agent.participant.ParticipantAgent.{ - DataProvision, - RegistrationFailedMessage, - RegistrationSuccessfulMessage, -} +import edu.ie3.simona.agent.participant.ParticipantAgent.{DataProvision, RegistrationFailedMessage, RegistrationSuccessfulMessage} import edu.ie3.simona.agent.participant.ParticipantAgentInit.ParticipantRefs import edu.ie3.simona.agent.participant.{ParticipantAgent, ParticipantAgentInit} 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, - FlexOptionRequest, - FlexOptionRequestResult, - FlexOptions, -} +import edu.ie3.simona.api.data.model.em.{EmSetPoint, FlexOptionRequest, FlexOptionRequestResult, FlexOptions} import edu.ie3.simona.api.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.api.ontology.em.{ - EmCompletion, - EmSetPointDataResponse, - FlexOptionsResponse, - FlexRequestResponse, -} +import edu.ie3.simona.api.ontology.em.{EmCompletion, EmResultResponse, FlexOptionsResponse} import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt -import edu.ie3.simona.config.RuntimeConfig.{ - LoadRuntimeConfig, - PvRuntimeConfig, - StorageRuntimeConfig, -} +import edu.ie3.simona.config.RuntimeConfig.{LoadRuntimeConfig, PvRuntimeConfig, StorageRuntimeConfig} import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.model.InputModelContainer.SimpleInputContainer -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} -import edu.ie3.simona.ontology.messages.ServiceMessage.{ - Create, - PrimaryServiceRegistrationMessage, - SecondaryServiceRegistrationMessage, -} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.ontology.messages.ServiceMessage.{Create, PrimaryServiceRegistrationMessage, SecondaryServiceRegistrationMessage} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.Data.SecondaryData.WeatherData import edu.ie3.simona.service.ServiceType import edu.ie3.simona.service.em.ExtEmDataService.InitExtEmData import edu.ie3.simona.service.primary.PrimaryServiceProxy +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import edu.ie3.simona.service.weather.WeatherService import edu.ie3.simona.service.weather.WeatherService.Coordinate import edu.ie3.simona.test.common.TestSpawnerTyped @@ -61,10 +37,7 @@ import edu.ie3.simona.test.matchers.QuantityMatchers import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.util.quantities.QuantityUtils.* import edu.ie3.util.scala.quantities.WattsPerSquareMeter -import org.apache.pekko.actor.testkit.typed.scaladsl.{ - ScalaTestWithActorTestKit, - TestProbe, -} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} import org.apache.pekko.actor.typed.ActorRef import org.scalatest.OptionValues.convertOptionToValuable import org.scalatest.wordspec.AnyWordSpecLike @@ -76,11 +49,7 @@ import tech.units.indriya.ComparableQuantity import java.util.{Optional, UUID} import javax.measure.quantity.Power import scala.concurrent.duration.{DurationInt, FiniteDuration} -import scala.jdk.CollectionConverters.{ - MapHasAsJava, - MapHasAsScala, - SeqHasAsJava, -} +import scala.jdk.CollectionConverters.{MapHasAsJava, MapHasAsScala, SeqHasAsJava} import scala.jdk.OptionConverters.RichOptional class ExtEmCommunicationIT @@ -110,7 +79,7 @@ class ExtEmCommunicationIT private val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") private val gridAgent = TestProbe[GridAgent.Message]("GridAgent") - private val resultListener = TestProbe[ResultEvent]("ResultListener") + private val resultListener = TestProbe[ResultEvent | ExpectResult]("ResultListener") private val primaryServiceProxy = TestProbe[PrimaryServiceProxy.Message]("PrimaryServiceProxy") private val weatherService = @@ -119,8 +88,8 @@ class ExtEmCommunicationIT private given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryServiceProxy.ref, + resultServiceProxy = resultListener.ref, services = Map(ServiceType.WeatherService -> weatherService.ref), - resultListener = Iterable(resultListener.ref), ) "An ExtEmDataService in communication mode" should { @@ -137,7 +106,7 @@ class ExtEmCommunicationIT "PROPORTIONAL", simulationStart, parent = Left(scheduler.ref), - listener = Iterable(resultListener.ref), + listener = resultListener.ref, Some(service), ) ) @@ -150,7 +119,7 @@ class ExtEmCommunicationIT "PRIORITIZED", simulationStart, parent = Right(emAgentSup), - listener = Iterable(resultListener.ref), + listener = resultListener.ref, Some(service), ) ) @@ -163,7 +132,7 @@ class ExtEmCommunicationIT "PRIORITIZED", simulationStart, parent = Right(emAgentSup), - listener = Iterable(resultListener.ref), + listener = resultListener.ref, Some(service), ) ) @@ -416,8 +385,8 @@ class ExtEmCommunicationIT // we expect to receive a request per inferior em agent val requestsToInferior = connection - .receiveWithType(classOf[FlexRequestResponse]) - .flexRequests() + .receiveWithType(classOf[EmResultResponse]) + .emResults .asScala requestsToInferior.size shouldBe 1 @@ -526,27 +495,32 @@ class ExtEmCommunicationIT // we expect a new set point for each inferior em agent val inferiorSetPoints = connection - .receiveWithType(classOf[EmSetPointDataResponse]) - .emData() + .receiveWithType(classOf[EmResultResponse]) + .emResults .asScala - .flatMap(_._2.getReceiverToSetPoint.asScala) + .filter { case (uuid, _) => uuid == emNode3Uuid || uuid == emNode4Uuid} if inferiorSetPoints.size != 2 then { inferiorSetPoints.addAll( connection - .receiveWithType(classOf[EmSetPointDataResponse]) - .emData() + .receiveWithType(classOf[EmResultResponse]) + .emResults .asScala - .flatMap(_._2.getReceiverToSetPoint.asScala) + .filter { case (uuid, _) => uuid == emNode3Uuid || uuid == emNode4Uuid} ) } inferiorSetPoints.keySet shouldBe inferiorEms inferiorSetPoints.foreach { case (receiver, results) => - results.getP.toScala.value should equalWithTolerance( - setPoints(receiver) - ) + results.size shouldBe 1 + + results.getFirst match { + case setPoint: EmSetPoint => + setPoint.power.flatMap(_.getP).toScala.value should equalWithTolerance( + setPoints(receiver) + ) + } } def toSetPoint(uuid: UUID): (UUID, EmSetPoint) = From fdaae674797ad5e880622a50ebb2f509fc97ca2b Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 1 Sep 2025 14:48:23 +0200 Subject: [PATCH 082/125] Saving changes. --- .../messages/flex/PowerLimitFlexOptions.scala | 13 +- .../service/em/EmCommunicationCore.scala | 624 +++++++++--------- .../service/em/EmCommunicationCore2.scala | 433 ------------ .../simona/service/em/ExtEmDataService.scala | 2 +- .../service/em/ExtEmCommunicationIT.scala | 61 +- 5 files changed, 371 insertions(+), 762 deletions(-) delete mode 100644 src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/PowerLimitFlexOptions.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/PowerLimitFlexOptions.scala index 5f352488ba..960eb9d9fa 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/PowerLimitFlexOptions.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/PowerLimitFlexOptions.scala @@ -75,10 +75,17 @@ object PowerLimitFlexOptions extends FlexOptionsExtra[PowerLimitFlexOptions] { ): Power = flexCtrl match { case IssuePowerControl(_, setPower) => - // sanity check: setPower is in range of latest flex options - checkSetPower(flexOptions, setPower) - setPower + if setPower < flexOptions.min then { + flexOptions.min + } else if setPower > flexOptions.max then { + flexOptions.max + } else { + // sanity check: setPower is in range of latest flex options + checkSetPower(flexOptions, setPower) + + setPower + } case IssueNoControl(_) => // no override, take reference power diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 04a52efe3f..44310137f2 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -6,114 +6,111 @@ package edu.ie3.simona.service.em +import edu.ie3.datamodel.models.result.ResultEntity +import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue -import edu.ie3.simona.agent.em.EmAgent +import edu.ie3.simona.agent.em.EmAgent.Message import edu.ie3.simona.api.data.model.em.{ EmSetPointResult, ExtendedFlexOptionsResult, FlexOptionRequestResult, } 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.{FlexType, PowerLimitFlexOptions} -import edu.ie3.simona.service.em.EmCommunicationCore.DataMap +import edu.ie3.simona.ontology.messages.flex.{ + FlexType, + FlexibilityMessage, + PowerLimitFlexOptions, +} +import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} -import edu.ie3.simona.util.TickUtil.TickLong -import edu.ie3.simona.util.{ReceiveDataMap, ReceiveHierarchicalDataMap} -import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKW -import edu.ie3.util.scala.quantities.QuantityConversionUtils.PowerConversionSimona +import edu.ie3.simona.util.TickUtil.* +import edu.ie3.simona.util.{ReceiveDataMap, ReceiveMultiDataMap} +import edu.ie3.util.scala.quantities.QuantityConversionUtils.* import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger -import tech.units.indriya.ComparableQuantity +import squants.Power import java.time.ZonedDateTime import java.util.UUID -import javax.measure.quantity.Power -import scala.jdk.CollectionConverters.{ - IterableHasAsScala, - MapHasAsJava, - MapHasAsScala, - SetHasAsJava, -} -import scala.jdk.OptionConverters.RichOption +import scala.jdk.CollectionConverters.* +import scala.jdk.OptionConverters.* +import scala.util.Try -final case class EmCommunicationCore( +case class EmCommunicationCore( + disaggregated: Map[UUID, Boolean] = Map.empty, override val lastFinishedTick: Long = PRE_INIT_TICK, - override val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, - agentToUuid: Map[ActorRef[FlexResponse] | ActorRef[FlexRequest], UUID] = + override val uuidToAgent: Map[UUID, ActorRef[Message]] = Map.empty, + refToUuid: Map[ActorRef[FlexResponse] | ActorRef[FlexRequest], UUID] = Map.empty, uuidToInferior: Map[UUID, Seq[UUID]] = Map.empty, - activatedAgents: Set[UUID] = Set.empty, + uuidToParent: Map[UUID, UUID] = Map.empty, override val completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, - uuidToPRef: Map[UUID, ComparableQuantity[Power]] = Map.empty, - toSchedule: Map[UUID, ScheduleFlexActivation] = Map.empty, - flexRequestReceived: DataMap[UUID, Boolean] = - ReceiveHierarchicalDataMap.empty, - flexOptionResponse: DataMap[UUID, ExtendedFlexOptionsResult] = - ReceiveHierarchicalDataMap.empty, - additionalFlexOptions: Map[UUID, ExtendedFlexOptionsResult] = Map.empty, - setPointResponse: DataMap[UUID, PValue] = ReceiveHierarchicalDataMap.empty, - disaggregatedFlex: Boolean = false, + requestedFlexType: Map[UUID, FlexType] = Map.empty, + allFlexOptions: Map[UUID, FlexOptionsResult] = Map.empty, + currentSetPoint: Map[UUID, Power] = Map.empty, + activatedAgents: Set[UUID] = Set.empty, + waitingForFlexOptions: Set[UUID] = Set.empty, + waitingForSetPoint: Set[UUID] = Set.empty, + expectDataFrom: ReceiveMultiDataMap[UUID, ResultEntity] = + ReceiveMultiDataMap.empty, ) extends EmServiceCore { override def handleRegistration( emServiceRegistration: EmServiceRegistration ): EmServiceCore = { - val ref = emServiceRegistration.requestingActor val uuid = emServiceRegistration.inputUuid - val parentEm = emServiceRegistration.parentEm - val parentUuid = emServiceRegistration.parentUuid - - val updatedInferior = emServiceRegistration.parentUuid match { - case Some(parent) => - val inferior = uuidToInferior.get(parent) match { - case Some(inferiorUuids) => - inferiorUuids.appended(uuid) - case None => - Seq(uuid) - } + val ref = emServiceRegistration.requestingActor - uuidToInferior.updated(parent, inferior) - case None => - uuidToInferior - } + val (updatedInferior, updatedUuidToParent) = + emServiceRegistration.parentUuid match { + case Some(parent) => + val inferior = uuidToInferior.get(parent) match { + case Some(inferiorUuids) => + inferiorUuids.appended(uuid) + case None => + Seq(uuid) + } + + ( + uuidToInferior.updated(parent, inferior), + uuidToParent.updated(uuid, parent), + ) + case None => + (uuidToInferior, uuidToParent) + } copy( uuidToAgent = uuidToAgent.updated(uuid, ref), - agentToUuid = agentToUuid.updated(ref, uuid), + refToUuid = refToUuid.updated(ref, uuid), uuidToInferior = updatedInferior, - flexRequestReceived = - flexRequestReceived.updateStructure(parentUuid, uuid), - flexOptionResponse = flexOptionResponse.updateStructure(parentUuid, uuid), - setPointResponse = setPointResponse.updateStructure(parentUuid, uuid), - completions = completions.addExpectedKeys(Set(uuid)), + uuidToParent = updatedUuidToParent, ) } - override def handleExtMessage( - tick: Long, - extMSg: EmDataMessageFromExt, - )(implicit + override def handleExtMessage(tick: Long, extMSg: EmDataMessageFromExt)(using log: Logger ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMSg match { case requestEmCompletion: RequestEmCompletion => + // finish tick and return next tick val extTick = requestEmCompletion.tick if extTick != tick then { - log.warn( - s"Received completion request for tick '${requestEmCompletion.tick}' in tick '$tick'." + throw new CriticalFailureException( + s"Received completion request for tick '$extTick', while being in tick '$tick'." ) - (this, None) - } else { - log.info(s"Receive a request for completion for tick '$tick'.") + 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: Option[java.lang.Long] = if activatedAgents.nonEmpty then { - Some(extTick + 1) + requestEmCompletion.maybeNextTick.toScala } else getMaybeNextTick ( @@ -122,69 +119,103 @@ final case class EmCommunicationCore( ) } - case provideFlexRequests: ProvideFlexRequestData => - // TODO: Check tick? + case provideEmData: ProvideEmData => + // provide em data + val extTick = provideEmData.tick - log.warn(s"Received message: $provideFlexRequests") - - val activated = provideFlexRequests.flexRequests.asScala.flatMap { - case (uuid, _) => - uuidToAgent.get(uuid).map { agent => - // TODO: Support more FlexTypes - agent ! FlexActivation(tick, FlexType.PowerLimit) - uuid - } - }.toSet + if extTick != tick then { + throw new CriticalFailureException( + s"Received request for tick '$extTick', while being in tick '$tick'." + ) + } else { + // handle flex requests + val requests = provideEmData.flexRequests.asScala + val activated = requests.flatMap { + case (uuid, _) if !activatedAgents.contains(uuid) => + uuidToAgent.get(uuid).map { agent => + agent ! FlexShiftActivation( + tick, + requestedFlexType.getOrElse(uuid, FlexType.PowerLimit), + ) + uuid -> Try(uuidToInferior(uuid).size).getOrElse(1) + } + case _ => + None + }.toMap - ( - copy( - activatedAgents = activatedAgents ++ activated, - flexRequestReceived = - flexRequestReceived.addSubKeysToExpectedKeys(activated), - flexOptionResponse = - flexOptionResponse.addSubKeysToExpectedKeys(activated), - setPointResponse = - setPointResponse.addSubKeysToExpectedKeys(activated), - completions = completions.addExpectedKeys(activated), - ), - None, - ) + val updatedDisaggregated = disaggregated ++ requests.map { + case (uuid, request) => uuid -> request.disaggregated + } - case provideFlexOptions: ProvideEmFlexOptionData => - // TODO: Check tick? - log.warn(s"Received message: $provideFlexOptions") - - provideFlexOptions - .flexOptions() - .asScala - .foreach { case (agent, flexOptions) => - uuidToAgent.get(agent) match { - case Some(receiver) => - flexOptions.asScala.foreach { option => - receiver ! ProvideFlexOptions( - option.sender, - PowerLimitFlexOptions( - option.pRef.toSquants, - option.pMin.toSquants, - option.pMax.toSquants, - ), - ) - } + // handle flex options + val expectFlexOptions = provideEmData.flexOptions.asScala.flatMap { + case (uuid, options) if waitingForFlexOptions.contains(uuid) => + val agent = uuidToAgent(uuid) + + // send flex options to agent + options.asScala.foreach { option => + agent ! ProvideFlexOptions( + option.sender, + PowerLimitFlexOptions( + option.pRef.toSquants, + option.pMin.toSquants, + option.pMax.toSquants, + ), + ) + } - case None => - log.warn(s"No em agent with uuid '$agent' registered!") - } - } + Some(uuid -> 1) + case _ => None + }.toMap + + // handle set points + val expectedSetPoints = provideEmData.setPoints.asScala.flatMap { + case (uuid, setPoint) + if waitingForSetPoint.contains(uuid) | waitingForFlexOptions + .contains(uuid) => + val agent = uuidToAgent(uuid) + + setPoint.power.toScala.flatMap( + _.getP.toScala.map(_.toSquants) + ) match { + case Some(power) => + agent ! IssuePowerControl(extTick, power) + + case None => + agent ! IssueNoControl(extTick) + } - (this, None) + Some(uuid -> Try(uuidToInferior(uuid).size).getOrElse(0)) + case _ => None + }.toMap - case providedSetPoints: ProvideEmSetPointData => - // TODO: Check tick? - log.warn(s"Received message: $providedSetPoints") + val activatedKeys = activated.keySet + val flexOptionKeys = expectFlexOptions.keys + val setPointKeys = expectedSetPoints.keys - handleSetPoint(tick, providedSetPoints, log) + val updatedExpectDataFrom = expectDataFrom + .addExpectedKeys(activated) + .addExpectedKeys(expectFlexOptions) + .addExpectedKeys(expectedSetPoints) + log.warn( + s"ExpectDataFrom: $updatedExpectDataFrom, Request: $activated, FlexOption: $expectFlexOptions, SetPoint: $expectedSetPoints" + ) - (this, None) + // update state data + ( + copy( + disaggregated = updatedDisaggregated, + activatedAgents = activatedAgents ++ activatedKeys, + waitingForFlexOptions = + waitingForFlexOptions ++ activatedKeys -- flexOptionKeys -- setPointKeys, + waitingForSetPoint = + waitingForSetPoint ++ flexOptionKeys -- setPointKeys, + expectDataFrom = updatedExpectDataFrom, + completions = completions.addExpectedKeys(activatedKeys), + ), + None, + ) + } case _: RequestEmFlexResults => // should not happen, this should be done by ProvideFlexRequestData @@ -192,6 +223,11 @@ final case class EmCommunicationCore( s"Received request for flex results. This is not supported by ${this.getClass}!" ) + (this, None) + + case other => + log.warn(s"Deprecated message received! Message: $other") + (this, None) } @@ -202,144 +238,134 @@ final case class EmCommunicationCore( )(using startTime: ZonedDateTime, log: Logger, - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = flexResponse match { - case scheduleFlexActivation: ScheduleFlexActivation => - if scheduleFlexActivation.tick == INIT_SIM_TICK then { - receiver match { - case Right(ref) => - log.warn(s"$ref: $scheduleFlexActivation") - ref ! scheduleFlexActivation - - case Left(uuid) => - uuidToAgent(uuid) ! FlexActivation(INIT_SIM_TICK, PowerLimit) - } - } else { - log.warn(s"$scheduleFlexActivation not handled!") - } - - (this, None) + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { + val receiverUuid = receiver match { + case Left(value) => + value + case Right(ref) => + refToUuid(ref) + } - case provideFlexOptions: ProvideFlexOptions => - if tick == INIT_SIM_TICK then { - receiver match { - case Right(otherRef) => - otherRef ! provideFlexOptions + flexResponse match { + case scheduleFlexActivation @ ScheduleFlexActivation( + modelUuid, + _, + scheduleKey, + ) => + if tick == INIT_SIM_TICK then { + scheduleKey.foreach(_.unlock()) + + uuidToAgent(receiverUuid) ! FlexActivation( + INIT_SIM_TICK, + FlexType.PowerLimit, + ) - case Left(self: UUID) => - uuidToAgent(self) ! IssuePowerControl( - INIT_SIM_TICK, - zeroKW, - ) + } else { + log.warn(s"$scheduleFlexActivation not handled!") } (this, None) - } else { - val receiverUuid = receiver match { - case Right(otherRef) => - agentToUuid(otherRef) - case Left(self: UUID) => - self - } - - val (updated, updatedAdditional) = provideFlexOptions match { - case ProvideFlexOptions( - modelUuid, - PowerLimitFlexOptions(ref, min, max), - ) => - val result = new ExtendedFlexOptionsResult( - tick.toDateTime(using startTime), - modelUuid, - receiverUuid, - ref.toQuantity, - min.toQuantity, - max.toQuantity, - ) + case provideFlexOptions @ ProvideFlexOptions(sender, flexOptions) => + if tick == INIT_SIM_TICK then { + uuidToAgent(receiverUuid) ! provideFlexOptions - ( - flexOptionResponse.addData(modelUuid, result), - additionalFlexOptions.updated(modelUuid, result), - ) + (this, None) + } else { + // flex option to ext + val (resultToExt, pRef) = flexOptions match { + case PowerLimitFlexOptions(ref, min, max) => + val flexOptionResult = new ExtendedFlexOptionsResult( + tick.toDateTime(using startTime), + sender, + receiverUuid, + ref.toQuantity, + min.toQuantity, + max.toQuantity, + ) + + if disaggregated.contains(receiverUuid) then { + uuidToInferior(receiverUuid) + .flatMap(allFlexOptions.get) + .foreach { result => + flexOptionResult + .addDisaggregated(result.getInputModel, result) + } + } - case other => - log.warn(s"Flex option type '$other' is currently not supported!") - (flexOptionResponse, additionalFlexOptions) - } + (flexOptionResult, ref) - if updated.hasCompletedKeys then { - // all responses received, forward them to external simulation in a bundle + case other => + throw CriticalFailureException( + s"Flex option type '$other' is currently not supported!" + ) + } - val (data, updatedFlexOptionResponse) = updated.getFinishedData + val updated = expectDataFrom.addData(sender, resultToExt) - val pRefs = data.map { case (uuid, options) => - uuid -> options.getpRef + if updated.isComplete then { + ( + copy( + allFlexOptions = allFlexOptions.updated(sender, resultToExt), + currentSetPoint = currentSetPoint.updated(sender, pRef), + expectDataFrom = ReceiveMultiDataMap.empty, + ), + Some(new EmResultResponse(updated.receivedData.asJava)), + ) + } else { + ( + copy( + allFlexOptions = allFlexOptions.updated(sender, resultToExt), + currentSetPoint = currentSetPoint.updated(sender, pRef), + expectDataFrom = updated, + ), + None, + ) } + } - val processedData = data.map { case (entity, value) => - entity -> value + case completion @ FlexCompletion( + sender, + requestAtNextActivation, + requestAtTick, + ) => + // the completion can be sent directly to the receiver, since it's not used by the external communication + uuidToAgent(receiverUuid) ! completion + + if tick == INIT_SIM_TICK then { + receiver match { + case Left(value) => + (copy(lastFinishedTick = tick), None) + case Right(_) => + (this, None) } + } else { + val updatedData = completions.addData(sender, completion) - if disaggregatedFlex then { - processedData.foreach { case (key, value) => - updatedFlexOptionResponse.structure(key).foreach { inferior => - value.addDisaggregated(inferior, updatedAdditional(inferior)) - } - - } + if updatedData.isComplete then { + ( + copy( + lastFinishedTick = tick, + completions = ReceiveDataMap.empty, + requestedFlexType = Map.empty, + allFlexOptions = Map.empty, + currentSetPoint = Map.empty, + activatedAgents = Set.empty, + expectDataFrom = ReceiveMultiDataMap.empty, + ), + Some(new EmCompletion(getMaybeNextTick.toJava)), + ) + } else { + (copy(completions = updatedData), None) } - - val msgToExt = new FlexOptionsResponse(processedData.asJava) - - ( - copy( - flexOptionResponse = updatedFlexOptionResponse, - additionalFlexOptions = updatedAdditional, - uuidToPRef = uuidToPRef ++ pRefs, - ), - Some(msgToExt), - ) - - } else { - // responses are still incomplete - ( - copy( - flexOptionResponse = updated, - additionalFlexOptions = updatedAdditional, - ), - None, - ) } - } - - case completion: FlexCompletion => - receiver.map(_ ! completion) - val updated = completions.addData(completion.modelUuid, completion) - - if updated.isComplete then { - val allKeys = updated.receivedData.keySet - - val extMsgOption = 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.toJava)) - } else None - - // every em agent has sent a completion message - ( - copy( - lastFinishedTick = tick, - additionalFlexOptions = Map.empty, - completions = ReceiveDataMap(allKeys), - ), - extMsgOption, - ) - - } else (copy(completions = updated), None) - - case other => - log.warn(s"Message $other is currently not supported.") - (this, None) + // not supported + case other => + log.warn(s"Flex response $other is not supported!") + (this, None) + } } override def handleFlexRequest( @@ -348,95 +374,67 @@ final case class EmCommunicationCore( )(using startTime: ZonedDateTime, log: Logger, - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = flexRequest match { - case flexActivation @ FlexActivation(tick, _) => - if tick == INIT_SIM_TICK then { - receiver ! flexActivation - - (this, None) - } else { - val uuid = agentToUuid(receiver) - - val updated = flexRequestReceived.addData( - uuid, - true, + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { + val receiverUuid = refToUuid(receiver) // the controlled em + val sender = uuidToParent(receiverUuid) // the controlling em + + val updated = flexRequest match { + case FlexActivation(tick, flexType) => + // send request to ext + expectDataFrom.addData( + sender, + new FlexOptionRequestResult( + tick.toDateTime(using startTime), + sender, + Seq(receiverUuid).asJava, + disaggregated.getOrElse(sender, false), + ), ) - if updated.hasCompletedKeys then { + case control: IssueFlexControl => + // send set point to ext + log.warn(s"Receiver $receiverUuid got flex request from $sender") - val (dataMap, _, updatedFlexRequest) = - updated.getFinishedDataHierarchical + val (time, power) = control match { + case IssueNoControl(tick) => + log.warn(s"Set points: $currentSetPoint") - val map = dataMap.map { case (sender, receivers) => - sender -> new FlexOptionRequestResult( - flexActivation.tick.toDateTime, - sender, - receivers.asJava, + ( + tick.toDateTime, + new PValue(currentSetPoint(receiverUuid).toQuantity), ) - } - - ( - copy(flexRequestReceived = updatedFlexRequest), - None//Some(new FlexRequestResponse(map.asJava)), - ) - - } else { - (copy(flexRequestReceived = updated), None) - } - } - - case issueFlexControl: IssueFlexControl => - if issueFlexControl.tick == INIT_SIM_TICK then { - - receiver ! issueFlexControl - - (this, None) - - } else { - val uuid = agentToUuid(receiver) - - val (time, power) = issueFlexControl match { - case IssueNoControl(tick) => - (tick.toDateTime, new PValue(uuidToPRef(uuid))) case IssuePowerControl(tick, setPower) => (tick.toDateTime, new PValue(setPower.toQuantity)) - } - - val updated = setPointResponse.addData(uuid, power) - - if updated.hasCompletedKeys then { - - val (structureMap, dataMap, updatedSetPointResponse) = - updated.getFinishedDataHierarchical - val setPointResults = structureMap.map { case (sender, receivers) => - sender -> new EmSetPointResult( - time, - sender, - receivers - .map(receiver => receiver -> dataMap(receiver)) - .toMap - .asJava, + case other => + throw new CriticalFailureException( + s"Flex control $other is not supported!" ) - } - - ( - copy(setPointResponse = updatedSetPointResponse), - None//Some(new EmSetPointDataResponse(setPointResults.asJava)), - ) - - } else { - // responses are still incomplete - (copy(setPointResponse = updated), None) } - } - } -} -object EmCommunicationCore { + expectDataFrom.addData( + sender, + new EmSetPointResult( + time, + sender, + java.util.Map.of(receiverUuid, power), + ), + ) + + case other => + log.warn(s"$other is not supported!") + expectDataFrom + } - type DataMap[K, V] = ReceiveHierarchicalDataMap[K, V] + if updated.isComplete then { + ( + copy(expectDataFrom = ReceiveMultiDataMap.empty), + Some(new EmResultResponse(updated.receivedData.asJava)), + ) + } else { + (copy(expectDataFrom = updated), None) + } + } - def empty: EmCommunicationCore = EmCommunicationCore() } diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala deleted file mode 100644 index 163b12414c..0000000000 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore2.scala +++ /dev/null @@ -1,433 +0,0 @@ -/* - * © 2025. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.service.em - -import edu.ie3.datamodel.models.result.ResultEntity -import edu.ie3.datamodel.models.result.system.FlexOptionsResult -import edu.ie3.datamodel.models.value.PValue -import edu.ie3.simona.agent.em.EmAgent.Message -import edu.ie3.simona.api.data.model.em.{ - EmSetPointResult, - ExtendedFlexOptionsResult, - FlexOptionRequestResult, -} -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.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.flex.{ - FlexType, - FlexibilityMessage, - PowerLimitFlexOptions, -} -import edu.ie3.simona.util.CollectionUtils.asJava -import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} -import edu.ie3.simona.util.TickUtil.* -import edu.ie3.simona.util.{ReceiveDataMap, ReceiveMultiDataMap} -import edu.ie3.util.scala.quantities.QuantityConversionUtils.* -import org.apache.pekko.actor.typed.ActorRef -import org.slf4j.Logger -import squants.Power - -import java.time.ZonedDateTime -import java.util.UUID -import scala.jdk.CollectionConverters.* -import scala.jdk.OptionConverters.* -import scala.util.Try - -case class EmCommunicationCore2( - disaggregated: Boolean = false, - override val lastFinishedTick: Long = PRE_INIT_TICK, - override val uuidToAgent: Map[UUID, ActorRef[Message]] = Map.empty, - refToUuid: Map[ActorRef[FlexResponse] | ActorRef[FlexRequest], UUID] = - Map.empty, - uuidToInferior: Map[UUID, Seq[UUID]] = Map.empty, - uuidToParent: Map[UUID, UUID] = Map.empty, - override val completions: ReceiveDataMap[UUID, FlexCompletion] = - ReceiveDataMap.empty, - requestedFlexType: Map[UUID, FlexType] = Map.empty, - allFlexOptions: Map[UUID, FlexOptionsResult] = Map.empty, - currentSetPoint: Map[UUID, Power] = Map.empty, - activatedAgents: Set[UUID] = Set.empty, - waitingForFlexOptions: Set[UUID] = Set.empty, - waitingForSetPoint: Set[UUID] = Set.empty, - expectDataFrom: ReceiveMultiDataMap[UUID, ResultEntity] = - ReceiveMultiDataMap.empty, -) extends EmServiceCore { - - override def handleRegistration( - emServiceRegistration: EmServiceRegistration - ): EmServiceCore = { - val uuid = emServiceRegistration.inputUuid - val ref = emServiceRegistration.requestingActor - - val (updatedInferior, updatedUuidToParent) = - emServiceRegistration.parentUuid match { - case Some(parent) => - val inferior = uuidToInferior.get(parent) match { - case Some(inferiorUuids) => - inferiorUuids.appended(uuid) - case None => - Seq(uuid) - } - - ( - uuidToInferior.updated(parent, inferior), - uuidToParent.updated(uuid, parent), - ) - case None => - (uuidToInferior, uuidToParent) - } - - copy( - uuidToAgent = uuidToAgent.updated(uuid, ref), - refToUuid = refToUuid.updated(ref, uuid), - uuidToInferior = updatedInferior, - uuidToParent = updatedUuidToParent, - ) - } - - override def handleExtMessage(tick: Long, extMSg: EmDataMessageFromExt)(using - log: Logger - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMSg match { - 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: Option[java.lang.Long] = - if activatedAgents.nonEmpty then { - requestEmCompletion.maybeNextTick.toScala - } else getMaybeNextTick - - ( - copy(lastFinishedTick = tick), - Some(new EmCompletion(nextTick.toJava)), - ) - } - - case provideEmData: ProvideEmData => - // provide em data - val extTick = provideEmData.tick - - if extTick != tick then { - throw new CriticalFailureException( - s"Received request for tick '$extTick', while being in tick '$tick'." - ) - } else { - // handle flex requests - val activated = provideEmData.flexRequests.asScala.flatMap { - case (uuid, _) if !activatedAgents.contains(uuid) => - uuidToAgent.get(uuid).map { agent => - agent ! FlexShiftActivation( - tick, - requestedFlexType.getOrElse(uuid, FlexType.PowerLimit), - ) - uuid -> Try(uuidToInferior(uuid).size).getOrElse(1) - } - case _ => - None - }.toMap - - // handle flex options - val expectFlexOptions = provideEmData.flexOptions.asScala.flatMap { - case (uuid, options) if waitingForFlexOptions.contains(uuid) => - val agent = uuidToAgent(uuid) - - // send flex options to agent - options.asScala.foreach { option => - agent ! ProvideFlexOptions( - option.sender, - PowerLimitFlexOptions( - option.pRef.toSquants, - option.pMin.toSquants, - option.pMax.toSquants, - ), - ) - } - - Some(uuid -> 1) - case _ => None - }.toMap - - // handle set points - val expectedSetPoints = provideEmData.setPoints.asScala.flatMap { - case (uuid, setPoint) - if waitingForSetPoint.contains(uuid) | waitingForFlexOptions - .contains(uuid) => - val agent = uuidToAgent(uuid) - - setPoint.power.toScala.flatMap( - _.getP.toScala.map(_.toSquants) - ) match { - case Some(power) => - agent ! IssuePowerControl(extTick, power) - - case None => - agent ! IssueNoControl(extTick) - } - - Some(uuid -> Try(uuidToInferior(uuid).size).getOrElse(0)) - case _ => None - }.toMap - - val activatedKeys = activated.keySet - val flexOptionKeys = expectFlexOptions.keys - val setPointKeys = expectedSetPoints.keys - - val updatedExpectDataFrom = expectDataFrom - .addExpectedKeys(activated) - .addExpectedKeys(expectFlexOptions) - .addExpectedKeys(expectedSetPoints) - log.warn( - s"ExpectDataFrom: $updatedExpectDataFrom, Request: $activated, FlexOption: $expectFlexOptions, SetPoint: $expectedSetPoints" - ) - - // add activated agents - ( - copy( - activatedAgents = activatedAgents ++ activatedKeys, - waitingForFlexOptions = - waitingForFlexOptions ++ activatedKeys -- flexOptionKeys -- setPointKeys, - waitingForSetPoint = - waitingForSetPoint ++ flexOptionKeys -- setPointKeys, - expectDataFrom = updatedExpectDataFrom, - completions = completions.addExpectedKeys(activatedKeys), - ), - None, - ) - } - - case _: RequestEmFlexResults => - // should not happen, this should be done by ProvideFlexRequestData - log.warn( - s"Received request for flex results. This is not supported by ${this.getClass}!" - ) - - (this, None) - - case other => - log.warn(s"Deprecated message received! Message: $other") - - (this, None) - } - - override def handleFlexResponse( - tick: Long, - flexResponse: FlexResponse, - receiver: Either[UUID, ActorRef[FlexResponse]], - )(using - startTime: ZonedDateTime, - log: Logger, - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { - val receiverUuid = receiver match { - case Left(value) => - value - case Right(ref) => - refToUuid(ref) - } - - flexResponse match { - case scheduleFlexActivation @ ScheduleFlexActivation( - modelUuid, - _, - scheduleKey, - ) => - if tick == INIT_SIM_TICK then { - scheduleKey.foreach(_.unlock()) - - uuidToAgent(receiverUuid) ! FlexActivation( - INIT_SIM_TICK, - FlexType.PowerLimit, - ) - - } else { - log.warn(s"$scheduleFlexActivation not handled!") - } - - (this, None) - - case provideFlexOptions @ ProvideFlexOptions(sender, flexOptions) => - if tick == INIT_SIM_TICK then { - uuidToAgent(receiverUuid) ! provideFlexOptions - - (this, None) - } else { - // flex option to ext - val (resultToExt, pRef) = flexOptions match { - case PowerLimitFlexOptions(ref, min, max) => - val flexOptionResult = new ExtendedFlexOptionsResult( - tick.toDateTime(using startTime), - sender, - receiverUuid, - ref.toQuantity, - min.toQuantity, - max.toQuantity, - ) - - if disaggregated then { - uuidToInferior(receiverUuid) - .flatMap(allFlexOptions.get) - .foreach { result => - flexOptionResult - .addDisaggregated(result.getInputModel, result) - } - } - - (flexOptionResult, ref) - - case other => - throw CriticalFailureException( - s"Flex option type '$other' is currently not supported!" - ) - } - - val updated = expectDataFrom.addData(sender, resultToExt) - - if updated.isComplete then { - ( - copy( - allFlexOptions = allFlexOptions.updated(sender, resultToExt), - currentSetPoint = currentSetPoint.updated(sender, pRef), - expectDataFrom = ReceiveMultiDataMap.empty, - ), - Some(new EmResultResponse(updated.receivedData.asJava)), - ) - } else { - ( - copy( - allFlexOptions = allFlexOptions.updated(sender, resultToExt), - currentSetPoint = currentSetPoint.updated(sender, pRef), - expectDataFrom = updated, - ), - None, - ) - } - } - - case completion @ FlexCompletion( - sender, - requestAtNextActivation, - requestAtTick, - ) => - // the completion can be sent directly to the receiver, since it's not used by the external communication - uuidToAgent(receiverUuid) ! completion - - if tick == INIT_SIM_TICK then { - receiver match { - case Left(value) => - (copy(lastFinishedTick = tick), None) - case Right(_) => - (this, None) - } - } else { - val updatedData = completions.addData(sender, completion) - - if updatedData.isComplete then { - ( - copy( - lastFinishedTick = tick, - completions = ReceiveDataMap.empty, - requestedFlexType = Map.empty, - allFlexOptions = Map.empty, - currentSetPoint = Map.empty, - activatedAgents = Set.empty, - expectDataFrom = ReceiveMultiDataMap.empty, - ), - Some(new EmCompletion(getMaybeNextTick.toJava)), - ) - } else { - (copy(completions = updatedData), None) - } - } - - // not supported - case other => - log.warn(s"Flex response $other is not supported!") - - (this, None) - } - } - - override def handleFlexRequest( - flexRequest: FlexRequest, - receiver: ActorRef[FlexRequest], - )(using - startTime: ZonedDateTime, - log: Logger, - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { - val receiverUuid = refToUuid(receiver) // the controlled em - val sender = uuidToParent(receiverUuid) // the controlling em - - val updated = flexRequest match { - case FlexActivation(tick, flexType) => - // send request to ext - expectDataFrom.addData( - sender, - new FlexOptionRequestResult( - tick.toDateTime(using startTime), - sender, - Seq(receiverUuid).asJava, - ), - ) - - case control: IssueFlexControl => - // send set point to ext - log.warn(s"Receiver $receiverUuid got flex request from $sender") - - val (time, power) = control match { - case IssueNoControl(tick) => - log.warn(s"Set points: $currentSetPoint") - - ( - tick.toDateTime, - new PValue(currentSetPoint(receiverUuid).toQuantity), - ) - - case IssuePowerControl(tick, setPower) => - (tick.toDateTime, new PValue(setPower.toQuantity)) - - case other => - throw new CriticalFailureException( - s"Flex control $other is not supported!" - ) - } - - expectDataFrom.addData( - sender, - new EmSetPointResult( - time, - sender, - java.util.Map.of(receiverUuid, power), - ), - ) - - case other => - log.warn(s"$other is not supported!") - expectDataFrom - } - - if updated.isComplete then { - ( - copy(expectDataFrom = ReceiveMultiDataMap.empty), - Some(new EmResultResponse(updated.receivedData.asJava)), - ) - } else { - (copy(expectDataFrom = updated), None) - } - } - -} 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 f697e3de4e..950c7755e4 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -117,7 +117,7 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { case EmMode.BASE => EmServiceBaseCore.empty case EmMode.EM_COMMUNICATION => - EmCommunicationCore2() + EmCommunicationCore() } val emDataInitializedStateData = diff --git a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala index 962c14a1eb..2a5f90363c 100644 --- a/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala +++ b/src/test/scala/edu/ie3/simona/service/em/ExtEmCommunicationIT.scala @@ -8,20 +8,44 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.grid.GridAgent -import edu.ie3.simona.agent.participant.ParticipantAgent.{DataProvision, RegistrationFailedMessage, RegistrationSuccessfulMessage} +import edu.ie3.simona.agent.participant.ParticipantAgent.{ + DataProvision, + RegistrationFailedMessage, + RegistrationSuccessfulMessage, +} import edu.ie3.simona.agent.participant.ParticipantAgentInit.ParticipantRefs import edu.ie3.simona.agent.participant.{ParticipantAgent, ParticipantAgentInit} 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, FlexOptionRequest, FlexOptionRequestResult, FlexOptions} +import edu.ie3.simona.api.data.model.em.{ + EmSetPoint, + FlexOptionRequest, + FlexOptionRequestResult, + FlexOptions, +} import edu.ie3.simona.api.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.api.ontology.em.{EmCompletion, EmResultResponse, FlexOptionsResponse} +import edu.ie3.simona.api.ontology.em.{ + EmCompletion, + EmResultResponse, + FlexOptionsResponse, +} import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt -import edu.ie3.simona.config.RuntimeConfig.{LoadRuntimeConfig, PvRuntimeConfig, StorageRuntimeConfig} +import edu.ie3.simona.config.RuntimeConfig.{ + LoadRuntimeConfig, + PvRuntimeConfig, + StorageRuntimeConfig, +} import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.model.InputModelContainer.SimpleInputContainer -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} -import edu.ie3.simona.ontology.messages.ServiceMessage.{Create, PrimaryServiceRegistrationMessage, SecondaryServiceRegistrationMessage} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} +import edu.ie3.simona.ontology.messages.ServiceMessage.{ + Create, + PrimaryServiceRegistrationMessage, + SecondaryServiceRegistrationMessage, +} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.Data.SecondaryData.WeatherData @@ -37,7 +61,10 @@ import edu.ie3.simona.test.matchers.QuantityMatchers import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.util.quantities.QuantityUtils.* import edu.ie3.util.scala.quantities.WattsPerSquareMeter -import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} import org.apache.pekko.actor.typed.ActorRef import org.scalatest.OptionValues.convertOptionToValuable import org.scalatest.wordspec.AnyWordSpecLike @@ -49,7 +76,11 @@ import tech.units.indriya.ComparableQuantity import java.util.{Optional, UUID} import javax.measure.quantity.Power import scala.concurrent.duration.{DurationInt, FiniteDuration} -import scala.jdk.CollectionConverters.{MapHasAsJava, MapHasAsScala, SeqHasAsJava} +import scala.jdk.CollectionConverters.{ + MapHasAsJava, + MapHasAsScala, + SeqHasAsJava, +} import scala.jdk.OptionConverters.RichOptional class ExtEmCommunicationIT @@ -79,7 +110,8 @@ class ExtEmCommunicationIT private val extSimAdapter = TestProbe[ControlResponseMessageFromExt]("extSimAdapter") private val gridAgent = TestProbe[GridAgent.Message]("GridAgent") - private val resultListener = TestProbe[ResultEvent | ExpectResult]("ResultListener") + private val resultListener = + TestProbe[ResultEvent | ExpectResult]("ResultListener") private val primaryServiceProxy = TestProbe[PrimaryServiceProxy.Message]("PrimaryServiceProxy") private val weatherService = @@ -498,7 +530,7 @@ class ExtEmCommunicationIT .receiveWithType(classOf[EmResultResponse]) .emResults .asScala - .filter { case (uuid, _) => uuid == emNode3Uuid || uuid == emNode4Uuid} + .filter { case (uuid, _) => uuid == emNode3Uuid || uuid == emNode4Uuid } if inferiorSetPoints.size != 2 then { inferiorSetPoints.addAll( @@ -506,7 +538,9 @@ class ExtEmCommunicationIT .receiveWithType(classOf[EmResultResponse]) .emResults .asScala - .filter { case (uuid, _) => uuid == emNode3Uuid || uuid == emNode4Uuid} + .filter { case (uuid, _) => + uuid == emNode3Uuid || uuid == emNode4Uuid + } ) } @@ -517,7 +551,10 @@ class ExtEmCommunicationIT results.getFirst match { case setPoint: EmSetPoint => - setPoint.power.flatMap(_.getP).toScala.value should equalWithTolerance( + setPoint.power + .flatMap(_.getP) + .toScala + .value should equalWithTolerance( setPoints(receiver) ) } From 6adbb935f871e18ac307d6f1bf8d7afbbeb192b8 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 3 Sep 2025 11:24:58 +0200 Subject: [PATCH 083/125] Adapting to changes in API. --- .../event/listener/ExtResultEvent.scala | 22 +- .../service/em/EmCommunicationCore.scala | 29 +- .../service/results/ExtResultProvider.scala | 269 ------------------ .../service/results/ExtResultSchedule.scala | 86 ------ 4 files changed, 8 insertions(+), 398 deletions(-) delete mode 100644 src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala delete mode 100644 src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala diff --git a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala index a35557ccc4..c7eab3a83b 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala @@ -46,8 +46,6 @@ object ExtResultEvent { resultProxy: ActorRef[RequestResult], connection: ExtResultDataConnection, extMessage: Option[ResultDataMessageFromExt] = None, - gridAssets: Seq[UUID], - flexAssets: Seq[UUID], ) def listener(connection: ExtResultListener): Behavior[Message] = @@ -68,14 +66,7 @@ object ExtResultEvent { scheduler: ActorRef[SchedulerMessage], resultProxy: ActorRef[RequestResult], ): Behavior[Message | DataMessageFromExt | Activation] = { - val stateData = - ProviderState( - scheduler, - resultProxy, - connection, - gridAssets = connection.getGridResultDataAssets.asScala.toSeq, - flexAssets = connection.getFlexOptionAssets.asScala.toSeq, - ) + val stateData = ProviderState(scheduler, resultProxy, connection) provider(stateData) } @@ -121,16 +112,7 @@ object ExtResultEvent { extMsg match { case requestResultEntities: RequestResultEntities => - val requestedResults = - new util.ArrayList(requestResultEntities.requestedResults) - - // TODO: flex result are currently not supported by the result provider - requestedResults.removeAll(stateData.flexAssets.asJava) - - if requestResultEntities.tick == 0 then { - // removing the grid assets for tick 0, since SIMONA will produce no output - requestedResults.removeAll(stateData.gridAssets.asJava) - } + val requestedResults = new util.ArrayList(requestResultEntities.requestedResults) // request results from result proxy stateData.resultProxy ! RequestResult( diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 44310137f2..63b8883fa1 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -6,24 +6,15 @@ package edu.ie3.simona.service.em -import edu.ie3.datamodel.models.result.ResultEntity import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent.Message -import edu.ie3.simona.api.data.model.em.{ - EmSetPointResult, - ExtendedFlexOptionsResult, - FlexOptionRequestResult, -} +import edu.ie3.simona.api.data.model.em.{EmData, EmSetPoint, ExtendedFlexOptionsResult, FlexOptionRequest} 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.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.flex.{ - FlexType, - FlexibilityMessage, - PowerLimitFlexOptions, -} +import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.* @@ -55,7 +46,7 @@ case class EmCommunicationCore( activatedAgents: Set[UUID] = Set.empty, waitingForFlexOptions: Set[UUID] = Set.empty, waitingForSetPoint: Set[UUID] = Set.empty, - expectDataFrom: ReceiveMultiDataMap[UUID, ResultEntity] = + expectDataFrom: ReceiveMultiDataMap[UUID, EmData] = ReceiveMultiDataMap.empty, ) extends EmServiceCore { @@ -383,10 +374,9 @@ case class EmCommunicationCore( // send request to ext expectDataFrom.addData( sender, - new FlexOptionRequestResult( - tick.toDateTime(using startTime), + new FlexOptionRequest( + receiverUuid, sender, - Seq(receiverUuid).asJava, disaggregated.getOrElse(sender, false), ), ) @@ -413,14 +403,7 @@ case class EmCommunicationCore( ) } - expectDataFrom.addData( - sender, - new EmSetPointResult( - time, - sender, - java.util.Map.of(receiverUuid, power), - ), - ) + expectDataFrom.addData(sender, new EmSetPoint(receiverUuid, sender, power)) case other => log.warn(s"$other is not supported!") diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala deleted file mode 100644 index 570ce34896..0000000000 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala +++ /dev/null @@ -1,269 +0,0 @@ -/* - * © 2025. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.service.results - -import edu.ie3.datamodel.models.result.ResultEntity -import edu.ie3.datamodel.models.result.system.SystemParticipantResult -import edu.ie3.simona.api.data.connection.ExtResultDataConnection -import edu.ie3.simona.api.ontology.DataMessageFromExt -import edu.ie3.simona.api.ontology.results.{ - ProvideResultEntities, - RequestResultEntities, - ResultDataMessageFromExt, -} -import edu.ie3.simona.exceptions.{InitializationException, ServiceException} -import edu.ie3.simona.ontology.messages.ServiceMessage.{ - ResultResponseMessage, - ServiceRegistrationMessage, - ServiceResponseMessage, -} -import edu.ie3.simona.service.ServiceStateData.{ - InitializeServiceStateData, - ServiceBaseStateData, -} -import edu.ie3.simona.service.{ExtDataSupport, SimonaService} -import edu.ie3.simona.util.ReceiveDataMap -import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK -import org.apache.pekko.actor.typed.scaladsl.ActorContext - -import java.time.ZonedDateTime -import java.util.UUID -import scala.jdk.CollectionConverters.{ListHasAsScala, SeqHasAsJava} -import scala.util.{Failure, Success, Try} - -@deprecated -object ExtResultProvider extends SimonaService with ExtDataSupport { - - override type S = ExtResultStateData - - final case class ExtResultStateData( - extResultDataConnection: ExtResultDataConnection, - powerFlowResolution: Long, - currentTick: Long = INIT_SIM_TICK, - extResultSchedule: ExtResultSchedule, - extResultsMessage: Option[ResultDataMessageFromExt] = None, - receiveDataMap: ReceiveDataMap[UUID, ResultEntity] = ReceiveDataMap.empty, - resultStorage: Map[UUID, ResultEntity] = Map.empty, - startTime: ZonedDateTime, - ) extends ServiceBaseStateData - - final case class InitExtResultData( - extResultDataConnection: ExtResultDataConnection, - powerFlowResolution: Long, - startTime: ZonedDateTime, - ) extends InitializeServiceStateData - - override def init( - initServiceData: InitializeServiceStateData - ): Try[(ExtResultStateData, Option[Long])] = initServiceData match { - case InitExtResultData( - extResultDataConnection, - powerFlowResolution, - startTime, - ) => - val initGridSubscribers = - extResultDataConnection.getGridResultDataAssets.asScala - val initParticipantSubscribers = - extResultDataConnection.getParticipantResultDataAssets.asScala - val initFlexOptionSubscribers = - extResultDataConnection.getFlexOptionAssets.asScala - - // First result for system participants expected for tick 0 - // First result for grid expected for tick power flow resolution - val initResultScheduleMap = Map( - 0L -> (initParticipantSubscribers ++ initFlexOptionSubscribers).toSet - ) ++ Map(powerFlowResolution -> initGridSubscribers.toSet) - - Success( - ExtResultStateData( - extResultDataConnection, - powerFlowResolution, - extResultSchedule = ExtResultSchedule(initResultScheduleMap), - startTime = startTime, - ), - None, - ) - - case invalidData => - Failure( - new InitializationException( - s"Provided init data '${invalidData.getClass.getSimpleName}' for ExtResultProvider are invalid!" - ) - ) - } - - override protected def handleRegistrationRequest( - registrationMessage: ServiceRegistrationMessage - )(using - serviceStateData: ExtResultStateData, - ctx: ActorContext[Message], - ): Try[ExtResultStateData] = { - // this should not happen - ctx.log.warn( - s"Received registration request '$registrationMessage', but no registration is required for ExtResultProvider!" - ) - Success(serviceStateData) - } - - override protected def announceInformation(tick: Long)(using - serviceStateData: ExtResultStateData, - ctx: ActorContext[Message], - ): (ExtResultStateData, Option[Long]) = { - - val extMsg = serviceStateData.extResultsMessage.getOrElse( - // this should not be possible because the external simulation schedules this service - throw ServiceException( - "ExtResultDataService was triggered without ResultDataMessageFromExt available" - ) - ) - - val updatedStateData = extMsg match { - case request: RequestResultEntities => - if request.tick != tick then { - ctx.log.warn( - s"Received result request for tick '${request.tick}', but current simulation tick is '$tick'!" - ) - - serviceStateData - } else { - // handle result request - - val currentTick = tick - val requestedKeys = request.requestedResults.asScala.toSet - - // Search for too old schedules - val shiftedSchedule = - serviceStateData.extResultSchedule.shiftPastTicksToCurrentTick( - currentTick - ) - - val expectedKeys = shiftedSchedule - .getExpectedKeys(currentTick) - .intersect(requestedKeys) - - val receiveDataMap = - serviceStateData.receiveDataMap.addExpectedKeys(expectedKeys) - - val updatedSchedule = shiftedSchedule.handleActivationWithRequest( - currentTick, - requestedKeys, - ) - - val currentStorage = serviceStateData.resultStorage - val storageKeys = expectedKeys.filter(currentStorage.contains) - - val updated = storageKeys.foldLeft(receiveDataMap) { - case (dataMap, key) => - dataMap.addData(key, currentStorage(key)) - } - - if updated.isComplete then { - - serviceStateData.extResultDataConnection.queueExtResponseMsg( - new ProvideResultEntities( - updated.receivedData.values.toList.asJava - ) - ) - - serviceStateData.copy( - currentTick = tick, - extResultSchedule = updatedSchedule, - extResultsMessage = None, - receiveDataMap = ReceiveDataMap.empty, - resultStorage = currentStorage.removedAll(storageKeys), - ) - } else { - - serviceStateData.copy( - currentTick = tick, - extResultSchedule = updatedSchedule, - extResultsMessage = None, - receiveDataMap = updated, - resultStorage = currentStorage.removedAll(storageKeys), - ) - } - } - - case unsupported => - ctx.log.warn(s"Received unsupported external message: $unsupported") - - serviceStateData.copy(extResultsMessage = None) - } - - (updatedStateData, None) - } - - override protected def handleDataMessage( - extMsg: DataMessageFromExt - )(using serviceStateData: ExtResultStateData): ExtResultStateData = - extMsg match { - case extMsg: ResultDataMessageFromExt => - serviceStateData.copy( - extResultsMessage = Some(extMsg) - ) - } - - override protected def handleDataResponseMessage( - extResponseMsg: ServiceResponseMessage - )(using serviceStateData: ExtResultStateData): ExtResultStateData = - extResponseMsg match { - case ResultResponseMessage(results) => - val receiveDataMap = serviceStateData.receiveDataMap - - val participantResults = results.map { - case res: SystemParticipantResult => - res.getInputModel -> res - }.toMap - - if receiveDataMap.getExpectedKeys.isEmpty then { - - // we currently expect no result, - // save received result in storage - serviceStateData.copy( - resultStorage = serviceStateData.resultStorage ++ participantResults - ) - - } else { - // we expect results, - - val (expectedModels, otherModels) = participantResults.keys.partition( - receiveDataMap.getExpectedKeys.contains - ) - - // storing the result, that were not requested - val updatedResultStorage = - serviceStateData.resultStorage ++ otherModels - .map(model => model -> participantResults(model)) - .toMap - - val updated = expectedModels.foldLeft(receiveDataMap) { - case (dataMap, model) => - dataMap.addData(model, participantResults(model)) - } - - if updated.isComplete then { - - serviceStateData.extResultDataConnection.queueExtResponseMsg( - new ProvideResultEntities( - updated.receivedData.values.toList.asJava - ) - ) - - serviceStateData.copy( - receiveDataMap = ReceiveDataMap.empty, - resultStorage = updatedResultStorage, - ) - } else { - - serviceStateData.copy( - receiveDataMap = updated, - resultStorage = updatedResultStorage, - ) - } - } - } -} diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala deleted file mode 100644 index 898b60652e..0000000000 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultSchedule.scala +++ /dev/null @@ -1,86 +0,0 @@ -/* - * © 2025. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.service.results - -import edu.ie3.simona.ontology.messages.ServiceMessage.ResultResponseMessage - -import java.util.UUID - -@deprecated -final case class ExtResultSchedule( - scheduleMap: Map[Long, Set[UUID]] = Map.empty, - unscheduledList: Set[UUID] = Set.empty, -) { - def shiftPastTicksToCurrentTick( - currentTick: Long - ): ExtResultSchedule = { - // Sammle alle Sets von Keys, deren Schlüssel kleiner als currentTick - val (toMerge, remaining) = scheduleMap.partition { case (tick, _) => - tick < currentTick - } - - // Kombiniere die Sets zu einem einzigen Set - val mergedSet = toMerge.values.flatten.toSet - - // Aktualisiere den scheduleMap mit dem neuen Set für currentTick - val updatedScheduleMap = remaining.updated( - currentTick, - scheduleMap.getOrElse(currentTick, Set.empty) ++ mergedSet, - ) - - // Rückgabe eines neuen ExtResultSchedule mit dem aktualisierten scheduleMap - copy( - scheduleMap = updatedScheduleMap - ) - } - - def getExpectedKeys(tick: Long): Set[UUID] = { - scheduleMap.getOrElse( - tick, - Set(), - ) ++ unscheduledList - } - - private def getScheduledKeys(tick: Long): Set[UUID] = { - scheduleMap.getOrElse(tick, Set[UUID]()) - } - - def handleActivation(tick: Long): ExtResultSchedule = { - copy( - scheduleMap = scheduleMap.-(tick) - ) - } - - def handleActivationWithRequest( - tick: Long, - keys: Iterable[UUID], - ): ExtResultSchedule = { - val remainingKeys = - scheduleMap.get(tick).map(_.diff(keys.toSet)).getOrElse(Set.empty) - if remainingKeys.isEmpty then { - copy( - scheduleMap = scheduleMap.-(tick) - ) - } else { - copy( - scheduleMap = scheduleMap.updated(tick, remainingKeys) - ) - } - } - - def handleResult( - msg: ResultResponseMessage, - nextTick: Long, - ): ExtResultSchedule = { - copy( - scheduleMap = scheduleMap.updated( - nextTick, - getScheduledKeys(nextTick) ++ msg.results.map(_.getInputModel), - ) - ) - } -} From edaf63471890909534ff029bdd74c69143a13f34 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 9 Sep 2025 12:51:13 +0200 Subject: [PATCH 084/125] Adapting to changes. --- src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bb495a3b17..9121a82f3d 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -18,7 +18,7 @@ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.quantities.QuantityUtils.asMegaWatt -import edu.ie3.util.scala.quantities.QuantityConversionUtils.PowerConversionSimona +import edu.ie3.util.scala.quantities.QuantityConversionUtils.toSquants import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger import squants.Power From 1a764fd6959ba8cf06af81aa50fe2d443eb44b80 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 10 Sep 2025 16:31:49 +0200 Subject: [PATCH 085/125] Saving changes. --- .../ie3/simona/sim/setup/ExtSimSetup.scala | 12 +++++----- .../ie3/simona/sim/setup/SimonaSetup.scala | 22 ++++++++++++++----- .../sim/setup/SimonaStandaloneSetup.scala | 14 ++---------- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index 6b1371decb..c80cbd5d71 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.sim.setup +import edu.ie3.datamodel.models.input.container.JointGridContainer import edu.ie3.simona.api.data.connection.* import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt @@ -13,11 +14,7 @@ import edu.ie3.simona.api.simulation.{ExtSimAdapterData, ExtSimulation} import edu.ie3.simona.api.{ExtLinkInterface, ExtSimAdapter} import edu.ie3.simona.event.listener.ExtResultEvent import edu.ie3.simona.exceptions.ServiceException -import edu.ie3.simona.ontology.messages.{ - RequestResult, - SchedulerMessage, - ServiceMessage, -} +import edu.ie3.simona.ontology.messages.{RequestResult, SchedulerMessage, ServiceMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceStateData.InitializeServiceStateData import edu.ie3.simona.service.em.ExtEmDataService @@ -46,6 +43,8 @@ object ExtSimSetup { * Interfaces that hold information regarding external simulations. * @param args * The main args the simulation is started with. + * @param grid + * The electrical grid. * @param context * The actor context of this actor system. * @param scheduler @@ -58,6 +57,7 @@ object ExtSimSetup { def setupExtSim( extLinks: List[ExtLinkInterface], args: Array[String], + grid: JointGridContainer, )(using context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], @@ -73,7 +73,7 @@ object ExtSimSetup { // creating the adapter data given extSimAdapterData: ExtSimAdapterData = - new ExtSimAdapterData(extSimAdapter, args) + new ExtSimAdapterData(extSimAdapter, args, grid) Try { // sets up the external simulation diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala index 1491b53db5..2cb1e50673 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -8,17 +8,16 @@ package edu.ie3.simona.sim.setup import edu.ie3.datamodel.graph.SubGridGate import edu.ie3.datamodel.models.input.connector.Transformer3WInput +import edu.ie3.datamodel.models.input.container.{JointGridContainer, ThermalGrid} +import edu.ie3.datamodel.models.input.thermal.ThermalBusInput import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.event.ResultEvent.ResultResponse import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} -import edu.ie3.simona.ontology.messages.{ - RequestResult, - SchedulerMessage, - ServiceMessage, -} +import edu.ie3.simona.io.grid.GridProvider +import edu.ie3.simona.ontology.messages.{RequestResult, SchedulerMessage, ServiceMessage} import edu.ie3.simona.scheduler.TimeAdvancer import edu.ie3.simona.scheduler.core.Core.CoreFactory import edu.ie3.simona.scheduler.core.RegularSchedulerCore @@ -49,6 +48,19 @@ trait SimonaSetup { */ val args: Array[String] + /** + * The electrical grid. + */ + lazy val grid: JointGridContainer = GridProvider.gridFromConfig( + simonaConfig.simona.simulationName, + simonaConfig.simona.input.grid.datasource, + ) + + /** + * Map: thermal bus to thermal grid. + */ + lazy val thermalGridsByThermalBus: Map[ThermalBusInput, ThermalGrid] = GridProvider.getThermalGridsFromConfig(simonaConfig.simona.input.grid.datasource) + /** Directory of the log output. */ def logOutputDir: Path diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index 99b80098b2..d0c47963d8 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -18,7 +18,6 @@ import edu.ie3.simona.config.{GridConfigParser, SimonaConfig} import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.exceptions.agent.GridAgentInitializationException -import edu.ie3.simona.io.grid.GridProvider import edu.ie3.simona.ontology.messages.{ RequestResult, SchedulerMessage, @@ -32,7 +31,6 @@ import edu.ie3.simona.service.load.LoadProfileService.InitLoadProfileServiceStat import edu.ie3.simona.service.primary.PrimaryServiceProxy import edu.ie3.simona.service.primary.PrimaryServiceProxy.InitPrimaryServiceProxyStateData import edu.ie3.simona.service.results.ResultServiceProxy -import edu.ie3.simona.service.results.ResultServiceProxy.Message import edu.ie3.simona.service.weather.WeatherService import edu.ie3.simona.service.weather.WeatherService.InitWeatherServiceStateData import edu.ie3.simona.sim.SimonaSim @@ -72,15 +70,7 @@ class SimonaStandaloneSetup( ): Iterable[ActorRef[GridAgent.Message]] = { /* get the grid */ - val subGridTopologyGraph = GridProvider - .gridFromConfig( - simonaConfig.simona.simulationName, - simonaConfig.simona.input.grid.datasource, - ) - .getSubGridTopologyGraph - val thermalGridsByThermalBus = GridProvider.getThermalGridsFromConfig( - simonaConfig.simona.input.grid.datasource - ) + val subGridTopologyGraph = grid.getSubGridTopologyGraph /* extract and prepare refSystem information from config */ val (configRefSystems, configVoltageLimits) = @@ -233,7 +223,7 @@ class SimonaStandaloneSetup( val jars = ExtSimLoader.scanInputFolder(extSimPath) val extLinks = jars.flatMap(ExtSimLoader.loadExtLink).toList - setupExtSim(extLinks, args)(using + setupExtSim(extLinks, args, grid)(using context, scheduler, resultProxy, From 6598bb5c3043c2857bc975b2c728f81b3517af97 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 11 Sep 2025 11:31:38 +0200 Subject: [PATCH 086/125] Saving changes. --- src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala | 6 +++++- .../edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index c80cbd5d71..def8c2cc2f 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.sim.setup +import com.typesafe.config.Config import edu.ie3.datamodel.models.input.container.JointGridContainer import edu.ie3.simona.api.data.connection.* import edu.ie3.simona.api.ontology.DataMessageFromExt @@ -43,6 +44,8 @@ object ExtSimSetup { * Interfaces that hold information regarding external simulations. * @param args * The main args the simulation is started with. + * @param config + * The simona config. * @param grid * The electrical grid. * @param context @@ -57,6 +60,7 @@ object ExtSimSetup { def setupExtSim( extLinks: List[ExtLinkInterface], args: Array[String], + config: Config, grid: JointGridContainer, )(using context: ActorContext[?], @@ -73,7 +77,7 @@ object ExtSimSetup { // creating the adapter data given extSimAdapterData: ExtSimAdapterData = - new ExtSimAdapterData(extSimAdapter, args, grid) + new ExtSimAdapterData(extSimAdapter, args, config, grid) Try { // sets up the external simulation diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index d0c47963d8..c574ad8560 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -223,7 +223,7 @@ class SimonaStandaloneSetup( val jars = ExtSimLoader.scanInputFolder(extSimPath) val extLinks = jars.flatMap(ExtSimLoader.loadExtLink).toList - setupExtSim(extLinks, args, grid)(using + setupExtSim(extLinks, args, typeSafeConfig, grid)(using context, scheduler, resultProxy, From dcbd78de582b32988da820025baa1ebd1f5149a7 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 12 Sep 2025 16:43:08 +0200 Subject: [PATCH 087/125] Added external result provider --- CHANGELOG.md | 1 + build.gradle | 2 +- .../ie3/simona/agent/EnvironmentRefs.scala | 4 + .../edu/ie3/simona/agent/em/EmAgent.scala | 30 +- .../edu/ie3/simona/agent/grid/GridAgent.scala | 10 +- .../simona/agent/grid/GridAgentBuilder.scala | 22 +- .../ie3/simona/agent/grid/GridAgentData.scala | 18 +- .../participant/ParticipantAgentInit.scala | 9 +- .../ParticipantResultHandler.scala | 40 +-- .../edu/ie3/simona/event/ResultEvent.scala | 8 +- .../event/listener/DelayedStopHelper.scala | 2 +- ...entListener.scala => ResultListener.scala} | 185 ++++------- .../ontology/messages/ResultMessage.scala | 41 +++ .../service/results/ExtResultProvider.scala | 149 +++++++++ .../service/results/ResultServiceProxy.scala | 287 ++++++++++++++++++ .../results}/Transformer3wResultSupport.scala | 6 +- .../scala/edu/ie3/simona/sim/SimonaSim.scala | 47 +-- .../ie3/simona/sim/setup/ExtSimSetup.scala | 52 +++- .../simona/sim/setup/ExtSimSetupData.scala | 101 +++--- .../ie3/simona/sim/setup/SimonaSetup.scala | 116 ++++--- .../sim/setup/SimonaStandaloneSetup.scala | 41 ++- .../edu/ie3/simona/util/CollectionUtils.scala | 10 + .../edu/ie3/simona/agent/em/EmAgentIT.scala | 79 ++--- .../edu/ie3/simona/agent/em/EmAgentSpec.scala | 51 ++-- .../agent/grid/DBFSAlgorithmCenGridSpec.scala | 28 +- .../DBFSAlgorithmFailedPowerFlowSpec.scala | 58 +++- .../grid/DBFSAlgorithmParticipantSpec.scala | 7 +- .../agent/grid/DBFSAlgorithmSupGridSpec.scala | 14 +- .../agent/grid/GridAgentSetupSpec.scala | 2 - .../ie3/simona/agent/grid/ThermalGridIT.scala | 146 ++++----- .../congestion/CongestionTestBaseData.scala | 10 +- .../grid/congestion/DCMAlgorithmSpec.scala | 2 +- .../ParticipantAgentInitSpec.scala | 33 +- .../participant/ParticipantAgentSpec.scala | 193 ++++++------ ...nerSpec.scala => ResultListenerSpec.scala} | 131 ++------ .../io/result/ResultEntityKafkaSpec.scala | 20 +- .../model/participant/evcs/EvcsModelIT.scala | 31 +- .../ThreeWindingResultHandlingSpec.scala | 7 +- .../results}/ThreeWindingResultTestData.scala | 2 +- .../edu/ie3/simona/sim/SimonaSimSpec.scala | 48 +-- .../sim/setup/ExtSimSetupDataSpec.scala | 111 +++---- .../simona/sim/setup/ExtSimSetupSpec.scala | 2 +- .../simona/sim/setup/SimonaSetupSpec.scala | 20 +- .../test/common/model/grid/DbfsTestGrid.scala | 9 + .../common/result/PowerFlowResultData.scala | 9 +- 45 files changed, 1319 insertions(+), 875 deletions(-) rename src/main/scala/edu/ie3/simona/event/listener/{ResultEventListener.scala => ResultListener.scala} (58%) create mode 100644 src/main/scala/edu/ie3/simona/ontology/messages/ResultMessage.scala create mode 100644 src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala create mode 100644 src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala rename src/main/scala/edu/ie3/simona/{event/listener => service/results}/Transformer3wResultSupport.scala (97%) rename src/test/scala/edu/ie3/simona/event/listener/{ResultEventListenerSpec.scala => ResultListenerSpec.scala} (75%) rename src/test/scala/edu/ie3/simona/{event/listener => service/results}/ThreeWindingResultHandlingSpec.scala (95%) rename src/test/scala/edu/ie3/simona/{event/listener => service/results}/ThreeWindingResultTestData.scala (97%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eb067eeeb..f8bd1952a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Considering primary data that start before simulation [#1034](https://github.com/ie3-institute/simona/issues/1034) - Implement time series retrieval for WeatherSource [#1511](https://github.com/ie3-institute/simona/issues/1511) - Implement weather forecast provision by WeatherService [#1512](https://github.com/ie3-institute/simona/issues/1512) +- Added external result provider [#1530](https://github.com/ie3-institute/simona/issues/1530) ### Changed - Upgraded `scala2` to `scala3` [#53](https://github.com/ie3-institute/simona/issues/53) diff --git a/build.gradle b/build.gradle index 5b53c94ca3..5d0534267b 100644 --- a/build.gradle +++ b/build.gradle @@ -94,7 +94,7 @@ dependencies { exclude group: 'edu.ie3' } - implementation('com.github.ie3-institute:simonaAPI:0.10-SNAPSHOT') { + implementation('com.github.ie3-institute:simonaAPI:0.10.0') { exclude group: 'org.apache.logging.log4j' exclude group: 'org.slf4j' /* Exclude our own nested dependencies */ diff --git a/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala b/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala index 5b372bd87e..eeeb8a3dc5 100644 --- a/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala +++ b/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala @@ -8,6 +8,7 @@ package edu.ie3.simona.agent import edu.ie3.simona.event.RuntimeEvent import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} +import edu.ie3.simona.service.results.ResultServiceProxy import org.apache.pekko.actor.typed.ActorRef /** Container class, that gather together reference to relevant entities, that @@ -19,6 +20,8 @@ import org.apache.pekko.actor.typed.ActorRef * Reference to the runtime event listener. * @param primaryServiceProxy * Reference to the primary service proxy. + * @param resultProxy + * Reference to the result service proxy. * @param weather * Reference to the service, that provides weather information. * @param loadProfiles @@ -30,6 +33,7 @@ final case class EnvironmentRefs( scheduler: ActorRef[SchedulerMessage], runtimeEventListener: ActorRef[RuntimeEvent], primaryServiceProxy: ActorRef[ServiceMessage], + resultProxy: ActorRef[ResultServiceProxy.Message], weather: ActorRef[ServiceMessage], loadProfiles: ActorRef[ServiceMessage], evDataService: Option[ActorRef[ServiceMessage]], diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 2de62f427e..82d9e36ccc 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -60,7 +60,7 @@ object EmAgent { * agent is em-controlled, or a [[Left]] with a reference to the scheduler * that is activating this agent. * @param listener - * A collection of result event listeners. + * A listener for result events. */ def apply( inputModel: EmInput, @@ -69,7 +69,7 @@ object EmAgent { modelStrategy: String, simulationStartDate: ZonedDateTime, parent: Either[ActorRef[SchedulerMessage], ActorRef[FlexResponse]], - listener: Iterable[ActorRef[ResultEvent]], + listener: ActorRef[ResultEvent], ): Behavior[Message] = Behaviors.setup[Message] { ctx => parent.map { @@ -213,9 +213,7 @@ object EmAgent { ) ) - emData.listener.foreach { - _ ! FlexOptionsResultEvent(flexResult) - } + emData.listener ! FlexOptionsResultEvent(flexResult) } emData.parent match { @@ -365,17 +363,15 @@ object EmAgent { } maybeResult.foreach { result => - emData.listener.foreach { - _ ! ParticipantResultEvent( - new EmResult( - lastActiveTick - .toDateTime(using emData.simulationStartDate), - modelShell.uuid, - result.p.toMegawatts.asMegaWatt, - result.q.toMegavars.asMegaVar, - ) + emData.listener ! ParticipantResultEvent( + new EmResult( + lastActiveTick + .toDateTime(using emData.simulationStartDate), + modelShell.uuid, + result.p.toMegawatts.asMegaWatt, + result.q.toMegavars.asMegaVar, ) - } + ) emData.parent.foreach { _ ! FlexResult(modelShell.uuid, result) @@ -406,13 +402,13 @@ object EmAgent { * agent is em-controlled, or a [[Left]] with a reference to the scheduler * that is activating this agent. * @param listener - * A collection of result event listeners. + * A listener for result events. */ private final case class EmData( outputConfig: NotifierConfig, simulationStartDate: ZonedDateTime, parent: Either[ActorRef[SchedulerMessage], ActorRef[FlexResponse]], - listener: Iterable[ActorRef[ResultEvent]], + listener: ActorRef[ResultEvent], ) /** The existence of this data object indicates that the corresponding agent diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala index bd6e9b462e..e5ea501e32 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -32,6 +32,7 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, } +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import edu.ie3.simona.util.TickUtil.TickLong import org.apache.pekko.actor.typed.scaladsl.AskPattern.Askable import org.apache.pekko.actor.typed.scaladsl.{ @@ -68,7 +69,6 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { def apply( environmentRefs: EnvironmentRefs, simonaConfig: SimonaConfig, - listener: Iterable[ActorRef[ResultEvent]], ): Behavior[Message] = Behaviors.withStash(100) { buffer => val cfg = simonaConfig.simona @@ -83,7 +83,6 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { val agentValues = GridAgentConstantData( environmentRefs, simonaConfig, - listener, resolution, simStartTime, simEndTime, @@ -229,6 +228,13 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { ctx.self, Some(activation.tick), ) + + // inform the result proxy that this grid agent will send new results + constantData.environmentRefs.resultProxy ! ExpectResult( + gridAgentBaseData.assets, + activation.tick, + ) + buffer.unstashAll(simulateGrid(gridAgentBaseData, activation.tick)) case (_, msg: Message) => diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala index 129d355fb7..2a15855501 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala @@ -291,9 +291,9 @@ object GridAgentBuilder { given ParticipantRefs = ParticipantRefs( gridAgentContext.self, - constantData.environmentRefs.primaryServiceProxy, + environmentRefs.primaryServiceProxy, + environmentRefs.resultProxy, serviceMap, - constantData.listener, ) given SimulationParameters = SimulationParameters( @@ -316,7 +316,7 @@ object GridAgentBuilder { constantData.outputConfigUtil.getOrDefault( NotifierIdentifier.FixedFeedIn ), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case input: LoadInput => @@ -326,7 +326,7 @@ object GridAgentBuilder { input.getUuid ), constantData.outputConfigUtil.getOrDefault(NotifierIdentifier.Load), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case input: PvInput => @@ -338,7 +338,7 @@ object GridAgentBuilder { constantData.outputConfigUtil.getOrDefault( NotifierIdentifier.PvPlant ), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case input: BmInput => @@ -350,7 +350,7 @@ object GridAgentBuilder { constantData.outputConfigUtil.getOrDefault( NotifierIdentifier.BioMassPlant ), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case input: WecInput => @@ -360,7 +360,7 @@ object GridAgentBuilder { input.getUuid ), constantData.outputConfigUtil.getOrDefault(NotifierIdentifier.Wec), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case input: EvcsInput => @@ -370,7 +370,7 @@ object GridAgentBuilder { input.getUuid ), constantData.outputConfigUtil.getOrDefault(NotifierIdentifier.Evcs), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case input: HpInput => @@ -382,7 +382,7 @@ object GridAgentBuilder { input.getUuid ), constantData.outputConfigUtil.getOrDefault(NotifierIdentifier.Hp), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case None => @@ -399,7 +399,7 @@ object GridAgentBuilder { constantData.outputConfigUtil.getOrDefault( NotifierIdentifier.Storage ), - constantData.environmentRefs.scheduler, + environmentRefs.scheduler, maybeControllingEm, ) case input: SystemParticipantInput => @@ -468,7 +468,7 @@ object GridAgentBuilder { maybeControllingEm.toRight( constantData.environmentRefs.scheduler ), - constantData.listener, + constantData.environmentRefs.resultProxy, ), actorName(classOf[EmAgent.type], emInput.getId), ) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala index b8ed1645ee..abe97f2d5d 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -46,9 +46,6 @@ object GridAgentData { * the grid agent. * @param simonaConfig * Configuration of SIMONA, that is used for. - * @param listener - * A sequence of listeners, that will receive the results from the grid - * agent. * @param resolution * That is used for the power flow. If no power flow should be carried out, * this value is set to [[Long.MaxValue]]. @@ -60,14 +57,12 @@ object GridAgentData { final case class GridAgentConstantData( environmentRefs: EnvironmentRefs, simonaConfig: SimonaConfig, - listener: Iterable[ActorRef[ResultEvent]], resolution: Long, simStartTime: ZonedDateTime, simEndTime: ZonedDateTime, ) { - def notifyListeners(event: ResultEvent): Unit = { - listener.foreach(_ ! event) - } + def notifyListeners(event: ResultEvent): Unit = + environmentRefs.resultProxy ! event val participantConfigUtil: ParticipantConfigUtil = ConfigUtil.ParticipantConfigUtil(simonaConfig.simona.runtime.participant) @@ -323,6 +318,15 @@ object GridAgentData { ) extends GridAgentData with GridAgentDataHelper { + val assets: Seq[UUID] = { + val components = gridEnv.gridModel.gridComponents + components.nodes.map(_.uuid) ++ components.lines.map( + _.uuid + ) ++ components.switches.map(_.uuid) ++ components.transformers.map( + _.uuid + ) ++ components.transformers3w.map(_.uuid) + } + override protected val subgridGates: Vector[SubGridGate] = gridEnv.subgridGateToActorRef.keys.toVector override protected val subgridId: Int = gridEnv.gridModel.subnetNo diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala index 938179e4ac..d874a19d84 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala @@ -35,6 +35,7 @@ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.simona.service.ServiceType +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import edu.ie3.simona.service.weather.WeatherDataType import edu.ie3.simona.service.weather.WeatherService.WeatherRegistrationData import edu.ie3.simona.util.Coordinate @@ -59,16 +60,16 @@ object ParticipantAgentInit { * Reference to the grid agent. * @param primaryServiceProxy * Reference to the primary service proxy. + * @param resultServiceProxy + * Reference to the result service proxy. * @param services * References to services by service type. - * @param resultListener - * Reference to the result listeners. */ final case class ParticipantRefs( gridAgent: ActorRef[GridAgent.Message], primaryServiceProxy: ActorRef[ServiceMessage], + resultServiceProxy: ActorRef[ResultEvent | ExpectResult], services: Map[ServiceType, ActorRef[ServiceMessage]], - resultListener: Iterable[ActorRef[ResultEvent]], ) /** Container class that holds parameters related to the simulation. @@ -421,7 +422,7 @@ object ParticipantAgentInit { simulationParams.requestVoltageDeviationTolerance, ), ParticipantResultHandler( - participantRefs.resultListener, + participantRefs.resultServiceProxy, notifierConfig, ), ) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala index ce95d1d299..f3803131e9 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala @@ -20,18 +20,21 @@ import edu.ie3.simona.event.ResultEvent.{ } import edu.ie3.simona.event.notifier.NotifierConfig import edu.ie3.simona.exceptions.CriticalFailureException +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import org.apache.pekko.actor.typed.ActorRef +import java.util.UUID + /** Handles all kind of results stemming from the participant by sending them to - * the result listener, if applicable. + * the result proxy, if applicable. * - * @param listener - * The actor reference to the result listener. + * @param resultProxy + * The actor reference to the result resultProxy. * @param config * The result configuration. */ final case class ParticipantResultHandler( - private val listener: Iterable[ActorRef[ResultEvent]], + private val resultProxy: ActorRef[ResultEvent | ExpectResult], private val config: NotifierConfig, ) { @@ -42,18 +45,16 @@ final case class ParticipantResultHandler( */ def maybeSend(result: ResultEntity): Unit = if config.simulationResultInfo then { - listener.foreach(actor => - result match { - case thermalResult: ThermalUnitResult => - actor ! ThermalResultEvent(thermalResult) - case participantResult: SystemParticipantResult => - actor ! ParticipantResultEvent(participantResult) - case unsupported => - throw new CriticalFailureException( - s"Results of class '${unsupported.getClass.getSimpleName}' are currently not supported." - ) - } - ) + result match { + case thermalResult: ThermalUnitResult => + resultProxy ! ThermalResultEvent(thermalResult) + case participantResult: SystemParticipantResult => + resultProxy ! ParticipantResultEvent(participantResult) + case unsupported => + throw new CriticalFailureException( + s"Results of class '${unsupported.getClass.getSimpleName}' are currently not supported." + ) + } } /** Send the flex options result to all listeners, if enabled. @@ -63,9 +64,10 @@ final case class ParticipantResultHandler( */ def maybeSend(result: FlexOptionsResult): Unit = if config.flexResult then { - listener.foreach( - _ ! FlexOptionsResultEvent(result) - ) + resultProxy ! FlexOptionsResultEvent(result) } + def informProxy(uuid: UUID, tick: Long): Unit = + resultProxy ! ExpectResult(uuid, tick) + } diff --git a/src/main/scala/edu/ie3/simona/event/ResultEvent.scala b/src/main/scala/edu/ie3/simona/event/ResultEvent.scala index 49b345fa15..c3264c6a2f 100644 --- a/src/main/scala/edu/ie3/simona/event/ResultEvent.scala +++ b/src/main/scala/edu/ie3/simona/event/ResultEvent.scala @@ -6,14 +6,15 @@ package edu.ie3.simona.event -import edu.ie3.datamodel.models.result.{CongestionResult, NodeResult} import edu.ie3.datamodel.models.result.connector.{ LineResult, SwitchResult, Transformer2WResult, } import edu.ie3.datamodel.models.result.system.{ + EmResult, FlexOptionsResult, + HpResult, SystemParticipantResult, } import edu.ie3.datamodel.models.result.thermal.{ @@ -21,16 +22,15 @@ import edu.ie3.datamodel.models.result.thermal.{ ThermalHouseResult, ThermalUnitResult, } -import edu.ie3.datamodel.models.result.system.{EmResult, HpResult} +import edu.ie3.datamodel.models.result.{CongestionResult, NodeResult} import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult -import edu.ie3.simona.event.listener.ResultEventListener import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.UUID import javax.measure.quantity.{Energy, Power, Temperature} -sealed trait ResultEvent extends Event with ResultEventListener.Request +sealed trait ResultEvent extends Event /** Calculation result events */ diff --git a/src/main/scala/edu/ie3/simona/event/listener/DelayedStopHelper.scala b/src/main/scala/edu/ie3/simona/event/listener/DelayedStopHelper.scala index c76924af63..7b4ccbaf94 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/DelayedStopHelper.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/DelayedStopHelper.scala @@ -21,7 +21,7 @@ object DelayedStopHelper { * functionality */ sealed trait StoppingMsg - extends ResultEventListener.Request + extends ResultListener.Request with RuntimeEventListener.Request /** Message indicating that [[RuntimeEventListener]] should stop. Instead of diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultListener.scala similarity index 58% rename from src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala rename to src/main/scala/edu/ie3/simona/event/listener/ResultListener.scala index 28866b54f6..73cc708223 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultListener.scala @@ -6,53 +6,49 @@ package edu.ie3.simona.event.listener -import org.apache.pekko.actor.typed.scaladsl.Behaviors -import org.apache.pekko.actor.typed.{Behavior, PostStop} import edu.ie3.datamodel.io.processor.result.ResultEntityProcessor import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} -import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult -import edu.ie3.simona.event.ResultEvent.{ - FlexOptionsResultEvent, - ParticipantResultEvent, - PowerFlowResultEvent, - ThermalResultEvent, -} +import edu.ie3.simona.api.data.connection.ExtResultListener +import edu.ie3.simona.api.ontology.results.ProvideResultEntities +import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.exceptions.{ FileHierarchyException, ProcessResultEventException, } import edu.ie3.simona.io.result.* +import edu.ie3.simona.ontology.messages.ResultMessage +import edu.ie3.simona.ontology.messages.ResultMessage.ResultResponse import edu.ie3.simona.util.ResultFileHierarchy +import org.apache.pekko.actor.typed.scaladsl.Behaviors +import org.apache.pekko.actor.typed.{Behavior, PostStop} import org.slf4j.Logger +import edu.ie3.simona.util.CollectionUtils.asJava import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration.DurationInt import scala.concurrent.{Await, Future} import scala.util.{Failure, Success, Try} -object ResultEventListener extends Transformer3wResultSupport { +object ResultListener { trait Request + type Message = Request | ResultMessage.Response + private final case class SinkResponse( response: Map[Class[?], ResultEntitySink] ) extends Request private final case class InitFailed(ex: Exception) extends Request - /** [[ResultEventListener]] base data containing all information the listener - * needs + /** [[ResultListener]] base data containing all information the listener needs * * @param classToSink * a map containing the sink for each class that should be processed by the * listener */ private final case class BaseData( - classToSink: Map[Class[?], ResultEntitySink], - threeWindingResults: Map[ - Transformer3wKey, - AggregatedTransformer3wResult, - ] = Map.empty, + classToSink: Map[Class[?], ResultEntitySink] ) /** Initialize the sinks for this listener based on the provided collection @@ -147,73 +143,19 @@ object ResultEventListener extends Transformer3wResultSupport { } } - /** Handle the given result and possibly update the state data + /** Handle the given results. * - * @param resultEntity - * Result entity to handle + * @param resultEntities + * Results entity to handle. * @param baseData - * Base data - * @return - * The possibly update base data + * Base data. */ - private def handleResult( - resultEntity: ResultEntity, + private def handleResults( + resultEntities: Iterable[ResultEntity], baseData: BaseData, log: Logger, - ): BaseData = { - handOverToSink(resultEntity, baseData.classToSink, log) - baseData - } - - /** Handle a partial three winding result properly by adding it to an - * [[AggregatedTransformer3wResult]] and flushing then possibly completed - * results. Finally, the base data are updated. - * - * @param result - * Result entity to handle - * @param baseData - * Base data - * @return - * The possibly update base data - */ - private def handlePartialTransformer3wResult( - result: PartialTransformer3wResult, - baseData: BaseData, - log: Logger, - ): BaseData = { - val key = Transformer3wKey(result.input, result.time) - // retrieve existing partial result or use empty one - val partialResult = - baseData.threeWindingResults.getOrElse( - key, - AggregatedTransformer3wResult.EMPTY, - ) - // add partial result - val updatedResults = partialResult.add(result).map { updatedResult => - if updatedResult.ready then { - // if result is complete, we can write it out - updatedResult.consolidate.foreach { - handOverToSink(_, baseData.classToSink, log) - } - // also remove partial result from map - baseData.threeWindingResults.removed(key) - } else { - // if result is not complete yet, just update it - baseData.threeWindingResults + (key -> updatedResult) - } - } match { - case Success(results) => results - case Failure(exception) => - log.warn( - "Failure when handling partial Transformer3w result", - exception, - ) - // on failure, we just continue with previous results - baseData.threeWindingResults - } - - baseData.copy(threeWindingResults = updatedResults) - } + ): Unit = + resultEntities.foreach(handOverToSink(_, baseData.classToSink, log)) /** Handing over the given result entity to the sink, that might be apparent * in the map @@ -236,9 +178,29 @@ object ResultEventListener extends Transformer3wResultSupport { log.error("Error while writing result event: ", exception) } + /** Method to create an external result listener. + * + * @param connection + * Result listener data connection. + * @return + * The behavior of the listener. + */ + def external(connection: ExtResultListener): Behavior[Message] = + Behaviors.receivePartial[Message] { + case (_, ResultResponse(results)) => + connection.queueExtResponseMsg( + new ProvideResultEntities(results.asJava) + ) + + Behaviors.same + + case (ctx, msg: DelayedStopHelper.StoppingMsg) => + DelayedStopHelper.handleMsg((ctx, msg)) + } + def apply( resultFileHierarchy: ResultFileHierarchy - ): Behavior[Request] = Behaviors.setup[Request] { ctx => + ): Behavior[Message] = Behaviors.setup[Message] { ctx => ctx.log.debug("Starting initialization!") resultFileHierarchy.resultSinkType match { case _: ResultSinkType.Kafka => @@ -254,18 +216,18 @@ object ResultEventListener extends Transformer3wResultSupport { ctx.pipeToSelf( Future.sequence( - ResultEventListener.initializeSinks(resultFileHierarchy) + ResultListener.initializeSinks(resultFileHierarchy) ) ) { case Failure(exception: Exception) => InitFailed(exception) case Success(result) => SinkResponse(result.toMap) } - init() + init } - private def init(): Behavior[Request] = Behaviors.withStash(200) { buffer => - Behaviors.receive[Request] { + private def init: Behavior[Message] = Behaviors.withStash(200) { buffer => + Behaviors.receive[Message] { case (ctx, SinkResponse(response)) => ctx.log.debug("Initialization complete!") buffer.unstashAll(idle(BaseData(response))) @@ -281,47 +243,12 @@ object ResultEventListener extends Transformer3wResultSupport { } } - private def idle(baseData: BaseData): Behavior[Request] = Behaviors - .receivePartial[Request] { - case (ctx, ParticipantResultEvent(participantResult)) => - val updatedBaseData = handleResult(participantResult, baseData, ctx.log) - idle(updatedBaseData) - - case (ctx, ThermalResultEvent(thermalResult)) => - val updatedBaseData = handleResult(thermalResult, baseData, ctx.log) - idle(updatedBaseData) - - case ( - ctx, - PowerFlowResultEvent( - nodeResults, - switchResults, - lineResults, - transformer2wResults, - transformer3wResults, - congestionResults, - ), - ) => - val updatedBaseData = - (nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ transformer3wResults ++ congestionResults) - .foldLeft(baseData) { - case (currentBaseData, resultEntity: ResultEntity) => - handleResult(resultEntity, currentBaseData, ctx.log) - case ( - currentBaseData, - partialTransformerResult: PartialTransformer3wResult, - ) => - handlePartialTransformer3wResult( - partialTransformerResult, - currentBaseData, - ctx.log, - ) - } - idle(updatedBaseData) + private def idle(baseData: BaseData): Behavior[Message] = Behaviors + .receivePartial[Message] { + case (ctx, ResultResponse(results)) => + handleResults(results.values.flatten, baseData, ctx.log) - case (ctx, FlexOptionsResultEvent(flexOptionsResult)) => - val updatedBaseData = handleResult(flexOptionsResult, baseData, ctx.log) - idle(updatedBaseData) + Behaviors.same case (ctx, msg: DelayedStopHelper.StoppingMsg) => DelayedStopHelper.handleMsg((ctx, msg)) @@ -329,15 +256,7 @@ object ResultEventListener extends Transformer3wResultSupport { } .receiveSignal { case (ctx, PostStop) => // wait until all I/O has finished - ctx.log.debug( - "Shutdown initiated.\n\tThe following three winding results are not comprehensive and are not " + - "handled in sinks:{}\n\tWaiting until writing result data is completed ...", - baseData.threeWindingResults.keys - .map { case Transformer3wKey(model, zdt) => - s"model '$model' at $zdt" - } - .mkString("\n\t\t"), - ) + ctx.log.debug("Shutdown initiated.") // close sinks concurrently to speed up closing (closing calls might be blocking) Await.ready( diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/ResultMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/ResultMessage.scala new file mode 100644 index 0000000000..dd2efb9d4f --- /dev/null +++ b/src/main/scala/edu/ie3/simona/ontology/messages/ResultMessage.scala @@ -0,0 +1,41 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.ontology.messages + +import edu.ie3.datamodel.models.result.ResultEntity +import org.apache.pekko.actor.typed.ActorRef + +import java.util.UUID + +object ResultMessage { + + /** Message to request results. + * + * @param requestedResults + * The uuids of the input models. + * @param tick + * For which results are requested. + * @param replyTo + * The actor that should receive the results. + */ + final case class RequestResult( + requestedResults: Seq[UUID], + tick: Long, + replyTo: ActorRef[Response], + ) + + /** Trait that is extended by all responses to a [[RequestResult]]. + */ + sealed trait Response + + /** Response message that contains the requested results. + * @param results + * Map: uuid to results. + */ + final case class ResultResponse(results: Map[UUID, Iterable[ResultEntity]]) + extends Response +} diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala new file mode 100644 index 0000000000..920e5385be --- /dev/null +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala @@ -0,0 +1,149 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.results + +import edu.ie3.simona.api.data.connection.ExtResultDataConnection +import edu.ie3.simona.api.ontology.DataMessageFromExt +import edu.ie3.simona.api.ontology.results.{ + ProvideResultEntities, + RequestResultEntities, + ResultDataMessageFromExt, +} +import edu.ie3.simona.event.listener.DelayedStopHelper +import edu.ie3.simona.exceptions.CriticalFailureException +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} +import edu.ie3.simona.ontology.messages.ServiceMessage.ScheduleServiceActivation +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.ontology.messages.ResultMessage +import edu.ie3.simona.ontology.messages.ResultMessage.{ + RequestResult, + ResultResponse, +} +import org.apache.pekko.actor.typed.scaladsl.Behaviors +import org.apache.pekko.actor.typed.{ActorRef, Behavior} + +import java.util +import edu.ie3.simona.util.CollectionUtils.asJava + +import scala.jdk.CollectionConverters.* + +object ExtResultProvider { + + type Message = ResultMessage.Response | DelayedStopHelper.StoppingMsg + + /** State data for a result [[provider]]. + * + * @param scheduler + * Reference to the scheduler. + * @param resultProxy + * The result service proxy. + * @param connection + * Result data connection to the external simulation. + * @param extMessage + * Option for the current message from the external simulation. + */ + private final case class ProviderState( + scheduler: ActorRef[SchedulerMessage], + resultProxy: ActorRef[RequestResult], + connection: ExtResultDataConnection, + extMessage: Option[ResultDataMessageFromExt] = None, + ) + + /** Method to create an external result provider. In contrast to the listener, + * the result provider will only provide those result that were requested. + * + * @param connection + * Result data connection to the external simulation. + * @param scheduler + * Reference to the scheduler. + * @param resultProxy + * The result service proxy. + * @return + * The behavior of the result provider. + */ + def apply( + connection: ExtResultDataConnection, + scheduler: ActorRef[SchedulerMessage], + resultProxy: ActorRef[RequestResult], + ): Behavior[Message | DataMessageFromExt | Activation] = { + val stateData = ProviderState(scheduler, resultProxy, connection) + + provider(stateData) + } + + /** Definition of the behavior of the result provider. + * + * @param stateData + * The state data of the provider. + * @return + * The behavior of the result provider. + */ + private def provider( + stateData: ProviderState + ): Behavior[Message | DataMessageFromExt | Activation] = + Behaviors.receivePartial[Message | DataMessageFromExt | Activation] { + case (ctx, ResultResponse(results)) => + ctx.log.warn(s"Sending results to ext. Results: $results") + + // send result to external simulation + stateData.connection.queueExtResponseMsg( + new ProvideResultEntities(results.asJava) + ) + + stateData.scheduler ! Completion(ctx.self) + + Behaviors.same + + case (_, messageFromExt: ResultDataMessageFromExt) => + // save ext message + provider(stateData.copy(extMessage = Some(messageFromExt))) + + case (ctx, ScheduleServiceActivation(tick, unlockKey)) => + stateData.scheduler ! ScheduleActivation( + ctx.self, + tick, + Some(unlockKey), + ) + + Behaviors.same + + case (ctx, Activation(tick)) => + // handle ext message + + val extMsg = stateData.extMessage.getOrElse( + // this should not be possible because the external simulation schedules this provider + throw CriticalFailureException( + "ExtResultDataService was triggered without ResultDataMessageFromExt available" + ) + ) + + extMsg match { + case requestResultEntities: RequestResultEntities => + val requestedResults = + new util.ArrayList(requestResultEntities.requestedResults) + + // request results from result proxy + stateData.resultProxy ! RequestResult( + requestedResults.asScala.toSeq, + tick, + ctx.self, + ) + + Behaviors.same + case other => + ctx.log.warn(s"Cannot handle external result message: $other") + Behaviors.same + } + + case (ctx, msg: DelayedStopHelper.StoppingMsg) => + DelayedStopHelper.handleMsg((ctx, msg)) + + } +} diff --git a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala new file mode 100644 index 0000000000..245d7e2da2 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala @@ -0,0 +1,287 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.results + +import edu.ie3.datamodel.models.result.ResultEntity +import edu.ie3.datamodel.models.result.connector.Transformer3WResult +import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult +import edu.ie3.simona.event.ResultEvent +import edu.ie3.simona.event.ResultEvent.* +import edu.ie3.simona.event.listener.DelayedStopHelper +import edu.ie3.simona.ontology.messages.ResultMessage.{ + RequestResult, + ResultResponse, +} +import edu.ie3.simona.service.ServiceStateData.ServiceBaseStateData +import edu.ie3.simona.service.results.Transformer3wResultSupport.{ + AggregatedTransformer3wResult, + Transformer3wKey, +} +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import edu.ie3.simona.util.TickUtil.RichZonedDateTime +import org.apache.pekko.actor.typed.scaladsl.{Behaviors, StashBuffer} +import org.apache.pekko.actor.typed.{ActorRef, Behavior, PostStop} +import org.slf4j.Logger + +import java.time.ZonedDateTime +import java.util.UUID +import scala.util.{Failure, Success} + +object ResultServiceProxy { + + type Message = ResultEvent | RequestResult | ExpectResult | + DelayedStopHelper.StoppingMsg + + final case class ExpectResult(assets: UUID | Seq[UUID], tick: Long) + + private final case class ResultServiceStateData( + listeners: Seq[ActorRef[ResultResponse]], + simStartTime: ZonedDateTime, + currentTick: Long = INIT_SIM_TICK, + threeWindingResults: Map[ + Transformer3wKey, + AggregatedTransformer3wResult, + ] = Map.empty, + gridResults: Map[UUID, Iterable[ResultEntity]] = Map.empty, + results: Map[UUID, Iterable[ResultEntity]] = Map.empty, + waitingForResults: Map[UUID, Long] = Map.empty, + ) extends ServiceBaseStateData { + def notifyListener(results: Map[UUID, Iterable[ResultEntity]]): Unit = + listeners.foreach(_ ! ResultResponse(results)) + + def notifyListener(result: ResultEntity): Unit = + listeners.foreach( + _ ! ResultResponse(Map(result.getInputModel -> List(result))) + ) + + def isWaiting(uuids: Iterable[UUID], tick: Long): Boolean = { + uuids.exists { uuid => + waitingForResults.get(uuid) match { + case Some(nextTick) if nextTick <= tick => true + case _ => false + } + } + } + + def updateTick(tick: Long): ResultServiceStateData = + copy(currentTick = tick) + + def waitForResult(expectResult: ExpectResult): ResultServiceStateData = + expectResult.assets match { + case uuid: UUID => + copy(waitingForResults = + waitingForResults.updated(uuid, expectResult.tick) + ) + case uuids: Seq[UUID] => + val tick = expectResult.tick + + copy(waitingForResults = + waitingForResults ++ uuids.map(uuid => uuid -> tick).toMap + ) + } + + def addResult(result: ResultEntity): ResultServiceStateData = { + val uuid = result.getInputModel + val tick = result.getTime.toTick(using simStartTime) + + val updatedWaitingForResults = + if waitingForResults.get(uuid).contains(tick) then { + waitingForResults.removed(uuid) + } else waitingForResults + + val updatedResults = results.get(uuid) match { + case Some(values) => + val updatedValues = values + .map { value => value.getClass -> value } + .toMap + .updated(result.getClass, result) + .values + + results.updated(uuid, updatedValues) + + case None => + results.updated(uuid, Iterable(result)) + } + + copy( + results = updatedResults, + waitingForResults = updatedWaitingForResults, + ) + } + + def getResults(uuids: Seq[UUID]): Map[UUID, Iterable[ResultEntity]] = { + uuids.flatMap { uuid => + gridResults.get(uuid) match { + case Some(values) => + Some(uuid -> values) + case None => + results.get(uuid).map { res => uuid -> res } + } + }.toMap + } + + } + + def apply( + listeners: Seq[ActorRef[ResultResponse]], + simStartTime: ZonedDateTime, + bufferSize: Int = 10000, + ): Behavior[Message] = Behaviors.withStash(bufferSize) { buffer => + idle(ResultServiceStateData(listeners, simStartTime))(using buffer) + } + + private def idle( + stateData: ResultServiceStateData + )(using + buffer: StashBuffer[Message] + ): Behavior[Message] = Behaviors + .receivePartial[Message] { + case (_, expectResult: ExpectResult) => + idle(stateData.waitForResult(expectResult)) + + case (ctx, resultEvent: ResultEvent) => + // ctx.log.warn(s"Received results: $resultEvent") + + // handles the event and updates the state data + val updatedStateData = + handleResultEvent(resultEvent, stateData)(using ctx.log) + + // un-stash received requests + buffer.unstashAll(idle(updatedStateData)) + case (ctx, requestResultMessage: RequestResult) => + val requestedResults = requestResultMessage.requestedResults + val tick = requestResultMessage.tick + + if stateData.isWaiting(requestedResults, tick) then { + // ctx.log.warn(s"Cannot answer request: $requestedResults") + + buffer.stash(requestResultMessage) + } else { + + requestResultMessage.replyTo ! ResultResponse( + stateData.getResults(requestedResults) + ) + } + + Behaviors.same + + case (ctx, msg: DelayedStopHelper.StoppingMsg) => + DelayedStopHelper.handleMsg((ctx, msg)) + } + .receiveSignal { case (ctx, PostStop) => + ctx.log.debug( + "Shutdown initiated.\n\tThe following three winding results are not comprehensive and are not " + + "handled in sinks:{}\n\tWaiting until writing result data is completed ...", + stateData.threeWindingResults.keys + .map { case Transformer3wKey(model, zdt) => + s"model '$model' at $zdt" + } + .mkString("\n\t\t"), + ) + + Behaviors.same + } + + private def handleResultEvent( + resultEvent: ResultEvent, + stateData: ResultServiceStateData, + )(using log: Logger): ResultServiceStateData = resultEvent match { + case PowerFlowResultEvent( + nodeResults, + switchResults, + lineResults, + transformer2wResults, + partialTransformer3wResults, + congestionResults, + ) => + // handling of three winding transformers + val (updatedResults, transformer3wResults) = + handleThreeWindingTransformers( + partialTransformer3wResults, + stateData.threeWindingResults, + ) + + val gridResults = + (transformer3wResults ++ nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ congestionResults) + .groupBy(_.getInputModel) + + // notify listener + stateData.notifyListener(gridResults) + + stateData.copy( + gridResults = stateData.gridResults ++ gridResults, + threeWindingResults = updatedResults, + waitingForResults = + stateData.waitingForResults.removedAll(gridResults.keys), + ) + + case ParticipantResultEvent(systemParticipantResult) => + // notify listener + stateData.notifyListener(systemParticipantResult) + + stateData.addResult(systemParticipantResult) + + case ThermalResultEvent(thermalResult) => + // notify listener + stateData.notifyListener(thermalResult) + + stateData.addResult(thermalResult) + + case FlexOptionsResultEvent(flexOptionsResult) => + // notify listener + stateData.notifyListener(flexOptionsResult) + + stateData.addResult(flexOptionsResult) + } + + private def handleThreeWindingTransformers( + transformer3wResults: Iterable[PartialTransformer3wResult], + threeWindingResults: Map[Transformer3wKey, AggregatedTransformer3wResult], + )(using log: Logger) = transformer3wResults.foldLeft( + threeWindingResults, + Seq.empty[Transformer3WResult], + ) { case ((allPartialResults, allResults), result) => + val key = Transformer3wKey(result.input, result.time) + // retrieve existing partial result or use empty one + val partialResult = + allPartialResults.getOrElse( + key, + AggregatedTransformer3wResult.EMPTY, + ) + // add partial result + partialResult.add(result).map { updatedResult => + if updatedResult.ready then { + // if result is complete, we can write it out + updatedResult.consolidate match { + case Failure(exception) => + log.warn( + "Failure when handling partial Transformer3w result", + exception, + ) + // on failure, we just continue with previous results + (allPartialResults, allResults) + case Success(res) => + (allPartialResults.removed(key), allResults.appended(res)) + } + + } else { + // if result is not complete yet, just update it + (allPartialResults + (key -> updatedResult), allResults) + } + } match { + case Success(results) => results + case Failure(exception) => + log.warn( + "Failure when handling partial Transformer3w result", + exception, + ) + // on failure, we just continue with previous results + (allPartialResults, allResults) + } + } + +} diff --git a/src/main/scala/edu/ie3/simona/event/listener/Transformer3wResultSupport.scala b/src/main/scala/edu/ie3/simona/service/results/Transformer3wResultSupport.scala similarity index 97% rename from src/main/scala/edu/ie3/simona/event/listener/Transformer3wResultSupport.scala rename to src/main/scala/edu/ie3/simona/service/results/Transformer3wResultSupport.scala index 1065475d06..c6f9787823 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/Transformer3wResultSupport.scala +++ b/src/main/scala/edu/ie3/simona/service/results/Transformer3wResultSupport.scala @@ -4,19 +4,19 @@ * Research group Distribution grid planning and operation */ -package edu.ie3.simona.event.listener +package edu.ie3.simona.service.results import edu.ie3.datamodel.models.result.connector.Transformer3WResult import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult -import tech.units.indriya.quantity.Quantities import edu.ie3.util.quantities.PowerSystemUnits +import tech.units.indriya.quantity.Quantities import tech.units.indriya.unit.Units import java.time.ZonedDateTime import java.util.UUID import scala.util.{Failure, Success, Try} -private[listener] trait Transformer3wResultSupport { +private[results] object Transformer3wResultSupport { /** Case class to serve as a map key for unfulfilled three winding results * diff --git a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala index 9438cdd5b1..7987d2106e 100644 --- a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala +++ b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala @@ -26,8 +26,8 @@ import java.nio.file.Path * overall simulation has been successful or not. For specific status * information, the user needs to pass in and subscribe to the corresponding * listener e.g. [[edu.ie3.simona.event.listener.RuntimeEventListener]] for - * simulation status or [[edu.ie3.simona.event.listener.ResultEventListener]] - * for result events + * simulation status or [[edu.ie3.simona.event.listener.ResultListener]] for + * result events * * @since 01.07.20 */ @@ -70,8 +70,15 @@ object SimonaSim { ): Behavior[Request] = Behaviors .receivePartial[Request] { case (ctx, Start(_)) => - val resultEventListeners = simonaSetup.resultEventListener(ctx) val runtimeEventListener = simonaSetup.runtimeEventListener(ctx) + val resultEventListeners = simonaSetup.resultEventListener(ctx) + + // result proxy + val resultProxy = simonaSetup.resultServiceProxy( + ctx, + resultEventListeners, + simonaSetup.simonaConfig.simona.time.simStartTime, + ) val timeAdvancer = simonaSetup.timeAdvancer(ctx, ctx.self, runtimeEventListener) @@ -86,7 +93,11 @@ object SimonaSim { simonaSetup.simonaConfig.simona.input.extSimDir.map(Path.of(_)) val extSimulationData = - simonaSetup.extSimulations(ctx, scheduler, extSimDir) + simonaSetup.extSimulations(ctx, scheduler, resultProxy, extSimDir) + + val allResultEventListeners = + resultEventListeners ++ extSimulationData.resultListeners + val resultProviders = extSimulationData.resultProviders /* start services */ // primary service proxy @@ -104,17 +115,14 @@ object SimonaSim { scheduler, runtimeEventListener, primaryServiceProxy, + resultProxy, weatherService, loadProfileService, extSimulationData.evDataService, ) /* start grid agents */ - val gridAgents = simonaSetup.gridAgents( - ctx, - environmentRefs, - resultEventListeners, - ) + val gridAgents = simonaSetup.gridAgents(ctx, environmentRefs) val otherActors = Iterable[ActorRef[?]]( timeAdvancer, @@ -123,16 +131,15 @@ object SimonaSim { weatherService, ) ++ gridAgents ++ - extSimulationData.extDataServices.map(_._2) + extSimulationData.allServiceRefs /* watch all actors */ - resultEventListeners.foreach(ctx.watch) + allResultEventListeners.foreach(ctx.watch) + resultProviders.foreach(ctx.watch) ctx.watch(runtimeEventListener) - extSimulationData.extResultListeners.foreach { case (_, ref) => - ctx.watch(ref) - } - extSimulationData.extSimAdapters.foreach(ctx.watch) + ctx.watch(resultProxy) otherActors.foreach(ctx.watch) + extSimulationData.extSimAdapters.foreach(ctx.watch) // End pre-initialization phase preInitKey.unlock() @@ -140,11 +147,11 @@ object SimonaSim { // Start simulation timeAdvancer ! TimeAdvancer.Start - val delayedActors = resultEventListeners.appended(runtimeEventListener) - - extSimulationData.extResultListeners.foreach(ref => - delayedActors.appended(ref) - ) + val delayedActors = + allResultEventListeners + .appendedAll(resultProviders) + .appended(runtimeEventListener) + .appended(resultProxy) idle( ActorData( diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index 4563b5e88e..ed80751e6d 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -6,26 +6,26 @@ package edu.ie3.simona.sim.setup -import edu.ie3.simona.api.data.connection.{ - ExtEvDataConnection, - ExtInputDataConnection, - ExtPrimaryDataConnection, -} +import edu.ie3.simona.api.data.connection.* import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt import edu.ie3.simona.api.simulation.{ExtSimAdapterData, ExtSimulation} import edu.ie3.simona.api.{ExtLinkInterface, ExtSimAdapter} +import edu.ie3.simona.event.listener.ResultListener import edu.ie3.simona.exceptions.ServiceException import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} +import edu.ie3.simona.ontology.messages.ResultMessage.RequestResult import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceStateData.InitializeServiceStateData import edu.ie3.simona.service.ev.ExtEvDataService import edu.ie3.simona.service.ev.ExtEvDataService.InitExtEvData +import edu.ie3.simona.service.results.ExtResultProvider import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext import org.slf4j.{Logger, LoggerFactory} +import java.time.ZonedDateTime import java.util.UUID import scala.jdk.CollectionConverters.{ListHasAsScala, SetHasAsScala} import scala.util.{Failure, Success, Try} @@ -44,6 +44,10 @@ object ExtSimSetup { * The actor context of this actor system. * @param scheduler * The scheduler of simona. + * @param resultProxy + * The result service proxy. + * @param startTime + * The start time of the simulation. * @return * An [[ExtSimSetupData]] that holds information regarding the external * data connections as well as the actor references of the created @@ -55,6 +59,8 @@ object ExtSimSetup { )(using context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], + resultProxy: ActorRef[RequestResult], + startTime: ZonedDateTime, ): ExtSimSetupData = extLinks.zipWithIndex.foldLeft(ExtSimSetupData.apply) { case (extSimSetupData, (extLink, index)) => // external simulation always needs at least an ExtSimAdapter @@ -79,14 +85,14 @@ object ExtSimSetup { ) // setup data services that belong to this external simulation - val updatedSetupData = connect(extSimulation, extSimSetupData) + val updatedSetupData = connect(extSimulation, extSimSetupData, index) // starting external simulation new Thread(extSimulation, s"External simulation $index") .start() // updating the data with newly connected external simulation - updatedSetupData.update(extSimAdapter) + updatedSetupData.updateAdapter(extSimAdapter) } match { case Failure(exception) => log.warn( @@ -104,6 +110,8 @@ object ExtSimSetup { * To connect. * @param extSimSetupData * That contains information about all external simulations. + * @param index + * Index of the external link interface. * @param context * The actor context of this actor system. * @param scheduler @@ -116,10 +124,13 @@ object ExtSimSetup { private[setup] def connect( extSimulation: ExtSimulation, extSimSetupData: ExtSimSetupData, + index: Int, )(using context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], extSimAdapterData: ExtSimAdapterData, + resultProxy: ActorRef[RequestResult], + startTime: ZonedDateTime, ): ExtSimSetupData = { given extSimAdapter: ActorRef[ControlResponseMessageFromExt] = extSimAdapterData.getAdapter @@ -135,7 +146,7 @@ object ExtSimSetup { case (setupData, connection) => connection match { case extEvDataConnection: ExtEvDataConnection => - if setupData.evDataConnection.nonEmpty then { + if setupData.evDataService.nonEmpty then { throw ServiceException( s"Trying to connect another EvDataConnection. Currently only one is allowed." ) @@ -154,6 +165,31 @@ object ExtSimSetup { extSimSetupData.update(extEvDataConnection, serviceRef) + case extResultDataConnection: ExtResultDataConnection => + val extResultProvider = context.spawn( + ExtResultProvider( + extResultDataConnection, + scheduler, + resultProxy, + ), + s"ExtResultProvider", + ) + + extResultDataConnection.setActorRefs( + extResultProvider, + extSimAdapter, + ) + + extSimSetupData.update(extResultDataConnection, extResultProvider) + + case extResultListener: ExtResultListener => + val extResultEventListener = context.spawn( + ResultListener.external(extResultListener), + s"ExtResultListener_$index", + ) + + extSimSetupData.update(extResultListener, extResultEventListener) + case otherConnection => log.warn( s"There is currently no implementation for the connection: $otherConnection." diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index a08f71d1ab..0db9e5ef1b 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -8,7 +8,10 @@ package edu.ie3.simona.sim.setup import edu.ie3.simona.api.ExtSimAdapter import edu.ie3.simona.api.data.connection.* +import edu.ie3.simona.event.listener.ResultListener import edu.ie3.simona.ontology.messages.ServiceMessage +import edu.ie3.simona.service.ev.ExtEvDataService +import edu.ie3.simona.service.results.ExtResultProvider import org.apache.pekko.actor.typed.ActorRef /** Case class that holds information regarding the external data connections as @@ -16,82 +19,73 @@ import org.apache.pekko.actor.typed.ActorRef * * @param extSimAdapters * All adapters to external simulations. - * @param extPrimaryDataServices + * @param primaryDataServices * Seq: external primary data connections to service references. - * @param extDataServices - * Seq: external input data connection to service references. - * @param extResultListeners - * Map: external result data connections to result data providers. + * @param evDataService + * Option for an external ev data service. + * @param resultListeners + * Seq: external result listeners. + * @param resultProviders + * Seq: external result providers. */ final case class ExtSimSetupData( extSimAdapters: Iterable[ActorRef[ExtSimAdapter.Request]], - extPrimaryDataServices: Seq[ + primaryDataServices: Seq[ (ExtPrimaryDataConnection, ActorRef[ServiceMessage]) ], - extDataServices: Seq[ - (? <: ExtInputDataConnection[?], ActorRef[ServiceMessage]) - ], - extResultListeners: Seq[(ExtResultDataConnection, ActorRef[ServiceMessage])], + evDataService: Option[ActorRef[ExtEvDataService.Message]], + resultListeners: Seq[ActorRef[ResultListener.Message]], + resultProviders: Seq[ActorRef[ExtResultProvider.Message]], ) { private[setup] def update( connection: ExtPrimaryDataConnection, ref: ActorRef[ServiceMessage], ): ExtSimSetupData = - copy(extPrimaryDataServices = - extPrimaryDataServices ++ Seq((connection, ref)) - ) + copy(primaryDataServices = primaryDataServices ++ Seq((connection, ref))) private[setup] def update( - connection: ExtInputDataConnection[?], - ref: ActorRef[ServiceMessage], - ): ExtSimSetupData = connection match { - case primaryConnection: ExtPrimaryDataConnection => - update(primaryConnection, ref) - case _ => - copy(extDataServices = extDataServices ++ Seq((connection, ref))) + connection: ExtDataConnection, + ref: ActorRef[?], + ): ExtSimSetupData = (connection, ref) match { + case ( + primaryConnection: ExtPrimaryDataConnection, + serviceRef: ActorRef[ServiceMessage], + ) => + update(primaryConnection, serviceRef) + case ( + _: ExtEvDataConnection, + serviceRef: ActorRef[ExtEvDataService.Message], + ) => + copy(evDataService = Some(serviceRef)) + case ( + _: ExtResultDataConnection, + providerRef: ActorRef[ExtResultProvider.Message], + ) => + copy(resultProviders = resultProviders ++ Seq(providerRef)) + case ( + _: ExtResultListener, + listenerRef: ActorRef[ResultListener.Message], + ) => + copy(resultListeners = resultListeners ++ Seq(listenerRef)) + case (_, _) => + this } - private[setup] def update( - connection: ExtResultDataConnection, - ref: ActorRef[ServiceMessage], - ): ExtSimSetupData = - copy(extResultListeners = extResultListeners ++ Seq((connection, ref))) - - private[setup] def update( + private[setup] def updateAdapter( extSimAdapter: ActorRef[ExtSimAdapter.Request] ): ExtSimSetupData = copy(extSimAdapters = extSimAdapters ++ Set(extSimAdapter)) - def evDataService: Option[ActorRef[ServiceMessage]] = - extDataServices.collectFirst { - case (_: ExtEvDataConnection, ref: ActorRef[ServiceMessage]) => ref - } - - def emDataService: Option[ActorRef[ServiceMessage]] = - extDataServices.collectFirst { - case (_: ExtEmDataConnection, ref: ActorRef[ServiceMessage]) => ref - } - - def evDataConnection: Option[ExtEvDataConnection] = - extDataServices.collectFirst { case (connection: ExtEvDataConnection, _) => - connection - } - - def emDataConnection: Option[ExtEmDataConnection] = - extDataServices.collectFirst { case (connection: ExtEmDataConnection, _) => - connection - } - def primaryDataConnections: Seq[ExtPrimaryDataConnection] = - extPrimaryDataServices.map { - case (connection: ExtPrimaryDataConnection, _) => connection - } - - def resultDataConnections: Seq[ExtResultDataConnection] = - extResultListeners.map { case (connection: ExtResultDataConnection, _) => + primaryDataServices.map { case (connection: ExtPrimaryDataConnection, _) => connection } + + def allServiceRefs: Iterable[ActorRef[?]] = + primaryDataServices.map(_._2) ++ Seq( + evDataService + ).flatten ++ resultListeners ++ resultProviders } object ExtSimSetupData { @@ -101,6 +95,7 @@ object ExtSimSetupData { def apply: ExtSimSetupData = ExtSimSetupData( Iterable.empty, Seq.empty, + None, Seq.empty, Seq.empty, ) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala index 8398ba9533..0b4696ca3a 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -11,17 +11,23 @@ import edu.ie3.datamodel.models.input.connector.Transformer3WInput import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.config.SimonaConfig -import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} +import edu.ie3.simona.event.listener.{ResultListener, RuntimeEventListener} import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} +import edu.ie3.simona.ontology.messages.ResultMessage.{ + RequestResult, + ResultResponse, +} import edu.ie3.simona.scheduler.TimeAdvancer import edu.ie3.simona.scheduler.core.Core.CoreFactory import edu.ie3.simona.scheduler.core.RegularSchedulerCore +import edu.ie3.simona.service.results.ResultServiceProxy import edu.ie3.simona.sim.SimonaSim import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext import java.nio.file.Path +import java.time.ZonedDateTime /** Trait that can be used to set up a customized simona simulation by providing * implementations for all setup information required by a @@ -38,7 +44,7 @@ trait SimonaSetup { /** Main arguments of the executable. May be used to pass additional * configuration parameters to the setup e.g. for external simulation - * configuration + * configuration. */ val args: Array[String] @@ -46,41 +52,41 @@ trait SimonaSetup { */ def logOutputDir: Path - /** Creates the runtime event listener + /** Creates the runtime event listener. * * @param context - * Actor context to use + * Actor context to use. * @return - * An actor reference to the runtime event listener + * An actor reference to the runtime event listener. */ def runtimeEventListener( context: ActorContext[?] ): ActorRef[RuntimeEventListener.Request] - /** Creates a sequence of result event listeners + /** Creates a sequence of result event listeners. * * @param context - * Actor context to use + * Actor context to use. * @return - * A sequence of actor references to result event listeners + * A sequence of actor references to result event listeners. */ def resultEventListener( context: ActorContext[?] - ): Seq[ActorRef[ResultEventListener.Request]] + ): Seq[ActorRef[ResultListener.Message]] /** Creates a primary service proxy. The proxy is the first instance to ask * for primary data. If necessary, it delegates the registration request to * it's subordinate workers. * * @param context - * Actor context to use + * Actor context to use. * @param scheduler - * Actor reference to it's according scheduler to use + * Actor reference to it's according scheduler to use. * @param extSimSetupData - * that can contain external + * that can contain external. * [[edu.ie3.simona.api.data.primarydata.ExtPrimaryDataConnection]] * @return - * An actor reference to the service + * An actor reference to the service. */ def primaryServiceProxy( context: ActorContext[?], @@ -88,63 +94,88 @@ trait SimonaSetup { extSimSetupData: ExtSimSetupData, ): ActorRef[ServiceMessage] - /** Creates a weather service + /** Creates a result service proxy. The proxy will receive information about + * the result that should be expected for the current tick and all result + * events that are send by the agents. The proxy is responsible for + * processing the result events and passing the processed data to the + * different result listeners and providers. + * + * @param context + * Actor context to use. + * @param listeners + * The internal result event listeners. + * @param simStartTime + * The start time of the simulation. + * @return + * An actor reference to the service. + */ + def resultServiceProxy( + context: ActorContext[?], + listeners: Seq[ActorRef[ResultResponse]], + simStartTime: ZonedDateTime, + ): ActorRef[ResultServiceProxy.Message] + + /** Creates a weather service. * * @param context - * Actor context to use + * Actor context to use. * @param scheduler - * Actor reference to it's according scheduler to use + * Actor reference to it's according scheduler to use. * @return * An actor reference to the service as well as matching data to initialize - * the service + * the service. */ def weatherService( context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], ): ActorRef[ServiceMessage] - /** Creates a load profile service + /** Creates a load profile service. * * @param context - * Actor context to use + * Actor context to use. * @param scheduler - * Actor reference to it's according scheduler to use + * Actor reference to it's according scheduler to use. * @return * An actor reference to the service as well as matching data to initialize - * the service + * the service. */ def loadProfileService( context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], ): ActorRef[ServiceMessage] - /** Loads external simulations and provides corresponding actors and init data + /** Loads external simulations and provides corresponding actors and init + * data. * * @param context - * Actor context to use + * Actor context to use. * @param scheduler - * Actor reference to the scheduler to use + * Actor reference to the scheduler to use. + * @param resultProxy + * Actor reference to the result provider. * @param extSimPath - * option for a directory with external simulations + * Option for a directory with external simulations. * @return - * External simulations and their init data + * External simulations and their init data. */ def extSimulations( context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], + resultProxy: ActorRef[RequestResult], extSimPath: Option[Path], ): ExtSimSetupData - /** Creates the time advancer + /** Creates the time advancer. * * @param context - * Actor context to use + * Actor context to use. * @param simulation - * The simulation root actor ([[edu.ie3.simona.sim.SimonaSim]]) + * The simulation root actor ([[edu.ie3.simona.sim.SimonaSim]]). * @param runtimeEventListener - * Runtime event listener + * Runtime event listener. * @return - * An actor reference to the time advancer + * An actor reference to the time advancer. */ def timeAdvancer( context: ActorContext[?], @@ -152,17 +183,17 @@ trait SimonaSetup { runtimeEventListener: ActorRef[RuntimeEvent], ): ActorRef[TimeAdvancer.Request] - /** Creates a scheduler service + /** Creates a scheduler service. * * @param context - * Actor context to use + * Actor context to use. * @param parent - * The parent scheduler, which could be a time advancer + * The parent scheduler, which could be a time advancer. * @param coreFactory * The factory creating a scheduler core that determines the scheduler's - * behavior, defaulting to a regular scheduler + * behavior, defaulting to a regular scheduler. * @return - * An actor reference to the scheduler + * An actor reference to the scheduler. */ def scheduler( context: ActorContext[?], @@ -170,27 +201,24 @@ trait SimonaSetup { coreFactory: CoreFactory = RegularSchedulerCore, ): ActorRef[SchedulerMessage] - /** Creates all the needed grid agents + /** Creates all the needed grid agents. * * @param context - * Actor context to use + * Actor context to use. * @param environmentRefs - * EnvironmentRefs to use - * @param resultEventListeners - * Listeners that await events from system participants + * EnvironmentRefs to use. * @return * A mapping from actor reference to it's according initialization data to - * be used when setting up the agents + * be used when setting up the agents. */ def gridAgents( context: ActorContext[?], environmentRefs: EnvironmentRefs, - resultEventListeners: Seq[ActorRef[ResultEvent]], ): Iterable[ActorRef[GridAgent.Message]] /** SIMONA links sub grids connected by a three winding transformer a bit * different. Therefore, the internal node has to be set as superior node. - * All other gates are left unchanged + * All other gates are left unchanged. */ protected val modifySubGridGateForThreeWindingSupport : SubGridGate => SubGridGate = diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index 854094e44d..3eb44caf66 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -15,11 +15,15 @@ import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.agent.grid.GridAgentMessages.CreateGridAgent import edu.ie3.simona.config.{GridConfigParser, SimonaConfig} -import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} +import edu.ie3.simona.event.listener.{ResultListener, RuntimeEventListener} import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.exceptions.agent.GridAgentInitializationException import edu.ie3.simona.io.grid.GridProvider import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} +import edu.ie3.simona.ontology.messages.ResultMessage.{ + RequestResult, + ResultResponse, +} import edu.ie3.simona.scheduler.core.Core.CoreFactory import edu.ie3.simona.scheduler.core.RegularSchedulerCore import edu.ie3.simona.scheduler.{ScheduleLock, Scheduler, TimeAdvancer} @@ -27,6 +31,7 @@ import edu.ie3.simona.service.load.LoadProfileService import edu.ie3.simona.service.load.LoadProfileService.InitLoadProfileServiceStateData import edu.ie3.simona.service.primary.PrimaryServiceProxy import edu.ie3.simona.service.primary.PrimaryServiceProxy.InitPrimaryServiceProxyStateData +import edu.ie3.simona.service.results.ResultServiceProxy import edu.ie3.simona.service.weather.WeatherService import edu.ie3.simona.service.weather.WeatherService.InitWeatherServiceStateData import edu.ie3.simona.sim.SimonaSim @@ -39,6 +44,7 @@ import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext import java.nio.file.Path +import java.time.ZonedDateTime import java.util.UUID import java.util.concurrent.LinkedBlockingQueue import scala.jdk.CollectionConverters.* @@ -62,7 +68,6 @@ class SimonaStandaloneSetup( override def gridAgents( context: ActorContext[?], environmentRefs: EnvironmentRefs, - resultEventListeners: Seq[ActorRef[ResultEvent]], ): Iterable[ActorRef[GridAgent.Message]] = { /* get the grid */ @@ -85,7 +90,6 @@ class SimonaStandaloneSetup( subGridTopologyGraph, context, environmentRefs, - resultEventListeners, ) val keys = ScheduleLock.multiKey( @@ -164,6 +168,16 @@ class SimonaStandaloneSetup( primaryServiceProxy } + override def resultServiceProxy( + context: ActorContext[?], + listeners: Seq[ActorRef[ResultResponse]], + simStartTime: ZonedDateTime, + ): ActorRef[ResultServiceProxy.Message] = + context.spawn( + ResultServiceProxy(listeners, simStartTime), + "resultEventProxyAgent", + ) + override def weatherService( context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], @@ -211,6 +225,7 @@ class SimonaStandaloneSetup( override def extSimulations( context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], + resultProxy: ActorRef[RequestResult], extSimPath: Option[Path], ): ExtSimSetupData = { val jars = ExtSimLoader.scanInputFolder(extSimPath) @@ -219,6 +234,8 @@ class SimonaStandaloneSetup( setupExtSim(extLinks, args)(using context, scheduler, + resultProxy, + simonaConfig.simona.time.simStartTime, ) } @@ -227,12 +244,8 @@ class SimonaStandaloneSetup( simulation: ActorRef[SimonaSim.SimulationEnded.type], runtimeEventListener: ActorRef[RuntimeEvent], ): ActorRef[TimeAdvancer.Request] = { - val startDateTime = TimeUtil.withDefaults.toZonedDateTime( - simonaConfig.simona.time.startDateTime - ) - val endDateTime = TimeUtil.withDefaults.toZonedDateTime( - simonaConfig.simona.time.endDateTime - ) + val startDateTime = simonaConfig.simona.time.simStartTime + val endDateTime = simonaConfig.simona.time.simEndTime context.spawn( TimeAdvancer( @@ -271,15 +284,13 @@ class SimonaStandaloneSetup( override def resultEventListener( context: ActorContext[?] - ): Seq[ActorRef[ResultEventListener.Request]] = { + ): Seq[ActorRef[ResultListener.Message]] = { // append ResultEventListener as well to write raw output files Seq( context .spawn( - ResultEventListener( - resultFileHierarchy - ), - ResultEventListener.getClass.getSimpleName, + ResultListener(resultFileHierarchy), + ResultListener.getClass.getSimpleName, ) ) } @@ -288,7 +299,6 @@ class SimonaStandaloneSetup( subGridTopologyGraph: SubGridTopologyGraph, context: ActorContext[?], environmentRefs: EnvironmentRefs, - resultEventListeners: Seq[ActorRef[ResultEvent]], ): Map[Int, ActorRef[GridAgent.Message]] = { subGridTopologyGraph .vertexSet() @@ -299,7 +309,6 @@ class SimonaStandaloneSetup( GridAgent( environmentRefs, simonaConfig, - resultEventListeners, ), subGridContainer.getSubnet.toString, ) diff --git a/src/main/scala/edu/ie3/simona/util/CollectionUtils.scala b/src/main/scala/edu/ie3/simona/util/CollectionUtils.scala index e84e14efa1..31a8b585a9 100644 --- a/src/main/scala/edu/ie3/simona/util/CollectionUtils.scala +++ b/src/main/scala/edu/ie3/simona/util/CollectionUtils.scala @@ -10,9 +10,19 @@ 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 [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/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala index 2b1f679014..76b0d73ebc 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -31,10 +31,12 @@ import edu.ie3.simona.ontology.messages.ServiceMessage.{ SecondaryServiceRegistrationMessage, } import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.ontology.messages.ResultMessage.RequestResult import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.Data.SecondaryData.WeatherData import edu.ie3.simona.service.ServiceType import edu.ie3.simona.service.primary.PrimaryServiceProxy +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import edu.ie3.simona.service.weather.WeatherService.WeatherRegistrationData import edu.ie3.simona.service.weather.{WeatherDataType, WeatherService} import edu.ie3.simona.test.common.TestSpawnerTyped @@ -105,7 +107,8 @@ class EmAgentIT "having load, pv and storage agents connected" should { "be initialized correctly and run through some activations" in { val gridAgent = TestProbe[GridAgent.Message]("GridAgent") - val resultListener = TestProbe[ResultEvent]("ResultListener") + val resultServiceProxy = + TestProbe[ResultEvent | ExpectResult]("ResultServiceProxy") val primaryServiceProxy = TestProbe[PrimaryServiceProxy.Message]("PrimaryServiceProxy") val weatherService = TestProbe[WeatherService.Message]("WeatherService") @@ -114,8 +117,8 @@ class EmAgentIT given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryServiceProxy.ref, + resultServiceProxy = resultServiceProxy.ref, services = Map(ServiceType.WeatherService -> weatherService.ref), - resultListener = Iterable(resultListener.ref), ) val keys = ScheduleLock @@ -133,7 +136,7 @@ class EmAgentIT "PRIORITIZED", simulationStartDate, parent = Left(scheduler.ref), - listener = Iterable(resultListener.ref), + listener = resultServiceProxy.ref, ), "EmAgent", ) @@ -244,14 +247,14 @@ class EmAgentIT Some(7200), ) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 0L.toDateTime emResult.getP should equalWithTolerance(-0.00057340027.asMegaWatt) emResult.getQ should equalWithTolerance(-0.0018318880807.asMegaVar) } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(7200))) /* TICK 7200 @@ -275,14 +278,14 @@ class EmAgentIT Some(14400), ) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 7200.toDateTime emResult.getP should equalWithTolerance(0.asMegaWatt) emResult.getQ should equalWithTolerance(-0.00113292701968.asMegaVar) } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(13246))) /* TICK 13246 @@ -294,14 +297,14 @@ class EmAgentIT */ emAgentActivation ! Activation(13246) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 13246.toDateTime emResult.getP should equalWithTolerance(-0.00344685673.asMegaWatt) emResult.getQ should equalWithTolerance(-0.001132927.asMegaVar) } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(14400))) /* TICK 14400 @@ -328,14 +331,14 @@ class EmAgentIT emAgentActivation ! Activation(14400) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 14400.toDateTime emResult.getP should equalWithTolerance(0.asMegaWatt) emResult.getQ should equalWithTolerance(0.000065375.asMegaVar) } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(21600))) } } @@ -343,7 +346,8 @@ class EmAgentIT "having load, pv and heat pump agents connected" should { "be initialized correctly and run through some activations" in { val gridAgent = TestProbe[GridAgent.Message]("GridAgent") - val resultListener = TestProbe[ResultEvent]("ResultListener") + val resultServiceProxy = + TestProbe[ResultEvent | ExpectResult]("ResultServiceProxy") val primaryServiceProxy = TestProbe[PrimaryServiceProxy.Message]("PrimaryServiceProxy") val weatherService = TestProbe[WeatherService.Message]("WeatherService") @@ -352,8 +356,8 @@ class EmAgentIT given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryServiceProxy.ref, + resultServiceProxy = resultServiceProxy.ref, services = Map(ServiceType.WeatherService -> weatherService.ref), - resultListener = Iterable(resultListener.ref), ) val keys = ScheduleLock @@ -371,7 +375,7 @@ class EmAgentIT "PRIORITIZED", simulationStartDate, parent = Left(scheduler.ref), - listener = Iterable(resultListener.ref), + listener = resultServiceProxy.ref, ), "EmAgent1", ) @@ -502,14 +506,14 @@ class EmAgentIT ) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 0.toDateTime emResult.getP should equalWithTolerance(-0.0055734002706.asMegaWatt) emResult.getQ should equalWithTolerance(-0.0018318880807.asMegaVar) } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(7200))) /* TICK 7200 @@ -535,14 +539,14 @@ class EmAgentIT ) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 7200.toDateTime emResult.getP should equalWithTolerance(0.001403143271.asMegaWatt) emResult.getQ should equalWithTolerance(-0.00014809252.asMegaVar) } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(10800))) /* TICK 10800 @@ -569,14 +573,14 @@ class EmAgentIT ) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 10800.toDateTime emResult.getP should equalWithTolerance(0.0011098586291.asMegaWatt) emResult.getQ should equalWithTolerance(-0.000244490516.asMegaVar) } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(11000))) /* TICK 11000 @@ -603,14 +607,14 @@ class EmAgentIT ) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 11000.toDateTime emResult.getP should equalWithTolerance(0.00021037894.asMegaWatt) emResult.getQ should equalWithTolerance(0.0000691482.asMegaVar) } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(11500))) /* TICK 11500 @@ -637,14 +641,14 @@ class EmAgentIT ) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 11500.toDateTime emResult.getP should equalWithTolerance(0.00013505248.asMegaWatt) emResult.getQ should equalWithTolerance(0.000044389603878.asMegaVar) } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(28800))) } } @@ -652,7 +656,8 @@ class EmAgentIT "having a pv and a load agent connected" should { "have correct values also for agents with limited operation time" in { val gridAgent = TestProbe[GridAgent.Message]("GridAgent") - val resultListener = TestProbe[ResultEvent]("ResultListener") + val resultServiceProxy = + TestProbe[ResultEvent | ExpectResult]("ResultServiceProxy") val primaryServiceProxy = TestProbe[PrimaryServiceProxy.Message]("PrimaryServiceProxy") val weatherService = TestProbe[WeatherService.Message]("WeatherService") @@ -661,8 +666,8 @@ class EmAgentIT given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryServiceProxy.ref, + resultServiceProxy = resultServiceProxy.ref, services = Map(ServiceType.WeatherService -> weatherService.ref), - resultListener = Iterable(resultListener.ref), ) val keys = ScheduleLock @@ -680,7 +685,7 @@ class EmAgentIT "PRIORITIZED", simulationStartDate, parent = Left(scheduler.ref), - listener = Iterable(resultListener.ref), + listener = resultServiceProxy.ref, ), "EmAgentReactivePower", ) @@ -782,14 +787,14 @@ class EmAgentIT ) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 0.toDateTime emResult.getP should equalWithTolerance(0.000268603.asMegaWatt) emResult.getQ should equalWithTolerance(0.0000882855367.asMegaVar) } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(3600))) /* TICK 3600 @@ -813,14 +818,14 @@ class EmAgentIT ) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 3600.toDateTime emResult.getP should equalWithTolerance(0.000268603.asMegaWatt) emResult.getQ should equalWithTolerance(0.0000882855367.asMegaVar) } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(7200))) /* TICK 7200 @@ -844,14 +849,14 @@ class EmAgentIT emAgentActivation ! Activation(7200) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 7200.toDateTime emResult.getP should equalWithTolerance(-0.008423564.asMegaWatt) emResult.getQ should equalWithTolerance(-0.0027686916118.asMegaVar) } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(10800))) /* TICK 10800 @@ -860,14 +865,14 @@ class EmAgentIT -> expect P and Q values of PV */ emAgentActivation ! Activation(10800) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 10800.toDateTime emResult.getP should equalWithTolerance(-0.008692167.asMegaWatt) emResult.getQ should equalWithTolerance(-0.00285697715.asMegaVar) } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(14400))) /* TICK 14400 @@ -876,14 +881,14 @@ class EmAgentIT -> expect P: 0 W Q: 0 var */ emAgentActivation ! Activation(14400) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 14400.toDateTime emResult.getP should equalWithTolerance(0.asMegaWatt) emResult.getQ should equalWithTolerance(0.asMegaVar) } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, None)) } } diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentSpec.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentSpec.scala index 80bd7813cf..ec9e43640e 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentSpec.scala @@ -67,7 +67,7 @@ class EmAgentSpec "A self-optimizing EM agent" should { "be initialized correctly and run through some activations" in { - val resultListener = TestProbe[ResultEvent]("ResultListener") + val resultProxy = TestProbe[ResultEvent]("ResultProxy") val scheduler = TestProbe[SchedulerMessage]("Scheduler") val emAgent = spawn( @@ -78,7 +78,7 @@ class EmAgentSpec "PRIORITIZED", simulationStart, parent = Left(scheduler.ref), - listener = Iterable(resultListener.ref), + listener = resultProxy.ref, ) ) @@ -116,7 +116,7 @@ class EmAgentSpec ) // expect no results for init - resultListener.expectNoMessage() + resultProxy.expectNoMessage() // expect completion from EmAgent scheduler.expectMessage( Completion(emAgent, Some(0)) @@ -178,7 +178,7 @@ class EmAgentSpec ) // expect correct results - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(flexResult) => flexResult.getInputModel shouldBe emInput.getUuid flexResult.getTime shouldBe 0.toDateTime @@ -187,7 +187,7 @@ class EmAgentSpec flexResult.getpMax() should equalWithTolerance(.006.asMegaWatt) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe simulationStart @@ -232,7 +232,7 @@ class EmAgentSpec emAgent ! FlexCompletion(modelUuid = evcsInput.getUuid) // expect correct results - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(flexResult) => flexResult.getInputModel shouldBe emInput.getUuid flexResult.getTime shouldBe 300.toDateTime @@ -241,7 +241,7 @@ class EmAgentSpec flexResult.getpMax() should equalWithTolerance(-.005.asMegaWatt) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 300.toDateTime @@ -255,7 +255,7 @@ class EmAgentSpec } "revoke triggers correctly" in { - val resultListener = TestProbe[ResultEvent]("ResultListener") + val resultProxy = TestProbe[ResultEvent]("ResultProxy") val scheduler = TestProbe[SchedulerMessage]("Scheduler") val emAgent = spawn( @@ -266,7 +266,7 @@ class EmAgentSpec "PRIORITIZED", simulationStart, parent = Left(scheduler.ref), - listener = Iterable(resultListener.ref), + listener = resultProxy.ref, ) ) @@ -344,7 +344,7 @@ class EmAgentSpec ) // expect correct results - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(flexResult) => flexResult.getInputModel shouldBe emInput.getUuid flexResult.getTime shouldBe 0.toDateTime @@ -353,7 +353,7 @@ class EmAgentSpec flexResult.getpMax() should equalWithTolerance(.006.asMegaWatt) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe simulationStart @@ -414,7 +414,7 @@ class EmAgentSpec ) // expect correct results - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(flexResult) => flexResult.getInputModel shouldBe emInput.getUuid flexResult.getTime shouldBe 300.toDateTime @@ -423,7 +423,7 @@ class EmAgentSpec flexResult.getpMax() should equalWithTolerance(.008.asMegaWatt) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 300.toDateTime @@ -438,7 +438,7 @@ class EmAgentSpec } "handle ChangingFlexOptions indicator correctly" in { - val resultListener = TestProbe[ResultEvent]("ResultListener") + val resultProxy = TestProbe[ResultEvent]("ResultProxy") val scheduler = TestProbe[SchedulerMessage]("Scheduler") val emAgent = spawn( @@ -449,7 +449,7 @@ class EmAgentSpec "PRIORITIZED", simulationStart, parent = Left(scheduler.ref), - listener = Iterable(resultListener.ref), + listener = resultProxy.ref, ) ) @@ -528,7 +528,7 @@ class EmAgentSpec requestAtTick = Some(600), ) - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(flexResult) => flexResult.getInputModel shouldBe emInput.getUuid flexResult.getTime shouldBe 0.toDateTime @@ -537,7 +537,7 @@ class EmAgentSpec flexResult.getpMax() should equalWithTolerance(.006.asMegaWatt) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 0.toDateTime @@ -607,7 +607,7 @@ class EmAgentSpec ) // expect correct results - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(flexResult) => flexResult.getInputModel shouldBe emInput.getUuid flexResult.getTime shouldBe 300.toDateTime @@ -616,7 +616,7 @@ class EmAgentSpec flexResult.getpMax() should equalWithTolerance(.008.asMegaWatt) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 300.toDateTime @@ -632,8 +632,7 @@ class EmAgentSpec "An EM-controlled EM agent" should { "be initialized correctly and run through some activations" in { - val resultListener = TestProbe[ResultEvent]("ResultListener") - + val resultProxy = TestProbe[ResultEvent]("ResultProxy") val parentEmAgent = TestProbe[FlexResponse]("ParentEmAgent") val emAgent = spawn( @@ -644,7 +643,7 @@ class EmAgentSpec "PRIORITIZED", simulationStart, parent = Right(parentEmAgent.ref), - listener = Iterable(resultListener.ref), + listener = resultProxy.ref, ) ) @@ -685,7 +684,7 @@ class EmAgentSpec ) // expect no results for init - resultListener.expectNoMessage() + resultProxy.expectNoMessage() // expect completion from EmAgent parentEmAgent.expectMessage( FlexCompletion( @@ -723,7 +722,7 @@ class EmAgentSpec ), ) - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(flexResult) => flexResult.getInputModel shouldBe emInput.getUuid flexResult.getTime shouldBe 0.toDateTime @@ -780,7 +779,7 @@ class EmAgentSpec ) // expect correct results - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 0.toDateTime @@ -830,7 +829,7 @@ class EmAgentSpec ) // expect correct results - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 150.toDateTime diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala index a633cb5c4c..cd93c5eea5 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala @@ -25,6 +25,8 @@ import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.load.LoadProfileService import edu.ie3.simona.service.primary.PrimaryServiceProxy +import edu.ie3.simona.service.results.ResultServiceProxy +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import edu.ie3.simona.service.weather.WeatherService import edu.ie3.simona.test.common.model.grid.DbfsTestGrid import edu.ie3.simona.test.common.{ConfigTestData, TestSpawnerTyped} @@ -37,6 +39,7 @@ import org.apache.pekko.actor.testkit.typed.scaladsl.{ import squants.electro.Kilovolts import squants.energy.Megawatts +import java.util.UUID import scala.language.postfixOps /** Test to ensure the functions that a [[GridAgent]] in center position should @@ -60,6 +63,7 @@ class DBFSAlgorithmCenGridSpec ) private val primaryService = TestProbe[PrimaryServiceProxy.Message]("primaryService") + private val resultProxy = TestProbe[ResultServiceProxy.Message]("resultProxy") private val weatherService = TestProbe[WeatherService.Message]("weatherService") private val loadProfileService = @@ -85,23 +89,16 @@ class DBFSAlgorithmCenGridSpec scheduler = scheduler.ref, runtimeEventListener = runtimeEvents.ref, primaryServiceProxy = primaryService.ref, + resultProxy = resultProxy.ref, weather = weatherService.ref, loadProfiles = loadProfileService.ref, evDataService = None, ) - val resultListener: TestProbe[ResultEvent] = TestProbe("resultListener") - "A GridAgent actor in center position with async test" should { val centerGridAgent = - testKit.spawn( - GridAgent( - environmentRefs, - simonaConfig, - listener = Iterable(resultListener.ref), - ) - ) + testKit.spawn(GridAgent(environmentRefs, simonaConfig)) s"initialize itself when it receives an init activation" in { @@ -156,6 +153,17 @@ class DBFSAlgorithmCenGridSpec // send the start grid simulation trigger centerGridAgent ! Activation(3600) + resultProxy.expectMessageType[ExpectResult] match { + case ExpectResult(assets, tick) => + assets match { + case uuids: Seq[UUID] => + uuids.toSet shouldBe assetsHv.toSet + case uuid: UUID => + fail(s"Received uuid $uuid, but expected grid asset uuids.") + } + tick shouldBe 3600 + } + /* We expect one grid power request message per inferior grid */ val firstPowerRequestSender11 = inferiorGrid11.expectGridPowerRequest() @@ -441,7 +449,7 @@ class DBFSAlgorithmCenGridSpec // after all grids have received a FinishGridSimulationTrigger, the scheduler should receive a Completion scheduler.expectMessageType[Completion].newTick shouldBe Some(7200) - val resultMessage = resultListener.expectMessageType[ResultEvent] + val resultMessage = resultProxy.expectMessageType[ResultEvent] resultMessage match { case powerFlowResultEvent: PowerFlowResultEvent => // we expect results for 4 nodes, 5 lines and 2 transformer2ws diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala index ad6e11f740..fd3cea9f00 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala @@ -14,7 +14,7 @@ import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ ExchangePower, ExchangeVoltage, } -import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} +import edu.ie3.simona.event.RuntimeEvent import edu.ie3.simona.model.grid.{RefSystem, VoltageLimits} import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, @@ -24,6 +24,8 @@ import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.load.LoadProfileService import edu.ie3.simona.service.primary.PrimaryServiceProxy +import edu.ie3.simona.service.results.ResultServiceProxy +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import edu.ie3.simona.service.weather.WeatherService import edu.ie3.simona.test.common.model.grid.DbfsTestGrid import edu.ie3.simona.test.common.{ConfigTestData, TestSpawnerTyped} @@ -38,6 +40,7 @@ import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import squants.electro.Kilovolts import squants.energy.Megawatts +import java.util.UUID import scala.concurrent.duration.DurationInt import scala.language.postfixOps @@ -54,6 +57,7 @@ class DBFSAlgorithmFailedPowerFlowSpec ) private val primaryService = TestProbe[PrimaryServiceProxy.Message]("primaryService") + private val resultProxy = TestProbe[ResultServiceProxy.Message]("resultProxy") private val weatherService = TestProbe[WeatherService.Message]("weatherService") private val loadProfileService = @@ -71,26 +75,19 @@ class DBFSAlgorithmFailedPowerFlowSpec scheduler = scheduler.ref, runtimeEventListener = runtimeEvents.ref, primaryServiceProxy = primaryService.ref, + resultProxy = resultProxy.ref, weather = weatherService.ref, loadProfiles = loadProfileService.ref, evDataService = None, ) - val resultListener: TestProbe[ResultEvent] = TestProbe("resultListener") - "A GridAgent actor in center position with async test" should { // since the grid agent is stopped after a failed power flow // we need to initialize the agent for each test def initAndGoToSimulateGrid: ActorRef[GridAgent.Message] = { val centerGridAgent = - testKit.spawn( - GridAgent( - environmentRefs, - simonaConfig, - listener = Iterable(resultListener.ref), - ) - ) + testKit.spawn(GridAgent(environmentRefs, simonaConfig)) // this subnet has 1 superior grid (ehv) and 3 inferior grids (mv). Map the gates to test probes accordingly val subGridGateToActorRef = hvSubGridGatesPF.map { @@ -143,6 +140,18 @@ class DBFSAlgorithmFailedPowerFlowSpec // send the start grid simulation trigger centerGridAgent ! Activation(3600) + + resultProxy.expectMessageType[ExpectResult] match { + case ExpectResult(assets, tick) => + assets match { + case uuids: Seq[UUID] => + uuids.toSet shouldBe assetsHvPF.toSet + case uuid: UUID => + fail(s"Received uuid $uuid, but expected grid asset uuids.") + } + tick shouldBe 3600 + } + // we expect a request for grid power values here for sweepNo $sweepNo val powerRequestSender = inferiorGridAgent.expectGridPowerRequest() @@ -213,7 +222,7 @@ class DBFSAlgorithmFailedPowerFlowSpec // after all grids have received a FinishGridSimulationTrigger, the scheduler should receive a Completion scheduler.expectMessageType[Completion].newTick shouldBe Some(7200) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() // PowerFlowFailed events are only sent by the slack subgrid runtimeEvents.expectNoMessage() @@ -227,6 +236,17 @@ class DBFSAlgorithmFailedPowerFlowSpec // send the start grid simulation trigger centerGridAgent ! Activation(3600) + resultProxy.expectMessageType[ExpectResult] match { + case ExpectResult(assets, tick) => + assets match { + case uuids: Seq[UUID] => + uuids.toSet shouldBe assetsHvPF.toSet + case uuid: UUID => + fail(s"Received uuid $uuid, but expected grid asset uuids.") + } + tick shouldBe 3600 + } + // we expect a request for grid power values here for sweepNo 0 val powerRequestSender = inferiorGridAgent.expectGridPowerRequest() @@ -285,7 +305,7 @@ class DBFSAlgorithmFailedPowerFlowSpec // after all grids have received a FinishGridSimulationTrigger, the scheduler should receive a Completion scheduler.expectMessageType[Completion].newTick shouldBe Some(7200) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() // PowerFlowFailed events are only sent by the slack subgrid runtimeEvents.expectNoMessage() @@ -302,7 +322,6 @@ class DBFSAlgorithmFailedPowerFlowSpec GridAgent( environmentRefs, simonaConfig, // stopOnFailure is enabled - listener = Iterable(resultListener.ref), ) ) @@ -341,6 +360,17 @@ class DBFSAlgorithmFailedPowerFlowSpec // send the start grid simulation trigger slackGridAgent ! Activation(3600) + resultProxy.expectMessageType[ExpectResult] match { + case ExpectResult(assets, tick) => + assets match { + case uuids: Seq[UUID] => + uuids.toSet shouldBe assetsEhv.toSet + case uuid: UUID => + fail(s"Received uuid $uuid, but expected grid asset uuids.") + } + tick shouldBe 3600 + } + val powerRequestSender = hvGridAgent.expectGridPowerRequest() // normally the inferior grid agents ask for the slack voltage as well to run their power flow calculation @@ -376,7 +406,7 @@ class DBFSAlgorithmFailedPowerFlowSpec hvGridAgent.gaProbe.expectNoMessage() scheduler.expectNoMessage() - resultListener.expectNoMessage() + resultProxy.expectNoMessage() } } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala index cb6c89a22e..ebe77c7411 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala @@ -26,6 +26,7 @@ import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.load.LoadProfileService import edu.ie3.simona.service.primary.PrimaryServiceProxy +import edu.ie3.simona.service.results.ResultServiceProxy import edu.ie3.simona.service.weather.WeatherService import edu.ie3.simona.test.common.model.grid.DbfsTestGridWithParticipants import edu.ie3.simona.test.common.{ConfigTestData, TestSpawnerTyped} @@ -53,6 +54,7 @@ class DBFSAlgorithmParticipantSpec TestProbe("runtimeEvents") private val primaryService = TestProbe[PrimaryServiceProxy.Message]("primaryService") + private val resultProxy = TestProbe[ResultServiceProxy.Message]("resultProxy") private val weatherService = TestProbe[WeatherService.Message]("weatherService") private val loadProfileService = @@ -62,14 +64,12 @@ class DBFSAlgorithmParticipantSpec scheduler = scheduler.ref, runtimeEventListener = runtimeEvents.ref, primaryServiceProxy = primaryService.ref, + resultProxy = resultProxy.ref, weather = weatherService.ref, loadProfiles = loadProfileService.ref, evDataService = None, ) - protected val resultListener: TestProbe[ResultEvent] = - TestProbe("resultListener") - private val superiorGridAgent = SuperiorGA( TestProbe("superiorGridAgent_1000"), Seq(supNodeA.getUuid), @@ -80,7 +80,6 @@ class DBFSAlgorithmParticipantSpec GridAgent( environmentRefs, simonaConfig, - Iterable(resultListener.ref), ) ) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala index e24b668a95..8d94e25d11 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala @@ -23,6 +23,8 @@ import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.load.LoadProfileService import edu.ie3.simona.service.primary.PrimaryServiceProxy +import edu.ie3.simona.service.results.ResultServiceProxy +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import edu.ie3.simona.service.weather.WeatherService import edu.ie3.simona.test.common.model.grid.DbfsTestGrid import edu.ie3.simona.test.common.{ConfigTestData, TestSpawnerTyped, UnitSpec} @@ -55,6 +57,7 @@ class DBFSAlgorithmSupGridSpec private val runtimeEvents = TestProbe[RuntimeEvent]("runtimeEvents") private val primaryService = TestProbe[PrimaryServiceProxy.Message]("primaryService") + private val resultProxy = TestProbe[ResultServiceProxy.Message]("resultProxy") private val weatherService = TestProbe[WeatherService.Message]("weatherService") private val loadProfileService = @@ -65,19 +68,17 @@ class DBFSAlgorithmSupGridSpec scheduler = scheduler.ref, runtimeEventListener = runtimeEvents.ref, primaryServiceProxy = primaryService.ref, + resultProxy = resultProxy.ref, weather = weatherService.ref, loadProfiles = loadProfileService.ref, evDataService = None, ) - val resultListener: TestProbe[ResultEvent] = TestProbe("resultListener") - "A GridAgent actor in superior position with async test" should { val superiorGridAgentFSM: ActorRef[GridAgent.Message] = testKit.spawn( GridAgent( environmentRefs, simonaConfig, - listener = Iterable(resultListener.ref), ) ) @@ -161,8 +162,9 @@ class DBFSAlgorithmSupGridSpec case Completion(_, Some(3600)) => // we expect another completion message when the agent is in SimulateGrid again case Completion(_, Some(7200)) => + resultProxy.expectMessageType[ExpectResult] // agent should be in Idle again and listener should contain power flow result data - val resultMessage = resultListener.expectMessageType[ResultEvent] + val resultMessage = resultProxy.expectMessageType[ResultEvent] resultMessage match { case powerFlowResultEvent: PowerFlowResultEvent => @@ -281,9 +283,9 @@ class DBFSAlgorithmSupGridSpec // when we received a FinishGridSimulationTrigger (as inferior grid agent) // we expect another completion message then as well (scheduler view) case Completion(_, Some(7200)) => + resultProxy.expectMessageType[ExpectResult] // after doing cleanup stuff, our agent should go back to idle again and listener should contain power flow result data - val resultMessage = - resultListener.expectMessageType[ResultEvent] + val resultMessage = resultProxy.expectMessageType[ResultEvent] resultMessage match { case powerFlowResultEvent: PowerFlowResultEvent => diff --git a/src/test/scala/edu/ie3/simona/agent/grid/GridAgentSetupSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/GridAgentSetupSpec.scala index 39d0b02811..1c9f2657f8 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/GridAgentSetupSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/GridAgentSetupSpec.scala @@ -39,7 +39,6 @@ class GridAgentSetupSpec gridContainer.getSubGridTopologyGraph, ctx, mock[EnvironmentRefs], - Seq.empty, ) Behaviors.stopped @@ -66,7 +65,6 @@ class GridAgentSetupSpec threeWindingTestGrid.getSubGridTopologyGraph, ctx, mock[EnvironmentRefs], - Seq.empty, ) Behaviors.stopped diff --git a/src/test/scala/edu/ie3/simona/agent/grid/ThermalGridIT.scala b/src/test/scala/edu/ie3/simona/agent/grid/ThermalGridIT.scala index 51148bfda6..f5348044c5 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/ThermalGridIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/ThermalGridIT.scala @@ -31,10 +31,12 @@ import edu.ie3.simona.ontology.messages.ServiceMessage.{ SecondaryServiceRegistrationMessage, } import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.ontology.messages.ResultMessage.RequestResult import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.Data.SecondaryData.WeatherData import edu.ie3.simona.service.ServiceType import edu.ie3.simona.service.primary.PrimaryServiceProxy +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import edu.ie3.simona.service.weather.WeatherService.WeatherRegistrationData import edu.ie3.simona.service.weather.{WeatherDataType, WeatherService} import edu.ie3.simona.test.common.input.EmInputTestData @@ -110,7 +112,8 @@ class ThermalGridIT ) val gridAgent = TestProbe[GridAgent.Message]("GridAgent") - val resultListener = TestProbe[ResultEvent]("ResultListener") + val resultServiceProxy = + TestProbe[ResultEvent | ExpectResult]("ResultProxy") val scheduler: TestProbe[SchedulerMessage] = TestProbe("scheduler") val primaryServiceProxy = TestProbe[PrimaryServiceProxy.Message]("PrimaryServiceProxy") @@ -119,8 +122,8 @@ class ThermalGridIT given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryServiceProxy.ref, + resultServiceProxy = resultServiceProxy.ref, services = Map(ServiceType.WeatherService -> weatherService.ref), - resultListener = Iterable(resultListener.ref), ) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, PRE_INIT_TICK) @@ -179,7 +182,7 @@ class ThermalGridIT val weatherDependentAgents = Seq(hpAgent) scheduler.expectMessage(Completion(heatPumpAgent, Some(0))) - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() /* TICK 0 Start of Simulation @@ -205,7 +208,7 @@ class ThermalGridIT Range(0, 3) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -240,7 +243,7 @@ class ThermalGridIT energy should equalWithTolerance(0.asMegaWattHour) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(heatPumpAgent, Some(3416))) /* TICK 3416 @@ -253,7 +256,7 @@ class ThermalGridIT Range(0, 3) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -290,7 +293,7 @@ class ThermalGridIT energy should equalWithTolerance(0.01044.asMegaWattHour) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(heatPumpAgent, Some(3600))) /* TICK 3600 @@ -315,7 +318,7 @@ class ThermalGridIT ) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(hpResult) => hpResult.getInputModel shouldBe typicalHpInputModel.getUuid hpResult.getTime shouldBe 3600.toDateTime @@ -328,7 +331,7 @@ class ThermalGridIT // Since this activation is caused by new weather data, we don't expect any // message for house or heat storage since there is no change of their operating // point nor one of it reached any boundary. - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(heatPumpAgent, Some(4412))) /* TICK 4412 @@ -341,7 +344,7 @@ class ThermalGridIT Range(0, 2) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -368,7 +371,7 @@ class ThermalGridIT ) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(heatPumpAgent, Some(21600))) /* TICK 21600 @@ -394,7 +397,7 @@ class ThermalGridIT ) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(hpResult) => hpResult.getInputModel shouldBe typicalHpInputModel.getUuid hpResult.getTime shouldBe 21600.toDateTime @@ -405,7 +408,7 @@ class ThermalGridIT // Since this activation is caused by new weather data, we don't expect any // message for house or heat storage since there is no change of their operating // point nor one of it reached any boundary. - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(heatPumpAgent, Some(23288))) /* TICK 23288 @@ -418,7 +421,7 @@ class ThermalGridIT Range(0, 3) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -455,7 +458,7 @@ class ThermalGridIT energy should equalWithTolerance(0.01044.asMegaWattHour) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(heatPumpAgent, Some(25000))) /* TICK 25000 @@ -481,7 +484,7 @@ class ThermalGridIT ) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(hpResult) => hpResult.getInputModel shouldBe typicalHpInputModel.getUuid hpResult.getTime shouldBe 25000.toDateTime @@ -492,7 +495,7 @@ class ThermalGridIT // Since this activation is caused by new weather data, we don't expect any // message for house or heat storage since there is no change of their operating // point nor one of it reached any boundary. - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(heatPumpAgent, Some(26887))) /* TICK 26887 @@ -505,7 +508,7 @@ class ThermalGridIT Range(0, 3) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -542,7 +545,7 @@ class ThermalGridIT energy should equalWithTolerance(0.asMegaWattHour) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(heatPumpAgent, Some(28000))) /* TICK 28000 @@ -566,7 +569,7 @@ class ThermalGridIT Some(151200), ) } - resultListener.expectMessageType[ParticipantResultEvent] match { + resultServiceProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(hpResult) => hpResult.getInputModel shouldBe typicalHpInputModel.getUuid hpResult.getTime shouldBe 28000.toDateTime @@ -577,7 +580,7 @@ class ThermalGridIT // Since this activation is caused by new weather data, we don't expect any // message for house or storage since there is no change of their operating // point nor one of it reached any boundary. - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(heatPumpAgent, Some(32043))) /* TICK 32043 @@ -590,7 +593,7 @@ class ThermalGridIT Range(0, 3) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -627,7 +630,7 @@ class ThermalGridIT energy should equalWithTolerance(0.asMegaWattHour) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(heatPumpAgent, Some(35459))) /* TICK 35459 @@ -640,7 +643,7 @@ class ThermalGridIT Range(0, 3) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -677,7 +680,7 @@ class ThermalGridIT energy should equalWithTolerance(0.01044.asMegaWattHour) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(heatPumpAgent, Some(35995))) /* TICK 35995 @@ -690,7 +693,7 @@ class ThermalGridIT Range(0, 2) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -715,7 +718,7 @@ class ThermalGridIT indoorTemperature should equalWithTolerance(20.asDegreeCelsius) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(heatPumpAgent, Some(74629))) } } @@ -735,7 +738,8 @@ class ThermalGridIT ) val gridAgent = TestProbe[GridAgent.Message]("GridAgent") - val resultListener: TestProbe[ResultEvent] = TestProbe("resultListener") + val resultServiceProxy: TestProbe[ResultEvent | ExpectResult] = + TestProbe("resultServiceProxy") val scheduler: TestProbe[SchedulerMessage] = TestProbe("scheduler") val primaryServiceProxy = TestProbe[PrimaryServiceProxy.Message]("PrimaryServiceProxy") @@ -744,8 +748,8 @@ class ThermalGridIT given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryServiceProxy.ref, + resultServiceProxy = resultServiceProxy.ref, services = Map(ServiceType.WeatherService -> weatherService.ref), - resultListener = Iterable(resultListener.ref), ) val keys = ScheduleLock @@ -763,7 +767,7 @@ class ThermalGridIT "PRIORITIZED", simulationStartWithPv, parent = Left(scheduler.ref), - listener = Iterable(resultListener.ref), + listener = resultServiceProxy.ref, ), "EmAgent", ) @@ -860,7 +864,7 @@ class ThermalGridIT weatherService.ref, 0L, ) - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(0))) val weatherDependentAgents = Seq(hpAgent.toClassic, pvAgent.toClassic) @@ -890,7 +894,7 @@ class ThermalGridIT Range(0, 4) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -930,7 +934,7 @@ class ThermalGridIT energy should equalWithTolerance(0.asMegaWattHour) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(1800))) /* TICK 1800 @@ -958,7 +962,7 @@ class ThermalGridIT Range(0, 3) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -993,7 +997,7 @@ class ThermalGridIT energy should equalWithTolerance(0.asMegaWattHour) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(5216))) /* TICK 5216 @@ -1007,7 +1011,7 @@ class ThermalGridIT Range(0, 4) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1050,7 +1054,7 @@ class ThermalGridIT energy should equalWithTolerance(0.01044.asMegaWattHour) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(5400))) /* TICK 5400 @@ -1078,7 +1082,7 @@ class ThermalGridIT Range(0, 4) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1120,7 +1124,7 @@ class ThermalGridIT energy should equalWithTolerance(0.01044.asMegaWattHour) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(6824))) /* TICK 6824 @@ -1134,7 +1138,7 @@ class ThermalGridIT Range(0, 4) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1176,7 +1180,7 @@ class ThermalGridIT energy should equalWithTolerance(0.0063104.asMegaWattHour) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(9200))) /* TICK 9200 @@ -1205,7 +1209,7 @@ class ThermalGridIT Range(0, 3) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1242,7 +1246,7 @@ class ThermalGridIT ) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(10551))) /* TICK 10551 @@ -1257,7 +1261,7 @@ class ThermalGridIT Range(0, 4) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1299,7 +1303,7 @@ class ThermalGridIT energy should equalWithTolerance(0.01044.asMegaWattHour) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(11638))) /* TICK 11638 @@ -1313,7 +1317,7 @@ class ThermalGridIT Range(0, 3) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1345,7 +1349,7 @@ class ThermalGridIT ) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(12000))) /* TICK 12000 @@ -1374,7 +1378,7 @@ class ThermalGridIT Range(0, 3) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1406,7 +1410,7 @@ class ThermalGridIT ) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(12139))) /* TICK 12139 @@ -1420,7 +1424,7 @@ class ThermalGridIT Range(0, 3) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1450,7 +1454,7 @@ class ThermalGridIT indoorTemperature should equalWithTolerance(20.asDegreeCelsius) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(12500))) /* TICK 12500 @@ -1479,7 +1483,7 @@ class ThermalGridIT Range(0, 2) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => participantResult match { @@ -1499,7 +1503,7 @@ class ThermalGridIT ) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(24413))) /* TICK 24413 @@ -1513,7 +1517,7 @@ class ThermalGridIT Range(0, 4) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1553,7 +1557,7 @@ class ThermalGridIT energy should equalWithTolerance(0.01044.asMegaWattHour) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(25200))) /* TICK 25200 @@ -1581,7 +1585,7 @@ class ThermalGridIT Range(0, 4) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1629,7 +1633,7 @@ class ThermalGridIT ) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(27500))) /* TICK 27500 @@ -1657,7 +1661,7 @@ class ThermalGridIT Range(0, 2) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => participantResult match { @@ -1677,7 +1681,7 @@ class ThermalGridIT ) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(30923))) /* TICK 30923 @@ -1691,7 +1695,7 @@ class ThermalGridIT Range(0, 4) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1738,7 +1742,7 @@ class ThermalGridIT ) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(31000))) /* TICK 31000 @@ -1766,7 +1770,7 @@ class ThermalGridIT Range(0, 3) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1802,7 +1806,7 @@ class ThermalGridIT ) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(40964))) /* TICK 40964 @@ -1816,7 +1820,7 @@ class ThermalGridIT Range(0, 4) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1858,7 +1862,7 @@ class ThermalGridIT ) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(43858))) /* TICK 43858 @@ -1876,7 +1880,7 @@ class ThermalGridIT Range(0, 4) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1919,7 +1923,7 @@ class ThermalGridIT energy should equalWithTolerance(0.asMegaWattHour) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(46635))) /* TICK 46635 @@ -1933,7 +1937,7 @@ class ThermalGridIT Range(0, 3) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -1963,7 +1967,7 @@ class ThermalGridIT indoorTemperature should equalWithTolerance(18.asDegreeCelsius) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(56278))) /* TICK 56278 @@ -1977,7 +1981,7 @@ class ThermalGridIT Range(0, 3) .map { _ => - resultListener.expectMessageType[ResultEvent] + resultServiceProxy.expectMessageType[ResultEvent] } .foreach { case ParticipantResultEvent(participantResult) => @@ -2007,7 +2011,7 @@ class ThermalGridIT indoorTemperature should equalWithTolerance(20.asDegreeCelsius) } } - resultListener.expectNoMessage() + resultServiceProxy.expectNoMessage() scheduler.expectMessage(Completion(emAgentActivation, Some(66279))) } } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/congestion/CongestionTestBaseData.scala b/src/test/scala/edu/ie3/simona/agent/grid/congestion/CongestionTestBaseData.scala index c6e8383ef1..37d370c915 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/congestion/CongestionTestBaseData.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/congestion/CongestionTestBaseData.scala @@ -19,6 +19,7 @@ import edu.ie3.simona.model.grid.RefSystem import edu.ie3.simona.ontology.messages.SchedulerMessage import edu.ie3.simona.service.load.LoadProfileService import edu.ie3.simona.service.primary.PrimaryServiceProxy +import edu.ie3.simona.service.results.ResultServiceProxy import edu.ie3.simona.service.weather.WeatherService import edu.ie3.simona.test.common.result.CongestedComponentsTestData import edu.ie3.simona.test.common.{ConfigTestData, TestSpawnerTyped} @@ -66,6 +67,9 @@ trait CongestionTestBaseData TestProbe( "primaryService" ) + protected val resultProxy: TestProbe[ResultServiceProxy.Message] = TestProbe( + "resultServiceProxy" + ) protected val weatherService: TestProbe[WeatherService.Message] = TestProbe( "weatherService" ) @@ -78,20 +82,16 @@ trait CongestionTestBaseData scheduler = scheduler.ref, runtimeEventListener = runtimeEvents.ref, primaryServiceProxy = primaryService.ref, + resultProxy = resultProxy.ref, weather = weatherService.ref, loadProfiles = loadProfileService.ref, evDataService = None, ) - protected val resultListener: TestProbe[ResultEvent] = TestProbe( - "resultListener" - ) - protected implicit val constantData: GridAgentConstantData = GridAgentConstantData( environmentRefs, simonaConfig, - Iterable(resultListener.ref), 3600, startTime, endTime, diff --git a/src/test/scala/edu/ie3/simona/agent/grid/congestion/DCMAlgorithmSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/congestion/DCMAlgorithmSpec.scala index fde0c5b5ca..9e925102ee 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/congestion/DCMAlgorithmSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/congestion/DCMAlgorithmSpec.scala @@ -88,7 +88,7 @@ class DCMAlgorithmSpec ) // we should receive an empty result event - resultListener.expectMessageType[PowerFlowResultEvent] match { + resultProxy.expectMessageType[PowerFlowResultEvent] match { case PowerFlowResultEvent( nodeResults, _, diff --git a/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentInitSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentInitSpec.scala index 489f7b2693..f90e3feb4a 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentInitSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentInitSpec.scala @@ -36,6 +36,7 @@ import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.Data.PrimaryData.ActivePowerExtra import edu.ie3.simona.service.ServiceType import edu.ie3.simona.service.primary.PrimaryServiceProxy +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import edu.ie3.simona.service.weather.WeatherDataType import edu.ie3.simona.service.weather.WeatherService.WeatherRegistrationData import edu.ie3.simona.test.common.input.{LoadInputTestData, PvInputTestData} @@ -94,13 +95,13 @@ class ParticipantAgentInitSpec val gridAgent = createTestProbe[GridAgent.Message]() val primaryService = createTestProbe[PrimaryServiceProxy.Message]() - val resultListener = createTestProbe[ResultEvent]() + val resultServiceProxy = createTestProbe[ResultEvent | ExpectResult]() given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryService.ref, + resultServiceProxy = resultServiceProxy.ref, services = Map.empty, - resultListener = Iterable(resultListener.ref), ) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, PRE_INIT_TICK) @@ -143,13 +144,13 @@ class ParticipantAgentInitSpec val gridAgent = createTestProbe[GridAgent.Message]() val primaryService = createTestProbe[Any]() - val resultListener = createTestProbe[ResultEvent]() + val resultServiceProxy = createTestProbe[ResultEvent | ExpectResult]() given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryService.ref, + resultServiceProxy = resultServiceProxy.ref, services = Map.empty, - resultListener = Iterable(resultListener.ref), ) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, PRE_INIT_TICK) @@ -201,13 +202,13 @@ class ParticipantAgentInitSpec val gridAgent = createTestProbe[GridAgent.Message]() val primaryService = createTestProbe[Any]() - val resultListener = createTestProbe[ResultEvent]() + val resultServiceProxy = createTestProbe[ResultEvent | ExpectResult]() given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryService.ref, + resultServiceProxy = resultServiceProxy.ref, services = Map.empty, - resultListener = Iterable(resultListener.ref), ) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, PRE_INIT_TICK) @@ -264,13 +265,13 @@ class ParticipantAgentInitSpec val gridAgent = createTestProbe[GridAgent.Message]() val primaryService = createTestProbe[Any]() - val resultListener = createTestProbe[ResultEvent]() + val resultServiceProxy = createTestProbe[ResultEvent | ExpectResult]() given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryService.ref, + resultServiceProxy = resultServiceProxy.ref, services = Map.empty, - resultListener = Iterable(resultListener.ref), ) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, PRE_INIT_TICK) @@ -351,14 +352,14 @@ class ParticipantAgentInitSpec val gridAgent = createTestProbe[GridAgent.Message]() val primaryService = createTestProbe[Any]() - val resultListener = createTestProbe[ResultEvent]() + val resultServiceProxy = createTestProbe[ResultEvent | ExpectResult]() val service = createTestProbe[Any]() given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryService.ref, + resultServiceProxy = resultServiceProxy.ref, services = Map(ServiceType.WeatherService -> service.ref), - resultListener = Iterable(resultListener.ref), ) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, PRE_INIT_TICK) @@ -419,14 +420,14 @@ class ParticipantAgentInitSpec val gridAgent = createTestProbe[GridAgent.Message]() val primaryService = createTestProbe[Any]() - val resultListener = createTestProbe[ResultEvent]() + val resultServiceProxy = createTestProbe[ResultEvent | ExpectResult]() val service = createTestProbe[Any]() given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryService.ref, + resultServiceProxy = resultServiceProxy.ref, services = Map(ServiceType.WeatherService -> service.ref), - resultListener = Iterable(resultListener.ref), ) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, PRE_INIT_TICK) @@ -482,14 +483,14 @@ class ParticipantAgentInitSpec val gridAgent = createTestProbe[GridAgent.Message]() val primaryService = createTestProbe[Any]() - val resultListener = createTestProbe[ResultEvent]() + val resultServiceProxy = createTestProbe[ResultEvent | ExpectResult]() val service = createTestProbe[Any]() given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryService.ref, + resultServiceProxy = resultServiceProxy.ref, services = Map(ServiceType.WeatherService -> service.ref), - resultListener = Iterable(resultListener.ref), ) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, PRE_INIT_TICK) @@ -563,14 +564,14 @@ class ParticipantAgentInitSpec val gridAgent = createTestProbe[GridAgent.Message]() val primaryService = createTestProbe[Any]() - val resultListener = createTestProbe[ResultEvent]() + val resultServiceProxy = createTestProbe[ResultEvent | ExpectResult]() val service = createTestProbe[Any]() given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryService.ref, + resultServiceProxy = resultServiceProxy.ref, services = Map(ServiceType.WeatherService -> service.ref), - resultListener = Iterable(resultListener.ref), ) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, PRE_INIT_TICK) diff --git a/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentSpec.scala index f511824c62..a2d9161d63 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentSpec.scala @@ -44,6 +44,7 @@ import edu.ie3.simona.ontology.messages.{ ServiceMessage, } import edu.ie3.simona.service.Data.PrimaryData.{ActivePower, ActivePowerExtra} +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import edu.ie3.simona.test.common.UnitSpec import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.util.TimeUtil @@ -95,7 +96,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { val scheduler = createTestProbe[SchedulerMessage]() val gridAgent = createTestProbe[GridAgent.Message]() - val resultListener = createTestProbe[ResultEvent]() + val resultProxy = createTestProbe[ResultEvent | ExpectResult]() val responseReceiver = createTestProbe[MockResponseMessage]() // no additional activation ticks @@ -117,10 +118,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { expectedRequestTick = 12 * 3600, requestVoltageDeviationTolerance = Each(1e-14), ), - ParticipantResultHandler( - Iterable(resultListener.ref), - notifierConfig, - ), + ParticipantResultHandler(resultProxy.ref, notifierConfig), )(using Left(scheduler.ref)) ) @@ -134,7 +132,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! Activation(8 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (8 * 3600).toDateTime @@ -209,7 +207,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! Activation(20 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (20 * 3600).toDateTime @@ -245,7 +243,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { val scheduler = createTestProbe[SchedulerMessage]() val gridAgent = createTestProbe[GridAgent.Message]() - val resultListener = createTestProbe[ResultEvent]() + val resultProxy = createTestProbe[ResultEvent | ExpectResult]() val responseReceiver = createTestProbe[MockResponseMessage]() // with additional activation ticks @@ -273,10 +271,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { expectedRequestTick = 12 * 3600, requestVoltageDeviationTolerance = Each(1e-14), ), - ParticipantResultHandler( - Iterable(resultListener.ref), - notifierConfig, - ), + ParticipantResultHandler(resultProxy.ref, notifierConfig), )(using Left(scheduler.ref)) ) @@ -290,7 +285,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! Activation(8 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (8 * 3600).toDateTime @@ -323,13 +318,13 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { q should approximate(Kilovars(0.968644209676)) } - resultListener.expectNoMessage() + resultProxy.expectNoMessage() scheduler.expectNoMessage() participantAgent ! GridSimulationFinished(12 * 3600, 24 * 3600) // calculation should start now - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (12 * 3600).toDateTime @@ -353,7 +348,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! Activation(20 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (20 * 3600).toDateTime @@ -393,7 +388,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { val scheduler = createTestProbe[SchedulerMessage]() val gridAgent = createTestProbe[GridAgent.Message]() - val resultListener = createTestProbe[ResultEvent]() + val resultProxy = createTestProbe[ResultEvent | ExpectResult]() val responseReceiver = createTestProbe[MockResponseMessage]() val service = createTestProbe[ServiceMessage]() @@ -422,10 +417,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { expectedRequestTick = 12 * 3600, requestVoltageDeviationTolerance = Each(1e-14), ), - ParticipantResultHandler( - Iterable(resultListener.ref), - notifierConfig, - ), + ParticipantResultHandler(resultProxy.ref, notifierConfig), )(using Left(scheduler.ref) ) @@ -439,7 +431,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! Activation(0) // nothing should happen, still waiting for secondary data... - resultListener.expectNoMessage() + resultProxy.expectNoMessage() scheduler.expectNoMessage() participantAgent ! DataProvision( @@ -450,7 +442,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { ) // outside of operation interval, 0 MW - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe simulationStartDate @@ -476,7 +468,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { Some(12 * 3600), ) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() scheduler.expectNoMessage() // TICK 8 * 3600: Start of operation interval @@ -489,7 +481,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! Activation(8 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (8 * 3600).toDateTime @@ -526,7 +518,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! GridSimulationFinished(12 * 3600, 24 * 3600) // nothing should happen, still waiting for secondary data... - resultListener.expectNoMessage() + resultProxy.expectNoMessage() scheduler.expectNoMessage() participantAgent ! DataProvision( @@ -537,7 +529,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { ) // calculation should start now - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (12 * 3600).toDateTime @@ -567,7 +559,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { ) // no-op activation, thus no result expected - resultListener.expectNoMessage() + resultProxy.expectNoMessage() // new data is expected at 18 hours scheduler.expectMessage( @@ -582,7 +574,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! Activation(18 * 3600) // nothing should happen, still waiting for secondary data... - resultListener.expectNoMessage() + resultProxy.expectNoMessage() scheduler.expectNoMessage() participantAgent ! DataProvision( @@ -593,7 +585,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { ) // calculation should start now - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (18 * 3600).toDateTime @@ -612,7 +604,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! Activation(20 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (20 * 3600).toDateTime @@ -644,7 +636,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! GridSimulationFinished(24 * 3600, 36 * 3600) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() scheduler.expectNoMessage() } @@ -657,7 +649,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { val scheduler = createTestProbe[SchedulerMessage]() val gridAgent = createTestProbe[GridAgent.Message]() - val resultListener = createTestProbe[ResultEvent]() + val resultProxy = createTestProbe[ResultEvent | ExpectResult]() val service = createTestProbe[ServiceMessage]() // no additional activation ticks @@ -684,10 +676,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { expectedRequestTick = 12 * 3600, requestVoltageDeviationTolerance = Each(1e-14), ), - ParticipantResultHandler( - Iterable(resultListener.ref), - notifierConfig, - ), + ParticipantResultHandler(resultProxy.ref, notifierConfig), )(using Left(scheduler.ref) ) @@ -698,7 +687,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! Activation(0) // nothing should happen, still waiting for primary data... - resultListener.expectNoMessage() + resultProxy.expectNoMessage() scheduler.expectNoMessage() participantAgent ! DataProvision( @@ -709,7 +698,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { ) // outside of operation interval, 0 MW - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe simulationStartDate @@ -732,14 +721,14 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { Some(12 * 3600), ) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() scheduler.expectNoMessage() // TICK 8 * 3600: Start of operation interval participantAgent ! Activation(8 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (8 * 3600).toDateTime @@ -773,7 +762,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! GridSimulationFinished(12 * 3600, 24 * 3600) // nothing should happen, still waiting for primary data... - resultListener.expectNoMessage() + resultProxy.expectNoMessage() scheduler.expectNoMessage() participantAgent ! DataProvision( @@ -784,7 +773,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { ) // calculation should start now - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (12 * 3600).toDateTime @@ -802,7 +791,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! Activation(18 * 3600) // nothing should happen, still waiting for primary data... - resultListener.expectNoMessage() + resultProxy.expectNoMessage() scheduler.expectNoMessage() participantAgent ! DataProvision( @@ -813,7 +802,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { ) // calculation should start now - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (18 * 3600).toDateTime @@ -829,7 +818,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! Activation(20 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (20 * 3600).toDateTime @@ -858,7 +847,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! GridSimulationFinished(24 * 3600, 36 * 3600) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() scheduler.expectNoMessage() } @@ -875,7 +864,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { val em = createTestProbe[FlexResponse]() val gridAgent = createTestProbe[GridAgent.Message]() - val resultListener = createTestProbe[ResultEvent]() + val resultProxy = createTestProbe[ResultEvent | ExpectResult]() val responseReceiver = createTestProbe[MockResponseMessage]() // no additional activation ticks @@ -897,10 +886,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { expectedRequestTick = 12 * 3600, requestVoltageDeviationTolerance = Each(1e-14), ), - ParticipantResultHandler( - Iterable(resultListener.ref), - notifierConfig, - ), + ParticipantResultHandler(resultProxy.ref, notifierConfig), )(using Right(em.ref) ) @@ -927,7 +913,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(3)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (8 * 3600).toDateTime @@ -938,7 +924,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssuePowerControl(8 * 3600, Kilowatts(3)) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (8 * 3600).toDateTime @@ -999,7 +985,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(0)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (20 * 3600).toDateTime @@ -1010,7 +996,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssueNoControl(20 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (20 * 3600).toDateTime @@ -1051,7 +1037,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { val em = createTestProbe[FlexResponse]() val gridAgent = createTestProbe[GridAgent.Message]() - val resultListener = createTestProbe[ResultEvent]() + val resultProxy = createTestProbe[ResultEvent | ExpectResult]() val responseReceiver = createTestProbe[MockResponseMessage]() // with additional activation ticks @@ -1084,10 +1070,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { expectedRequestTick = 12 * 3600, requestVoltageDeviationTolerance = Each(1e-14), ), - ParticipantResultHandler( - Iterable(resultListener.ref), - notifierConfig, - ), + ParticipantResultHandler(resultProxy.ref, notifierConfig), )(using Right(em.ref) ) @@ -1108,7 +1091,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(3)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (8 * 3600).toDateTime @@ -1119,7 +1102,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssuePowerControl(8 * 3600, Kilowatts(3)) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (8 * 3600).toDateTime @@ -1161,7 +1144,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { q should approximate(Kilovars(0.48432210483)) } - resultListener.expectNoMessage() + resultProxy.expectNoMessage() em.expectNoMessage() participantAgent ! GridSimulationFinished(12 * 3600, 24 * 3600) @@ -1178,7 +1161,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(3)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (12 * 3600).toDateTime @@ -1189,7 +1172,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssueNoControl(12 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (12 * 3600).toDateTime @@ -1227,7 +1210,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(0)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (20 * 3600).toDateTime @@ -1238,7 +1221,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssueNoControl(20 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (20 * 3600).toDateTime @@ -1286,7 +1269,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { val em = createTestProbe[FlexResponse]() val gridAgent = createTestProbe[GridAgent.Message]() - val resultListener = createTestProbe[ResultEvent]() + val resultProxy = createTestProbe[ResultEvent | ExpectResult]() val responseReceiver = createTestProbe[MockResponseMessage]() val service = createTestProbe[ServiceMessage]() @@ -1320,10 +1303,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { expectedRequestTick = 12 * 3600, requestVoltageDeviationTolerance = Each(1e-14), ), - ParticipantResultHandler( - Iterable(resultListener.ref), - notifierConfig, - ), + ParticipantResultHandler(resultProxy.ref, notifierConfig), )(using Right(em.ref)) ) @@ -1335,7 +1315,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! FlexActivation(0) // nothing should happen, still waiting for secondary data... - resultListener.expectNoMessage() + resultProxy.expectNoMessage() em.expectNoMessage() participantAgent ! DataProvision( @@ -1356,7 +1336,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(0)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe simulationStartDate @@ -1368,7 +1348,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssueNoControl(0) // outside of operation interval, 0 MW - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe simulationStartDate @@ -1404,7 +1384,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { Some(12 * 3600), ) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() em.expectNoMessage() // TICK 8 * 3600: Start of operation interval @@ -1428,7 +1408,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(4)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (8 * 3600).toDateTime @@ -1439,7 +1419,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssuePowerControl(8 * 3600, Kilowatts(3)) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (8 * 3600).toDateTime @@ -1486,7 +1466,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! GridSimulationFinished(12 * 3600, 24 * 3600) // nothing should happen, still waiting for secondary data... - resultListener.expectNoMessage() + resultProxy.expectNoMessage() em.expectNoMessage() participantAgent ! DataProvision( @@ -1508,7 +1488,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(5)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (12 * 3600).toDateTime @@ -1519,7 +1499,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssueNoControl(12 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (12 * 3600).toDateTime @@ -1549,7 +1529,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! FlexActivation(18 * 3600) // nothing should happen, still waiting for secondary data... - resultListener.expectNoMessage() + resultProxy.expectNoMessage() em.expectNoMessage() participantAgent ! DataProvision( @@ -1571,7 +1551,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(8)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (18 * 3600).toDateTime @@ -1582,7 +1562,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssueNoControl(18 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (18 * 3600).toDateTime @@ -1623,7 +1603,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(0)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (20 * 3600).toDateTime @@ -1634,7 +1614,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssueNoControl(20 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (20 * 3600).toDateTime @@ -1673,7 +1653,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! GridSimulationFinished(24 * 3600, 36 * 3600) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() em.expectNoMessage() } @@ -1686,7 +1666,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { val em = createTestProbe[FlexResponse]() val gridAgent = createTestProbe[GridAgent.Message]() - val resultListener = createTestProbe[ResultEvent]() + val resultProxy = createTestProbe[ResultEvent | ExpectResult]() val service = createTestProbe[ServiceMessage]() // no additional activation ticks @@ -1713,10 +1693,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { expectedRequestTick = 12 * 3600, requestVoltageDeviationTolerance = Each(1e-14), ), - ParticipantResultHandler( - Iterable(resultListener.ref), - notifierConfig, - ), + ParticipantResultHandler(resultProxy.ref, notifierConfig), )(using Right(em.ref) ) @@ -1727,7 +1704,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! FlexActivation(0) // nothing should happen, still waiting for primary data... - resultListener.expectNoMessage() + resultProxy.expectNoMessage() em.expectNoMessage() participantAgent ! DataProvision( @@ -1748,7 +1725,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(0)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe simulationStartDate @@ -1760,7 +1737,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssueNoControl(0) // outside of operation interval, 0 MW - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe simulationStartDate @@ -1793,7 +1770,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { Some(12 * 3600), ) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() em.expectNoMessage() // TICK 8 * 3600: Start of operation interval @@ -1811,7 +1788,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(3)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (8 * 3600).toDateTime @@ -1822,7 +1799,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssuePowerControl(8 * 3600, Kilowatts(3)) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (8 * 3600).toDateTime @@ -1866,7 +1843,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! GridSimulationFinished(12 * 3600, 24 * 3600) // nothing should happen, still waiting for primary data... - resultListener.expectNoMessage() + resultProxy.expectNoMessage() em.expectNoMessage() participantAgent ! DataProvision( @@ -1888,7 +1865,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(6)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (12 * 3600).toDateTime @@ -1899,7 +1876,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssueNoControl(12 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (12 * 3600).toDateTime @@ -1926,7 +1903,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! FlexActivation(18 * 3600) // nothing should happen, still waiting for primary data... - resultListener.expectNoMessage() + resultProxy.expectNoMessage() em.expectNoMessage() participantAgent ! DataProvision( @@ -1948,7 +1925,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(3)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (18 * 3600).toDateTime @@ -1959,7 +1936,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssueNoControl(18 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (18 * 3600).toDateTime @@ -1996,7 +1973,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { max should approximate(Kilowatts(0)) } - resultListener.expectMessageType[FlexOptionsResultEvent] match { + resultProxy.expectMessageType[FlexOptionsResultEvent] match { case FlexOptionsResultEvent(result: FlexOptionsResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (20 * 3600).toDateTime @@ -2007,7 +1984,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! IssueNoControl(20 * 3600) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultProxy.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(result: MockResult) => result.getInputModel shouldBe MockParticipantModel.uuid result.getTime shouldBe (20 * 3600).toDateTime @@ -2043,7 +2020,7 @@ class ParticipantAgentSpec extends ScalaTestWithActorTestKit with UnitSpec { participantAgent ! GridSimulationFinished(24 * 3600, 36 * 3600) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() em.expectNoMessage() } diff --git a/src/test/scala/edu/ie3/simona/event/listener/ResultEventListenerSpec.scala b/src/test/scala/edu/ie3/simona/event/listener/ResultListenerSpec.scala similarity index 75% rename from src/test/scala/edu/ie3/simona/event/listener/ResultEventListenerSpec.scala rename to src/test/scala/edu/ie3/simona/event/listener/ResultListenerSpec.scala index 742c3b4d5d..6a3704d2a1 100644 --- a/src/test/scala/edu/ie3/simona/event/listener/ResultEventListenerSpec.scala +++ b/src/test/scala/edu/ie3/simona/event/listener/ResultListenerSpec.scala @@ -23,6 +23,10 @@ import edu.ie3.simona.event.ResultEvent.{ import edu.ie3.simona.io.result.ResultSinkType.Csv import edu.ie3.simona.io.result.{ResultEntitySink, ResultSinkType} import edu.ie3.simona.logging.LogbackConfiguration +import edu.ie3.simona.service.results.{ + ThreeWindingResultTestData, + Transformer3wResultSupport, +} import edu.ie3.simona.test.common.result.PowerFlowResultData import edu.ie3.simona.test.common.{IOTestCommons, UnitSpec} import edu.ie3.simona.util.ResultFileHierarchy @@ -34,6 +38,7 @@ import org.apache.pekko.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, } import org.apache.pekko.testkit.TestKit.awaitCond +import edu.ie3.simona.ontology.messages.ResultMessage.ResultResponse import java.io.{File, FileInputStream} import java.util.UUID @@ -44,7 +49,7 @@ import scala.concurrent.{Await, Future} import scala.io.Source import scala.language.postfixOps -class ResultEventListenerSpec +class ResultListenerSpec extends ScalaTestWithActorTestKit( ActorTestKit.ApplicationTestConfig.withValue( "org.apache.pekko.actor.testkit.typed.filter-leeway", @@ -54,8 +59,7 @@ class ResultEventListenerSpec with UnitSpec with IOTestCommons with PowerFlowResultData - with ThreeWindingResultTestData - with Transformer3wResultSupport { + with ThreeWindingResultTestData { val simulationName = "testSim" val resultEntitiesToBeWritten: Set[Class[? <: ResultEntity]] = Set( classOf[PvResult], @@ -97,7 +101,7 @@ class ResultEventListenerSpec Symbol("initializeSinks") ) - ResultEventListener invokePrivate initializeSinks(resultFileHierarchy) + ResultListener invokePrivate initializeSinks(resultFileHierarchy) } private def getFileLinesLength(file: File) = { @@ -141,7 +145,7 @@ class ResultEventListenerSpec resultFileHierarchy(2, ".ttt", Set(classOf[Transformer3WResult])) val deathWatch = createTestProbe("deathWatch") val listener = spawn( - ResultEventListener( + ResultListener( fileHierarchy ) ) @@ -152,16 +156,16 @@ class ResultEventListenerSpec } "handling ordinary results" should { - "process a valid participants result correctly" in { + "process participants results correctly" in { val specificOutputFileHierarchy = resultFileHierarchy(3, ".csv") val listenerRef = spawn( - ResultEventListener( + ResultListener( specificOutputFileHierarchy ) ) - listenerRef ! ParticipantResultEvent(dummyPvResult) + listenerRef ! ResultResponse(dummyPvResult) val outputFile = specificOutputFileHierarchy.rawOutputDataFilePaths .getOrElse( @@ -203,22 +207,20 @@ class ResultEventListenerSpec resultFileSource.close() } - "process a valid power flow result correctly" in { + "process grid results correctly" in { val specificOutputFileHierarchy = resultFileHierarchy(4, ".csv") - val listenerRef = spawn( - ResultEventListener( - specificOutputFileHierarchy + val listenerRef = + spawn(ResultListener(specificOutputFileHierarchy)) + + listenerRef ! ResultResponse( + Iterable( + dummyNodeResult, + dummySwitchResult, + dummyLineResult, + dummyTrafo2wResult, ) ) - listenerRef ! PowerFlowResultEvent( - Iterable(dummyNodeResult), - Iterable(dummySwitchResult), - Iterable(dummyLineResult), - Iterable(dummyTrafo2wResult), - Iterable.empty[PartialTransformer3wResult], - ) - val outputFiles = Map( dummyNodeResultString -> specificOutputFileHierarchy.rawOutputDataFilePaths .getOrElse( @@ -286,103 +288,18 @@ class ResultEventListenerSpec } } - "handling three winding transformer results" should { - def powerflow3wResult( - partialResult: PartialTransformer3wResult - ): PowerFlowResultEvent = - PowerFlowResultEvent( - Iterable.empty[NodeResult], - Iterable.empty[SwitchResult], - Iterable.empty[LineResult], - Iterable.empty[Transformer2WResult], - Iterable(partialResult), - ) - - "correctly reacts on received results" in { - val fileHierarchy = - resultFileHierarchy(5, ".csv", Set(classOf[Transformer3WResult])) - val listener = spawn( - ResultEventListener( - fileHierarchy - ) - ) - - val outputFile = fileHierarchy.rawOutputDataFilePaths - .getOrElse( - classOf[Transformer3WResult], - fail( - s"Cannot get filepath for raw result file of class '${classOf[Transformer3WResult].getSimpleName}' from outputFileHierarchy!'" - ), - ) - .toFile - - /* The result file is created at start up and only contains a headline. */ - awaitCond( - outputFile.exists(), - interval = 500.millis, - max = timeoutDuration, - ) - getFileLinesLength(outputFile) shouldBe 1 - - /* Face the listener with data, as long as they are not comprehensive */ - listener ! powerflow3wResult(resultA) - - listener ! powerflow3wResult(resultC) - - /* Also add unrelated result for different input model */ - val otherResultA = resultA.copy(input = UUID.randomUUID()) - listener ! powerflow3wResult(otherResultA) - - /* Add result A again, which should lead to a failure internally, - but everything should still continue normally - */ - listener ! powerflow3wResult(resultA) - - /* Make sure, that there still is no content in file */ - getFileLinesLength(outputFile) shouldBe 1 - - /* Complete awaited result */ - listener ! powerflow3wResult(resultB) - - // stop listener so that result is flushed out - listener ! DelayedStopHelper.FlushAndStop - - /* Await that the result is written */ - awaitCond( - getFileLinesLength(outputFile) == 2, - interval = 500.millis, - max = timeoutDuration, - ) - /* Check the result */ - val resultFileSource = Source.fromFile(outputFile) - val resultFileLines = resultFileSource.getLines().toSeq - - resultFileLines.size shouldBe 2 - val resultLine = resultFileLines.lastOption.getOrElse( - fail( - "Cannot get csv row that should have been written out by the listener!" - ) - ) - - resultLine shouldBe "2.0,1.0,4.0,3.0,6.0,5.0,40d02538-d8dd-421c-8e68-400f1da170c7,-5," + TimeUtil.withDefaults - .toString(time) - - resultFileSource.close() - } - } - "shutting down" should { "shutdown and compress the data when requested to do so without any errors" in { val specificOutputFileHierarchy = resultFileHierarchy(6, ".csv.gz", compressResults = true) val listenerRef = spawn( - ResultEventListener( + ResultListener( specificOutputFileHierarchy ) ) ResultSinkType.Csv(fileFormat = ".csv.gz", delimiter = ",") - listenerRef ! ParticipantResultEvent(dummyPvResult) + listenerRef ! ResultResponse(dummyPvResult) val outputFile = new File( ".gz$".r.replaceAllIn( diff --git a/src/test/scala/edu/ie3/simona/io/result/ResultEntityKafkaSpec.scala b/src/test/scala/edu/ie3/simona/io/result/ResultEntityKafkaSpec.scala index bc6edb13dd..7e186d8379 100644 --- a/src/test/scala/edu/ie3/simona/io/result/ResultEntityKafkaSpec.scala +++ b/src/test/scala/edu/ie3/simona/io/result/ResultEntityKafkaSpec.scala @@ -6,10 +6,11 @@ package edu.ie3.simona.io.result -import org.apache.pekko.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import edu.ie3.datamodel.models.result.NodeResult +import edu.ie3.simona.api.ontology.results.ProvideResultEntities import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent -import edu.ie3.simona.event.listener.ResultEventListener +import edu.ie3.simona.ontology.messages.ResultMessage.ResultResponse +import edu.ie3.simona.event.listener.ResultListener import edu.ie3.simona.io.result.plain.PlainResult.PlainNodeResult import edu.ie3.simona.io.result.plain.PlainWriter import edu.ie3.simona.logging.LogbackConfiguration @@ -23,6 +24,7 @@ import org.apache.kafka.clients.consumer.KafkaConsumer import org.apache.kafka.common.TopicPartition import org.apache.kafka.common.serialization.{Deserializer, Serdes} import org.apache.kafka.common.utils.Bytes +import org.apache.pekko.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import org.scalatest.GivenWhenThen import org.scalatest.concurrent.Eventually import org.scalatest.wordspec.AnyWordSpecLike @@ -91,7 +93,7 @@ class ResultEntityKafkaSpec // build the listener val listenerRef = spawn( - ResultEventListener( + ResultListener( ResultFileHierarchy( "out", "simName", @@ -132,12 +134,12 @@ class ResultEntityKafkaSpec ) When("receiving the NodeResults") - listenerRef ! PowerFlowResultEvent( - Iterable(nodeRes1, nodeRes2, nodeRes3), - Iterable.empty, - Iterable.empty, - Iterable.empty, - Iterable.empty, + listenerRef ! ResultResponse( + Map( + nodeRes1.getInputModel -> Iterable(nodeRes1), + nodeRes2.getInputModel -> Iterable(nodeRes2), + nodeRes3.getInputModel -> Iterable(nodeRes3), + ) ) Then("records can be fetched from Kafka") diff --git a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelIT.scala b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelIT.scala index 24c8e0e6a1..4cc9b3e53d 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelIT.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelIT.scala @@ -16,8 +16,8 @@ import edu.ie3.simona.agent.participant.ParticipantAgentInit.{ } import edu.ie3.simona.api.data.connection.ExtEvDataConnection import edu.ie3.simona.api.data.model.ev.EvModel -import edu.ie3.simona.api.ontology.ev.* import edu.ie3.simona.api.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.api.ontology.ev.* import edu.ie3.simona.config.RuntimeConfig.EvcsRuntimeConfig import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.event.ResultEvent.ParticipantResultEvent @@ -31,11 +31,13 @@ import edu.ie3.simona.ontology.messages.ServiceMessage.{ PrimaryServiceRegistrationMessage, } import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.ontology.messages.ResultMessage.RequestResult import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceType import edu.ie3.simona.service.ev.ExtEvDataService import edu.ie3.simona.service.ev.ExtEvDataService.InitExtEvData import edu.ie3.simona.service.primary.PrimaryServiceProxy +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import edu.ie3.simona.test.common.input.EvcsInputTestData import edu.ie3.simona.test.common.{TestSpawnerTyped, UnitSpec} import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} @@ -88,7 +90,8 @@ class EvcsModelIT "handle a few requests and arrivals as expected" in { val gridAgent = TestProbe[GridAgent.Message]("GridAgent") - val resultListener = TestProbe[ResultEvent]("ResultListener") + val resultProxy = + TestProbe[ResultEvent | ExpectResult]("ResultServiceProxy") val primaryServiceProxy = TestProbe[PrimaryServiceProxy.Message]("PrimaryServiceProxy") val scheduler = TestProbe[SchedulerMessage]("Scheduler") @@ -124,8 +127,8 @@ class EvcsModelIT given ParticipantRefs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryServiceProxy.ref, + resultServiceProxy = resultProxy.ref, services = Map(ServiceType.EvMovementService -> evService), - resultListener = Iterable(resultListener.ref), ) val evcsKey = @@ -207,7 +210,7 @@ class EvcsModelIT scheduler.expectMessage(Completion(evService, None)) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() // Send arrivals extEvData.provideArrivingEvs( @@ -227,7 +230,7 @@ class EvcsModelIT evcsActivation ! Activation(0) - resultListener + resultProxy .receiveMessages(3) .map { case ParticipantResultEvent(result) => result @@ -271,12 +274,12 @@ class EvcsModelIT scheduler.expectMessage(Completion(evService, None)) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() // EVCS activation without arrivals evcsActivation ! Activation(1800) - resultListener + resultProxy .receiveMessages(2) .map { case ParticipantResultEvent(result) => result @@ -303,7 +306,7 @@ class EvcsModelIT evcsActivation ! Activation(3600) - resultListener + resultProxy .receiveMessages(2) .map { case ParticipantResultEvent(result) => result @@ -374,7 +377,7 @@ class EvcsModelIT evcsActivation ! Activation(9000) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() // Next data at 10800 scheduler.expectMessage(Completion(evcsActivation, Some(10800))) @@ -394,7 +397,7 @@ class EvcsModelIT scheduler.expectMessage(Completion(evService, None)) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() // Send arrivals extEvData.provideArrivingEvs( @@ -414,7 +417,7 @@ class EvcsModelIT evcsActivation ! Activation(10800) - resultListener + resultProxy .receiveMessages(2) .map { case ParticipantResultEvent(result) => result @@ -456,7 +459,7 @@ class EvcsModelIT // EVCS activation evcsActivation ! Activation(12600) - resultListener + resultProxy .receiveMessages(2) .map { case ParticipantResultEvent(result) => result @@ -527,7 +530,7 @@ class EvcsModelIT evcsActivation ! Activation(14400) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() // evB is departing at 18000 scheduler.expectMessage(Completion(evcsActivation, Some(18000))) @@ -580,7 +583,7 @@ class EvcsModelIT evcsActivation ! Activation(18000) - resultListener.expectNoMessage() + resultProxy.expectNoMessage() // No future arrivals planned, next activation: end of simulation scheduler.expectMessage(Completion(evcsActivation, Some(48 * 3600))) diff --git a/src/test/scala/edu/ie3/simona/event/listener/ThreeWindingResultHandlingSpec.scala b/src/test/scala/edu/ie3/simona/service/results/ThreeWindingResultHandlingSpec.scala similarity index 95% rename from src/test/scala/edu/ie3/simona/event/listener/ThreeWindingResultHandlingSpec.scala rename to src/test/scala/edu/ie3/simona/service/results/ThreeWindingResultHandlingSpec.scala index 227b4ce9f5..1615462104 100644 --- a/src/test/scala/edu/ie3/simona/event/listener/ThreeWindingResultHandlingSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/results/ThreeWindingResultHandlingSpec.scala @@ -4,9 +4,11 @@ * Research group Distribution grid planning and operation */ -package edu.ie3.simona.event.listener +package edu.ie3.simona.service.results import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult +import edu.ie3.simona.service.results.Transformer3wResultSupport +import edu.ie3.simona.service.results.Transformer3wResultSupport.AggregatedTransformer3wResult import edu.ie3.simona.test.common.UnitSpec import edu.ie3.util.TimeUtil import org.scalatest.prop.TableDrivenPropertyChecks @@ -19,8 +21,7 @@ import scala.util.{Failure, Success} class ThreeWindingResultHandlingSpec extends UnitSpec with TableDrivenPropertyChecks - with ThreeWindingResultTestData - with Transformer3wResultSupport { + with ThreeWindingResultTestData { "Handling three winding results" when { "assembling joint values" should { val mockAResult = PartialTransformer3wResult.PortA( diff --git a/src/test/scala/edu/ie3/simona/event/listener/ThreeWindingResultTestData.scala b/src/test/scala/edu/ie3/simona/service/results/ThreeWindingResultTestData.scala similarity index 97% rename from src/test/scala/edu/ie3/simona/event/listener/ThreeWindingResultTestData.scala rename to src/test/scala/edu/ie3/simona/service/results/ThreeWindingResultTestData.scala index de3ae98ec7..92011d19eb 100644 --- a/src/test/scala/edu/ie3/simona/event/listener/ThreeWindingResultTestData.scala +++ b/src/test/scala/edu/ie3/simona/service/results/ThreeWindingResultTestData.scala @@ -4,7 +4,7 @@ * Research group Distribution grid planning and operation */ -package edu.ie3.simona.event.listener +package edu.ie3.simona.service.results import edu.ie3.datamodel.models.StandardUnits import edu.ie3.datamodel.models.result.connector.Transformer3WResult diff --git a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala index 9eb3ba7b71..68abb1100e 100644 --- a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala @@ -12,15 +12,20 @@ import edu.ie3.simona.api.ExtSimAdapter import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.event.listener.{ DelayedStopHelper, - ResultEventListener, + ResultListener, RuntimeEventListener, } import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.main.RunSimona.SimonaEnded +import edu.ie3.simona.ontology.messages.ResultMessage.{ + RequestResult, + ResultResponse, +} import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.scheduler.TimeAdvancer import edu.ie3.simona.scheduler.core.Core.CoreFactory import edu.ie3.simona.scheduler.core.RegularSchedulerCore +import edu.ie3.simona.service.results.ResultServiceProxy import edu.ie3.simona.sim.SimonaSim.SimulationEnded import edu.ie3.simona.sim.SimonaSimSpec.* import edu.ie3.simona.sim.setup.{ExtSimSetupData, SimonaSetup} @@ -29,11 +34,11 @@ import org.apache.pekko.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe, } -import org.apache.pekko.actor.typed.scaladsl.adapter.* import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} import org.apache.pekko.actor.typed.{ActorRef, Behavior} import java.nio.file.Path +import java.time.ZonedDateTime import java.util.UUID class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { @@ -47,9 +52,9 @@ class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { val runtimeListener = TestProbe[RuntimeEventListener.Request]("runtimeEventListener") val resultListener = - TestProbe[ResultEventListener.Request]("resultEventListener") + TestProbe[ResultListener.Message]("resultEventListener") val timeAdvancer = TestProbe[TimeAdvancer.Request]("timeAdvancer") - val extSimAdapter = TestProbe[ExtSimAdapter.Stop]("extSimAdapter") + val extSimAdapter = TestProbe[ExtSimAdapter.Request]("extSimAdapter") val simonaSim = spawn( SimonaSim( @@ -61,6 +66,7 @@ class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { override def extSimulations( context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], + resultProxy: ActorRef[RequestResult], extSimPath: Option[Path], ): ExtSimSetupData = { // We cannot return a TestProbe ref here, @@ -70,8 +76,9 @@ class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { uniqueName("extSimAdapterForwarder"), ) ExtSimSetupData( - Iterable(extSim.toClassic), + Iterable(extSim), Seq.empty, + None, Seq.empty, Seq.empty, ) @@ -113,7 +120,7 @@ class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { val runtimeListener = TestProbe[RuntimeEventListener.Request]("runtimeEventListener") val resultListener = - TestProbe[ResultEventListener.Request]("resultEventListener") + TestProbe[ResultListener.Message]("resultEventListener") val timeAdvancer = TestProbe[TimeAdvancer.Request]("timeAdvancer") val receiveThrowingActor = @@ -179,7 +186,7 @@ class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { val runtimeListener = TestProbe[RuntimeEventListener.Request]("runtimeEventListener") val resultListener = - TestProbe[ResultEventListener.Request]("resultEventListener") + TestProbe[ResultListener.Message]("resultEventListener") val timeAdvancer = TestProbe[TimeAdvancer.Request]("timeAdvancer") val receiveStoppingActor = @@ -242,7 +249,7 @@ class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { "RuntimeEventListener stops unexpectedly" in { val starter = TestProbe[SimonaEnded]("starter") val resultListener = - TestProbe[ResultEventListener.Request]("resultEventListener") + TestProbe[ResultListener.Message]("resultEventListener") val timeAdvancer = TestProbe[TimeAdvancer.Request]("timeAdvancer") val receiveThrowingActor = @@ -309,7 +316,7 @@ class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { override def resultEventListener( context: ActorContext[?] - ): Seq[ActorRef[ResultEventListener.Request]] = + ): Seq[ActorRef[ResultListener.Message]] = throwTestException() } ), @@ -380,25 +387,25 @@ object SimonaSimSpec { "This is an exception for test purposes. It is expected to be thrown." ) - /** Makes the given actor name unique by appending a random UUID */ + /** Makes the given actor name unique by appending a random UUID. */ def uniqueName(name: String): String = s"${name}_${UUID.randomUUID()}" - /** Mock implementation of [[SimonaSetup]] + /** Mock implementation of [[SimonaSetup]]. * * @param runtimeEventProbe * Optional ActorRef that messages received by RuntimeEventListener are - * forwarded to + * forwarded to. * @param resultEventProbe * Optional ActorRef that messages received by ResultEventListener are - * forwarded to + * forwarded to. * @param timeAdvancerProbe * Optional ActorRef that messages received by TimeAdvancer are forwarded - * to + * to. */ class MockSetup( runtimeEventProbe: Option[ActorRef[RuntimeEventListener.Request]] = None, - resultEventProbe: Option[ActorRef[ResultEventListener.Request]] = None, + resultEventProbe: Option[ActorRef[ResultListener.Message]] = None, timeAdvancerProbe: Option[ActorRef[TimeAdvancer.Request]] = None, ) extends SimonaSetup with ConfigTestData { @@ -417,7 +424,7 @@ object SimonaSimSpec { override def resultEventListener( context: ActorContext[?] - ): Seq[ActorRef[ResultEventListener.Request]] = Seq( + ): Seq[ActorRef[ResultListener.Message]] = Seq( context.spawn( stoppableForwardMessage(resultEventProbe), uniqueName("resultEventForwarder"), @@ -431,6 +438,13 @@ object SimonaSimSpec { ): ActorRef[ServiceMessage] = context.spawn(empty, uniqueName("primaryService")) + override def resultServiceProxy( + context: ActorContext[?], + listeners: Seq[ActorRef[ResultResponse]], + simStartTime: ZonedDateTime, + ): ActorRef[ResultServiceProxy.Message] = + context.spawn(stoppableForwardMessage(None), uniqueName("resultService")) + override def weatherService( context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], @@ -463,12 +477,12 @@ object SimonaSimSpec { override def gridAgents( context: ActorContext[?], environmentRefs: EnvironmentRefs, - resultEventListeners: Seq[ActorRef[ResultEvent]], ): Iterable[ActorRef[GridAgent.Message]] = Iterable.empty override def extSimulations( context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], + resultProxy: ActorRef[RequestResult], extSimPath: Option[Path], ): ExtSimSetupData = ExtSimSetupData.apply diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala index 2899223cfa..ff7492978e 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupDataSpec.scala @@ -30,7 +30,7 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { "An ExtSimSetupData" should { - val emptyMapInput = Map.empty[UUID, Class[Value]].asJava + val emptyMapInput = Map.empty[UUID, Class[? <: Value]].asJava val emptyUuidList = List.empty[UUID].asJava "be updated with an ExtPrimaryDataConnection correctly" in { @@ -46,9 +46,10 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { ) updated.extSimAdapters shouldBe empty - updated.extPrimaryDataServices shouldBe Seq((connection, primaryRef)) - updated.extDataServices shouldBe empty - updated.extResultListeners shouldBe empty + updated.primaryDataServices shouldBe Seq((connection, primaryRef)) + updated.evDataService shouldBe None + updated.resultListeners shouldBe empty + updated.resultProviders shouldBe empty } "be updated with multiple ExtPrimaryDataConnection correctly" in { @@ -67,12 +68,13 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { .update(connection2, primaryRef2) updated.extSimAdapters shouldBe empty - updated.extPrimaryDataServices shouldBe Seq( + updated.primaryDataServices shouldBe Seq( (connection1, primaryRef1), (connection2, primaryRef2), ) - updated.extDataServices shouldBe empty - updated.extResultListeners shouldBe empty + updated.evDataService shouldBe None + updated.resultListeners shouldBe empty + updated.resultProviders shouldBe empty } "be updated with an ExtInputDataConnection correctly" in { @@ -85,27 +87,19 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val evConnection = new ExtEvDataConnection() val evRef = TestProbe[ExtEvDataService.Message]("ev_service").ref - val emConnection = new ExtEmDataConnection(emptyUuidList, EmMode.BASE) - val emRef = TestProbe[ServiceMessage]("em_service").ref - val cases = Table( ("connection", "serviceRef", "expected"), ( primaryConnection, primaryRef, - extSimSetupData.copy(extPrimaryDataServices = + extSimSetupData.copy(primaryDataServices = Seq((primaryConnection, primaryRef)) ), ), ( evConnection, evRef, - extSimSetupData.copy(extDataServices = Seq((evConnection, evRef))), - ), - ( - emConnection, - emRef, - extSimSetupData.copy(extDataServices = Seq((emConnection, emRef))), + extSimSetupData.copy(evDataService = Some(evRef)), ), ) @@ -113,25 +107,28 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val updated = extSimSetupData.update(connection, serviceRef) updated.extSimAdapters shouldBe expected.extSimAdapters - updated.extPrimaryDataServices shouldBe expected.extPrimaryDataServices - updated.extDataServices shouldBe expected.extDataServices - updated.extResultListeners shouldBe expected.extResultListeners + updated.primaryDataServices shouldBe expected.primaryDataServices + updated.evDataService shouldBe expected.evDataService + updated.resultListeners shouldBe expected.resultListeners + updated.resultProviders shouldBe expected.resultProviders } } "be updated with an ExtResultDataConnection correctly" in { val extSimSetupData = ExtSimSetupData.apply - val resultConnection = - new ExtResultDataConnection(emptyUuidList, emptyUuidList, emptyUuidList) - val resultRef = TestProbe[ServiceMessage]("result_service").ref + val resultConnection = new ExtResultDataConnection(emptyUuidList) + val resultServiceProxyRef = + TestProbe[ServiceMessage]("resultServiceProxy").ref - val updated = extSimSetupData.update(resultConnection, resultRef) + val updated = + extSimSetupData.update(resultConnection, resultServiceProxyRef) updated.extSimAdapters shouldBe empty - updated.extPrimaryDataServices shouldBe empty - updated.extDataServices shouldBe empty - updated.extResultListeners shouldBe Seq((resultConnection, resultRef)) + updated.primaryDataServices shouldBe empty + updated.evDataService shouldBe None + updated.resultListeners shouldBe empty + updated.resultProviders shouldBe Seq(resultServiceProxyRef) } "be updated with multiple different connections" in { @@ -144,31 +141,20 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val evConnection = new ExtEvDataConnection() val evRef = TestProbe[ExtEvDataService.Message]("ev_service").ref - val emConnection = new ExtEmDataConnection(emptyUuidList, EmMode.BASE) - val emRef = TestProbe[ServiceMessage]("em_service").ref - - val resultConnection = - new ExtResultDataConnection(emptyUuidList, emptyUuidList, emptyUuidList) - val resultRef = TestProbe[ServiceMessage]("result_service").ref + val resultConnection = new ExtResultDataConnection(emptyUuidList) + val resultServiceProxyRef = + TestProbe[ServiceMessage]("resultServiceProxy").ref val updated = extSimSetupData .update(primaryConnection, primaryRef) .update(evConnection, evRef) - .update(emConnection, emRef) - .update(resultConnection, resultRef) + .update(resultConnection, resultServiceProxyRef) updated.extSimAdapters shouldBe empty - updated.extPrimaryDataServices shouldBe Seq( - ( - primaryConnection, - primaryRef, - ) - ) - updated.extDataServices shouldBe Seq( - (evConnection, evRef), - (emConnection, emRef), - ) - updated.extResultListeners shouldBe Seq((resultConnection, resultRef)) + updated.primaryDataServices shouldBe Seq((primaryConnection, primaryRef)) + updated.evDataService shouldBe Some(evRef) + updated.resultListeners shouldBe empty + updated.resultProviders shouldBe Seq(resultServiceProxyRef) } "return evDataService correctly" in { @@ -176,41 +162,16 @@ class ExtSimSetupDataSpec extends ScalaTestWithActorTestKit with UnitSpec { val evRef = TestProbe[ExtEvDataService.Message]("ev_service").ref val cases = Table( - ("extSimSetupData", "expectedConnection", "expectedService"), - ( - ExtSimSetupData.apply.update(evConnection, evRef), - Some(evConnection), - Some(evRef), - ), - (ExtSimSetupData.apply, None, None), + ("extSimSetupData", "expectedService"), + (ExtSimSetupData.apply.update(evConnection, evRef), Some(evRef)), + (ExtSimSetupData.apply, None), ) - forAll(cases) { (extSimSetupData, expectedConnection, expectedService) => - extSimSetupData.evDataConnection shouldBe expectedConnection + forAll(cases) { (extSimSetupData, expectedService) => extSimSetupData.evDataService shouldBe expectedService } } - "return emDataService correctly" in { - val emConnection = new ExtEmDataConnection(emptyUuidList, EmMode.BASE) - val emRef = TestProbe[ServiceMessage]("em_service").ref - - val cases = Table( - ("extSimSetupData", "expectedConnection", "expectedService"), - ( - ExtSimSetupData.apply.update(emConnection, emRef), - Some(emConnection), - Some(emRef), - ), - (ExtSimSetupData.apply, None, None), - ) - - forAll(cases) { (extSimSetupData, expectedConnection, expectedService) => - extSimSetupData.emDataConnection shouldBe expectedConnection - extSimSetupData.emDataService shouldBe expectedService - } - } - } } diff --git a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala index 6a7bbc4d74..9fa01a4912 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/ExtSimSetupSpec.scala @@ -25,7 +25,7 @@ class ExtSimSetupSpec extends UnitSpec { val uuid5 = UUID.fromString("ebcefed4-a3e6-4a2a-b4a5-74226d548546") val uuid6 = UUID.fromString("4a9c8e14-c0ee-425b-af40-9552b9075414") - def toMap(uuids: Set[UUID]): java.util.Map[UUID, Class[Value]] = uuids + def toMap(uuids: Set[UUID]): java.util.Map[UUID, Class[? <: Value]] = uuids .map(uuid => uuid -> classOf[Value]) .toMap .asJava diff --git a/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala index 6819e6adc5..f9a01eb4f8 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala @@ -14,12 +14,17 @@ import edu.ie3.datamodel.models.input.connector.{ import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.config.SimonaConfig -import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} +import edu.ie3.simona.event.listener.{ResultListener, RuntimeEventListener} import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} +import edu.ie3.simona.ontology.messages.ResultMessage.{ + RequestResult, + ResultResponse, +} import edu.ie3.simona.scheduler.TimeAdvancer import edu.ie3.simona.scheduler.core.Core.CoreFactory import edu.ie3.simona.scheduler.core.RegularSchedulerCore +import edu.ie3.simona.service.results.ResultServiceProxy import edu.ie3.simona.sim.SimonaSim import edu.ie3.simona.test.common.model.grid.SubGridGateMokka import edu.ie3.simona.test.common.{ConfigTestData, UnitSpec} @@ -27,6 +32,7 @@ import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext import java.nio.file.Path +import java.time.ZonedDateTime import java.util.UUID class SimonaSetupSpec @@ -49,7 +55,7 @@ class SimonaSetupSpec override def resultEventListener( context: ActorContext[?] - ): Seq[ActorRef[ResultEventListener.Request]] = + ): Seq[ActorRef[ResultListener.Message]] = throw new NotImplementedException("This is a dummy setup") override def primaryServiceProxy( @@ -60,6 +66,14 @@ class SimonaSetupSpec "This is a dummy setup" ) + override def resultServiceProxy( + context: ActorContext[?], + listeners: Seq[ActorRef[ResultResponse]], + simStartTime: ZonedDateTime, + ): ActorRef[ResultServiceProxy.Message] = throw new NotImplementedException( + "This is a dummy setup" + ) + override def weatherService( context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], @@ -77,6 +91,7 @@ class SimonaSetupSpec override def extSimulations( context: ActorContext[?], scheduler: ActorRef[SchedulerMessage], + resultProxy: ActorRef[RequestResult], extSimPath: Option[Path], ): ExtSimSetupData = throw new NotImplementedException( "This is a dummy setup" @@ -101,7 +116,6 @@ class SimonaSetupSpec override def gridAgents( context: ActorContext[?], environmentRefs: EnvironmentRefs, - resultEventListeners: Seq[ActorRef[ResultEvent]], ): Iterable[ActorRef[GridAgent.Message]] = throw new NotImplementedException("This is a dummy setup") diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala index 48d46928a3..c40233138f 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala @@ -413,4 +413,13 @@ trait DbfsTestGrid extends SubGridGateMokka { subGridGates, ) } + + protected val assetsHv: Seq[UUID] = + hvGridContainer.allEntitiesAsList.asScala.map(_.getUuid).toSeq + protected val assetsHvPF: Seq[UUID] = + hvGridContainerPF.allEntitiesAsList.asScala.map(_.getUuid).toSeq + + protected val assetsEhv: Seq[UUID] = + ehvGridContainer.allEntitiesAsList.asScala.map(_.getUuid).toSeq + } diff --git a/src/test/scala/edu/ie3/simona/test/common/result/PowerFlowResultData.scala b/src/test/scala/edu/ie3/simona/test/common/result/PowerFlowResultData.scala index f4ea226071..08fcd183da 100644 --- a/src/test/scala/edu/ie3/simona/test/common/result/PowerFlowResultData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/result/PowerFlowResultData.scala @@ -7,9 +7,8 @@ package edu.ie3.simona.test.common.result import java.util.UUID - import edu.ie3.datamodel.models.StandardUnits -import edu.ie3.datamodel.models.result.NodeResult +import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} import edu.ie3.datamodel.models.result.connector.{ LineResult, SwitchResult, @@ -28,6 +27,12 @@ trait PowerFlowResultData { private val dummyInputModel = UUID.fromString("e5ac84d3-c7a5-4870-a42d-837920aec9bb") + given Conversion[ResultEntity, Map[UUID, Iterable[ResultEntity]]] = + (res: ResultEntity) => Map(res.getInputModel -> Iterable(res)) + + given Conversion[Iterable[ResultEntity], Map[UUID, Iterable[ResultEntity]]] = + (res: Iterable[ResultEntity]) => res.groupBy(_.getInputModel) + val dummyPvResult = new PvResult( dummyTime, dummyInputModel, From db9c8c1b6429cbba56ac719e3afccdb3d0c6962b Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 15 Sep 2025 13:33:04 +0200 Subject: [PATCH 088/125] Saving changes. --- .../simona/service/em/ExtEmDataService.scala | 2 +- .../primary/ExtPrimaryDataService.scala | 24 +++++-------------- 2 files changed, 7 insertions(+), 19 deletions(-) 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 950c7755e4..677ac45d87 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -111,7 +111,7 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { override def init( initServiceData: InitializeServiceStateData - ): Try[(ExtEmDataStateData, Option[Long])] = initServiceData match { + )(using log: Logger): Try[(ExtEmDataStateData, Option[Long])] = initServiceData match { case InitExtEmData(extEmDataConnection, startTime) => val serviceCore = extEmDataConnection.mode match { case EmMode.BASE => diff --git a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala index 8d7e8af703..333396bfd6 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala @@ -7,32 +7,20 @@ package edu.ie3.simona.service.primary import edu.ie3.simona.agent.participant.ParticipantAgent -import edu.ie3.simona.agent.participant.ParticipantAgent.{ - DataProvision, - PrimaryRegistrationSuccessfulMessage, -} +import edu.ie3.simona.agent.participant.ParticipantAgent.{DataProvision, PrimaryRegistrationSuccessfulMessage} import edu.ie3.simona.api.data.connection.ExtPrimaryDataConnection import edu.ie3.simona.api.ontology.DataMessageFromExt -import edu.ie3.simona.api.ontology.primary.{ - PrimaryDataMessageFromExt, - ProvidePrimaryData, -} +import edu.ie3.simona.api.ontology.primary.{PrimaryDataMessageFromExt, ProvidePrimaryData} import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{InitializationException, ServiceException} -import edu.ie3.simona.ontology.messages.ServiceMessage.{ - PrimaryServiceRegistrationMessage, - ServiceRegistrationMessage, - ServiceResponseMessage, -} +import edu.ie3.simona.ontology.messages.ServiceMessage.{PrimaryServiceRegistrationMessage, ServiceRegistrationMessage, ServiceResponseMessage} import edu.ie3.simona.service.Data.PrimaryData import edu.ie3.simona.service.Data.PrimaryData.RichValue -import edu.ie3.simona.service.ServiceStateData.{ - InitializeServiceStateData, - ServiceBaseStateData, -} +import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} import edu.ie3.simona.service.{ExtDataSupport, ServiceStateData, SimonaService} import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext +import org.slf4j.Logger import java.util.UUID import scala.jdk.CollectionConverters.MapHasAsScala @@ -58,7 +46,7 @@ object ExtPrimaryDataService extends SimonaService with ExtDataSupport { override def init( initServiceData: ServiceStateData.InitializeServiceStateData - ): Try[(ExtPrimaryDataStateData, Option[Long])] = initServiceData match { + )(using log: Logger): Try[(ExtPrimaryDataStateData, Option[Long])] = initServiceData match { case InitExtPrimaryData(extPrimaryData) => val primaryDataInitializedStateData = ExtPrimaryDataStateData( extPrimaryData From 147c85847282b9a14b8bf4a7996b8f8857382837 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 16 Sep 2025 08:31:34 +0200 Subject: [PATCH 089/125] Adding test for `ResultServiceProxy` and `ExtResultProvider`. --- .../service/results/ResultServiceProxy.scala | 9 +- .../results/ExtResultProviderSpec.scala | 95 ++++++ .../results/ResultServiceProxySpec.scala | 270 ++++++++++++++++++ .../simona/test/common/ConfigTestData.scala | 3 + .../result/CongestedComponentsTestData.scala | 5 - .../common/result/PowerFlowResultData.scala | 2 +- 6 files changed, 371 insertions(+), 13 deletions(-) create mode 100644 src/test/scala/edu/ie3/simona/service/results/ExtResultProviderSpec.scala create mode 100644 src/test/scala/edu/ie3/simona/service/results/ResultServiceProxySpec.scala diff --git a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala index 245d7e2da2..69a325adf1 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala @@ -21,7 +21,6 @@ import edu.ie3.simona.service.results.Transformer3wResultSupport.{ AggregatedTransformer3wResult, Transformer3wKey, } -import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.RichZonedDateTime import org.apache.pekko.actor.typed.scaladsl.{Behaviors, StashBuffer} import org.apache.pekko.actor.typed.{ActorRef, Behavior, PostStop} @@ -41,7 +40,6 @@ object ResultServiceProxy { private final case class ResultServiceStateData( listeners: Seq[ActorRef[ResultResponse]], simStartTime: ZonedDateTime, - currentTick: Long = INIT_SIM_TICK, threeWindingResults: Map[ Transformer3wKey, AggregatedTransformer3wResult, @@ -51,7 +49,7 @@ object ResultServiceProxy { waitingForResults: Map[UUID, Long] = Map.empty, ) extends ServiceBaseStateData { def notifyListener(results: Map[UUID, Iterable[ResultEntity]]): Unit = - listeners.foreach(_ ! ResultResponse(results)) + if results.nonEmpty then listeners.foreach(_ ! ResultResponse(results)) def notifyListener(result: ResultEntity): Unit = listeners.foreach( @@ -67,9 +65,6 @@ object ResultServiceProxy { } } - def updateTick(tick: Long): ResultServiceStateData = - copy(currentTick = tick) - def waitForResult(expectResult: ExpectResult): ResultServiceStateData = expectResult.assets match { case uuid: UUID => @@ -152,7 +147,7 @@ object ResultServiceProxy { // un-stash received requests buffer.unstashAll(idle(updatedStateData)) - case (ctx, requestResultMessage: RequestResult) => + case (_, requestResultMessage: RequestResult) => val requestedResults = requestResultMessage.requestedResults val tick = requestResultMessage.tick diff --git a/src/test/scala/edu/ie3/simona/service/results/ExtResultProviderSpec.scala b/src/test/scala/edu/ie3/simona/service/results/ExtResultProviderSpec.scala new file mode 100644 index 0000000000..bbca74d124 --- /dev/null +++ b/src/test/scala/edu/ie3/simona/service/results/ExtResultProviderSpec.scala @@ -0,0 +1,95 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.results + +import edu.ie3.simona.api.data.connection.ExtResultDataConnection +import edu.ie3.simona.api.ontology.ScheduleDataServiceMessage +import edu.ie3.simona.api.ontology.results.{ + ProvideResultEntities, + RequestResultEntities, +} +import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt +import edu.ie3.simona.ontology.messages.ResultMessage.{ + RequestResult, + ResultResponse, +} +import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.test.common.UnitSpec +import edu.ie3.simona.test.common.result.PowerFlowResultData +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} + +import scala.jdk.CollectionConverters.{ + ListHasAsScala, + SeqHasAsJava, + SetHasAsScala, +} + +class ExtResultProviderSpec + extends ScalaTestWithActorTestKit + with UnitSpec + with PowerFlowResultData { + + private val scheduler = TestProbe[SchedulerMessage]("scheduler") + private val resultProxy = TestProbe[RequestResult]("resultProxy") + + "The ExtResultProvider" should { + + "handle result responses correctly" in { + val connection = new ExtResultDataConnection(List(dummyInputModel).asJava) + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") + val provider = + spawn(ExtResultProvider(connection, scheduler.ref, resultProxy.ref)) + connection.setActorRefs(provider.ref, extSimAdapter.ref) + + provider ! ResultResponse( + Map(dummyInputModel -> List(dummyNodeResult, dummyPvResult)) + ) + + val results = + connection.receiveWithType(classOf[ProvideResultEntities]).results + results.keySet.asScala shouldBe Set(dummyInputModel) + results.get(dummyInputModel).asScala shouldBe List( + dummyNodeResult, + dummyPvResult, + ) + + scheduler.expectMessage(Completion(provider.ref)) + } + + "handle result data message from external" in { + val connection = new ExtResultDataConnection(List(dummyInputModel).asJava) + val extSimAdapter = + TestProbe[ControlResponseMessageFromExt]("extSimAdapter") + val provider = + spawn(ExtResultProvider(connection, scheduler.ref, resultProxy.ref)) + connection.setActorRefs(provider.ref, extSimAdapter.ref) + + // requesting results from the result provider + connection.sendExtMsg( + new RequestResultEntities(3600L, List(dummyInputModel).asJava) + ) + + extSimAdapter.expectMessage(new ScheduleDataServiceMessage(provider.ref)) + provider ! Activation(3600L) + + resultProxy.expectMessage( + RequestResult(Seq(dummyInputModel), 3600L, provider.ref) + ) + + provider ! ResultResponse(Map(dummyInputModel -> List(dummyPvResult))) + + scheduler.expectMessage(Completion(provider.ref)) + } + + } + +} diff --git a/src/test/scala/edu/ie3/simona/service/results/ResultServiceProxySpec.scala b/src/test/scala/edu/ie3/simona/service/results/ResultServiceProxySpec.scala new file mode 100644 index 0000000000..17eca14dbd --- /dev/null +++ b/src/test/scala/edu/ie3/simona/service/results/ResultServiceProxySpec.scala @@ -0,0 +1,270 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.results + +import edu.ie3.simona.event.ResultEvent.{ + ParticipantResultEvent, + PowerFlowResultEvent, +} +import edu.ie3.simona.ontology.messages.ResultMessage +import edu.ie3.simona.ontology.messages.ResultMessage.{ + RequestResult, + ResultResponse, +} +import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult +import edu.ie3.simona.test.common.result.PowerFlowResultData +import edu.ie3.simona.test.common.{ConfigTestData, UnitSpec} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} + +class ResultServiceProxySpec + extends ScalaTestWithActorTestKit + with UnitSpec + with ConfigTestData + with PowerFlowResultData + with ThreeWindingResultTestData { + + "The ResultServiceProxy" should { + + "answer request for results correctly without waiting for results" in { + val resultProvider = TestProbe[ResultMessage.Response]("listener") + + val resultProxy = spawn(ResultServiceProxy(Seq.empty, startTime, 10)) + + resultProxy ! RequestResult( + Seq(dummyInputModel, inputModel), + 3600L, + resultProvider.ref, + ) + + // no results, since the result proxy received not waiting for result information + resultProvider + .expectMessageType[ResultResponse] + .results shouldBe Map.empty + } + + "answer request for results correctly with waiting for some results" in { + val resultProvider = TestProbe[ResultMessage.Response]("listener") + + val resultProxy = spawn(ResultServiceProxy(Seq.empty, startTime, 10)) + + // tells the proxy to wait for the results of dummyInputModel for tick 3600L + resultProxy ! ExpectResult(Seq(dummyInputModel), 3600L) + + resultProxy ! RequestResult( + Seq(dummyInputModel, inputModel), + 3600L, + resultProvider.ref, + ) + + // still waiting for results + resultProvider.expectNoMessage() + + resultProxy ! PowerFlowResultEvent( + Seq(dummyNodeResult), + Seq(dummySwitchResult), + Seq(dummyLineResult), + Seq(dummyTrafo2wResult), + Seq(resultA), + ) + + // no results for three winding transformers, because the proxy is not told to wait and the results was not received beforehand + resultProvider.expectMessageType[ResultResponse].results shouldBe Map( + dummyInputModel -> List( + dummyNodeResult, + dummySwitchResult, + dummyLineResult, + dummyTrafo2wResult, + ) + ) + } + + "answer request for results correctly with waiting for some results with different receive order" in { + val resultProvider = TestProbe[ResultMessage.Response]("listener") + + val resultProxy = spawn(ResultServiceProxy(Seq.empty, startTime, 10)) + + // tells the proxy to wait for the results with dumyInputModel for tick 3600L + resultProxy ! ExpectResult(Seq(dummyInputModel), 3600L) + + resultProxy ! RequestResult( + Seq(dummyInputModel, inputModel), + 3600L, + resultProvider.ref, + ) + + // receiving three winding results for port B and C beforehand + resultProxy ! PowerFlowResultEvent( + Seq.empty, + Seq.empty, + Seq.empty, + Seq.empty, + Seq(resultB, resultC), + ) + + // still waiting for results + resultProvider.expectNoMessage() + + resultProxy ! PowerFlowResultEvent( + Seq(dummyNodeResult), + Seq(dummySwitchResult), + Seq(dummyLineResult), + Seq(dummyTrafo2wResult), + Seq(resultA), + ) + + // receives three winding result, because all partial results are present + resultProvider.expectMessageType[ResultResponse].results shouldBe Map( + dummyInputModel -> List( + dummyNodeResult, + dummySwitchResult, + dummyLineResult, + dummyTrafo2wResult, + ), + inputModel -> List(expected), + ) + } + + "answer request for results correctly with waiting for all results" in { + val resultProvider = TestProbe[ResultMessage.Response]("listener") + + val resultProxy = spawn(ResultServiceProxy(Seq.empty, startTime, 10)) + + // tells the proxy to wait for the results of dumyInputModel for tick 3600L + resultProxy ! ExpectResult(Seq(dummyInputModel), 3600L) + + // tells the proxy to also wait for the results of inputModel for tick 3600L + resultProxy ! ExpectResult(Seq(inputModel), 3600L) + + resultProxy ! RequestResult( + Seq(dummyInputModel, inputModel), + 3600L, + resultProvider.ref, + ) + + // still waiting for results + resultProvider.expectNoMessage() + + resultProxy ! PowerFlowResultEvent( + Seq(dummyNodeResult), + Seq(dummySwitchResult), + Seq(dummyLineResult), + Seq(dummyTrafo2wResult), + Seq(resultA), + ) + + // still waiting for results + resultProvider.expectNoMessage() + + // receiving three winding results for port B and C + resultProxy ! PowerFlowResultEvent( + Seq.empty, + Seq.empty, + Seq.empty, + Seq.empty, + Seq(resultB, resultC), + ) + + // no results for three winding transformers, because the proxy is not told to wait and the results was not received beforehand + resultProvider.expectMessageType[ResultResponse].results shouldBe Map( + dummyInputModel -> List( + dummyNodeResult, + dummySwitchResult, + dummyLineResult, + dummyTrafo2wResult, + ), + inputModel -> List(expected), + ) + } + + "correctly handle grid result events" in { + val listener = TestProbe[ResultResponse]("listener") + + val resultProxy = + spawn(ResultServiceProxy(Seq(listener.ref), startTime, 10)) + + resultProxy ! PowerFlowResultEvent( + Seq(dummyNodeResult), + Seq(dummySwitchResult), + Seq(dummyLineResult), + Seq(dummyTrafo2wResult), + Seq.empty, + ) + + // all results have the same uuid, therefore, all result a grouped to this uuid + listener.expectMessageType[ResultResponse].results shouldBe Map( + dummyInputModel -> List( + dummyNodeResult, + dummySwitchResult, + dummyLineResult, + dummyTrafo2wResult, + ) + ) + } + + "correctly handle three winding transformer result events" in { + val listener = TestProbe[ResultResponse]("listener") + + val resultProxy = + spawn(ResultServiceProxy(Seq(listener.ref), startTime, 10)) + + // sending result for port A + resultProxy ! PowerFlowResultEvent( + Seq.empty, + Seq.empty, + Seq.empty, + Seq.empty, + Seq(resultA), + ) + + // no message, because the three winding result is not complete + listener.expectNoMessage() + + // sending result for port C + resultProxy ! PowerFlowResultEvent( + Seq.empty, + Seq.empty, + Seq.empty, + Seq.empty, + Seq(resultC), + ) + + // no message, because the three winding result is not complete + listener.expectNoMessage() + + // sending result for port B + resultProxy ! PowerFlowResultEvent( + Seq.empty, + Seq.empty, + Seq.empty, + Seq.empty, + Seq(resultB), + ) + + listener.expectMessageType[ResultResponse].results shouldBe Map( + inputModel -> List(expected) + ) + } + + "correctly handle participant result events" in { + val listener = TestProbe[ResultResponse]("listener") + + val resultProxy = + spawn(ResultServiceProxy(Seq(listener.ref), startTime, 10)) + + resultProxy ! ParticipantResultEvent(dummyPvResult) + + listener.expectMessageType[ResultResponse].results shouldBe Map( + dummyPvResult.getInputModel -> List(dummyPvResult) + ) + } + + } + +} diff --git a/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala b/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala index 246548e7d2..c77fe63717 100644 --- a/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala @@ -9,6 +9,8 @@ package edu.ie3.simona.test.common import com.typesafe.config.{Config, ConfigFactory} import edu.ie3.simona.config.SimonaConfig +import java.time.ZonedDateTime + /** Simple (empty) configuration data. Furthermore, it would make sense to * implement another class which reads a config and provides config based * values in the future. @@ -151,4 +153,5 @@ trait ConfigTestData { ) protected val simonaConfig: SimonaConfig = SimonaConfig(typesafeConfig) + protected val startTime: ZonedDateTime = simonaConfig.simona.time.simStartTime } diff --git a/src/test/scala/edu/ie3/simona/test/common/result/CongestedComponentsTestData.scala b/src/test/scala/edu/ie3/simona/test/common/result/CongestedComponentsTestData.scala index 1f938f4da3..fceb3acf1e 100644 --- a/src/test/scala/edu/ie3/simona/test/common/result/CongestedComponentsTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/result/CongestedComponentsTestData.scala @@ -20,7 +20,6 @@ import edu.ie3.simona.model.grid.* import edu.ie3.simona.test.common.ConfigTestData import edu.ie3.simona.test.common.input.NodeInputTestData import edu.ie3.simona.test.common.model.grid.DbfsTestGrid -import edu.ie3.util.TimeUtil import edu.ie3.util.quantities.QuantityUtils.* import squants.electro.Kilovolts import squants.energy.Megawatts @@ -34,10 +33,6 @@ trait CongestedComponentsTestData with NodeInputTestData with DbfsTestGrid { - val startTime: ZonedDateTime = TimeUtil.withDefaults.toZonedDateTime( - simonaConfig.simona.time.startDateTime - ) - val endTime: ZonedDateTime = startTime.plusHours(2) val trafoType3W = new Transformer3WTypeInput( diff --git a/src/test/scala/edu/ie3/simona/test/common/result/PowerFlowResultData.scala b/src/test/scala/edu/ie3/simona/test/common/result/PowerFlowResultData.scala index 08fcd183da..b9a71382e6 100644 --- a/src/test/scala/edu/ie3/simona/test/common/result/PowerFlowResultData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/result/PowerFlowResultData.scala @@ -24,7 +24,7 @@ trait PowerFlowResultData { private val dummyTime = TimeUtil.withDefaults.toZonedDateTime("2020-01-30T17:26:44Z") - private val dummyInputModel = + protected val dummyInputModel: UUID = UUID.fromString("e5ac84d3-c7a5-4870-a42d-837920aec9bb") given Conversion[ResultEntity, Map[UUID, Iterable[ResultEntity]]] = From 4c318b5d0ddb2b91a38a1019711c2769647d6dfe Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 16 Sep 2025 08:53:48 +0200 Subject: [PATCH 090/125] Saving changes. --- .../participant/ParticipantAgentInit.scala | 23 ++++++-- .../event/listener/ExtResultEvent.scala | 3 +- .../messages/flex/PowerLimitFlexOptions.scala | 1 - .../service/em/EmCommunicationCore.scala | 18 +++++-- .../simona/service/em/ExtEmDataService.scala | 41 ++++++++------- .../primary/ExtPrimaryDataService.scala | 52 ++++++++++++------- .../ie3/simona/sim/setup/ExtSimSetup.scala | 22 ++++---- .../ie3/simona/sim/setup/SimonaSetup.scala | 28 ++++++---- 8 files changed, 119 insertions(+), 69 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala index 20f1a0da63..d874a19d84 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentInit.scala @@ -14,17 +14,30 @@ import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.event.notifier.NotifierConfig import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.model.InputModelContainer -import edu.ie3.simona.model.participant.ParticipantModel.{AdditionalFactoryData, ModelState, ParticipantModelFactory} -import edu.ie3.simona.model.participant.{ParticipantModelInit, ParticipantModelShell} -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} -import edu.ie3.simona.ontology.messages.ServiceMessage.{PrimaryServiceRegistrationMessage, SecondaryServiceRegistrationMessage} +import edu.ie3.simona.model.participant.ParticipantModel.{ + AdditionalFactoryData, + ModelState, + ParticipantModelFactory, +} +import edu.ie3.simona.model.participant.{ + ParticipantModelInit, + ParticipantModelShell, +} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} +import edu.ie3.simona.ontology.messages.ServiceMessage.{ + PrimaryServiceRegistrationMessage, + SecondaryServiceRegistrationMessage, +} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.simona.service.ServiceType import edu.ie3.simona.service.results.ResultServiceProxy.ExpectResult import edu.ie3.simona.service.weather.WeatherDataType -import edu.ie3.simona.service.weather.WeatherService.{Coordinate, WeatherRegistrationData} +import edu.ie3.simona.service.weather.WeatherService.WeatherRegistrationData import edu.ie3.simona.util.Coordinate import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.typed.scaladsl.Behaviors diff --git a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala index c7eab3a83b..685816a340 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala @@ -112,7 +112,8 @@ object ExtResultEvent { extMsg match { case requestResultEntities: RequestResultEntities => - val requestedResults = new util.ArrayList(requestResultEntities.requestedResults) + val requestedResults = + new util.ArrayList(requestResultEntities.requestedResults) // request results from result proxy stateData.resultProxy ! RequestResult( diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/PowerLimitFlexOptions.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/PowerLimitFlexOptions.scala index 960eb9d9fa..9cd4f9e25a 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/PowerLimitFlexOptions.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/PowerLimitFlexOptions.scala @@ -75,7 +75,6 @@ object PowerLimitFlexOptions extends FlexOptionsExtra[PowerLimitFlexOptions] { ): Power = flexCtrl match { case IssuePowerControl(_, setPower) => - if setPower < flexOptions.min then { flexOptions.min } else if setPower > flexOptions.max then { diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 63b8883fa1..302378cbf7 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -9,12 +9,21 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent.Message -import edu.ie3.simona.api.data.model.em.{EmData, EmSetPoint, ExtendedFlexOptionsResult, FlexOptionRequest} +import edu.ie3.simona.api.data.model.em.{ + EmData, + EmSetPoint, + ExtendedFlexOptionsResult, + FlexOptionRequest, +} 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.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} +import edu.ie3.simona.ontology.messages.flex.{ + FlexType, + FlexibilityMessage, + PowerLimitFlexOptions, +} import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.* @@ -403,7 +412,10 @@ case class EmCommunicationCore( ) } - expectDataFrom.addData(sender, new EmSetPoint(receiverUuid, sender, power)) + expectDataFrom.addData( + sender, + new EmSetPoint(receiverUuid, sender, power), + ) case other => log.warn(s"$other is not supported!") 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 677ac45d87..53053df3f5 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -111,30 +111,31 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { override def init( initServiceData: InitializeServiceStateData - )(using log: Logger): Try[(ExtEmDataStateData, Option[Long])] = initServiceData match { - case InitExtEmData(extEmDataConnection, startTime) => - val serviceCore = extEmDataConnection.mode match { - case EmMode.BASE => - EmServiceBaseCore.empty - case EmMode.EM_COMMUNICATION => - EmCommunicationCore() - } + )(using log: Logger): Try[(ExtEmDataStateData, Option[Long])] = + initServiceData match { + case InitExtEmData(extEmDataConnection, startTime) => + val serviceCore = extEmDataConnection.mode match { + case EmMode.BASE => + EmServiceBaseCore.empty + case EmMode.EM_COMMUNICATION => + EmCommunicationCore() + } - val emDataInitializedStateData = - ExtEmDataStateData(extEmDataConnection, startTime, serviceCore) + val emDataInitializedStateData = + ExtEmDataStateData(extEmDataConnection, startTime, serviceCore) - Success( - emDataInitializedStateData, - None, - ) + Success( + emDataInitializedStateData, + None, + ) - case invalidData => - Failure( - new InitializationException( - s"Provided init data '${invalidData.getClass.getSimpleName}' for ExtEmDataService are invalid!" + case invalidData => + Failure( + new InitializationException( + s"Provided init data '${invalidData.getClass.getSimpleName}' for ExtEmDataService are invalid!" + ) ) - ) - } + } override protected def handleRegistrationRequest( registrationMessage: ServiceRegistrationMessage diff --git a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala index 333396bfd6..6771615c6c 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala @@ -7,16 +7,29 @@ package edu.ie3.simona.service.primary import edu.ie3.simona.agent.participant.ParticipantAgent -import edu.ie3.simona.agent.participant.ParticipantAgent.{DataProvision, PrimaryRegistrationSuccessfulMessage} +import edu.ie3.simona.agent.participant.ParticipantAgent.{ + DataProvision, + PrimaryRegistrationSuccessfulMessage, +} import edu.ie3.simona.api.data.connection.ExtPrimaryDataConnection import edu.ie3.simona.api.ontology.DataMessageFromExt -import edu.ie3.simona.api.ontology.primary.{PrimaryDataMessageFromExt, ProvidePrimaryData} +import edu.ie3.simona.api.ontology.primary.{ + PrimaryDataMessageFromExt, + ProvidePrimaryData, +} import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{InitializationException, ServiceException} -import edu.ie3.simona.ontology.messages.ServiceMessage.{PrimaryServiceRegistrationMessage, ServiceRegistrationMessage, ServiceResponseMessage} +import edu.ie3.simona.ontology.messages.ServiceMessage.{ + PrimaryServiceRegistrationMessage, + ServiceRegistrationMessage, + ServiceResponseMessage, +} import edu.ie3.simona.service.Data.PrimaryData import edu.ie3.simona.service.Data.PrimaryData.RichValue -import edu.ie3.simona.service.ServiceStateData.{InitializeServiceStateData, ServiceBaseStateData} +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, +} import edu.ie3.simona.service.{ExtDataSupport, ServiceStateData, SimonaService} import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext @@ -46,23 +59,24 @@ object ExtPrimaryDataService extends SimonaService with ExtDataSupport { override def init( initServiceData: ServiceStateData.InitializeServiceStateData - )(using log: Logger): Try[(ExtPrimaryDataStateData, Option[Long])] = initServiceData match { - case InitExtPrimaryData(extPrimaryData) => - val primaryDataInitializedStateData = ExtPrimaryDataStateData( - extPrimaryData - ) - Success( - primaryDataInitializedStateData, - None, - ) + )(using log: Logger): Try[(ExtPrimaryDataStateData, Option[Long])] = + initServiceData match { + case InitExtPrimaryData(extPrimaryData) => + val primaryDataInitializedStateData = ExtPrimaryDataStateData( + extPrimaryData + ) + Success( + primaryDataInitializedStateData, + None, + ) - case invalidData => - Failure( - new InitializationException( - s"Provided init data '${invalidData.getClass.getSimpleName}' for ExtPrimaryService are invalid!" + case invalidData => + Failure( + new InitializationException( + s"Provided init data '${invalidData.getClass.getSimpleName}' for ExtPrimaryService are invalid!" + ) ) - ) - } + } override protected def handleRegistrationRequest( registrationMessage: ServiceRegistrationMessage diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index e6e6f3ba3d..56b5045c53 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -6,18 +6,20 @@ package edu.ie3.simona.sim.setup -import edu.ie3.simona.api.data.connection.{ - ExtEvDataConnection, - ExtInputDataConnection, - ExtPrimaryDataConnection, -} +import com.typesafe.config.Config +import edu.ie3.datamodel.models.input.container.JointGridContainer +import edu.ie3.simona.api.data.connection.* import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt import edu.ie3.simona.api.simulation.{ExtSimAdapterData, ExtSimulation} import edu.ie3.simona.api.{ExtLinkInterface, ExtSimAdapter} import edu.ie3.simona.event.listener.ExtResultEvent import edu.ie3.simona.exceptions.ServiceException -import edu.ie3.simona.ontology.messages.{RequestResult, SchedulerMessage, ServiceMessage} +import edu.ie3.simona.ontology.messages.{ + RequestResult, + SchedulerMessage, + ServiceMessage, +} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceStateData.InitializeServiceStateData import edu.ie3.simona.service.em.ExtEmDataService @@ -46,10 +48,10 @@ object ExtSimSetup { * Interfaces that hold information regarding external simulations. * @param args * The main args the simulation is started with. - * @param config - * The simona config. - * @param grid - * The electrical grid. + * @param config + * The simona config. + * @param grid + * The electrical grid. * @param context * The actor context of this actor system. * @param scheduler diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala index 2cb1e50673..0867309239 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -8,7 +8,10 @@ package edu.ie3.simona.sim.setup import edu.ie3.datamodel.graph.SubGridGate import edu.ie3.datamodel.models.input.connector.Transformer3WInput -import edu.ie3.datamodel.models.input.container.{JointGridContainer, ThermalGrid} +import edu.ie3.datamodel.models.input.container.{ + JointGridContainer, + ThermalGrid, +} import edu.ie3.datamodel.models.input.thermal.ThermalBusInput import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.GridAgent @@ -17,7 +20,11 @@ import edu.ie3.simona.event.ResultEvent.ResultResponse import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.io.grid.GridProvider -import edu.ie3.simona.ontology.messages.{RequestResult, SchedulerMessage, ServiceMessage} +import edu.ie3.simona.ontology.messages.{ + RequestResult, + SchedulerMessage, + ServiceMessage, +} import edu.ie3.simona.scheduler.TimeAdvancer import edu.ie3.simona.scheduler.core.Core.CoreFactory import edu.ie3.simona.scheduler.core.RegularSchedulerCore @@ -48,19 +55,20 @@ trait SimonaSetup { */ val args: Array[String] - /** - * The electrical grid. - */ + /** The electrical grid. + */ lazy val grid: JointGridContainer = GridProvider.gridFromConfig( simonaConfig.simona.simulationName, simonaConfig.simona.input.grid.datasource, ) - /** - * Map: thermal bus to thermal grid. - */ - lazy val thermalGridsByThermalBus: Map[ThermalBusInput, ThermalGrid] = GridProvider.getThermalGridsFromConfig(simonaConfig.simona.input.grid.datasource) - + /** Map: thermal bus to thermal grid. + */ + lazy val thermalGridsByThermalBus: Map[ThermalBusInput, ThermalGrid] = + GridProvider.getThermalGridsFromConfig( + simonaConfig.simona.input.grid.datasource + ) + /** Directory of the log output. */ def logOutputDir: Path From 38cc896cedb316c54a45a48e729f61e36829785d Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 19 Sep 2025 14:46:17 +0200 Subject: [PATCH 091/125] Saving changes. --- .../scala/edu/ie3/simona/service/em/EmCommunicationCore.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 302378cbf7..df072a3f46 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -392,7 +392,7 @@ case class EmCommunicationCore( case control: IssueFlexControl => // send set point to ext - log.warn(s"Receiver $receiverUuid got flex request from $sender") + log.warn(s"Receiver $receiverUuid got flex control message from $sender") val (time, power) = control match { case IssueNoControl(tick) => From 0c786d091087399c809ad2f7c805491e12b6806d Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 24 Sep 2025 10:46:53 +0200 Subject: [PATCH 092/125] Fixing bugs. --- .../simona/agent/grid/GridAgentBuilder.scala | 76 ++++++++++--------- .../service/em/EmCommunicationCore.scala | 4 +- .../ie3/simona/util/ReceiveMultiDataMap.scala | 31 ++++---- 3 files changed, 60 insertions(+), 51 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala index c2cbe18853..b27504baee 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentBuilder.scala @@ -19,9 +19,6 @@ import edu.ie3.simona.agent.participant.ParticipantAgentInit.{ } import edu.ie3.simona.agent.participant.{ParticipantAgent, ParticipantAgentInit} import edu.ie3.simona.config.RuntimeConfig.* -import edu.ie3.simona.config.OutputConfig.ParticipantOutputConfig -import edu.ie3.simona.config.RuntimeConfig.* -import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.event.notifier.NotifierConfig import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.exceptions.agent.GridAgentInitializationException @@ -36,8 +33,6 @@ import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceType import edu.ie3.simona.service.em.ExtEmDataService import edu.ie3.simona.util.ConfigUtil.* -import edu.ie3.simona.util.ConfigUtil -import edu.ie3.simona.util.ConfigUtil.* import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext @@ -227,29 +222,32 @@ object GridAgentBuilder { gridAgentContext: ActorContext[GridAgent.Message], ): Map[UUID, ActorRef[FlexResponse]] = { // For the current level, split controlled and uncontrolled EMs. + val (controlledEmInputs, uncontrolledEms) = emInputs.partition { + case (_, emInput) => emInput.getControllingEm.isPresent + } + // Uncontrolled EMs can be built right away. - val (controlledEmInputs, uncontrolledEms) = emInputs - .partitionMap { case (uuid, emInput) => - if emInput.getControllingEm.isPresent then Left(uuid -> emInput) - else { - val actor = buildEm( - emInput, - maybeControllingEm = None, - emDataService, - ) - Right(uuid -> actor) - } - } + val uncontrolledEmAgents = uncontrolledEms.flatMap { + case (uuid, emInput) if !previousLevelEms.contains(uuid) => + val actor = buildEm( + emInput, + maybeControllingEm = None, + emDataService, + ) + Some(uuid -> actor) + case (uuid, _) => + gridAgentContext.log.warn(s"Agent with uuid '$uuid' was already built!") + None + } val previousLevelAndUncontrolledEms = - previousLevelEms ++ uncontrolledEms.toMap + previousLevelEms ++ uncontrolledEmAgents if controlledEmInputs.nonEmpty then { // For controlled EMs at the current level, more EMs // might need to be built at the next recursion level. - val controllingEms = controlledEmInputs.toMap.flatMap { - case (_, emInput) => - emInput.getControllingEm.toScala.map(em => em.getUuid -> em) + val controllingEms = controlledEmInputs.flatMap { case (_, emInput) => + emInput.getControllingEm.toScala.map(em => em.getUuid -> em) } // Return value includes previous level and uncontrolled EMs of this level @@ -259,24 +257,28 @@ object GridAgentBuilder { emDataService, ) - val controlledEms = controlledEmInputs.map { case (uuid, emInput) => - val controllingEm = emInput.getControllingEm.toScala - .map(_.getUuid) - .map(uuid => - recursiveEms.getOrElse( - uuid, - throw new CriticalFailureException( - s"Actor for EM $uuid not found." - ), + val controlledEms = controlledEmInputs.flatMap { + case (uuid, emInput) if !recursiveEms.contains(uuid) => + val controllingEm = emInput.getControllingEm.toScala + .map(_.getUuid) + .map(uuid => + recursiveEms.getOrElse( + uuid, + throw new CriticalFailureException( + s"Actor for EM $uuid not found." + ), + ) ) - ) - uuid -> buildEm( - emInput, - maybeControllingEm = controllingEm, - emDataService, - ) - }.toMap + Some( + uuid -> buildEm( + emInput, + maybeControllingEm = controllingEm, + emDataService, + ) + ) + case _ => None + } recursiveEms ++ controlledEms } else { diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index df072a3f46..bdefe8526c 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -392,7 +392,9 @@ case class EmCommunicationCore( case control: IssueFlexControl => // send set point to ext - log.warn(s"Receiver $receiverUuid got flex control message from $sender") + log.warn( + s"Receiver $receiverUuid got flex control message from $sender" + ) val (time, power) = control match { case IssueNoControl(tick) => diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala index 3f7ae62629..225b887f8f 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala @@ -6,6 +6,9 @@ package edu.ie3.simona.util +import edu.ie3.simona.util.ReceiveMultiDataMap.log +import org.slf4j.{Logger, LoggerFactory} + final case class ReceiveMultiDataMap[K, V]( private val expectedKeys: Map[K, Int], receivedData: Map[K, Seq[V]], @@ -20,29 +23,29 @@ final case class ReceiveMultiDataMap[K, V]( key: K, value: V, ): ReceiveMultiDataMap[K, V] = { - if !expectedKeys.contains(key) then { - if !receivedData.contains(key) then { - throw new RuntimeException( - s"Received value $value for key $key, but no data has been expected or received for this key." - ) - } else { - val newValue = receivedData(key).appended(value) - - copy(receivedData = receivedData.updated(key, newValue)) - } - + if !expectedKeys.contains(key) && !receivedData.contains(key) then { + throw new RuntimeException( + s"Received value $value for key $key, but no data has been expected or received for this key." + ) } else { val count = expectedKeys(key) - 1 + val newValue = receivedData.get(key) match { + case Some(values) => + values.appended(value) + case None => + Seq(value) + } + if count == 0 then { copy( expectedKeys = expectedKeys.removed(key), - receivedData.updated(key, Seq(value)), + receivedData = receivedData.updated(key, newValue), ) } else { copy( expectedKeys = expectedKeys.updated(key, count), - receivedData.updated(key, Seq(value)), + receivedData = receivedData.updated(key, newValue), ) } } @@ -57,6 +60,8 @@ final case class ReceiveMultiDataMap[K, V]( object ReceiveMultiDataMap { + private val log: Logger = LoggerFactory.getLogger("ReceiveMultiDataMap") + def apply[K, V]( expectedKeys: Map[K, Int] ): ReceiveMultiDataMap[K, V] = From b623dd6ca9255ede815438bda8f53166caebf632 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 24 Sep 2025 11:05:28 +0200 Subject: [PATCH 093/125] Saving changes. --- .../edu/ie3/simona/agent/grid/GridAgent.scala | 5 +---- .../simona/agent/grid/GridResultsSupport.scala | 2 -- .../data/CongestionManagementData.scala | 1 - .../agent/participant/ParticipantAgent.scala | 6 ------ .../edu/ie3/simona/event/ResultEvent.scala | 9 --------- .../simona/event/listener/ExtResultEvent.scala | 10 ++++------ .../simona/event/listener/ResultListener.scala | 4 +--- .../ontology/messages/RequestResult.scala | 18 ------------------ .../service/results/ResultServiceProxy.scala | 3 --- .../edu/ie3/simona/sim/setup/ExtSimSetup.scala | 6 +----- .../ie3/simona/sim/setup/ExtSimSetupData.scala | 12 ++++++------ .../edu/ie3/simona/sim/setup/SimonaSetup.scala | 11 ++--------- .../sim/setup/SimonaStandaloneSetup.scala | 10 ++-------- .../edu/ie3/simona/agent/em/EmAgentSpec.scala | 2 +- .../agent/em/EmAgentWithServiceSpec.scala | 2 +- 15 files changed, 19 insertions(+), 82 deletions(-) delete mode 100644 src/main/scala/edu/ie3/simona/ontology/messages/RequestResult.scala diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala index 357598b5e6..9a545ba95f 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -279,7 +279,6 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { createResultModels( gridAgentBaseData.gridEnv.gridModel, valueStore, - nextTick, )(using currentTick.toDateTime(using constantData.simStartTime), ctx.log, @@ -330,9 +329,7 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { ): Behavior[Message] = { // notify listener about the results - results - .map(_.copy(nextTick = nextTick)) - .foreach(constantData.notifyListeners) + results.foreach(constantData.notifyListeners) // do my cleanup stuff ctx.log.debug("Doing my cleanup stuff") diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala index c7971eaa8e..7e84fed7cb 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala @@ -58,7 +58,6 @@ private[grid] trait GridResultsSupport { def createResultModels( grid: GridModel, sweepValueStore: SweepValueStore, - nextTick: Long, )(using timestamp: ZonedDateTime, log: Logger): PowerFlowResultEvent = { // no sanity check for duplicated uuid result data as we expect valid data at this point given sweepValueStoreData: Map[UUID, SweepValueStoreData] = @@ -88,7 +87,6 @@ private[grid] trait GridResultsSupport { buildLineResults(grid.gridComponents.lines), buildTransformer2wResults(grid.gridComponents.transformers), buildTransformer3wResults(grid.gridComponents.transformers3w), - nextTick = nextTick, ) } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala b/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala index 19fa3f2f3a..0a8ddab7cc 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/congestion/data/CongestionManagementData.scala @@ -179,7 +179,6 @@ object CongestionManagementData { Seq.empty, Seq.empty, Seq.empty, - nextTick = nextTick, ), ) } diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index dfa805c550..c9100b695e 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -7,16 +7,11 @@ package edu.ie3.simona.agent.participant import breeze.numerics.{pow, sqrt} -import edu.ie3.datamodel.models.result.system.{ - FlexOptionsResult, - SystemParticipantResult, -} import edu.ie3.simona.agent.grid.GridAgentMessages.{ AssetPowerChangedMessage, AssetPowerUnchangedMessage, ProvidedPowerResponse, } -import edu.ie3.simona.event.ResultEvent.ResultResponse import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.model.participant.ParticipantModel.AdditionalFactoryData import edu.ie3.simona.model.participant.ParticipantModelShell @@ -24,7 +19,6 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.{ Activation, - RequestResult, SchedulerMessage, ServiceMessage, } diff --git a/src/main/scala/edu/ie3/simona/event/ResultEvent.scala b/src/main/scala/edu/ie3/simona/event/ResultEvent.scala index fd3654a2dc..e4e4968ca0 100644 --- a/src/main/scala/edu/ie3/simona/event/ResultEvent.scala +++ b/src/main/scala/edu/ie3/simona/event/ResultEvent.scala @@ -152,8 +152,6 @@ object ResultEvent { * the partial power flow results for three winding transformers * @param congestionResults * the congestion found by the congestion managements (default: empty) - * @param nextTick - * The next tick, for which new result will be sent. */ final case class PowerFlowResultEvent( nodeResults: Iterable[NodeResult], @@ -162,7 +160,6 @@ object ResultEvent { transformer2wResults: Iterable[Transformer2WResult], transformer3wResults: Iterable[PartialTransformer3wResult], congestionResults: Iterable[CongestionResult] = Iterable.empty, - nextTick: Long, ) extends ResultEvent { def +(congestionResult: Iterable[CongestionResult]): PowerFlowResultEvent = @@ -181,10 +178,4 @@ object ResultEvent { final case class FlexOptionsResultEvent( flexOptionsResult: FlexOptionsResult ) extends ResultEvent - - sealed trait Response - - final case class ResultResponse(results: Map[UUID, Iterable[ResultEntity]]) - extends Response - } diff --git a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala index 685816a340..c5b4d14ca1 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ExtResultEvent.scala @@ -16,9 +16,8 @@ import edu.ie3.simona.api.ontology.results.{ RequestResultEntities, ResultDataMessageFromExt, } -import edu.ie3.simona.event.ResultEvent -import edu.ie3.simona.event.ResultEvent.ResultResponse import edu.ie3.simona.exceptions.CriticalFailureException +import edu.ie3.simona.ontology.messages.ResultMessage.* import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, @@ -26,20 +25,19 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ import edu.ie3.simona.ontology.messages.ServiceMessage.ScheduleServiceActivation import edu.ie3.simona.ontology.messages.{ Activation, - RequestResult, + ResultMessage, SchedulerMessage, } +import edu.ie3.simona.util.CollectionUtils.asJava import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.{ActorRef, Behavior} import java.util -import java.util.UUID -import edu.ie3.simona.util.CollectionUtils.asJava import scala.jdk.CollectionConverters.* object ExtResultEvent { - type Message = ResultEvent.Response | DelayedStopHelper.StoppingMsg + type Message = ResultMessage.Response | DelayedStopHelper.StoppingMsg private final case class ProviderState( scheduler: ActorRef[SchedulerMessage], diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultListener.scala index d2561089fc..1ce384e21f 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultListener.scala @@ -11,8 +11,6 @@ import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} import edu.ie3.simona.api.data.connection.ExtResultListener import edu.ie3.simona.api.ontology.results.ProvideResultEntities import edu.ie3.simona.event.ResultEvent -import edu.ie3.simona.event.ResultEvent -import edu.ie3.simona.event.ResultEvent.ResultResponse import edu.ie3.simona.exceptions.{ FileHierarchyException, ProcessResultEventException, @@ -20,11 +18,11 @@ import edu.ie3.simona.exceptions.{ import edu.ie3.simona.io.result.* import edu.ie3.simona.ontology.messages.ResultMessage import edu.ie3.simona.ontology.messages.ResultMessage.ResultResponse +import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.ResultFileHierarchy import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.{Behavior, PostStop} import org.slf4j.Logger -import edu.ie3.simona.util.CollectionUtils.asJava import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration.DurationInt diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/RequestResult.scala b/src/main/scala/edu/ie3/simona/ontology/messages/RequestResult.scala deleted file mode 100644 index efa33d355f..0000000000 --- a/src/main/scala/edu/ie3/simona/ontology/messages/RequestResult.scala +++ /dev/null @@ -1,18 +0,0 @@ -/* - * © 2025. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.ontology.messages - -import edu.ie3.simona.event.ResultEvent -import org.apache.pekko.actor.typed.ActorRef - -import java.util.UUID - -final case class RequestResult( - requestedResults: Seq[UUID], - tick: Long, - replyTo: ActorRef[ResultEvent.Response], -) diff --git a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala index 276a1b3cbd..b194e554c9 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala @@ -21,8 +21,6 @@ import edu.ie3.simona.service.results.Transformer3wResultSupport.{ AggregatedTransformer3wResult, Transformer3wKey, } -import edu.ie3.simona.ontology.messages.RequestResult -import edu.ie3.simona.service.ServiceStateData.ServiceBaseStateData import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.RichZonedDateTime import org.apache.pekko.actor.typed.scaladsl.{Behaviors, StashBuffer} @@ -199,7 +197,6 @@ object ResultServiceProxy { transformer2wResults, partialTransformer3wResults, congestionResults, - nextTick, ) => // handling of three winding transformers val (updatedResults, transformer3wResults) = diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index f331fe2bdb..24abaa7c2c 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -17,11 +17,7 @@ import edu.ie3.simona.api.{ExtLinkInterface, ExtSimAdapter} import edu.ie3.simona.event.listener.ExtResultEvent import edu.ie3.simona.event.listener.ResultListener import edu.ie3.simona.exceptions.ServiceException -import edu.ie3.simona.ontology.messages.{ - RequestResult, - SchedulerMessage, - ServiceMessage, -} +import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.ontology.messages.ResultMessage.RequestResult import edu.ie3.simona.scheduler.ScheduleLock diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index 275d535998..cb036fdd21 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -70,14 +70,14 @@ final case class ExtSimSetupData( ) => copy(evDataService = Some(serviceRef)) case ( - _: ExtResultDataConnection, - providerRef: ActorRef[ExtResultProvider.Message], - ) => + _: ExtResultDataConnection, + providerRef: ActorRef[ExtResultProvider.Message], + ) => copy(resultProviders = resultProviders ++ Seq(providerRef)) case ( - _: ExtResultListener, - listenerRef: ActorRef[ResultListener.Message], - ) => + _: ExtResultListener, + listenerRef: ActorRef[ResultListener.Message], + ) => copy(resultListeners = resultListeners ++ Seq(listenerRef)) case (_, _) => this diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala index 5ed60c0b2f..5eebc21295 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -17,20 +17,13 @@ import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.event.listener.{ResultListener, RuntimeEventListener} -import edu.ie3.simona.event.ResultEvent.ResultResponse -import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} -import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} +import edu.ie3.simona.io.grid.GridProvider import edu.ie3.simona.ontology.messages.ResultMessage.{ RequestResult, ResultResponse, } -import edu.ie3.simona.io.grid.GridProvider -import edu.ie3.simona.ontology.messages.{ - RequestResult, - SchedulerMessage, - ServiceMessage, -} +import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.scheduler.TimeAdvancer import edu.ie3.simona.scheduler.core.Core.CoreFactory import edu.ie3.simona.scheduler.core.RegularSchedulerCore diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index 40c502b90e..40502b7e1d 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -15,20 +15,14 @@ import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.agent.grid.GridAgentMessages.CreateGridAgent import edu.ie3.simona.config.{GridConfigParser, SimonaConfig} +import edu.ie3.simona.event.RuntimeEvent import edu.ie3.simona.event.listener.{ResultListener, RuntimeEventListener} -import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.exceptions.agent.GridAgentInitializationException -import edu.ie3.simona.io.grid.GridProvider -import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.ontology.messages.ResultMessage.{ RequestResult, ResultResponse, } -import edu.ie3.simona.ontology.messages.{ - RequestResult, - SchedulerMessage, - ServiceMessage, -} +import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.scheduler.core.Core.CoreFactory import edu.ie3.simona.scheduler.core.RegularSchedulerCore import edu.ie3.simona.scheduler.{ScheduleLock, Scheduler, TimeAdvancer} diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentSpec.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentSpec.scala index f14f0d1d4e..71df0986c5 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentSpec.scala @@ -267,7 +267,7 @@ class EmAgentSpec "PRIORITIZED", simulationStart, parent = Left(scheduler.ref), - listener = Iterable(resultListener.ref), + listener = resultProxy.ref, None, ) ) diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala index 80b1dd23bd..09be58adfb 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentWithServiceSpec.scala @@ -108,7 +108,7 @@ class EmAgentWithServiceSpec parentEmAgent .expectMessageType[RegisterControlledAsset] - .inputModel shouldBe emInput + .assetInput shouldBe emInput service.expectMessage( EmFlexMessage( From 0fcd0e0f048d4749a7211353ee5d7fbd42675509 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 24 Sep 2025 16:47:10 +0200 Subject: [PATCH 094/125] Saving changes. --- .../edu/ie3/simona/service/em/EmServiceBaseCore.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 977afb3c05..6162cc30a5 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -112,8 +112,13 @@ final case class EmServiceBaseCore( val tick = provideEmSetPoints.tick val emEntities = provideEmSetPoints.emSetPoints.keySet.asScala - emEntities.map(uuidToAgent).foreach { ref => - ref ! FlexActivation(tick, PowerLimit) + emEntities.foreach { entity => + uuidToAgent.get(entity) match { + case Some(ref) => + ref ! FlexActivation(tick, PowerLimit) + case None => + log.warn(s"Received entity: $entity") + } } ( From 7a6c1bfc7d2077ebec14658f0149b79f24f2e19c Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 30 Sep 2025 10:32:51 +0200 Subject: [PATCH 095/125] Saving changes. --- build.gradle | 2 +- src/main/resources/reference.conf | 1531 +++++++++++++++++ .../edu/ie3/simona/api/ExtSimAdapter.scala | 16 +- .../service/em/EmCommunicationCore.scala | 29 +- .../ie3/simona/sim/setup/ExtSimSetup.scala | 10 +- .../ie3/simona/util/ReceiveMultiDataMap.scala | 18 + 6 files changed, 1578 insertions(+), 28 deletions(-) create mode 100644 src/main/resources/reference.conf diff --git a/build.gradle b/build.gradle index 3c65142da5..97a484c082 100644 --- a/build.gradle +++ b/build.gradle @@ -191,7 +191,7 @@ application { // see https://pekko.apache.org/docs/pekko/current/additional/packaging.html#gradle-the-jar-task-from-the-java-plugin ////////////////////////////////////////////////////////////////////// tasks.shadowJar { - append 'reference.conf' + //append 'reference.conf' zip64 = true archiveBaseName = 'simona' } diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf new file mode 100644 index 0000000000..2ac51454dc --- /dev/null +++ b/src/main/resources/reference.conf @@ -0,0 +1,1531 @@ +# SPDX-License-Identifier: Apache-2.0 + +pekko.actor.typed { + + # List FQCN of `org.apache.pekko.actor.typed.ExtensionId`s which shall be loaded at actor system startup. + # Should be on the format: 'extensions = ["com.example.MyExtId1", "com.example.MyExtId2"]' etc. + # See the Pekko Documentation for more info about Extensions + extensions = [] + + # List FQCN of extensions which shall be loaded at actor system startup. + # Library extensions are regular extensions that are loaded at startup and are + # available for third party library authors to enable auto-loading of extensions when + # present on the classpath. This is done by appending entries: + # 'library-extensions += "Extension"' in the library `reference.conf`. + # + # Should not be set by end user applications in 'application.conf', use the extensions property for that + # + library-extensions = ${?pekko.actor.typed.library-extensions} [] + + # Receptionist is started eagerly to allow clustered receptionist to gather remote registrations early on. + library-extensions += "org.apache.pekko.actor.typed.receptionist.Receptionist$" + + # While an actor is restarted (waiting for backoff to expire and children to stop) + # incoming messages and signals are stashed, and delivered later to the newly restarted + # behavior. This property defines the capacity in number of messages of the stash + # buffer. If the capacity is exceed then additional incoming messages are dropped. + restart-stash-capacity = 1000 + + # Typed mailbox defaults to the single consumer mailbox as balancing dispatcher is not supported + default-mailbox { + mailbox-type = "org.apache.pekko.dispatch.SingleConsumerOnlyUnboundedMailbox" + } +} + +# Load typed extensions by a classic extension. +pekko.library-extensions += "org.apache.pekko.actor.typed.internal.adapter.ActorSystemAdapter$LoadTypedExtensions" + +pekko.actor { + serializers { + typed-misc = "org.apache.pekko.actor.typed.internal.MiscMessageSerializer" + service-key = "org.apache.pekko.actor.typed.internal.receptionist.ServiceKeySerializer" + } + + serialization-identifiers { + "org.apache.pekko.actor.typed.internal.MiscMessageSerializer" = 24 + "org.apache.pekko.actor.typed.internal.receptionist.ServiceKeySerializer" = 26 + } + + serialization-bindings { + "org.apache.pekko.actor.typed.ActorRef" = typed-misc + "org.apache.pekko.actor.typed.internal.adapter.ActorRefAdapter" = typed-misc + "org.apache.pekko.actor.typed.internal.receptionist.DefaultServiceKey" = service-key + } +} + +# When using Pekko Typed (having pekko-actor-typed in classpath) the +# org.apache.pekko.event.slf4j.Slf4jLogger is enabled instead of the DefaultLogger +# even though it has not been explicitly defined in `pekko.loggers` +# configuration. +# +# Slf4jLogger will be used for all Pekko classic logging via eventStream, +# including logging from Pekko internals. The Slf4jLogger is then using +# an ordinary org.slf4j.Logger to emit the log events. +# +# The Slf4jLoggingFilter is also enabled automatically. +# +# This behavior can be disabled by setting this property to `off`. +pekko.use-slf4j = on + +pekko.reliable-delivery { + producer-controller { + + # To avoid head of line blocking from serialization and transfer + # of large messages this can be enabled. + # Large messages are chunked into pieces of the given size in bytes. The + # chunked messages are sent separately and assembled on the consumer side. + # Serialization and deserialization is performed by the ProducerController and + # ConsumerController respectively instead of in the remote transport layer. + chunk-large-messages = off + + durable-queue { + # The ProducerController uses this timeout for the requests to + # the durable queue. If there is no reply within the timeout it + # will be retried. + request-timeout = 3s + + # The ProducerController retries requests to the durable queue this + # number of times before failing. + retry-attempts = 10 + + # The ProducerController retries sending the first message with this interval + # until it has been confirmed. + resend-first-interval = 1s + } + } + + consumer-controller { + # Number of messages in flight between ProducerController and + # ConsumerController. The ConsumerController requests for more messages + # when half of the window has been used. + flow-control-window = 50 + + # The ConsumerController resends flow control messages to the + # ProducerController with the resend-interval-min, and increasing + # it gradually to resend-interval-max when idle. + resend-interval-min = 2s + resend-interval-max = 30s + + # If this is enabled lost messages will not be resent, but flow control is used. + # This can be more efficient since messages don't have to be + # kept in memory in the `ProducerController` until they have been + # confirmed, but the drawback is that lost messages will not be delivered. + only-flow-control = false + } + + work-pulling { + producer-controller = ${pekko.reliable-delivery.producer-controller} + producer-controller { + # Limit of how many messages that can be buffered when there + # is no demand from the consumer side. + buffer-size = 1000 + + # Ask timeout for sending message to worker until receiving Ack from worker + internal-ask-timeout = 60s + + # Chunked messages not implemented for work-pulling yet. Override to not + # propagate property from pekko.reliable-delivery.producer-controller. + chunk-large-messages = off + } + } +} + +# SPDX-License-Identifier: Apache-2.0 + +##################################### +# Pekko Actor Reference Config File # +##################################### + +# This is the reference config file that contains all the default settings. +# Make your edits/overrides in your application.conf. + +# Pekko version, checked against the runtime version of Pekko. Loaded from generated conf file. +include "version" + +pekko { + # Home directory of Pekko, modules in the deploy directory will be loaded + home = "" + + # Loggers to register at boot time (org.apache.pekko.event.Logging$DefaultLogger logs + # to STDOUT) + loggers = ["org.apache.pekko.event.Logging$DefaultLogger"] + + # Filter of log events that is used by the LoggingAdapter before + # publishing log events to the eventStream. It can perform + # fine grained filtering based on the log source. The default + # implementation filters on the `loglevel`. + # FQCN of the LoggingFilter. The Class of the FQCN must implement + # org.apache.pekko.event.LoggingFilter and have a public constructor with + # (org.apache.pekko.actor.ActorSystem.Settings, org.apache.pekko.event.EventStream) parameters. + logging-filter = "org.apache.pekko.event.DefaultLoggingFilter" + + # Specifies the default loggers dispatcher + loggers-dispatcher = "pekko.actor.default-dispatcher" + + # Loggers are created and registered synchronously during ActorSystem + # start-up, and since they are actors, this timeout is used to bound the + # waiting time + logger-startup-timeout = 5s + + # Log level used by the configured loggers (see "loggers") as soon + # as they have been started; before that, see "stdout-loglevel" + # Options: OFF, ERROR, WARNING, INFO, DEBUG + loglevel = "INFO" + + # Log level for the very basic logger activated during ActorSystem startup. + # This logger prints the log messages to stdout (System.out). + # Options: OFF, ERROR, WARNING, INFO, DEBUG + stdout-loglevel = "WARNING" + + # Log the complete configuration at INFO level when the actor system is started. + # This is useful when you are uncertain of what configuration is used. + log-config-on-start = off + + # Log at info level when messages are sent to dead letters, or published to + # eventStream as `DeadLetter`, `Dropped` or `UnhandledMessage`. + # Possible values: + # on: all dead letters are logged + # off: no logging of dead letters + # n: positive integer, number of dead letters that will be logged + log-dead-letters = 10 + + # Possibility to turn off logging of dead letters while the actor system + # is shutting down. Logging is only done when enabled by 'log-dead-letters' + # setting. + log-dead-letters-during-shutdown = off + + # When log-dead-letters is enabled, this will re-enable the logging after configured duration. + # infinite: suspend the logging forever; + # or a duration (eg: 5 minutes), after which the logging will be re-enabled. + log-dead-letters-suspend-duration = 5 minutes + + # List FQCN of extensions which shall be loaded at actor system startup. + # Library extensions are regular extensions that are loaded at startup and are + # available for third party library authors to enable auto-loading of extensions when + # present on the classpath. This is done by appending entries: + # 'library-extensions += "Extension"' in the library `reference.conf`. + # + # Should not be set by end user applications in 'application.conf', use the extensions property for that + # + library-extensions = ${?pekko.library-extensions} ["org.apache.pekko.serialization.SerializationExtension$"] + + # List FQCN of extensions which shall be loaded at actor system startup. + # Should be on the format: 'extensions = ["foo", "bar"]' etc. + # See the Pekko Documentation for more info about Extensions + extensions = [] + + # Toggles whether threads created by this ActorSystem should be daemons or not + daemonic = off + + # JVM shutdown, System.exit(-1), in case of a fatal error, + # such as OutOfMemoryError + jvm-exit-on-fatal-error = on + + # Pekko installs JVM shutdown hooks by default, e.g. in CoordinatedShutdown and Artery. This property will + # not disable user-provided hooks registered using `CoordinatedShutdown#addCancellableJvmShutdownHook`. + # This property is related to `pekko.coordinated-shutdown.run-by-jvm-shutdown-hook` below. + # This property makes it possible to disable all such hooks if the application itself + # or a higher level framework such as Play prefers to install the JVM shutdown hook and + # terminate the ActorSystem itself, with or without using CoordinatedShutdown. + jvm-shutdown-hooks = on + + # Version must be the same across all modules and if they are different the startup + # will fail. It's possible but not recommended, to disable this check, and only log a warning, + # by setting this property to `off`. + fail-mixed-versions = on + + # Some modules (remoting only right now) can emit custom events to the Java Flight Recorder if running + # on JDK 11 or later. If you for some reason do not want that, it can be disabled and switched to no-ops + # with this toggle. + java-flight-recorder { + enabled = true + } + + actor { + + # Either one of "local", "remote" or "cluster" or the + # FQCN of the ActorRefProvider to be used; the below is the built-in default, + # note that "remote" and "cluster" requires the pekko-remote and pekko-cluster + # artifacts to be on the classpath. + provider = "local" + + # The guardian "/user" will use this class to obtain its supervisorStrategy. + # It needs to be a subclass of org.apache.pekko.actor.SupervisorStrategyConfigurator. + # In addition to the default there is org.apache.pekko.actor.StoppingSupervisorStrategy. + guardian-supervisor-strategy = "org.apache.pekko.actor.DefaultSupervisorStrategy" + + # Timeout for Extension creation and a few other potentially blocking + # initialization tasks. + creation-timeout = 20s + + # Serializes and deserializes (non-primitive) messages to ensure immutability, + # this is only intended for testing. + serialize-messages = off + + # Serializes and deserializes creators (in Props) to ensure that they can be + # sent over the network, this is only intended for testing. Purely local deployments + # as marked with deploy.scope == LocalScope are exempt from verification. + serialize-creators = off + + # If serialize-messages or serialize-creators are enabled classes that starts with + # a prefix listed here are not verified. + no-serialization-verification-needed-class-prefix = ["org.apache.pekko."] + + # Timeout for send operations to top-level actors which are in the process + # of being started. This is only relevant if using a bounded mailbox or the + # CallingThreadDispatcher for a top-level actor. + unstarted-push-timeout = 10s + + # TypedActor deprecated since Akka 2.6.0. + typed { + # Default timeout for the deprecated TypedActor (not the new actor APIs in Akka 2.6) + # methods with non-void return type. + timeout = 5s + } + + # Mapping between ´deployment.router' short names to fully qualified class names + router.type-mapping { + from-code = "org.apache.pekko.routing.NoRouter" + round-robin-pool = "org.apache.pekko.routing.RoundRobinPool" + round-robin-group = "org.apache.pekko.routing.RoundRobinGroup" + random-pool = "org.apache.pekko.routing.RandomPool" + random-group = "org.apache.pekko.routing.RandomGroup" + balancing-pool = "org.apache.pekko.routing.BalancingPool" + smallest-mailbox-pool = "org.apache.pekko.routing.SmallestMailboxPool" + broadcast-pool = "org.apache.pekko.routing.BroadcastPool" + broadcast-group = "org.apache.pekko.routing.BroadcastGroup" + scatter-gather-pool = "org.apache.pekko.routing.ScatterGatherFirstCompletedPool" + scatter-gather-group = "org.apache.pekko.routing.ScatterGatherFirstCompletedGroup" + tail-chopping-pool = "org.apache.pekko.routing.TailChoppingPool" + tail-chopping-group = "org.apache.pekko.routing.TailChoppingGroup" + consistent-hashing-pool = "org.apache.pekko.routing.ConsistentHashingPool" + consistent-hashing-group = "org.apache.pekko.routing.ConsistentHashingGroup" + } + + deployment { + + # deployment id pattern - on the format: /parent/child etc. + default { + + # The id of the dispatcher to use for this actor. + # If undefined or empty the dispatcher specified in code + # (Props.withDispatcher) is used, or default-dispatcher if not + # specified at all. + dispatcher = "" + + # The id of the mailbox to use for this actor. + # If undefined or empty the default mailbox of the configured dispatcher + # is used or if there is no mailbox configuration the mailbox specified + # in code (Props.withMailbox) is used. + # If there is a mailbox defined in the configured dispatcher then that + # overrides this setting. + mailbox = "" + + # routing (load-balance) scheme to use + # - available: "from-code", "round-robin", "random", "smallest-mailbox", + # "scatter-gather", "broadcast" + # - or: Fully qualified class name of the router class. + # The class must extend org.apache.pekko.routing.CustomRouterConfig and + # have a public constructor with com.typesafe.config.Config + # and optional org.apache.pekko.actor.DynamicAccess parameter. + # - default is "from-code"; + # Whether or not an actor is transformed to a Router is decided in code + # only (Props.withRouter). The type of router can be overridden in the + # configuration; specifying "from-code" means that the values specified + # in the code shall be used. + # In case of routing, the actors to be routed to can be specified + # in several ways: + # - nr-of-instances: will create that many children + # - routees.paths: will route messages to these paths using ActorSelection, + # i.e. will not create children + # - resizer: dynamically resizable number of routees as specified in + # resizer below + router = "from-code" + + # number of children to create in case of a router; + # this setting is ignored if routees.paths is given + nr-of-instances = 1 + + # within is the timeout used for routers containing future calls + within = 5 seconds + + # number of virtual nodes per node for consistent-hashing router + virtual-nodes-factor = 10 + + tail-chopping-router { + # interval is duration between sending message to next routee + interval = 10 milliseconds + } + + routees { + # Alternatively to giving nr-of-instances you can specify the full + # paths of those actors which should be routed to. This setting takes + # precedence over nr-of-instances + paths = [] + } + + # To use a dedicated dispatcher for the routees of the pool you can + # define the dispatcher configuration inline with the property name + # 'pool-dispatcher' in the deployment section of the router. + # For example: + # pool-dispatcher { + # fork-join-executor.parallelism-min = 5 + # fork-join-executor.parallelism-max = 5 + # } + + # Routers with dynamically resizable number of routees; this feature is + # enabled by including (parts of) this section in the deployment + resizer { + + enabled = off + + # The fewest number of routees the router should ever have. + lower-bound = 1 + + # The most number of routees the router should ever have. + # Must be greater than or equal to lower-bound. + upper-bound = 10 + + # Threshold used to evaluate if a routee is considered to be busy + # (under pressure). Implementation depends on this value (default is 1). + # 0: number of routees currently processing a message. + # 1: number of routees currently processing a message has + # some messages in mailbox. + # > 1: number of routees with at least the configured pressure-threshold + # messages in their mailbox. Note that estimating mailbox size of + # default UnboundedMailbox is O(N) operation. + pressure-threshold = 1 + + # Percentage to increase capacity whenever all routees are busy. + # For example, 0.2 would increase 20% (rounded up), i.e. if current + # capacity is 6 it will request an increase of 2 more routees. + rampup-rate = 0.2 + + # Minimum fraction of busy routees before backing off. + # For example, if this is 0.3, then we'll remove some routees only when + # less than 30% of routees are busy, i.e. if current capacity is 10 and + # 3 are busy then the capacity is unchanged, but if 2 or less are busy + # the capacity is decreased. + # Use 0.0 or negative to avoid removal of routees. + backoff-threshold = 0.3 + + # Fraction of routees to be removed when the resizer reaches the + # backoffThreshold. + # For example, 0.1 would decrease 10% (rounded up), i.e. if current + # capacity is 9 it will request an decrease of 1 routee. + backoff-rate = 0.1 + + # Number of messages between resize operation. + # Use 1 to resize before each message. + messages-per-resize = 10 + } + + # Routers with dynamically resizable number of routees based on + # performance metrics. + # This feature is enabled by including (parts of) this section in + # the deployment, cannot be enabled together with default resizer. + optimal-size-exploring-resizer { + + enabled = off + + # The fewest number of routees the router should ever have. + lower-bound = 1 + + # The most number of routees the router should ever have. + # Must be greater than or equal to lower-bound. + upper-bound = 10 + + # probability of doing a ramping down when all routees are busy + # during exploration. + chance-of-ramping-down-when-full = 0.2 + + # Interval between each resize attempt + action-interval = 5s + + # If the routees have not been fully utilized (i.e. all routees busy) + # for such length, the resizer will downsize the pool. + downsize-after-underutilized-for = 72h + + # Duration exploration, the ratio between the largest step size and + # current pool size. E.g. if the current pool size is 50, and the + # explore-step-size is 0.1, the maximum pool size change during + # exploration will be +- 5 + explore-step-size = 0.1 + + # Probability of doing an exploration v.s. optimization. + chance-of-exploration = 0.4 + + # When downsizing after a long streak of under-utilization, the resizer + # will downsize the pool to the highest utilization multiplied by + # a downsize ratio. This downsize ratio determines the new pools size + # in comparison to the highest utilization. + # E.g. if the highest utilization is 10, and the down size ratio + # is 0.8, the pool will be downsized to 8 + downsize-ratio = 0.8 + + # When optimizing, the resizer only considers the sizes adjacent to the + # current size. This number indicates how many adjacent sizes to consider. + optimization-range = 16 + + # The weight of the latest metric over old metrics when collecting + # performance metrics. + # E.g. if the last processing speed is 10 millis per message at pool + # size 5, and if the new processing speed collected is 6 millis per + # message at pool size 5. Given a weight of 0.3, the metrics + # representing pool size 5 will be 6 * 0.3 + 10 * 0.7, i.e. 8.8 millis + # Obviously, this number should be between 0 and 1. + weight-of-latest-metric = 0.5 + } + } + + "/IO-DNS/inet-address" { + mailbox = "unbounded" + router = "consistent-hashing-pool" + nr-of-instances = 4 + } + + "/IO-DNS/inet-address/*" { + dispatcher = "pekko.actor.default-blocking-io-dispatcher" + } + + "/IO-DNS/async-dns" { + mailbox = "unbounded" + router = "round-robin-pool" + nr-of-instances = 1 + } + } + + default-dispatcher { + # Must be one of the following + # Dispatcher, PinnedDispatcher, or a FQCN to a class inheriting + # MessageDispatcherConfigurator with a public constructor with + # both com.typesafe.config.Config parameter and + # org.apache.pekko.dispatch.DispatcherPrerequisites parameters. + # PinnedDispatcher must be used together with executor=thread-pool-executor. + type = "Dispatcher" + + # Which kind of ExecutorService to use for this dispatcher + # Valid options: + # - "default-executor" requires a "default-executor" section + # - "fork-join-executor" requires a "fork-join-executor" section + # - "virtual-thread-executor" requires a "virtual-thread-executor" section + # - "thread-pool-executor" requires a "thread-pool-executor" section + # - "affinity-pool-executor" requires an "affinity-pool-executor" section + # - A FQCN of a class extending ExecutorServiceConfigurator + executor = "default-executor" + + # This will be used if you have set "executor = "default-executor"". + # If an ActorSystem is created with a given ExecutionContext, this + # ExecutionContext will be used as the default executor for all + # dispatchers in the ActorSystem configured with + # executor = "default-executor". Note that "default-executor" + # is the default value for executor, and therefore used if not + # specified otherwise. If no ExecutionContext is given, + # the executor configured in "fallback" will be used. + default-executor { + fallback = "fork-join-executor" + } + + # This will be used if you have set "executor = "affinity-pool-executor"" + # Underlying thread pool implementation is org.apache.pekko.dispatch.affinity.AffinityPool. + # This executor is classified as "ApiMayChange". + affinity-pool-executor { + # Min number of threads to cap factor-based parallelism number to + parallelism-min = 4 + + # The parallelism factor is used to determine thread pool size using the + # following formula: ceil(available processors * factor). Resulting size + # is then bounded by the parallelism-min and parallelism-max values. + parallelism-factor = 0.8 + + # Max number of threads to cap factor-based parallelism number to. + parallelism-max = 64 + + # Each worker in the pool uses a separate bounded MPSC queue. This value + # indicates the upper bound of the queue. Whenever an attempt to enqueue + # a task is made and the queue does not have capacity to accommodate + # the task, the rejection handler created by the rejection handler specified + # in "rejection-handler" is invoked. + task-queue-size = 512 + + # FQCN of the Rejection handler used in the pool. + # Must have an empty public constructor and must + # implement org.apache.pekko.actor.affinity.RejectionHandlerFactory. + rejection-handler = "org.apache.pekko.dispatch.affinity.ThrowOnOverflowRejectionHandler" + + # Level of CPU time used, on a scale between 1 and 10, during backoff/idle. + # The tradeoff is that to have low latency more CPU time must be used to be + # able to react quickly on incoming messages or send as fast as possible after + # backoff backpressure. + # Level 1 strongly prefer low CPU consumption over low latency. + # Level 10 strongly prefer low latency over low CPU consumption. + idle-cpu-level = 5 + + # FQCN of the org.apache.pekko.dispatch.affinity.QueueSelectorFactory. + # The Class of the FQCN must have a public constructor with a + # (com.typesafe.config.Config) parameter. + # A QueueSelectorFactory create instances of org.apache.pekko.dispatch.affinity.QueueSelector, + # that is responsible for determining which task queue a Runnable should be enqueued in. + queue-selector = "org.apache.pekko.dispatch.affinity.FairDistributionHashCache" + + # When using the "org.apache.pekko.dispatch.affinity.FairDistributionHashCache" queue selector + # internally the AffinityPool uses two methods to determine which task + # queue to allocate a Runnable to: + # - map based - maintains a round robin counter and a map of Runnable + # hashcodes to queues that they have been associated with. This ensures + # maximum fairness in terms of work distribution, meaning that each worker + # will get approximately equal amount of mailboxes to execute. This is suitable + # in cases where we have a small number of actors that will be scheduled on + # the pool and we want to ensure the maximum possible utilization of the + # available threads. + # - hash based - the task - queue in which the runnable should go is determined + # by using an uniformly distributed int to int hash function which uses the + # hash code of the Runnable as an input. This is preferred in situations where we + # have enough number of distinct actors to ensure statistically uniform + # distribution of work across threads or we are ready to sacrifice the + # former for the added benefit of avoiding map look-ups. + fair-work-distribution { + # The value serves as a threshold which determines the point at which the + # pool switches from the first to the second work distribution schemes. + # For example, if the value is set to 128, the pool can observe up to + # 128 unique actors and schedule their mailboxes using the map based + # approach. Once this number is reached the pool switches to hash based + # task distribution mode. If the value is set to 0, the map based + # work distribution approach is disabled and only the hash based is + # used irrespective of the number of unique actors. Valid range is + # 0 to 2048 (inclusive) + threshold = 128 + } + } + + # This will be used if you have set "executor = "fork-join-executor"" + # Underlying thread pool implementation is java.util.concurrent.ForkJoinPool + fork-join-executor { + # Min number of threads to cap factor-based parallelism number to + parallelism-min = 8 + + # The parallelism factor is used to determine thread pool size using the + # following formula: ceil(available processors * factor). Resulting size + # is then bounded by the parallelism-min and parallelism-max values. + parallelism-factor = 1.0 + + # Max number of threads to cap factor-based parallelism number to + parallelism-max = 64 + + # Setting to "FIFO" to use queue like peeking mode which "poll" or "LIFO" to use stack + # like peeking mode which "pop". + task-peeking-mode = "FIFO" + + # This config is new in Pekko v1.1.0 and only has an effect if you are running with JDK 9 and above. + # Read the documentation on `java.util.concurrent.ForkJoinPool` to find out more. Default in hex is 0x7fff. + maximum-pool-size = 32767 + + # This config is new in Pekko v1.2.0 and only has an effect if you are running with JDK 21 and above, + # When set to `on` but underlying runtime does not support virtual threads, an Exception will throw. + # Virtualize this dispatcher as a virtual-thread-executor + # Valid values are: `on`, `off` + # + # Requirements: + # 1. JDK 21+ + # 2. add options to the JVM: + # --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED + # --add-opens=java.base/java.lang=ALL-UNNAMED + virtualize = off + } + + # This will be used if you have set "executor = "thread-pool-executor"" + # Underlying thread pool implementation is java.util.concurrent.ThreadPoolExecutor + thread-pool-executor { + # Keep alive time for threads + keep-alive-time = 60s + + # Define a fixed thread pool size with this property. The corePoolSize + # and the maximumPoolSize of the ThreadPoolExecutor will be set to this + # value, if it is defined. Then the other pool-size properties will not + # be used. + # + # Valid values are: `off` or a positive integer. + fixed-pool-size = off + + # Min number of threads to cap factor-based corePoolSize number to + core-pool-size-min = 8 + + # The core-pool-size-factor is used to determine corePoolSize of the + # ThreadPoolExecutor using the following formula: + # ceil(available processors * factor). + # Resulting size is then bounded by the core-pool-size-min and + # core-pool-size-max values. + core-pool-size-factor = 3.0 + + # Max number of threads to cap factor-based corePoolSize number to + core-pool-size-max = 64 + + # Minimum number of threads to cap factor-based maximumPoolSize number to + max-pool-size-min = 8 + + # The max-pool-size-factor is used to determine maximumPoolSize of the + # ThreadPoolExecutor using the following formula: + # ceil(available processors * factor) + # The maximumPoolSize will not be less than corePoolSize. + # It is only used if using a bounded task queue. + max-pool-size-factor = 3.0 + + # Max number of threads to cap factor-based maximumPoolSize number to + max-pool-size-max = 64 + + # Specifies the bounded capacity of the task queue (< 1 == unbounded) + task-queue-size = -1 + + # Specifies which type of task queue will be used, can be "array" or + # "linked" (default) + task-queue-type = "linked" + + # Allow core threads to time out + allow-core-timeout = on + } + + # This will be used if you have set "executor = "virtual-thread-executor" + # This executor will execute the every task on a new virtual thread. + # Underlying thread pool implementation is java.util.concurrent.ForkJoinPool for JDK <= 22 + # If the current runtime does not support virtual thread, + # then the executor configured in "fallback" will be used. + virtual-thread-executor { + #Please set the the underlying pool with system properties below: + #jdk.virtualThreadScheduler.parallelism + #jdk.virtualThreadScheduler.maxPoolSize + #jdk.virtualThreadScheduler.minRunnable + #jdk.unparker.maxPoolSize + fallback = "fork-join-executor" + } + # How long time the dispatcher will wait for new actors until it shuts down + shutdown-timeout = 1s + + # Throughput defines the number of messages that are processed in a batch + # before the thread is returned to the pool. Set to 1 for as fair as possible. + throughput = 5 + + # Throughput deadline for Dispatcher, set to 0 or negative for no deadline + throughput-deadline-time = 0ms + + # For BalancingDispatcher: If the balancing dispatcher should attempt to + # schedule idle actors using the same dispatcher when a message comes in, + # and the dispatchers ExecutorService is not fully busy already. + attempt-teamwork = on + + # If this dispatcher requires a specific type of mailbox, specify the + # fully-qualified class name here; the actually created mailbox will + # be a subtype of this type. The empty string signifies no requirement. + mailbox-requirement = "" + } + + # Default separate internal dispatcher to run Pekko internal tasks and actors on + # protecting them against starvation because of accidental blocking in user actors (which run on the + # default dispatcher) + internal-dispatcher { + type = "Dispatcher" + executor = "fork-join-executor" + throughput = 5 + fork-join-executor { + parallelism-min = 4 + parallelism-factor = 1.0 + parallelism-max = 64 + } + } + + default-blocking-io-dispatcher { + type = "Dispatcher" + executor = "thread-pool-executor" + throughput = 1 + + thread-pool-executor { + fixed-pool-size = 16 + } + } + + default-mailbox { + # FQCN of the MailboxType. The Class of the FQCN must have a public + # constructor with + # (org.apache.pekko.actor.ActorSystem.Settings, com.typesafe.config.Config) parameters. + mailbox-type = "org.apache.pekko.dispatch.UnboundedMailbox" + + # If the mailbox is bounded then it uses this setting to determine its + # capacity. The provided value must be positive. + # NOTICE: + # Up to version 2.1 the mailbox type was determined based on this setting; + # this is no longer the case, the type must explicitly be a bounded mailbox. + mailbox-capacity = 1000 + + # If the mailbox is bounded then this is the timeout for enqueueing + # in case the mailbox is full. Negative values signify infinite + # timeout, which should be avoided as it bears the risk of dead-lock. + mailbox-push-timeout-time = 10s + + # For Actor with Stash: The default capacity of the stash. + # If negative (or zero) then an unbounded stash is used (default) + # If positive then a bounded stash is used and the capacity is set using + # the property + stash-capacity = -1 + } + + mailbox { + # Mapping between message queue semantics and mailbox configurations. + # Used by org.apache.pekko.dispatch.RequiresMessageQueue[T] to enforce different + # mailbox types on actors. + # If your Actor implements RequiresMessageQueue[T], then when you create + # an instance of that actor its mailbox type will be decided by looking + # up a mailbox configuration via T in this mapping + requirements { + "org.apache.pekko.dispatch.UnboundedMessageQueueSemantics" = + pekko.actor.mailbox.unbounded-queue-based + "org.apache.pekko.dispatch.BoundedMessageQueueSemantics" = + pekko.actor.mailbox.bounded-queue-based + "org.apache.pekko.dispatch.DequeBasedMessageQueueSemantics" = + pekko.actor.mailbox.unbounded-deque-based + "org.apache.pekko.dispatch.UnboundedDequeBasedMessageQueueSemantics" = + pekko.actor.mailbox.unbounded-deque-based + "org.apache.pekko.dispatch.BoundedDequeBasedMessageQueueSemantics" = + pekko.actor.mailbox.bounded-deque-based + "org.apache.pekko.dispatch.MultipleConsumerSemantics" = + pekko.actor.mailbox.unbounded-queue-based + "org.apache.pekko.dispatch.ControlAwareMessageQueueSemantics" = + pekko.actor.mailbox.unbounded-control-aware-queue-based + "org.apache.pekko.dispatch.UnboundedControlAwareMessageQueueSemantics" = + pekko.actor.mailbox.unbounded-control-aware-queue-based + "org.apache.pekko.dispatch.BoundedControlAwareMessageQueueSemantics" = + pekko.actor.mailbox.bounded-control-aware-queue-based + "org.apache.pekko.event.LoggerMessageQueueSemantics" = + pekko.actor.mailbox.logger-queue + } + + unbounded-queue-based { + # FQCN of the MailboxType, The Class of the FQCN must have a public + # constructor with (org.apache.pekko.actor.ActorSystem.Settings, + # com.typesafe.config.Config) parameters. + mailbox-type = "org.apache.pekko.dispatch.UnboundedMailbox" + } + + bounded-queue-based { + # FQCN of the MailboxType, The Class of the FQCN must have a public + # constructor with (org.apache.pekko.actor.ActorSystem.Settings, + # com.typesafe.config.Config) parameters. + mailbox-type = "org.apache.pekko.dispatch.BoundedMailbox" + } + + unbounded-deque-based { + # FQCN of the MailboxType, The Class of the FQCN must have a public + # constructor with (org.apache.pekko.actor.ActorSystem.Settings, + # com.typesafe.config.Config) parameters. + mailbox-type = "org.apache.pekko.dispatch.UnboundedDequeBasedMailbox" + } + + bounded-deque-based { + # FQCN of the MailboxType, The Class of the FQCN must have a public + # constructor with (org.apache.pekko.actor.ActorSystem.Settings, + # com.typesafe.config.Config) parameters. + mailbox-type = "org.apache.pekko.dispatch.BoundedDequeBasedMailbox" + } + + unbounded-control-aware-queue-based { + # FQCN of the MailboxType, The Class of the FQCN must have a public + # constructor with (org.apache.pekko.actor.ActorSystem.Settings, + # com.typesafe.config.Config) parameters. + mailbox-type = "org.apache.pekko.dispatch.UnboundedControlAwareMailbox" + } + + bounded-control-aware-queue-based { + # FQCN of the MailboxType, The Class of the FQCN must have a public + # constructor with (org.apache.pekko.actor.ActorSystem.Settings, + # com.typesafe.config.Config) parameters. + mailbox-type = "org.apache.pekko.dispatch.BoundedControlAwareMailbox" + } + + # The LoggerMailbox will drain all messages in the mailbox + # when the system is shutdown and deliver them to the StandardOutLogger. + # Do not change this unless you know what you are doing. + logger-queue { + mailbox-type = "org.apache.pekko.event.LoggerMailboxType" + } + } + + debug { + # enable function of Actor.loggable(), which is to log any received message + # at DEBUG level, see the “Testing Actor Systems” section of the Pekko + # Documentation at https://pekko.apache.org/docs/pekko/current/ + receive = off + + # enable DEBUG logging of all AutoReceiveMessages (Kill, PoisonPill etc.) + autoreceive = off + + # enable DEBUG logging of actor lifecycle changes + lifecycle = off + + # enable DEBUG logging of all LoggingFSMs for events, transitions and timers + fsm = off + + # enable DEBUG logging of subscription changes on the eventStream + event-stream = off + + # enable DEBUG logging of unhandled messages + unhandled = off + + # enable WARN logging of misconfigured routers + router-misconfiguration = off + } + + # SECURITY BEST-PRACTICE is to disable java serialization for its multiple + # known attack surfaces. + # + # This setting is a short-cut to + # - using DisabledJavaSerializer instead of JavaSerializer + # + # Completely disable the use of `org.apache.pekko.serialization.JavaSerialization` by the + # Pekko Serialization extension, instead DisabledJavaSerializer will + # be inserted which will fail explicitly if attempts to use java serialization are made. + # + # The log messages emitted by such serializer SHOULD be treated as potential + # attacks which the serializer prevented, as they MAY indicate an external operator + # attempting to send malicious messages intending to use java serialization as attack vector. + # The attempts are logged with the SECURITY marker. + # + # Please note that this option does not stop you from manually invoking java serialization + # + allow-java-serialization = off + + # Log warnings when the Java serialization is used to serialize messages. + # Java serialization is not very performant and should not be used in production + # environments unless you don't care about performance and security. In that case + # you can turn this off. + warn-about-java-serializer-usage = on + + # To be used with the above warn-about-java-serializer-usage + # When warn-about-java-serializer-usage = on, and this warn-on-no-serialization-verification = off, + # warnings are suppressed for classes extending NoSerializationVerificationNeeded + # to reduce noise. + warn-on-no-serialization-verification = on + + # Entries for pluggable serializers and their bindings. + serializers { + java = "org.apache.pekko.serialization.JavaSerializer" + bytes = "org.apache.pekko.serialization.ByteArraySerializer" + primitive-long = "org.apache.pekko.serialization.LongSerializer" + primitive-int = "org.apache.pekko.serialization.IntSerializer" + primitive-string = "org.apache.pekko.serialization.StringSerializer" + primitive-bytestring = "org.apache.pekko.serialization.ByteStringSerializer" + primitive-boolean = "org.apache.pekko.serialization.BooleanSerializer" + } + + # Class to Serializer binding. You only need to specify the name of an + # interface or abstract base class of the messages. In case of ambiguity it + # is using the most specific configured class, or giving a warning and + # choosing the “first” one. + # + # To disable one of the default serializers, assign its class to "none", like + # "java.io.Serializable" = none + serialization-bindings { + "[B" = bytes + "java.io.Serializable" = java + + "java.lang.String" = primitive-string + "org.apache.pekko.util.ByteString$ByteString1C" = primitive-bytestring + "org.apache.pekko.util.ByteString$ByteString1" = primitive-bytestring + "org.apache.pekko.util.ByteString$ByteStrings" = primitive-bytestring + "java.lang.Long" = primitive-long + "scala.Long" = primitive-long + "java.lang.Integer" = primitive-int + "scala.Int" = primitive-int + "java.lang.Boolean" = primitive-boolean + "scala.Boolean" = primitive-boolean + } + + # Configuration namespace of serialization identifiers. + # Each serializer implementation must have an entry in the following format: + # `org.apache.pekko.actor.serialization-identifiers."FQCN" = ID` + # where `FQCN` is fully qualified class name of the serializer implementation + # and `ID` is globally unique serializer identifier number. + # Identifier values from 0 to 40 are reserved for Pekko internal usage. + serialization-identifiers { + "org.apache.pekko.serialization.JavaSerializer" = 1 + "org.apache.pekko.serialization.ByteArraySerializer" = 4 + + primitive-long = 18 + primitive-int = 19 + primitive-string = 20 + primitive-bytestring = 21 + primitive-boolean = 35 + } + + } + + serialization.protobuf { + # deprecated, use `allowed-classes` instead + whitelist-class = [ + "com.google.protobuf.GeneratedMessage", + "com.google.protobuf.GeneratedMessageV3", + "scalapb.GeneratedMessageCompanion", + "org.apache.pekko.protobufv3.internal.GeneratedMessage" + ] + + # Additional classes that are allowed even if they are not defined in `serialization-bindings`. + # It can be exact class name or name of super class or interfaces (one level). + # This is useful when a class is not used for serialization any more and therefore removed + # from `serialization-bindings`, but should still be possible to deserialize. + allowed-classes = ${pekko.serialization.protobuf.whitelist-class} + + } + + # Used to set the behavior of the scheduler. + # Changing the default values may change the system behavior drastically so make + # sure you know what you're doing! See the Scheduler section of the Pekko + # Documentation for more details. + scheduler { + # The LightArrayRevolverScheduler is used as the default scheduler in the + # system. It does not execute the scheduled tasks on exact time, but on every + # tick, it will run everything that is (over)due. You can increase or decrease + # the accuracy of the execution timing by specifying smaller or larger tick + # duration. If you are scheduling a lot of tasks you should consider increasing + # the ticks per wheel. + # Note that it might take up to 1 tick to stop the Timer, so setting the + # tick-duration to a high value will make shutting down the actor system + # take longer. + # + # Requirements: + # 1. The minimum supported tick-duration on Windows is 10ms, + # 2. The minimum supported tick-duration is 1ms + tick-duration = 10ms + + # An error will be throw when the tick-duration does not meet the requirements. + # When this is set to off, the tick-duration will be adjusted to meet the requirements + # and a warning will be logged. + error-on-tick-duration-verification-failed = on + + # The timer uses a circular wheel of buckets to store the timer tasks. + # This should be set such that the majority of scheduled timeouts (for high + # scheduling frequency) will be shorter than one rotation of the wheel + # (ticks-per-wheel * ticks-duration) + # THIS MUST BE A POWER OF TWO! + ticks-per-wheel = 512 + + # This setting selects the timer implementation which shall be loaded at + # system start-up. + # The class given here must implement the org.apache.pekko.actor.Scheduler interface + # and offer a public constructor which takes three arguments: + # 1) com.typesafe.config.Config + # 2) org.apache.pekko.event.LoggingAdapter + # 3) java.util.concurrent.ThreadFactory + implementation = org.apache.pekko.actor.LightArrayRevolverScheduler + + # When shutting down the scheduler, there will typically be a thread which + # needs to be stopped, and this timeout determines how long to wait for + # that to happen. In case of timeout the shutdown of the actor system will + # proceed without running possibly still enqueued tasks. + shutdown-timeout = 5s + } + + io { + + # By default the select loops run on dedicated threads, hence using a + # PinnedDispatcher + pinned-dispatcher { + type = "PinnedDispatcher" + executor = "thread-pool-executor" + thread-pool-executor.allow-core-timeout = off + } + + tcp { + + # The number of selectors to stripe the served channels over; each of + # these will use one select loop on the selector-dispatcher. + nr-of-selectors = 1 + + # Maximum number of open channels supported by this TCP module; there is + # no intrinsic general limit, this setting is meant to enable DoS + # protection by limiting the number of concurrently connected clients. + # Also note that this is a "soft" limit; in certain cases the implementation + # will accept a few connections more or a few less than the number configured + # here. Must be an integer > 0 or "unlimited". + max-channels = 256000 + + # When trying to assign a new connection to a selector and the chosen + # selector is at full capacity, retry selector choosing and assignment + # this many times before giving up + selector-association-retries = 10 + + # The maximum number of connection that are accepted in one go, + # higher numbers decrease latency, lower numbers increase fairness on + # the worker-dispatcher + batch-accept-limit = 10 + + # The number of bytes per direct buffer in the pool used to read or write + # network data from the kernel. + direct-buffer-size = 128 KiB + + # The maximal number of direct buffers kept in the direct buffer pool for + # reuse. + direct-buffer-pool-limit = 1000 + + # The duration a connection actor waits for a `Register` message from + # its commander before aborting the connection. + register-timeout = 5s + + # The maximum number of bytes delivered by a `Received` message. Before + # more data is read from the network the connection actor will try to + # do other work. + # The purpose of this setting is to impose a smaller limit than the + # configured receive buffer size. When using value 'unlimited' it will + # try to read all from the receive buffer. + max-received-message-size = unlimited + + # Enable fine grained logging of what goes on inside the implementation. + # Be aware that this may log more than once per message sent to the actors + # of the tcp implementation. + trace-logging = off + + # Fully qualified config path which holds the dispatcher configuration + # to be used for running the select() calls in the selectors + selector-dispatcher = "pekko.io.pinned-dispatcher" + + # Fully qualified config path which holds the dispatcher configuration + # for the read/write worker actors + worker-dispatcher = "pekko.actor.internal-dispatcher" + + # Fully qualified config path which holds the dispatcher configuration + # for the selector management actors + management-dispatcher = "pekko.actor.internal-dispatcher" + + # Fully qualified config path which holds the dispatcher configuration + # on which file IO tasks are scheduled + file-io-dispatcher = "pekko.actor.default-blocking-io-dispatcher" + + # The maximum number of bytes (or "unlimited") to transfer in one batch + # when using `WriteFile` command which uses `FileChannel.transferTo` to + # pipe files to a TCP socket. On some OS like Linux `FileChannel.transferTo` + # may block for a long time when network IO is faster than file IO. + # Decreasing the value may improve fairness while increasing may improve + # throughput. + file-io-transferTo-limit = 512 KiB + + # The number of times to retry the `finishConnect` call after being notified about + # OP_CONNECT. Retries are needed if the OP_CONNECT notification doesn't imply that + # `finishConnect` will succeed, which is the case on Android. + finish-connect-retries = 5 + + # On Windows connection aborts are not reliably detected unless an OP_READ is + # registered on the selector _after_ the connection has been reset. This + # workaround enables an OP_CONNECT which forces the abort to be visible on Windows. + # Enabling this setting on other platforms than Windows will cause various failures + # and undefined behavior. + # Possible values of this key are on, off and auto where auto will enable the + # workaround if Windows is detected automatically. + windows-connection-abort-workaround-enabled = off + } + + udp { + + # The number of selectors to stripe the served channels over; each of + # these will use one select loop on the selector-dispatcher. + nr-of-selectors = 1 + + # Maximum number of open channels supported by this UDP module Generally + # UDP does not require a large number of channels, therefore it is + # recommended to keep this setting low. + max-channels = 4096 + + # The select loop can be used in two modes: + # - setting "infinite" will select without a timeout, hogging a thread + # - setting a positive timeout will do a bounded select call, + # enabling sharing of a single thread between multiple selectors + # (in this case you will have to use a different configuration for the + # selector-dispatcher, e.g. using "type=Dispatcher" with size 1) + # - setting it to zero means polling, i.e. calling selectNow() + select-timeout = infinite + + # When trying to assign a new connection to a selector and the chosen + # selector is at full capacity, retry selector choosing and assignment + # this many times before giving up + selector-association-retries = 10 + + # The maximum number of datagrams that are read in one go, + # higher numbers decrease latency, lower numbers increase fairness on + # the worker-dispatcher + receive-throughput = 3 + + # The number of bytes per direct buffer in the pool used to read or write + # network data from the kernel. + direct-buffer-size = 128 KiB + + # The maximal number of direct buffers kept in the direct buffer pool for + # reuse. + direct-buffer-pool-limit = 1000 + + # Enable fine grained logging of what goes on inside the implementation. + # Be aware that this may log more than once per message sent to the actors + # of the tcp implementation. + trace-logging = off + + # Fully qualified config path which holds the dispatcher configuration + # to be used for running the select() calls in the selectors + selector-dispatcher = "pekko.io.pinned-dispatcher" + + # Fully qualified config path which holds the dispatcher configuration + # for the read/write worker actors + worker-dispatcher = "pekko.actor.internal-dispatcher" + + # Fully qualified config path which holds the dispatcher configuration + # for the selector management actors + management-dispatcher = "pekko.actor.internal-dispatcher" + } + + udp-connected { + + # The number of selectors to stripe the served channels over; each of + # these will use one select loop on the selector-dispatcher. + nr-of-selectors = 1 + + # Maximum number of open channels supported by this UDP module Generally + # UDP does not require a large number of channels, therefore it is + # recommended to keep this setting low. + max-channels = 4096 + + # The select loop can be used in two modes: + # - setting "infinite" will select without a timeout, hogging a thread + # - setting a positive timeout will do a bounded select call, + # enabling sharing of a single thread between multiple selectors + # (in this case you will have to use a different configuration for the + # selector-dispatcher, e.g. using "type=Dispatcher" with size 1) + # - setting it to zero means polling, i.e. calling selectNow() + select-timeout = infinite + + # When trying to assign a new connection to a selector and the chosen + # selector is at full capacity, retry selector choosing and assignment + # this many times before giving up + selector-association-retries = 10 + + # The maximum number of datagrams that are read in one go, + # higher numbers decrease latency, lower numbers increase fairness on + # the worker-dispatcher + receive-throughput = 3 + + # The number of bytes per direct buffer in the pool used to read or write + # network data from the kernel. + direct-buffer-size = 128 KiB + + # The maximal number of direct buffers kept in the direct buffer pool for + # reuse. + direct-buffer-pool-limit = 1000 + + # Enable fine grained logging of what goes on inside the implementation. + # Be aware that this may log more than once per message sent to the actors + # of the tcp implementation. + trace-logging = off + + # Fully qualified config path which holds the dispatcher configuration + # to be used for running the select() calls in the selectors + selector-dispatcher = "pekko.io.pinned-dispatcher" + + # Fully qualified config path which holds the dispatcher configuration + # for the read/write worker actors + worker-dispatcher = "pekko.actor.internal-dispatcher" + + # Fully qualified config path which holds the dispatcher configuration + # for the selector management actors + management-dispatcher = "pekko.actor.internal-dispatcher" + } + + dns { + # Fully qualified config path which holds the dispatcher configuration + # for the manager and resolver router actors. + # For actual router configuration see pekko.actor.deployment./IO-DNS/* + dispatcher = "pekko.actor.internal-dispatcher" + + # Name of the subconfig at path pekko.io.dns, see inet-address below + # + # Change to `async-dns` to use the new "native" DNS resolver, + # which is also capable of resolving SRV records. + resolver = "inet-address" + + # To-be-deprecated DNS resolver implementation which uses the Java InetAddress to resolve DNS records. + # To be replaced by `pekko.io.dns.async` which implements the DNS protocol natively and without blocking (which InetAddress does) + inet-address { + # Must implement org.apache.pekko.io.DnsProvider + provider-object = "org.apache.pekko.io.InetAddressDnsProvider" + + # To set the time to cache name resolutions + # Possible values: + # default: sun.net.InetAddressCachePolicy.get() and getNegative() + # forever: cache forever + # never: no caching + # n [time unit]: positive timeout with unit, for example 30s + positive-ttl = default + negative-ttl = default + + # How often to sweep out expired cache entries. + # Note that this interval has nothing to do with TTLs + cache-cleanup-interval = 120s + } + + async-dns { + provider-object = "org.apache.pekko.io.dns.internal.AsyncDnsProvider" + + # Set upper bound for caching successfully resolved dns entries + # if the DNS record has a smaller TTL value than the setting that + # will be used. Default is to use the record TTL with no cap. + # Possible values: + # forever: always use the minimum TTL from the found records + # never: never cache + # n [time unit] = cap the caching to this value + positive-ttl = forever + + # Set how long the fact that a DNS record could not be found is + # cached. If a new resolution is done while the fact is cached it will + # be failed and not result in an actual DNS resolution. Default is + # to never cache. + # Possible values: + # never: never cache + # forever: cache a missing DNS record forever (you probably will not want to do this) + # n [time unit] = cache for this long + negative-ttl = never + + # Configures nameservers to query during DNS resolution. + # Defaults to the nameservers that would be used by the JVM by default. + # Set to a list of IPs to override the servers, e.g. [ "8.8.8.8", "8.8.4.4" ] for Google's servers + # If multiple are defined then they are tried in order until one responds + nameservers = default + + # The time that a request is allowed to live before being discarded + # given no reply. The lower bound of this should always be the amount + # of time to reasonably expect a DNS server to reply within. + # If multiple name servers are provided then each gets this long to response before trying + # the next one + resolve-timeout = 5s + + # How often to sweep out expired cache entries. + # Note that this interval has nothing to do with TTLs + cache-cleanup-interval = 120s + + # Configures the list of search domains. + # Defaults to a system dependent lookup (on Unix like OSes, will attempt to parse /etc/resolv.conf, on + # other platforms, will not make any attempt to lookup the search domains). Set to a single domain, or + # a list of domains, eg, [ "example.com", "example.net" ]. + search-domains = default + + # Any hosts that have a number of dots less than this will not be looked up directly, instead, a search on + # the search domains will be tried first. This corresponds to the ndots option in /etc/resolv.conf, see + # https://linux.die.net/man/5/resolver for more info. + # Defaults to a system dependent lookup (on Unix like OSes, will attempt to parse /etc/resolv.conf, on + # other platforms, will default to 1). + ndots = default + + # The policy used to generate dns transaction ids. Options are `thread-local-random`, + # `enhanced-double-hash-random` or `secure-random`. Defaults to `enhanced-double-hash-random` which uses an + # enhanced double hashing algorithm optimized for minimizing collisions with a FIPS compliant initial seed. + # `thread-local-random` is similar to Netty and `secure-random` produces FIPS compliant random numbers every + # time but could block looking for entropy (these are short integers so are easy to brute-force, use + # `enhanced-double-hash-random` unless you really require FIPS compliant random numbers). + id-generator-policy = enhanced-double-hash-random + } + } + } + + + # CoordinatedShutdown is an extension that will perform registered + # tasks in the order that is defined by the phases. It is started + # by calling CoordinatedShutdown(system).run(). This can be triggered + # by different things, for example: + # - JVM shutdown hook will by default run CoordinatedShutdown + # - Cluster node will automatically run CoordinatedShutdown when it + # sees itself as Exiting + # - A management console or other application specific command can + # run CoordinatedShutdown + coordinated-shutdown { + # The timeout that will be used for a phase if not specified with + # 'timeout' in the phase + default-phase-timeout = 5 s + + # Terminate the ActorSystem in the last phase actor-system-terminate. + terminate-actor-system = on + + # Exit the JVM (System.exit(0)) in the last phase actor-system-terminate + # if this is set to 'on'. It is done after termination of the + # ActorSystem if terminate-actor-system=on, otherwise it is done + # immediately when the last phase is reached. + exit-jvm = off + + # Exit status to use on System.exit(int) when 'exit-jvm' is 'on'. + exit-code = 0 + + # Run the coordinated shutdown when the JVM process exits, e.g. + # via kill SIGTERM signal (SIGINT ctrl-c doesn't work). + # This property is related to `pekko.jvm-shutdown-hooks` above. + run-by-jvm-shutdown-hook = on + + # Run the coordinated shutdown when ActorSystem.terminate is called. + # Enabling this and disabling terminate-actor-system is not a supported + # combination (will throw ConfigurationException at startup). + run-by-actor-system-terminate = on + + # When Coordinated Shutdown is triggered an instance of `Reason` is + # required. That value can be used to override the default settings. + # Only 'exit-jvm', 'exit-code' and 'terminate-actor-system' may be + # overridden depending on the reason. + reason-overrides { + # Overrides are applied using the `reason.getClass.getName`. + # Overrides the `exit-code` when the `Reason` is a cluster + # Downing or a Cluster Join Unsuccessful event + "org.apache.pekko.actor.CoordinatedShutdown$ClusterDowningReason$" { + exit-code = -1 + } + "org.apache.pekko.actor.CoordinatedShutdown$ClusterJoinUnsuccessfulReason$" { + exit-code = -1 + } + } + + #//#coordinated-shutdown-phases + # CoordinatedShutdown is enabled by default and will run the tasks that + # are added to these phases by individual Pekko modules and user logic. + # + # The phases are ordered as a DAG by defining the dependencies between the phases + # to make sure shutdown tasks are run in the right order. + # + # In general user tasks belong in the first few phases, but there may be use + # cases where you would want to hook in new phases or register tasks later in + # the DAG. + # + # Each phase is defined as a named config section with the + # following optional properties: + # - timeout=15s: Override the default-phase-timeout for this phase. + # - recover=off: If the phase fails the shutdown is aborted + # and depending phases will not be executed. + # - enabled=off: Skip all tasks registered in this phase. DO NOT use + # this to disable phases unless you are absolutely sure what the + # consequences are. Many of the built in tasks depend on other tasks + # having been executed in earlier phases and may break if those are disabled. + # depends-on=[]: Run the phase after the given phases + phases { + + # The first pre-defined phase that applications can add tasks to. + # Note that more phases can be added in the application's + # configuration by overriding this phase with an additional + # depends-on. + before-service-unbind { + } + + # Stop accepting new incoming connections. + # This is where you can register tasks that makes a server stop accepting new connections. Already + # established connections should be allowed to continue and complete if possible. + service-unbind { + depends-on = [before-service-unbind] + } + + # Wait for requests that are in progress to be completed. + # This is where you register tasks that will wait for already established connections to complete, potentially + # also first telling them that it is time to close down. + service-requests-done { + depends-on = [service-unbind] + } + + # Final shutdown of service endpoints. + # This is where you would add tasks that forcefully kill connections that are still around. + service-stop { + depends-on = [service-requests-done] + } + + # Phase for custom application tasks that are to be run + # after service shutdown and before cluster shutdown. + before-cluster-shutdown { + depends-on = [service-stop] + } + + # Graceful shutdown of the Cluster Sharding regions. + # This phase is not meant for users to add tasks to. + cluster-sharding-shutdown-region { + timeout = 10 s + depends-on = [before-cluster-shutdown] + } + + # Emit the leave command for the node that is shutting down. + # This phase is not meant for users to add tasks to. + cluster-leave { + depends-on = [cluster-sharding-shutdown-region] + } + + # Shutdown cluster singletons + # This is done as late as possible to allow the shard region shutdown triggered in + # the "cluster-sharding-shutdown-region" phase to complete before the shard coordinator is shut down. + # This phase is not meant for users to add tasks to. + cluster-exiting { + timeout = 10 s + depends-on = [cluster-leave] + } + + # Wait until exiting has been completed + # This phase is not meant for users to add tasks to. + cluster-exiting-done { + depends-on = [cluster-exiting] + } + + # Shutdown the cluster extension + # This phase is not meant for users to add tasks to. + cluster-shutdown { + depends-on = [cluster-exiting-done] + } + + # Phase for custom application tasks that are to be run + # after cluster shutdown and before ActorSystem termination. + before-actor-system-terminate { + depends-on = [cluster-shutdown] + } + + # Last phase. See terminate-actor-system and exit-jvm above. + # Don't add phases that depends on this phase because the + # dispatcher and scheduler of the ActorSystem have been shutdown. + # This phase is not meant for users to add tasks to. + actor-system-terminate { + timeout = 10 s + depends-on = [before-actor-system-terminate] + } + } + #//#coordinated-shutdown-phases + } + + #//#circuit-breaker-default + # Configuration for circuit breakers created with the APIs accepting an id to + # identify or look up the circuit breaker. + # Note: Circuit breakers created without ids are not affected by this configuration. + # A child configuration section with the same name as the circuit breaker identifier + # will be used, with fallback to the `pekko.circuit-breaker.default` section. + circuit-breaker { + + # Default configuration that is used if a configuration section + # with the circuit breaker identifier is not defined. + default { + # Number of failures before opening the circuit. + max-failures = 10 + + # Duration of time after which to consider a call a failure. + call-timeout = 10s + + # Duration of time in open state after which to attempt to close + # the circuit, by first entering the half-open state. + reset-timeout = 15s + + # The upper bound of reset-timeout + max-reset-timeout = 36500d + + # Exponential backoff + # For details see https://en.wikipedia.org/wiki/Exponential_backoff + exponential-backoff = 1.0 + + # Additional random delay based on this factor is added to backoff + # For example 0.2 adds up to 20% delay + # In order to skip this additional delay set as 0 + random-factor = 0.0 + + # A allow-list of fqcn of Exceptions that the CircuitBreaker + # should not consider failures. By default all exceptions are + # considered failures. + exception-allowlist = [] + } + } + #//#circuit-breaker-default + +} \ No newline at end of file diff --git a/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala b/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala index e26468a1e2..653a8d34f3 100644 --- a/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala +++ b/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala @@ -6,20 +6,10 @@ package edu.ie3.simona.api +import edu.ie3.simona.api.data.ExtSimAdapterData import edu.ie3.simona.api.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.api.simulation.ExtSimAdapterData -import edu.ie3.simona.api.ontology.simulation.{ - ActivationMessage, - ControlResponseMessageFromExt, - TerminationCompleted, - TerminationMessage, - CompletionMessage as ExtCompletionMessage, -} -import edu.ie3.simona.api.simulation.ExtSimAdapterData -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} +import edu.ie3.simona.api.ontology.simulation.{ActivationMessage, ControlResponseMessageFromExt, TerminationCompleted, TerminationMessage, CompletionMessage as ExtCompletionMessage} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} import edu.ie3.simona.ontology.messages.ServiceMessage.ScheduleServiceActivation import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index bdefe8526c..f3101efa19 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -313,14 +313,27 @@ case class EmCommunicationCore( Some(new EmResultResponse(updated.receivedData.asJava)), ) } else { - ( - copy( - allFlexOptions = allFlexOptions.updated(sender, resultToExt), - currentSetPoint = currentSetPoint.updated(sender, pRef), - expectDataFrom = updated, - ), - None, - ) + if updated.hasCompleted then { + val (receivedData, updatedMap) = updated.getFinished + + ( + copy( + allFlexOptions = allFlexOptions.updated(sender, resultToExt), + currentSetPoint = currentSetPoint.updated(sender, pRef), + expectDataFrom = updatedMap, + ), + Some(new EmResultResponse(receivedData.asJava)) + ) + } else { + ( + copy( + allFlexOptions = allFlexOptions.updated(sender, resultToExt), + currentSetPoint = currentSetPoint.updated(sender, pRef), + expectDataFrom = updated, + ), + None, + ) + } } } diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index 24abaa7c2c..6251eed074 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -6,20 +6,18 @@ package edu.ie3.simona.sim.setup -import edu.ie3.simona.api.data.connection.* import com.typesafe.config.Config import edu.ie3.datamodel.models.input.container.JointGridContainer +import edu.ie3.simona.api.data.ExtSimAdapterData import edu.ie3.simona.api.data.connection.* import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt -import edu.ie3.simona.api.simulation.{ExtSimAdapterData, ExtSimulation} +import edu.ie3.simona.api.simulation.ExtSimulation import edu.ie3.simona.api.{ExtLinkInterface, ExtSimAdapter} -import edu.ie3.simona.event.listener.ExtResultEvent -import edu.ie3.simona.event.listener.ResultListener +import edu.ie3.simona.event.listener.{ExtResultEvent, ResultListener} import edu.ie3.simona.exceptions.ServiceException -import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} -import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.ontology.messages.ResultMessage.RequestResult +import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceStateData.InitializeServiceStateData import edu.ie3.simona.service.em.ExtEmDataService diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala index 225b887f8f..90b0a5d47d 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala @@ -11,14 +11,29 @@ import org.slf4j.{Logger, LoggerFactory} final case class ReceiveMultiDataMap[K, V]( private val expectedKeys: Map[K, Int], + finishedKeys: Set[K], receivedData: Map[K, Seq[V]], ) { def isComplete: Boolean = expectedKeys.isEmpty + def hasCompleted: Boolean = finishedKeys.nonEmpty + def nonComplete: Boolean = expectedKeys.nonEmpty def expects(key: K): Boolean = expectedKeys.contains(key) + def getFinished: (Map[K, Seq[V]], ReceiveMultiDataMap[K, V]) = { + val data = finishedKeys.map { key => key -> receivedData(key) }.toMap + + ( + data, + copy( + receivedData = receivedData.removedAll(finishedKeys), + finishedKeys = Set.empty + ) + ) + } + def addData[A]( key: K, value: V, @@ -40,6 +55,7 @@ final case class ReceiveMultiDataMap[K, V]( if count == 0 then { copy( expectedKeys = expectedKeys.removed(key), + finishedKeys = finishedKeys + key, receivedData = receivedData.updated(key, newValue), ) } else { @@ -67,12 +83,14 @@ object ReceiveMultiDataMap { ): ReceiveMultiDataMap[K, V] = ReceiveMultiDataMap( expectedKeys = expectedKeys, + finishedKeys = Set.empty, receivedData = Map.empty, ) def empty[K, V]: ReceiveMultiDataMap[K, V] = ReceiveMultiDataMap( expectedKeys = Map.empty, + finishedKeys = Set.empty, receivedData = Map.empty, ) From ad7dd09538864b09447f15c1f469f8425ba43689 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 30 Sep 2025 16:28:59 +0200 Subject: [PATCH 096/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 61 +++++++++++++------ .../ie3/simona/util/ReceiveMultiDataMap.scala | 3 +- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index f3101efa19..d8bf437ec7 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -54,6 +54,7 @@ case class EmCommunicationCore( currentSetPoint: Map[UUID, Power] = Map.empty, activatedAgents: Set[UUID] = Set.empty, waitingForFlexOptions: Set[UUID] = Set.empty, + missingFlexOptionsFromExt: Map[UUID, Int] = Map.empty, waitingForSetPoint: Set[UUID] = Set.empty, expectDataFrom: ReceiveMultiDataMap[UUID, EmData] = ReceiveMultiDataMap.empty, @@ -137,6 +138,8 @@ case class EmCommunicationCore( tick, requestedFlexType.getOrElse(uuid, FlexType.PowerLimit), ) + + // uuid -> number of sent flex requests uuid -> Try(uuidToInferior(uuid).size).getOrElse(1) } case _ => @@ -148,9 +151,9 @@ case class EmCommunicationCore( } // handle flex options - val expectFlexOptions = provideEmData.flexOptions.asScala.flatMap { - case (uuid, options) if waitingForFlexOptions.contains(uuid) => - val agent = uuidToAgent(uuid) + val receivedFlexOptions = provideEmData.flexOptions.asScala.flatMap { + case (receiver, options) if waitingForFlexOptions.contains(receiver) => + val agent = uuidToAgent(receiver) // send flex options to agent options.asScala.foreach { option => @@ -164,15 +167,24 @@ case class EmCommunicationCore( ) } - Some(uuid -> 1) + // receiver -> number of received flex options + Some(receiver -> options.size) case _ => None }.toMap + // TODO: Improve this part + val updatedMissingFlexOptions = receivedFlexOptions.map { case (receiver, numberOfOptions) => + val missing = missingFlexOptionsFromExt.getOrElse(receiver, 0) - numberOfOptions + receiver -> missing + } + val expectFlexOptions = receivedFlexOptions.map { case (receiver, _) => receiver -> 1} + + // handle set points val expectedSetPoints = provideEmData.setPoints.asScala.flatMap { case (uuid, setPoint) - if waitingForSetPoint.contains(uuid) | waitingForFlexOptions - .contains(uuid) => + if waitingForSetPoint.contains(uuid) | waitingForFlexOptions + .contains(uuid) => val agent = uuidToAgent(uuid) setPoint.power.toScala.flatMap( @@ -185,6 +197,7 @@ case class EmCommunicationCore( agent ! IssueNoControl(extTick) } + // sender -> number of set points to send Some(uuid -> Try(uuidToInferior(uuid).size).getOrElse(0)) case _ => None }.toMap @@ -202,19 +215,25 @@ case class EmCommunicationCore( ) // update state data - ( - copy( - disaggregated = updatedDisaggregated, - activatedAgents = activatedAgents ++ activatedKeys, - waitingForFlexOptions = - waitingForFlexOptions ++ activatedKeys -- flexOptionKeys -- setPointKeys, - waitingForSetPoint = - waitingForSetPoint ++ flexOptionKeys -- setPointKeys, - expectDataFrom = updatedExpectDataFrom, - completions = completions.addExpectedKeys(activatedKeys), - ), - None, + val newState = copy( + disaggregated = updatedDisaggregated, + activatedAgents = activatedAgents ++ activatedKeys, + waitingForFlexOptions = + waitingForFlexOptions ++ activatedKeys -- flexOptionKeys -- setPointKeys, + waitingForSetPoint = + waitingForSetPoint ++ flexOptionKeys -- setPointKeys, + missingFlexOptionsFromExt = missingFlexOptionsFromExt ++ updatedMissingFlexOptions, + expectDataFrom = updatedExpectDataFrom, + completions = completions.addExpectedKeys(activatedKeys), ) + + log.warn(s"Activated: ${newState.activatedAgents}; WaitingForFlexOptions: ${newState.waitingForFlexOptions}; WaitingForSetPoints: ${newState.waitingForSetPoint}") + log.warn(s"Missing flex options: ${newState.missingFlexOptionsFromExt}") + + val msgToExt = None + + log.warn(s"ExtMsg: $msgToExt") + (newState, msgToExt) } case _: RequestEmFlexResults => @@ -313,7 +332,11 @@ case class EmCommunicationCore( Some(new EmResultResponse(updated.receivedData.asJava)), ) } else { - if updated.hasCompleted then { + val hasCompleted = updated.hasCompleted + + log.warn(s"Internal state: $updated") + + if hasCompleted then { val (receivedData, updatedMap) = updated.getFinished ( diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala index 90b0a5d47d..15fc211d1f 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala @@ -6,7 +6,6 @@ package edu.ie3.simona.util -import edu.ie3.simona.util.ReceiveMultiDataMap.log import org.slf4j.{Logger, LoggerFactory} final case class ReceiveMultiDataMap[K, V]( @@ -22,6 +21,8 @@ final case class ReceiveMultiDataMap[K, V]( def expects(key: K): Boolean = expectedKeys.contains(key) + def getExpected(key: K): Int = expectedKeys.getOrElse(key, 0) + def getFinished: (Map[K, Seq[V]], ReceiveMultiDataMap[K, V]) = { val data = finishedKeys.map { key => key -> receivedData(key) }.toMap From cdb75177cf75af1bef1e93b76f98de73ee8c3653 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 30 Sep 2025 18:41:40 +0200 Subject: [PATCH 097/125] Saving changes. --- .../edu/ie3/simona/api/ExtSimAdapter.scala | 13 ++- .../service/em/EmCommunicationCore.scala | 82 ++++++------------- .../ie3/simona/util/ReceiveMultiDataMap.scala | 12 ++- 3 files changed, 42 insertions(+), 65 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala b/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala index 653a8d34f3..bdf069cd81 100644 --- a/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala +++ b/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala @@ -8,8 +8,17 @@ package edu.ie3.simona.api import edu.ie3.simona.api.data.ExtSimAdapterData import edu.ie3.simona.api.ontology.ScheduleDataServiceMessage -import edu.ie3.simona.api.ontology.simulation.{ActivationMessage, ControlResponseMessageFromExt, TerminationCompleted, TerminationMessage, CompletionMessage as ExtCompletionMessage} -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.api.ontology.simulation.{ + ActivationMessage, + ControlResponseMessageFromExt, + TerminationCompleted, + TerminationMessage, + CompletionMessage as ExtCompletionMessage, +} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} import edu.ie3.simona.ontology.messages.ServiceMessage.ScheduleServiceActivation import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index d8bf437ec7..7d4a8d4a32 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -9,21 +9,12 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent.Message -import edu.ie3.simona.api.data.model.em.{ - EmData, - EmSetPoint, - ExtendedFlexOptionsResult, - FlexOptionRequest, -} +import edu.ie3.simona.api.data.model.em.{EmData, EmSetPoint, ExtendedFlexOptionsResult, FlexOptionRequest} 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.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.flex.{ - FlexType, - FlexibilityMessage, - PowerLimitFlexOptions, -} +import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.* @@ -54,7 +45,6 @@ case class EmCommunicationCore( currentSetPoint: Map[UUID, Power] = Map.empty, activatedAgents: Set[UUID] = Set.empty, waitingForFlexOptions: Set[UUID] = Set.empty, - missingFlexOptionsFromExt: Map[UUID, Int] = Map.empty, waitingForSetPoint: Set[UUID] = Set.empty, expectDataFrom: ReceiveMultiDataMap[UUID, EmData] = ReceiveMultiDataMap.empty, @@ -151,7 +141,7 @@ case class EmCommunicationCore( } // handle flex options - val receivedFlexOptions = provideEmData.flexOptions.asScala.flatMap { + val expectFlexOptions = provideEmData.flexOptions.asScala.flatMap { case (receiver, options) if waitingForFlexOptions.contains(receiver) => val agent = uuidToAgent(receiver) @@ -168,23 +158,16 @@ case class EmCommunicationCore( } // receiver -> number of received flex options - Some(receiver -> options.size) + Some(receiver -> 1) case _ => None }.toMap - // TODO: Improve this part - val updatedMissingFlexOptions = receivedFlexOptions.map { case (receiver, numberOfOptions) => - val missing = missingFlexOptionsFromExt.getOrElse(receiver, 0) - numberOfOptions - receiver -> missing - } - val expectFlexOptions = receivedFlexOptions.map { case (receiver, _) => receiver -> 1} - - // handle set points - val expectedSetPoints = provideEmData.setPoints.asScala.flatMap { + val setPoints = provideEmData.setPoints.asScala + val expectedSetPoints = setPoints.flatMap { case (uuid, setPoint) - if waitingForSetPoint.contains(uuid) | waitingForFlexOptions - .contains(uuid) => + if waitingForSetPoint.contains(uuid) | waitingForFlexOptions + .contains(uuid) => val agent = uuidToAgent(uuid) setPoint.power.toScala.flatMap( @@ -214,26 +197,24 @@ case class EmCommunicationCore( s"ExpectDataFrom: $updatedExpectDataFrom, Request: $activated, FlexOption: $expectFlexOptions, SetPoint: $expectedSetPoints" ) + val updatedWaitingForFlexOptions = waitingForFlexOptions ++ activatedKeys.filter(uuidToInferior.contains) -- flexOptionKeys -- setPointKeys + // update state data val newState = copy( disaggregated = updatedDisaggregated, activatedAgents = activatedAgents ++ activatedKeys, - waitingForFlexOptions = - waitingForFlexOptions ++ activatedKeys -- flexOptionKeys -- setPointKeys, + waitingForFlexOptions = updatedWaitingForFlexOptions, waitingForSetPoint = waitingForSetPoint ++ flexOptionKeys -- setPointKeys, - missingFlexOptionsFromExt = missingFlexOptionsFromExt ++ updatedMissingFlexOptions, expectDataFrom = updatedExpectDataFrom, completions = completions.addExpectedKeys(activatedKeys), ) - log.warn(s"Activated: ${newState.activatedAgents}; WaitingForFlexOptions: ${newState.waitingForFlexOptions}; WaitingForSetPoints: ${newState.waitingForSetPoint}") - log.warn(s"Missing flex options: ${newState.missingFlexOptionsFromExt}") - - val msgToExt = None + log.warn( + s"Activated: ${newState.activatedAgents}; WaitingForFlexOptions: ${newState.waitingForFlexOptions}; WaitingForSetPoints: ${newState.waitingForSetPoint}" + ) - log.warn(s"ExtMsg: $msgToExt") - (newState, msgToExt) + (newState, None) } case _: RequestEmFlexResults => @@ -332,31 +313,14 @@ case class EmCommunicationCore( Some(new EmResultResponse(updated.receivedData.asJava)), ) } else { - val hasCompleted = updated.hasCompleted - - log.warn(s"Internal state: $updated") - - if hasCompleted then { - val (receivedData, updatedMap) = updated.getFinished - - ( - copy( - allFlexOptions = allFlexOptions.updated(sender, resultToExt), - currentSetPoint = currentSetPoint.updated(sender, pRef), - expectDataFrom = updatedMap, - ), - Some(new EmResultResponse(receivedData.asJava)) - ) - } else { - ( - copy( - allFlexOptions = allFlexOptions.updated(sender, resultToExt), - currentSetPoint = currentSetPoint.updated(sender, pRef), - expectDataFrom = updated, - ), - None, - ) - } + ( + copy( + allFlexOptions = allFlexOptions.updated(sender, resultToExt), + currentSetPoint = currentSetPoint.updated(sender, pRef), + expectDataFrom = updated, + ), + None, + ) } } diff --git a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala index 15fc211d1f..7a075b158f 100644 --- a/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala +++ b/src/main/scala/edu/ie3/simona/util/ReceiveMultiDataMap.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.util +import edu.ie3.simona.util.ReceiveMultiDataMap.log import org.slf4j.{Logger, LoggerFactory} final case class ReceiveMultiDataMap[K, V]( @@ -30,8 +31,8 @@ final case class ReceiveMultiDataMap[K, V]( data, copy( receivedData = receivedData.removedAll(finishedKeys), - finishedKeys = Set.empty - ) + finishedKeys = Set.empty, + ), ) } @@ -68,8 +69,11 @@ final case class ReceiveMultiDataMap[K, V]( } } - def addExpectedKeys(keys: Map[K, Int]): ReceiveMultiDataMap[K, V] = - copy(expectedKeys = expectedKeys ++ keys.filter(_._2 > 0)) + def addExpectedKeys(keys: Map[K, Int]): ReceiveMultiDataMap[K, V] = { + val (add, remove) = keys.partition(_._2 > 0) + val updated = (expectedKeys ++ add).removedAll(remove.keys) + copy(expectedKeys = updated) + } def getExpectedKeys: Set[K] = expectedKeys.keySet From 46def68bd30ab547ee7a707a9c448883fb6d8ae5 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 1 Oct 2025 11:15:12 +0200 Subject: [PATCH 098/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 153 +++++++++++++++--- .../simona/service/em/EmServiceBaseCore.scala | 8 +- .../ie3/simona/service/em/EmServiceCore.scala | 4 +- 3 files changed, 136 insertions(+), 29 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 7d4a8d4a32..b18beca8cc 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -15,6 +15,7 @@ import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} +import edu.ie3.simona.service.em.EmCommunicationCore.EmAgentState import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.* @@ -26,10 +27,81 @@ import squants.Power import java.time.ZonedDateTime import java.util.UUID +import scala.collection.mutable import scala.jdk.CollectionConverters.* import scala.jdk.OptionConverters.* import scala.util.Try +object EmCommunicationCore { + + final case class EmAgentState( + private var receivedActivation: Boolean = false, + private val awaitedFlexOptions: mutable.Set[UUID] = mutable.Set.empty, + private var awaitedSetPoint: Boolean = false, + private var waitingForInternal: Boolean = false, + ) { + def setReceivedRequest: Unit = { + receivedActivation = true + waitingForInternal = true + } + + def addSendRequest(request: UUID): Unit = { + awaitedFlexOptions.add(request) + } + + def addSendRequests(requests: Seq[UUID]): Unit = { + awaitedFlexOptions.addAll(requests) + } + + def handleReceivedFlexOption(flexOption: UUID): Unit = { + awaitedFlexOptions.remove(flexOption) + waitingForInternal = false + + if awaitedFlexOptions.isEmpty then { + awaitedSetPoint = true + waitingForInternal = true + } + } + + def handleReceivedFlexOptions(flexOptions: Seq[UUID]): Unit = { + flexOptions.foreach(awaitedFlexOptions.remove) + waitingForInternal = false + + if awaitedFlexOptions.isEmpty then { + awaitedSetPoint = true + waitingForInternal = true + } + } + + def setReceivedSetPoint: Unit = { + awaitedSetPoint = false + waitingForInternal = true + } + + def setWaitingForInternal(value: Boolean): Unit = { + waitingForInternal = value + } + + def getAwaited: Set[UUID] = awaitedFlexOptions.toSet + + def isWaitingForActivation: Boolean = !receivedActivation + + def isWaitingForExtern: Boolean = awaitedFlexOptions.nonEmpty && !waitingForInternal + + def isWaitingForSetPoint: Boolean = awaitedSetPoint + + def isWaitingForInternal: Boolean = waitingForInternal + + def clear(): Unit = { + receivedActivation = false + awaitedFlexOptions.clear + awaitedSetPoint = false + waitingForInternal = false + } + } +} + + case class EmCommunicationCore( disaggregated: Map[UUID, Boolean] = Map.empty, override val lastFinishedTick: Long = PRE_INIT_TICK, @@ -44,8 +116,7 @@ case class EmCommunicationCore( allFlexOptions: Map[UUID, FlexOptionsResult] = Map.empty, currentSetPoint: Map[UUID, Power] = Map.empty, activatedAgents: Set[UUID] = Set.empty, - waitingForFlexOptions: Set[UUID] = Set.empty, - waitingForSetPoint: Set[UUID] = Set.empty, + emStates: Map[UUID, EmAgentState] = Map.empty, expectDataFrom: ReceiveMultiDataMap[UUID, EmData] = ReceiveMultiDataMap.empty, ) extends EmServiceCore { @@ -79,12 +150,13 @@ case class EmCommunicationCore( refToUuid = refToUuid.updated(ref, uuid), uuidToInferior = updatedInferior, uuidToParent = updatedUuidToParent, + emStates = emStates.updated(uuid, EmAgentState()) ) } - override def handleExtMessage(tick: Long, extMSg: EmDataMessageFromExt)(using + override def handleExtMessage(tick: Long, extMsg: EmDataMessageFromExt)(using log: Logger - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMSg match { + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMsg match { case requestEmCompletion: RequestEmCompletion => // finish tick and return next tick val extTick = requestEmCompletion.tick @@ -111,6 +183,8 @@ case class EmCommunicationCore( } case provideEmData: ProvideEmData => + log.warn(s"Handling ext message: $provideEmData") + // provide em data val extTick = provideEmData.tick @@ -122,8 +196,11 @@ case class EmCommunicationCore( // handle flex requests val requests = provideEmData.flexRequests.asScala val activated = requests.flatMap { - case (uuid, _) if !activatedAgents.contains(uuid) => + case (uuid, _) if emStates(uuid).isWaitingForActivation => uuidToAgent.get(uuid).map { agent => + // update the em state + emStates(uuid).setReceivedRequest + agent ! FlexShiftActivation( tick, requestedFlexType.getOrElse(uuid, FlexType.PowerLimit), @@ -142,13 +219,20 @@ case class EmCommunicationCore( // handle flex options val expectFlexOptions = provideEmData.flexOptions.asScala.flatMap { - case (receiver, options) if waitingForFlexOptions.contains(receiver) => + case (receiver, options) if emStates(receiver).isWaitingForExtern => val agent = uuidToAgent(receiver) + val emState = emStates(receiver) + // send flex options to agent options.asScala.foreach { option => + val sender = option.sender + + // update the em state + emState.handleReceivedFlexOption(sender) + agent ! ProvideFlexOptions( - option.sender, + sender, PowerLimitFlexOptions( option.pRef.toSquants, option.pMin.toSquants, @@ -163,13 +247,13 @@ case class EmCommunicationCore( }.toMap // handle set points - val setPoints = provideEmData.setPoints.asScala - val expectedSetPoints = setPoints.flatMap { - case (uuid, setPoint) - if waitingForSetPoint.contains(uuid) | waitingForFlexOptions - .contains(uuid) => + val expectedSetPoints = provideEmData.setPoints().asScala.flatMap { + case (uuid, setPoint) if emStates(uuid).isWaitingForSetPoint => val agent = uuidToAgent(uuid) + // updates the em state + emStates(uuid).setReceivedRequest + setPoint.power.toScala.flatMap( _.getP.toScala.map(_.toSquants) ) match { @@ -185,6 +269,19 @@ case class EmCommunicationCore( case _ => None }.toMap + // check if we need to wait for internal answers + val mesgToExt = if emStates.exists(_._2.isWaitingForInternal) then { + None + } else { + val awaited = emStates.filter((_, x) => x.isWaitingForExtern).map { + case (uuid, state) => uuid -> state.getAwaited + } + + log.info(s"Waiting for external data: $awaited") + + None//Some(new EmResultResponse(Map.empty.asJava)) + } + val activatedKeys = activated.keySet val flexOptionKeys = expectFlexOptions.keys val setPointKeys = expectedSetPoints.keys @@ -197,24 +294,19 @@ case class EmCommunicationCore( s"ExpectDataFrom: $updatedExpectDataFrom, Request: $activated, FlexOption: $expectFlexOptions, SetPoint: $expectedSetPoints" ) - val updatedWaitingForFlexOptions = waitingForFlexOptions ++ activatedKeys.filter(uuidToInferior.contains) -- flexOptionKeys -- setPointKeys - // update state data val newState = copy( disaggregated = updatedDisaggregated, activatedAgents = activatedAgents ++ activatedKeys, - waitingForFlexOptions = updatedWaitingForFlexOptions, - waitingForSetPoint = - waitingForSetPoint ++ flexOptionKeys -- setPointKeys, expectDataFrom = updatedExpectDataFrom, completions = completions.addExpectedKeys(activatedKeys), ) - log.warn( - s"Activated: ${newState.activatedAgents}; WaitingForFlexOptions: ${newState.waitingForFlexOptions}; WaitingForSetPoints: ${newState.waitingForSetPoint}" - ) + log.warn(s"Activated: ${newState.activatedAgents}") + log.warn(s"EmStates: ${newState.emStates}") + log.warn(s"Message to ext: $mesgToExt") - (newState, None) + (newState, mesgToExt) } case _: RequestEmFlexResults => @@ -304,13 +396,19 @@ case class EmCommunicationCore( val updated = expectDataFrom.addData(sender, resultToExt) if updated.isComplete then { + val data = updated.receivedData + + // should no longer wait for internal data + data.keys.foreach { uuid => emStates(uuid).setWaitingForInternal(false) } + log.warn(s"Updated EmStates: $emStates") + ( copy( allFlexOptions = allFlexOptions.updated(sender, resultToExt), currentSetPoint = currentSetPoint.updated(sender, pRef), expectDataFrom = ReceiveMultiDataMap.empty, ), - Some(new EmResultResponse(updated.receivedData.asJava)), + Some(new EmResultResponse(data.asJava)), ) } else { ( @@ -380,6 +478,9 @@ case class EmCommunicationCore( val updated = flexRequest match { case FlexActivation(tick, flexType) => + // update the em state => waiting for external flex option provision + emStates(sender).addSendRequest(receiverUuid) + // send request to ext expectDataFrom.addData( sender, @@ -425,9 +526,15 @@ case class EmCommunicationCore( } if updated.isComplete then { + val data = updated.receivedData + + // should no longer wait for internal data + data.keys.foreach { uuid => emStates(uuid).setWaitingForInternal(false) } + log.warn(s"Updated EmStates: $emStates") + ( copy(expectDataFrom = ReceiveMultiDataMap.empty), - Some(new EmResultResponse(updated.receivedData.asJava)), + Some(new EmResultResponse(data.asJava)), ) } else { (copy(expectDataFrom = updated), None) 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 6162cc30a5..3cedb6039c 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -78,9 +78,9 @@ final case class EmServiceBaseCore( ) } - override def handleExtMessage(tick: Long, extMSg: EmDataMessageFromExt)(using - log: Logger - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMSg match { + override def handleExtMessage(tick: Long, extMsg: EmDataMessageFromExt)(using + log: Logger + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMsg match { case requestEmFlexResults: RequestEmFlexResults => val tick = requestEmFlexResults.tick val emEntities = requestEmFlexResults.emEntities.asScala @@ -132,7 +132,7 @@ final case class EmServiceBaseCore( case _ => throw new CriticalFailureException( - s"The EmServiceBaseCore is not able to handle the message: $extMSg" + s"The EmServiceBaseCore is not able to handle the message: $extMsg" ) } 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 9121a82f3d..be7852e1ae 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -46,8 +46,8 @@ trait EmServiceCore { ): EmServiceCore def handleExtMessage( - tick: Long, - extMSg: EmDataMessageFromExt, + tick: Long, + extMsg: EmDataMessageFromExt, )(using log: Logger ): (EmServiceCore, Option[EmDataResponseMessageToExt]) From cb417c20ec210554adbeba3135434b58d250b142 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 13 Oct 2025 16:04:04 +0200 Subject: [PATCH 099/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 147 +++++++++++------- .../simona/service/em/EmServiceBaseCore.scala | 2 +- .../ie3/simona/service/em/EmServiceCore.scala | 4 +- 3 files changed, 91 insertions(+), 62 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index b18beca8cc..3310a656dc 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -9,12 +9,21 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent.Message -import edu.ie3.simona.api.data.model.em.{EmData, EmSetPoint, ExtendedFlexOptionsResult, FlexOptionRequest} +import edu.ie3.simona.api.data.model.em.{ + EmData, + EmSetPoint, + ExtendedFlexOptionsResult, + FlexOptionRequest, +} 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.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} +import edu.ie3.simona.ontology.messages.flex.{ + FlexType, + FlexibilityMessage, + PowerLimitFlexOptions, +} import edu.ie3.simona.service.em.EmCommunicationCore.EmAgentState import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} @@ -35,14 +44,15 @@ import scala.util.Try object EmCommunicationCore { final case class EmAgentState( - private var receivedActivation: Boolean = false, - private val awaitedFlexOptions: mutable.Set[UUID] = mutable.Set.empty, - private var awaitedSetPoint: Boolean = false, - private var waitingForInternal: Boolean = false, - ) { - def setReceivedRequest: Unit = { + private var receivedActivation: Boolean = false, + private val awaitedFlexOptions: mutable.Set[UUID] = mutable.Set.empty, + private var awaitedSetPoint: Boolean = false, + private var waitingForInternal: Boolean = false, + ) { + def setReceivedRequest(): Unit = { receivedActivation = true waitingForInternal = true + awaitedSetPoint = true } def addSendRequest(request: UUID): Unit = { @@ -58,7 +68,6 @@ object EmCommunicationCore { waitingForInternal = false if awaitedFlexOptions.isEmpty then { - awaitedSetPoint = true waitingForInternal = true } } @@ -68,12 +77,11 @@ object EmCommunicationCore { waitingForInternal = false if awaitedFlexOptions.isEmpty then { - awaitedSetPoint = true waitingForInternal = true } } - def setReceivedSetPoint: Unit = { + def setReceivedSetPoint(): Unit = { awaitedSetPoint = false waitingForInternal = true } @@ -86,7 +94,7 @@ object EmCommunicationCore { def isWaitingForActivation: Boolean = !receivedActivation - def isWaitingForExtern: Boolean = awaitedFlexOptions.nonEmpty && !waitingForInternal + def isWaitingForExtern: Boolean = (awaitedFlexOptions.nonEmpty || awaitedSetPoint) && !waitingForInternal def isWaitingForSetPoint: Boolean = awaitedSetPoint @@ -101,7 +109,6 @@ object EmCommunicationCore { } } - case class EmCommunicationCore( disaggregated: Map[UUID, Boolean] = Map.empty, override val lastFinishedTick: Long = PRE_INIT_TICK, @@ -150,7 +157,7 @@ case class EmCommunicationCore( refToUuid = refToUuid.updated(ref, uuid), uuidToInferior = updatedInferior, uuidToParent = updatedUuidToParent, - emStates = emStates.updated(uuid, EmAgentState()) + emStates = emStates.updated(uuid, EmAgentState()), ) } @@ -199,7 +206,7 @@ case class EmCommunicationCore( case (uuid, _) if emStates(uuid).isWaitingForActivation => uuidToAgent.get(uuid).map { agent => // update the em state - emStates(uuid).setReceivedRequest + emStates(uuid).setReceivedRequest() agent ! FlexShiftActivation( tick, @@ -247,44 +254,37 @@ case class EmCommunicationCore( }.toMap // handle set points - val expectedSetPoints = provideEmData.setPoints().asScala.flatMap { - case (uuid, setPoint) if emStates(uuid).isWaitingForSetPoint => - val agent = uuidToAgent(uuid) - - // updates the em state - emStates(uuid).setReceivedRequest - - setPoint.power.toScala.flatMap( - _.getP.toScala.map(_.toSquants) - ) match { - case Some(power) => - agent ! IssuePowerControl(extTick, power) - - case None => - agent ! IssueNoControl(extTick) - } - - // sender -> number of set points to send - Some(uuid -> Try(uuidToInferior(uuid).size).getOrElse(0)) - case _ => None - }.toMap + val expectedSetPoints = provideEmData + .setPoints() + .asScala + .flatMap { + case (uuid, setPoint) if emStates(uuid).isWaitingForSetPoint => + val agent = uuidToAgent(uuid) + log.warn(s"Receiver of set point: $agent") + + // updates the em state + emStates(uuid).setReceivedSetPoint() + + setPoint.power.toScala.flatMap( + _.getP.toScala.map(_.toSquants) + ) match { + case Some(power) => + agent ! IssuePowerControl(extTick, power) + + case None => + agent ! IssueNoControl(extTick) + } - // check if we need to wait for internal answers - val mesgToExt = if emStates.exists(_._2.isWaitingForInternal) then { - None - } else { - val awaited = emStates.filter((_, x) => x.isWaitingForExtern).map { - case (uuid, state) => uuid -> state.getAwaited + // sender -> number of set points to send + Some(uuid -> Try(uuidToInferior(uuid).size).getOrElse(0)) + case _ => None } + .toMap - log.info(s"Waiting for external data: $awaited") - - None//Some(new EmResultResponse(Map.empty.asJava)) - } + // check if we need to wait for internal answers + val msgToExt = getMsgToExtOption val activatedKeys = activated.keySet - val flexOptionKeys = expectFlexOptions.keys - val setPointKeys = expectedSetPoints.keys val updatedExpectDataFrom = expectDataFrom .addExpectedKeys(activated) @@ -304,9 +304,9 @@ case class EmCommunicationCore( log.warn(s"Activated: ${newState.activatedAgents}") log.warn(s"EmStates: ${newState.emStates}") - log.warn(s"Message to ext: $mesgToExt") + log.warn(s"Message to ext: $msgToExt") - (newState, mesgToExt) + (newState, msgToExt) } case _: RequestEmFlexResults => @@ -395,18 +395,18 @@ case class EmCommunicationCore( val updated = expectDataFrom.addData(sender, resultToExt) - if updated.isComplete then { - val data = updated.receivedData + if updated.isComplete || updated.hasCompleted then { + val (data, updatedExpectDataFrom) = updated.getFinished // should no longer wait for internal data - data.keys.foreach { uuid => emStates(uuid).setWaitingForInternal(false) } + data.keys.foreach(emStates(_).setWaitingForInternal(false)) log.warn(s"Updated EmStates: $emStates") ( copy( allFlexOptions = allFlexOptions.updated(sender, resultToExt), currentSetPoint = currentSetPoint.updated(sender, pRef), - expectDataFrom = ReceiveMultiDataMap.empty, + expectDataFrom = updatedExpectDataFrom, ), Some(new EmResultResponse(data.asJava)), ) @@ -429,6 +429,7 @@ case class EmCommunicationCore( ) => // the completion can be sent directly to the receiver, since it's not used by the external communication uuidToAgent(receiverUuid) ! completion + emStates(sender).setWaitingForInternal(false) if tick == INIT_SIM_TICK then { receiver match { @@ -441,6 +442,8 @@ case class EmCommunicationCore( val updatedData = completions.addData(sender, completion) if updatedData.isComplete then { + emStates.foreach(_._2.clear()) + ( copy( lastFinishedTick = tick, @@ -454,7 +457,12 @@ case class EmCommunicationCore( Some(new EmCompletion(getMaybeNextTick.toJava)), ) } else { - (copy(completions = updatedData), None) + val msgToExt = getMsgToExtOption + log.warn(s"Not finished! Expected: ${updatedData.getExpectedKeys}") + log.warn(s"EmStates: $emStates") + log.warn(s"Message to ext: $msgToExt") + + (copy(completions = updatedData), msgToExt) } } @@ -478,8 +486,8 @@ case class EmCommunicationCore( val updated = flexRequest match { case FlexActivation(tick, flexType) => - // update the em state => waiting for external flex option provision - emStates(sender).addSendRequest(receiverUuid) + // update the em state => waiting for external flex option provision + emStates(sender).addSendRequest(receiverUuid) // send request to ext expectDataFrom.addData( @@ -492,12 +500,14 @@ case class EmCommunicationCore( ) case control: IssueFlexControl => + emStates(receiverUuid).setWaitingForInternal(false) + // send set point to ext log.warn( s"Receiver $receiverUuid got flex control message from $sender" ) - val (time, power) = control match { + val (_, power) = control match { case IssueNoControl(tick) => log.warn(s"Set points: $currentSetPoint") @@ -537,8 +547,27 @@ case class EmCommunicationCore( Some(new EmResultResponse(data.asJava)), ) } else { - (copy(expectDataFrom = updated), None) + val msgToExt = getMsgToExtOption + log.warn(s"Not finished! Expected: ${updated.getExpectedKeys}") + log.warn(s"EmStates: $emStates") + log.warn(s"Message to ext: $msgToExt") + + (copy(expectDataFrom = updated), msgToExt) } } + private def getMsgToExtOption(using log: Logger): Option[EmDataResponseMessageToExt] = { + if emStates.exists(_._2.isWaitingForInternal) then { + None + } else { + val awaited = emStates.filter((_, x) => x.isWaitingForExtern).map { + case (uuid, state) => uuid -> state.getAwaited + } + + log.info(s"Waiting for external data: $awaited") + + if awaited.isEmpty then None + else Some(new EmResultResponse(Map.empty.asJava)) + } + } } 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 3cedb6039c..e3188eb0b2 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -79,7 +79,7 @@ final case class EmServiceBaseCore( } override def handleExtMessage(tick: Long, extMsg: EmDataMessageFromExt)(using - log: Logger + log: Logger ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMsg match { case requestEmFlexResults: RequestEmFlexResults => val tick = requestEmFlexResults.tick 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 be7852e1ae..c13e97786b 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -46,8 +46,8 @@ trait EmServiceCore { ): EmServiceCore def handleExtMessage( - tick: Long, - extMsg: EmDataMessageFromExt, + tick: Long, + extMsg: EmDataMessageFromExt, )(using log: Logger ): (EmServiceCore, Option[EmDataResponseMessageToExt]) From 8044a10cab203b7dd15456ad0da01ef15b72d643 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 14 Oct 2025 14:20:40 +0200 Subject: [PATCH 100/125] Saving changes. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 12 ++++++-- .../service/em/EmCommunicationCore.scala | 30 ++++++++++++++++--- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 804e7502e7..9e4e21bf44 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -201,15 +201,18 @@ object EmAgent { case (_, msg: Activation) => activate(emData, modelShell, core, msg.tick) - case (_, msg: FlexActivation) => - activate(emData, modelShell, core, msg.tick) + case (ctx, msg: FlexActivation) => + val tick = msg.tick + ctx.log.info(s"EmAgent (${modelShell.uuid}) activated for tick $tick") + + activate(emData, modelShell, core, tick) case (ctx, msg: FlexShiftActivation) => val tick = msg.tick ctx.log.info( s"EmAgent (${modelShell.uuid}) activated by service for tick $tick" ) - activate(emData, modelShell, core.gotoTick(tick), msg.tick) + activate(emData, modelShell, core.gotoTick(tick), tick) case (ctx, msg: IssueFlexControl) => val flexOptionsCore = core.activate(msg.tick) @@ -398,6 +401,9 @@ object EmAgent { inactiveCore, lastActiveTick = updatedCore.activeTick, )(using ctx.self) + + ctx.log.info(s"${modelShell.uuid} -> inactive, next tick ${completion.requestAtTick}") + inactive(emData, modelShell, inactiveCore) } .getOrElse { diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 3310a656dc..94a9ba06e0 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -94,12 +94,15 @@ object EmCommunicationCore { def isWaitingForActivation: Boolean = !receivedActivation - def isWaitingForExtern: Boolean = (awaitedFlexOptions.nonEmpty || awaitedSetPoint) && !waitingForInternal + def isWaitingForExtern: Boolean = + (awaitedFlexOptions.nonEmpty || awaitedSetPoint) && !waitingForInternal def isWaitingForSetPoint: Boolean = awaitedSetPoint def isWaitingForInternal: Boolean = waitingForInternal + def isActivated: Boolean = receivedActivation + def clear(): Unit = { receivedActivation = false awaitedFlexOptions.clear @@ -126,6 +129,7 @@ case class EmCommunicationCore( emStates: Map[UUID, EmAgentState] = Map.empty, expectDataFrom: ReceiveMultiDataMap[UUID, EmData] = ReceiveMultiDataMap.empty, + nextActivation: Map[UUID, Long] = Map.empty ) extends EmServiceCore { override def handleRegistration( @@ -158,6 +162,7 @@ case class EmCommunicationCore( uuidToInferior = updatedInferior, uuidToParent = updatedUuidToParent, emStates = emStates.updated(uuid, EmAgentState()), + nextActivation = nextActivation.updated(uuid, 0) ) } @@ -213,8 +218,12 @@ case class EmCommunicationCore( requestedFlexType.getOrElse(uuid, FlexType.PowerLimit), ) + val count = Try { + uuidToInferior(uuid).count { id => nextActivation(id) <= tick} + }.getOrElse(1) + // uuid -> number of sent flex requests - uuid -> Try(uuidToInferior(uuid).size).getOrElse(1) + uuid -> count } case _ => None @@ -275,8 +284,12 @@ case class EmCommunicationCore( agent ! IssueNoControl(extTick) } + val count = Try { + uuidToInferior(uuid).count { id => emStates(id).isActivated } + }.getOrElse(0) + // sender -> number of set points to send - Some(uuid -> Try(uuidToInferior(uuid).size).getOrElse(0)) + Some(uuid -> count) case _ => None } .toMap @@ -443,6 +456,12 @@ case class EmCommunicationCore( if updatedData.isComplete then { emStates.foreach(_._2.clear()) + log.warn(s"Cleared EmStates: $emStates") + + // the next activations + val additionalActivation = updatedData.receivedData.flatMap { case (uuid, msg) => + msg.requestAtTick.map(uuid -> _) + } ( copy( @@ -453,6 +472,7 @@ case class EmCommunicationCore( currentSetPoint = Map.empty, activatedAgents = Set.empty, expectDataFrom = ReceiveMultiDataMap.empty, + nextActivation = nextActivation ++ additionalActivation, ), Some(new EmCompletion(getMaybeNextTick.toJava)), ) @@ -556,7 +576,9 @@ case class EmCommunicationCore( } } - private def getMsgToExtOption(using log: Logger): Option[EmDataResponseMessageToExt] = { + private def getMsgToExtOption(using + log: Logger + ): Option[EmDataResponseMessageToExt] = { if emStates.exists(_._2.isWaitingForInternal) then { None } else { From 4f1fbb5f599fba00734e1d55362873add01515f4 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 15 Oct 2025 15:26:45 +0200 Subject: [PATCH 101/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 94a9ba06e0..0ffab99daa 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -9,21 +9,12 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent.Message -import edu.ie3.simona.api.data.model.em.{ - EmData, - EmSetPoint, - ExtendedFlexOptionsResult, - FlexOptionRequest, -} +import edu.ie3.simona.api.data.model.em.{EmData, EmSetPoint, ExtendedFlexOptionsResult, FlexOptionRequest} 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.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.flex.{ - FlexType, - FlexibilityMessage, - PowerLimitFlexOptions, -} +import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} import edu.ie3.simona.service.em.EmCommunicationCore.EmAgentState import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} @@ -39,6 +30,7 @@ import java.util.UUID import scala.collection.mutable import scala.jdk.CollectionConverters.* import scala.jdk.OptionConverters.* +import scala.math.max import scala.util.Try object EmCommunicationCore { @@ -218,9 +210,9 @@ case class EmCommunicationCore( requestedFlexType.getOrElse(uuid, FlexType.PowerLimit), ) - val count = Try { + val count = max(Try { uuidToInferior(uuid).count { id => nextActivation(id) <= tick} - }.getOrElse(1) + }.getOrElse(1), 1) // uuid -> number of sent flex requests uuid -> count From 7fc1584359a169145c4dd86dfbb1fbcdd87b38e8 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 17 Oct 2025 13:49:34 +0200 Subject: [PATCH 102/125] Refactoring em communication. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 4 +- .../service/em/EmCommunicationCore.scala | 282 ++++++++++++++++-- .../simona/service/em/EmServiceBaseCore.scala | 1 - 3 files changed, 266 insertions(+), 21 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 9e4e21bf44..c25b9e4f3d 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -402,7 +402,9 @@ object EmAgent { lastActiveTick = updatedCore.activeTick, )(using ctx.self) - ctx.log.info(s"${modelShell.uuid} -> inactive, next tick ${completion.requestAtTick}") + ctx.log.info( + s"${modelShell.uuid} -> inactive, next tick ${completion.requestAtTick}" + ) inactive(emData, modelShell, inactiveCore) } diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 0ffab99daa..6009259e55 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -9,12 +9,23 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent.Message -import edu.ie3.simona.api.data.model.em.{EmData, EmSetPoint, ExtendedFlexOptionsResult, FlexOptionRequest} +import edu.ie3.simona.api.data.model.em.{ + EmCommunicationMessage, + EmData, + EmSetPoint, + ExtendedFlexOptionsResult, + FlexOptionRequest, + 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.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} +import edu.ie3.simona.ontology.messages.flex.{ + FlexType, + FlexibilityMessage, + PowerLimitFlexOptions, +} import edu.ie3.simona.service.em.EmCommunicationCore.EmAgentState import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} @@ -121,7 +132,7 @@ case class EmCommunicationCore( emStates: Map[UUID, EmAgentState] = Map.empty, expectDataFrom: ReceiveMultiDataMap[UUID, EmData] = ReceiveMultiDataMap.empty, - nextActivation: Map[UUID, Long] = Map.empty + nextActivation: Map[UUID, Long] = Map.empty, ) extends EmServiceCore { override def handleRegistration( @@ -154,7 +165,7 @@ case class EmCommunicationCore( uuidToInferior = updatedInferior, uuidToParent = updatedUuidToParent, emStates = emStates.updated(uuid, EmAgentState()), - nextActivation = nextActivation.updated(uuid, 0) + nextActivation = nextActivation.updated(uuid, 0), ) } @@ -186,6 +197,7 @@ case class EmCommunicationCore( ) } + /* case provideEmData: ProvideEmData => log.warn(s"Handling ext message: $provideEmData") @@ -210,9 +222,14 @@ case class EmCommunicationCore( requestedFlexType.getOrElse(uuid, FlexType.PowerLimit), ) - val count = max(Try { - uuidToInferior(uuid).count { id => nextActivation(id) <= tick} - }.getOrElse(1), 1) + val count = max( + Try { + uuidToInferior(uuid).count { id => + nextActivation(id) <= tick + } + }.getOrElse(1), + 1, + ) // uuid -> number of sent flex requests uuid -> count @@ -313,14 +330,229 @@ case class EmCommunicationCore( (newState, msgToExt) } + */ + case flexRequest: RequestEmFlexResults => + val disagg = flexRequest.disaggregated + val entities = flexRequest.emEntities.asScala + + val mapping = entities.flatMap { uuid => + if emStates(uuid).isWaitingForActivation then { + + uuidToAgent.get(uuid).map { agent => + // update the em state + emStates(uuid).setReceivedRequest() - case _: RequestEmFlexResults => - // should not happen, this should be done by ProvideFlexRequestData - log.warn( - s"Received request for flex results. This is not supported by ${this.getClass}!" + agent ! FlexShiftActivation( + tick, + requestedFlexType.getOrElse(uuid, FlexType.PowerLimit), + ) + + val count = max( + Try { + uuidToInferior(uuid).count { id => + nextActivation(id) <= tick + } + }.getOrElse(1), + 1, + ) + + // uuid -> number of sent flex requests + uuid -> count + } + } else None + }.toMap + + val updatedDisaggregated = disaggregated ++ entities.map(uuid => uuid -> disagg).toMap + + val updatedExpectDataFrom = expectDataFrom.addExpectedKeys(mapping) + + log.warn(s"ExpectDataFrom: $updatedExpectDataFrom, Changes: $mapping") + + // check if we need to wait for internal answers + val msgToExt = getMsgToExtOption + + // update state data + val newState = copy( + disaggregated = updatedDisaggregated, + expectDataFrom = updatedExpectDataFrom, + completions = completions.addExpectedKeys(mapping.keySet), ) - (this, None) + log.warn(s"EmStates: ${newState.emStates}") + log.warn(s"Message to ext: $msgToExt") + + (newState, msgToExt) + + case comMsg: EmCommunicationMessages => + log.warn(s"Handling ext message: $comMsg") + + val messages = comMsg.messages.asScala + val extTick = comMsg.tick + + val mapping = messages.flatMap { msg => + val receiver = msg.receiver + val sender = msg.sender + + msg.content match { + case flexRequest: FlexOptionRequest => + uuidToAgent.get(receiver) match { + case Some(agent) => + // update the em state + emStates(receiver).setReceivedRequest() + + agent ! FlexShiftActivation( + tick, + requestedFlexType.getOrElse(receiver, FlexType.PowerLimit), + ) + + val count = max( + Try { + uuidToInferior(receiver).count { id => + nextActivation(id) <= tick + } + }.getOrElse(1), + 1, + ) + + // uuid -> number of sent flex requests + Some(receiver -> count) + + case None => + log.warn(s"Cannot send flex request to receiver '$receiver'.") + None + } + + case flexOptions: FlexOptions => + val agent = uuidToAgent(receiver) + + val emState = emStates(receiver) + + // update the em state + emState.handleReceivedFlexOption(sender) + + // send flex options to agent + agent ! ProvideFlexOptions( + sender, + PowerLimitFlexOptions( + flexOptions.pRef.toSquants, + flexOptions.pMin.toSquants, + flexOptions.pMax.toSquants, + ), + ) + + // receiver -> number of received flex options + Some(receiver -> 1) + + case flexOptions: FlexOptionsResult => + val agent = uuidToAgent(receiver) + + val emState = emStates(receiver) + + // update the em state + emState.handleReceivedFlexOption(sender) + + // send flex options to agent + agent ! ProvideFlexOptions( + sender, + PowerLimitFlexOptions( + flexOptions.getpRef.toSquants, + flexOptions.getpMin.toSquants, + flexOptions.getpMax.toSquants, + ), + ) + + // receiver -> number of received flex options + Some(receiver -> 1) + + case setPoint: EmSetPoint => + val agent = uuidToAgent(receiver) + log.warn(s"Receiver of set point: $agent") + + // updates the em state + emStates(receiver).setReceivedSetPoint() + + setPoint.power.toScala.flatMap( + _.getP.toScala.map(_.toSquants) + ) match { + case Some(power) => + agent ! IssuePowerControl(extTick, power) + + case None => + agent ! IssueNoControl(extTick) + } + + val count = Try { + uuidToInferior(receiver).count { id => emStates(id).isActivated } + }.getOrElse(0) + + // sender -> number of set points to send + Some(receiver -> count) + } + }.toMap + + val updatedExpectDataFrom = expectDataFrom.addExpectedKeys(mapping) + + log.warn(s"ExpectDataFrom: $updatedExpectDataFrom, Changes: $mapping") + + // check if we need to wait for internal answers + val msgToExt = getMsgToExtOption + + // update state data + val newState = copy( + expectDataFrom = updatedExpectDataFrom, + completions = completions.addExpectedKeys(mapping.keySet), + ) + + log.warn(s"EmStates: ${newState.emStates}") + log.warn(s"Message to ext: $msgToExt") + + (newState, msgToExt) + + case provideEmSetPoints: ProvideEmSetPointData => + val extTick = provideEmSetPoints.tick + + val mapping = provideEmSetPoints.emSetPoints().asScala.flatMap { case (receiver, setPoint) => + val agent = uuidToAgent(receiver) + log.warn(s"Receiver of set point: $agent") + + // updates the em state + emStates(receiver).setReceivedSetPoint() + + setPoint.power.toScala.flatMap( + _.getP.toScala.map(_.toSquants) + ) match { + case Some(power) => + agent ! IssuePowerControl(extTick, power) + + case None => + agent ! IssueNoControl(extTick) + } + + val count = Try { + uuidToInferior(receiver).count { id => emStates(id).isActivated } + }.getOrElse(0) + + // sender -> number of set points to send + Some(receiver -> count) + }.toMap + + val updatedExpectDataFrom = expectDataFrom.addExpectedKeys(mapping) + + log.warn(s"ExpectDataFrom: $updatedExpectDataFrom, Changes: $mapping") + + // check if we need to wait for internal answers + val msgToExt = getMsgToExtOption + + // update state data + val newState = copy( + expectDataFrom = updatedExpectDataFrom, + completions = completions.addExpectedKeys(mapping.keySet), + ) + + log.warn(s"EmStates: ${newState.emStates}") + log.warn(s"Message to ext: $msgToExt") + + (newState, msgToExt) case other => log.warn(s"Deprecated message received! Message: $other") @@ -375,7 +607,6 @@ case class EmCommunicationCore( val flexOptionResult = new ExtendedFlexOptionsResult( tick.toDateTime(using startTime), sender, - receiverUuid, ref.toQuantity, min.toQuantity, max.toQuantity, @@ -398,7 +629,12 @@ case class EmCommunicationCore( ) } - val updated = expectDataFrom.addData(sender, resultToExt) + // wrap the result, if sender and receiver are not the same, since we want to use ext communication + val msg = if receiverUuid != sender then { + new EmCommunicationMessage(receiverUuid, sender, resultToExt) + } else resultToExt + + val updated = expectDataFrom.addData(sender, msg) if updated.isComplete || updated.hasCompleted then { val (data, updatedExpectDataFrom) = updated.getFinished @@ -451,8 +687,9 @@ case class EmCommunicationCore( log.warn(s"Cleared EmStates: $emStates") // the next activations - val additionalActivation = updatedData.receivedData.flatMap { case (uuid, msg) => - msg.requestAtTick.map(uuid -> _) + val additionalActivation = updatedData.receivedData.flatMap { + case (uuid, msg) => + msg.requestAtTick.map(uuid -> _) } ( @@ -504,10 +741,13 @@ case class EmCommunicationCore( // send request to ext expectDataFrom.addData( sender, - new FlexOptionRequest( + new EmCommunicationMessage( receiverUuid, sender, - disaggregated.getOrElse(sender, false), + new FlexOptionRequest( + receiverUuid, + disaggregated.getOrElse(sender, false), + ), ), ) @@ -539,7 +779,11 @@ case class EmCommunicationCore( expectDataFrom.addData( sender, - new EmSetPoint(receiverUuid, sender, power), + new EmCommunicationMessage( + receiverUuid, + sender, + new EmSetPoint(receiverUuid, power), + ), ) case other => 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 e3188eb0b2..7c67f5a76c 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -156,7 +156,6 @@ final case class EmServiceBaseCore( val result = new ExtendedFlexOptionsResult( tick.toDateTime, modelUuid, - modelUuid, min.toQuantity, ref.toQuantity, max.toQuantity, From 2adda4a1b0c24f5888cf14eeb6e3faf1c5d847bb Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 21 Oct 2025 10:52:58 +0200 Subject: [PATCH 103/125] Saving changes. --- .../edu/ie3/simona/service/results/ExtResultProvider.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala index 920e5385be..7bae8d2531 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ExtResultProvider.scala @@ -90,8 +90,6 @@ object ExtResultProvider { ): Behavior[Message | DataMessageFromExt | Activation] = Behaviors.receivePartial[Message | DataMessageFromExt | Activation] { case (ctx, ResultResponse(results)) => - ctx.log.warn(s"Sending results to ext. Results: $results") - // send result to external simulation stateData.connection.queueExtResponseMsg( new ProvideResultEntities(results.asJava) From 32f0aa4f68f1173a9fb5f83473b2a4a7d3492bd9 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 24 Oct 2025 12:27:14 +0200 Subject: [PATCH 104/125] Saving changes. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 80 +++++++++---------- .../service/em/EmCommunicationCore.scala | 49 +++++++----- .../ie3/simona/sim/setup/ExtSimSetup.scala | 26 +++--- 3 files changed, 79 insertions(+), 76 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 37501caaff..2893f55414 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -79,54 +79,54 @@ object EmAgent { ): Behavior[Message] = Behaviors.setup[Message] { ctx => val parentData = emDataService match { - case Some(service) => - // since we have a service, it will replace the default agent communication - given ActorContext[Message] = ctx + case Some(service) => + // since we have a service, it will replace the default agent communication + given ActorContext[Message] = ctx - val uuid = inputModel.getUuid + val uuid = inputModel.getUuid - service ! EmServiceRegistration( - ctx.self, - uuid, - parent.toOption, - inputModel.getControllingEm.toScala.map(_.getUuid), - ) + service ! EmServiceRegistration( + ctx.self, + uuid, + parent.toOption, + inputModel.getControllingEm.toScala.map(_.getUuid), + ) - // given to the parent - val requestAdapter = ExtEmDataService.emServiceRequestAdapter( - service, - ctx.self, - ) + // given to the parent + val requestAdapter = ExtEmDataService.emServiceRequestAdapter( + service, + ctx.self, + ) - val adaptedParent = parent match { - case Left(_) => - uuid - case Right(value) => - value - } + val adaptedParent = parent match { + case Left(_) => + uuid + case Right(value) => + value + } - // used by this agent - val responseAdapter = ExtEmDataService.emServiceResponseAdapter( - service, - adaptedParent, - ) + // used by this agent + val responseAdapter = ExtEmDataService.emServiceResponseAdapter( + service, + adaptedParent, + ) - parent.map { - _ ! RegisterControlledAsset( - requestAdapter, - inputModel, - ) - } + parent.map { + _ ! RegisterControlledAsset( + requestAdapter, + inputModel, + ) + } - Right(responseAdapter) + Right(responseAdapter) - case None => - parent.map { - _ ! RegisterControlledAsset( - ctx.self, - inputModel, - ) - } + case None => + parent.map { + _ ! RegisterControlledAsset( + ctx.self, + inputModel, + ) + } parent } diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 6009259e55..af0a15ef56 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -197,7 +197,7 @@ case class EmCommunicationCore( ) } - /* + /* case provideEmData: ProvideEmData => log.warn(s"Handling ext message: $provideEmData") @@ -330,7 +330,7 @@ case class EmCommunicationCore( (newState, msgToExt) } - */ + */ case flexRequest: RequestEmFlexResults => val disagg = flexRequest.disaggregated val entities = flexRequest.emEntities.asScala @@ -362,7 +362,8 @@ case class EmCommunicationCore( } else None }.toMap - val updatedDisaggregated = disaggregated ++ entities.map(uuid => uuid -> disagg).toMap + val updatedDisaggregated = + disaggregated ++ entities.map(uuid => uuid -> disagg).toMap val updatedExpectDataFrom = expectDataFrom.addExpectedKeys(mapping) @@ -511,30 +512,34 @@ case class EmCommunicationCore( case provideEmSetPoints: ProvideEmSetPointData => val extTick = provideEmSetPoints.tick - val mapping = provideEmSetPoints.emSetPoints().asScala.flatMap { case (receiver, setPoint) => - val agent = uuidToAgent(receiver) - log.warn(s"Receiver of set point: $agent") + val mapping = provideEmSetPoints + .emSetPoints() + .asScala + .flatMap { case (receiver, setPoint) => + val agent = uuidToAgent(receiver) + log.warn(s"Receiver of set point: $agent") - // updates the em state - emStates(receiver).setReceivedSetPoint() + // updates the em state + emStates(receiver).setReceivedSetPoint() - setPoint.power.toScala.flatMap( - _.getP.toScala.map(_.toSquants) - ) match { - case Some(power) => - agent ! IssuePowerControl(extTick, power) + setPoint.power.toScala.flatMap( + _.getP.toScala.map(_.toSquants) + ) match { + case Some(power) => + agent ! IssuePowerControl(extTick, power) - case None => - agent ! IssueNoControl(extTick) - } + case None => + agent ! IssueNoControl(extTick) + } - val count = Try { - uuidToInferior(receiver).count { id => emStates(id).isActivated } - }.getOrElse(0) + val count = Try { + uuidToInferior(receiver).count { id => emStates(id).isActivated } + }.getOrElse(0) - // sender -> number of set points to send - Some(receiver -> count) - }.toMap + // sender -> number of set points to send + Some(receiver -> count) + } + .toMap val updatedExpectDataFrom = expectDataFrom.addExpectedKeys(mapping) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index e85a598140..2942918c65 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -163,23 +163,21 @@ object ExtSimSetup { val updatedSetupData = connections.foldLeft(extSimSetupData) { case (setupData, connection) => connection match { - connection match { - case extPrimaryDataConnection: ExtPrimaryDataConnection => - val serviceRef = context.spawn( - ExtPrimaryDataService(scheduler), - "ExtPrimaryDataService", - ) - - setupService( - extPrimaryDataConnection, - serviceRef, - InitExtPrimaryData.apply, - ) + case extPrimaryDataConnection: ExtPrimaryDataConnection => + val serviceRef = context.spawn( + ExtPrimaryDataService(scheduler), + "ExtPrimaryDataService", + ) - extSimSetupData.update(extPrimaryDataConnection, serviceRef) + setupService( + extPrimaryDataConnection, + serviceRef, + InitExtPrimaryData.apply, + ) + extSimSetupData.update(extPrimaryDataConnection, serviceRef) - case extEmDataConnection: ExtEmDataConnection => + case extEmDataConnection: ExtEmDataConnection => if setupData.emDataService.nonEmpty then { throw ServiceException( s"Trying to connect another EmDataConnection. Currently only one is allowed." From f5498df3947583aaeab7d8a09c4a5f4989ef1995 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 24 Oct 2025 15:55:29 +0200 Subject: [PATCH 105/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 66 ++++++++++--------- .../simona/service/em/EmServiceBaseCore.scala | 57 +++++++++------- .../ie3/simona/service/em/EmServiceCore.scala | 14 ++-- .../ie3/simona/sim/setup/ExtSimSetup.scala | 17 ++--- .../simona/sim/setup/ExtSimSetupData.scala | 7 +- 5 files changed, 77 insertions(+), 84 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index af0a15ef56..f5252f0be9 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -9,23 +9,13 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent.Message -import edu.ie3.simona.api.data.model.em.{ - EmCommunicationMessage, - EmData, - EmSetPoint, - ExtendedFlexOptionsResult, - FlexOptionRequest, - FlexOptions, -} +import edu.ie3.simona.api.data.model.em +import edu.ie3.simona.api.data.model.em.{EmCommunicationMessage, EmData, EmSetPoint, ExtendedFlexOptionsResult, FlexOptionRequest, FlexOptions, GeneralFlexOptions} 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.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.flex.{ - FlexType, - FlexibilityMessage, - PowerLimitFlexOptions, -} +import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} import edu.ie3.simona.service.em.EmCommunicationCore.EmAgentState import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} @@ -40,7 +30,7 @@ import java.time.ZonedDateTime import java.util.UUID import scala.collection.mutable import scala.jdk.CollectionConverters.* -import scala.jdk.OptionConverters.* +import scala.jdk.OptionConverters.RichOptional import scala.math.max import scala.util.Try @@ -126,7 +116,7 @@ case class EmCommunicationCore( override val completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, requestedFlexType: Map[UUID, FlexType] = Map.empty, - allFlexOptions: Map[UUID, FlexOptionsResult] = Map.empty, + allFlexOptions: Map[UUID, FlexOptions] = Map.empty, currentSetPoint: Map[UUID, Power] = Map.empty, activatedAgents: Set[UUID] = Set.empty, emStates: Map[UUID, EmAgentState] = Map.empty, @@ -186,14 +176,14 @@ case class EmCommunicationCore( // deactivate agents by sending an IssueNoControl message // activatedAgents.map(uuidToAgent).foreach(_ ! IssueNoControl(tick)) - val nextTick: Option[java.lang.Long] = + val nextTick: java.util.Optional[java.lang.Long] = if activatedAgents.nonEmpty then { - requestEmCompletion.maybeNextTick.toScala + requestEmCompletion.maybeNextTick } else getMaybeNextTick ( copy(lastFinishedTick = tick), - Some(new EmCompletion(nextTick.toJava)), + Some(new EmCompletion(nextTick)), ) } @@ -423,23 +413,27 @@ case class EmCommunicationCore( None } - case flexOptions: FlexOptions => + case flexOption: FlexOptions => val agent = uuidToAgent(receiver) - val emState = emStates(receiver) // update the em state emState.handleReceivedFlexOption(sender) - // send flex options to agent - agent ! ProvideFlexOptions( - sender, - PowerLimitFlexOptions( - flexOptions.pRef.toSquants, - flexOptions.pMin.toSquants, - flexOptions.pMax.toSquants, - ), - ) + flexOption match { + case options: em.PowerLimitFlexOptions => + // send flex options to agent + agent ! ProvideFlexOptions( + sender, + PowerLimitFlexOptions( + options.pRef.toSquants, + options.pMin.toSquants, + options.pMax.toSquants, + ), + ) + case other => + log.warn(s"Cannot handle flex option: $other") + } // receiver -> number of received flex options Some(receiver -> 1) @@ -621,8 +615,16 @@ case class EmCommunicationCore( uuidToInferior(receiverUuid) .flatMap(allFlexOptions.get) .foreach { result => - flexOptionResult - .addDisaggregated(result.getInputModel, result) + val model = result match { + case result: ExtendedFlexOptionsResult => + result.getInputModel + case options: GeneralFlexOptions => + options.model + case options: em.PowerLimitFlexOptions => + options.model + } + + flexOptionResult.addDisaggregated(model, result) } } @@ -708,7 +710,7 @@ case class EmCommunicationCore( expectDataFrom = ReceiveMultiDataMap.empty, nextActivation = nextActivation ++ additionalActivation, ), - Some(new EmCompletion(getMaybeNextTick.toJava)), + Some(new EmCompletion(getMaybeNextTick)), ) } else { val msgToExt = getMsgToExtOption 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 9c55fd12c3..903fcb65a5 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -15,20 +15,14 @@ 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.ReceiveDataMap -import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK +import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.TickLong 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.OptionConverters.RichOption +import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala, SetHasAsScala} /** Basic service core for an [[ExtEmDataService]]. * @param lastFinishedTick @@ -161,10 +155,14 @@ final case class EmServiceBaseCore( case provideEmSetPoints: ProvideEmSetPointData => if canHandleSetPoints then { + log.warn(s"Handling of set points.") + handleSetPoint(tick, provideEmSetPoints, log) (this, None) } else { + log.warn(s"Cannot handle set points.") + val tick = provideEmSetPoints.tick val emEntities = provideEmSetPoints.emSetPoints.keySet.asScala @@ -258,23 +256,32 @@ final case class EmServiceBaseCore( } case completion: FlexCompletion => - val (updated, extMsgOption, finished) = - handleCompletion(tick, completion) - - if finished then { - ( - copy( - lastFinishedTick = tick, - completions = updated, - allFlexOptions = Map.empty, - disaggregated = Map.empty, - sendOptionsToExt = false, - canHandleSetPoints = false, - ), - extMsgOption, - ) - - } else (copy(completions = updated), extMsgOption) + if tick == INIT_SIM_TICK then { + receiver match { + case Left(value) => + (copy(lastFinishedTick = tick), None) + case Right(_) => + (this, None) + } + } else { + val (updated, extMsgOption, finished) = + handleCompletion(tick, completion) + + if finished then { + ( + copy( + lastFinishedTick = tick, + completions = updated, + allFlexOptions = Map.empty, + disaggregated = Map.empty, + sendOptionsToExt = false, + canHandleSetPoints = false, + ), + extMsgOption, + ) + + } else (copy(completions = updated), extMsgOption) + } case _ => (this, None) 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 afb530655f..77ecf6e8c9 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -9,13 +9,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.{ExtendedFlexOptionsResult, FlexOptions} import edu.ie3.simona.api.ontology.em.* -import edu.ie3.simona.ontology.messages.ServiceMessage.{ - EmFlexMessage, - EmServiceRegistration, - ServiceResponseMessage, -} +import edu.ie3.simona.ontology.messages.ServiceMessage.{EmFlexMessage, EmServiceRegistration, ServiceResponseMessage} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK @@ -35,8 +31,6 @@ import scala.jdk.OptionConverters.{RichOption, RichOptional} /** Trait for all em service cores. */ trait EmServiceCore { - def lastFinishedTick: Long - /** The last tick that was completed. */ val lastFinishedTick: Long @@ -47,7 +41,7 @@ trait EmServiceCore { /** Map: uuid to flex option result. */ - val allFlexOptions: Map[UUID, FlexOptionsResult] + val allFlexOptions: Map[UUID, FlexOptions] /** ReceiveDataMap: uuid to completions. */ @@ -280,7 +274,7 @@ trait EmServiceCore { * @return * An option for the next activation tick. */ - private final def getMaybeNextTick: java.util.Optional[java.lang.Long] = + protected final def getMaybeNextTick: java.util.Optional[java.lang.Long] = completions.receivedData .flatMap { case (_, completion) => completion.requestAtTick diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala index 2942918c65..eb570b3db1 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetup.scala @@ -9,12 +9,7 @@ package edu.ie3.simona.sim.setup import com.typesafe.config.Config import edu.ie3.datamodel.models.input.container.JointGridContainer import edu.ie3.simona.api.data.ExtSimAdapterData -import edu.ie3.simona.api.data.connection.{ - ExtEmDataConnection, - ExtEvDataConnection, - ExtInputDataConnection, - ExtPrimaryDataConnection, -} +import edu.ie3.simona.api.data.connection.* import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.api.ontology.simulation.ControlResponseMessageFromExt import edu.ie3.simona.api.simulation.ExtSimulation @@ -175,7 +170,7 @@ object ExtSimSetup { InitExtPrimaryData.apply, ) - extSimSetupData.update(extPrimaryDataConnection, serviceRef) + setupData.update(extPrimaryDataConnection, serviceRef) case extEmDataConnection: ExtEmDataConnection => if setupData.emDataService.nonEmpty then { @@ -201,7 +196,7 @@ object ExtSimSetup { InitExtEmData(_, startTime), ) - extSimSetupData.update(extEmDataConnection, serviceRef) + setupData.copy(emDataService = Some(serviceRef)) } case extEvDataConnection: ExtEvDataConnection => @@ -222,7 +217,7 @@ object ExtSimSetup { InitExtEvData.apply, ) - extSimSetupData.update(extEvDataConnection, serviceRef) + setupData.update(extEvDataConnection, serviceRef) case extResultDataConnection: ExtResultDataConnection => val extResultProvider = context.spawn( @@ -239,7 +234,7 @@ object ExtSimSetup { extSimAdapter, ) - extSimSetupData.update(extResultDataConnection, extResultProvider) + setupData.update(extResultDataConnection, extResultProvider) case extResultListener: ExtResultListener => val extResultEventListener = context.spawn( @@ -247,7 +242,7 @@ object ExtSimSetup { s"ExtResultListener_$index", ) - extSimSetupData.update(extResultListener, extResultEventListener) + setupData.update(extResultListener, extResultEventListener) case otherConnection => log.warn( diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index 87be5f9a5c..ff14358e2b 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -59,11 +59,6 @@ final case class ExtSimSetupData( serviceRef: ActorRef[ServiceMessage], ) => update(primaryConnection, serviceRef) - case ( - _: ExtEmDataConnection, - serviceRef: ActorRef[ExtEmDataService.Message], - ) => - copy(emDataService = Some(serviceRef)) case ( _: ExtEvDataConnection, serviceRef: ActorRef[ExtEvDataService.Message], @@ -97,7 +92,7 @@ final case class ExtSimSetupData( Seq( emDataService, evDataService, - ).flatten ++ extResultListeners ++ primaryDataServices.map(_._2) + ).flatten ++ resultListeners ++ resultProviders ++ primaryDataServices.map(_._2) } object ExtSimSetupData { From d00befffe5d74d5a0209df5c6d4c9b3747b6de52 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 27 Oct 2025 08:02:24 +0100 Subject: [PATCH 106/125] Saving changes. --- .../edu/ie3/simona/service/em/EmServiceBaseCore.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 903fcb65a5..3fc1ea0f7d 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -246,6 +246,8 @@ final case class EmServiceBaseCore( } } else { + log.warn(s"Missing flex options for: ${updated.getExpectedKeys}") + ( copy( flexOptions = updated, @@ -331,11 +333,15 @@ final case class EmServiceBaseCore( ) if flexOptions.expects(modelUuid) then { + println(s"Received expected: $modelUuid") + ( flexOptions.addData(modelUuid, result), - allFlexOptions, + allFlexOptions.updated(modelUuid, result), ) } else { + println(s"Received unexpected: $modelUuid") + ( flexOptions, allFlexOptions.updated(modelUuid, result), From 172e110203cfe46b31f56d907a4a2ccb7a6a9f2d Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 27 Oct 2025 13:31:01 +0100 Subject: [PATCH 107/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 6 ++-- .../simona/service/em/EmServiceBaseCore.scala | 31 ++++++++++++++++--- .../ie3/simona/service/em/EmServiceCore.scala | 19 ++++++------ 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index f5252f0be9..e5f5744419 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -30,7 +30,7 @@ import java.time.ZonedDateTime import java.util.UUID import scala.collection.mutable import scala.jdk.CollectionConverters.* -import scala.jdk.OptionConverters.RichOptional +import scala.jdk.OptionConverters.{RichOptional, RichOption} import scala.math.max import scala.util.Try @@ -179,7 +179,7 @@ case class EmCommunicationCore( val nextTick: java.util.Optional[java.lang.Long] = if activatedAgents.nonEmpty then { requestEmCompletion.maybeNextTick - } else getMaybeNextTick + } else getMaybeNextTick.map(long2Long).toJava ( copy(lastFinishedTick = tick), @@ -710,7 +710,7 @@ case class EmCommunicationCore( expectDataFrom = ReceiveMultiDataMap.empty, nextActivation = nextActivation ++ additionalActivation, ), - Some(new EmCompletion(getMaybeNextTick)), + Some(new EmCompletion(getMaybeNextTick.map(long2Long).toJava)), ) } else { val msgToExt = getMsgToExtOption 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 3fc1ea0f7d..e54d6a6d7f 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -64,6 +64,7 @@ final case class EmServiceBaseCore( sendOptionsToExt: Boolean = false, canHandleSetPoints: Boolean = false, setPointOption: Option[ProvideEmSetPointData] = None, + nextActivation: Map[UUID, Long] = Map.empty, ) extends EmServiceCore { override def handleRegistration( @@ -96,6 +97,7 @@ final case class EmServiceBaseCore( uuidToAgent = uuidToAgent + (modelUuid -> ref), completions = completions.addExpectedKeys(Set(modelUuid)), structure = updatedStructure ++ Map(modelUuid -> Set.empty[UUID]), + nextActivation = nextActivation.updated(modelUuid, 0), ) } @@ -266,23 +268,44 @@ final case class EmServiceBaseCore( (this, None) } } else { - 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 + log.warn(s"Keys: $keys") + ReceiveDataMap[UUID, FlexCompletion](keys) + case None => + updated + } + + log.warn(s"$updated") + ( copy( lastFinishedTick = tick, - completions = updated, - allFlexOptions = Map.empty, + completions = expectedCompletions, disaggregated = Map.empty, sendOptionsToExt = false, canHandleSetPoints = false, + nextActivation = updatedNextActivation, ), extMsgOption, ) - } else (copy(completions = updated), extMsgOption) + } else { + log.warn(s"$updated") + + (copy(completions = updated), extMsgOption) + } } case _ => 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 77ecf6e8c9..d801ecb56c 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -252,6 +252,7 @@ trait EmServiceCore { final def handleCompletion(tick: Long, completion: FlexCompletion): ( ReceiveDataMap[UUID, FlexCompletion], Option[EmDataResponseMessageToExt], + Option[Long], Boolean, ) = { val updated = completions.addData(completion.modelUuid, completion) @@ -259,27 +260,27 @@ trait EmServiceCore { 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.map(long2Long).toJava)), 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. */ - protected final def getMaybeNextTick: java.util.Optional[java.lang.Long] = + protected final def getMaybeNextTick: Option[Long] = completions.receivedData .flatMap { case (_, completion) => completion.requestAtTick } .minOption - .map(long2Long) - .toJava } From 99d007812a4277cc529e451064dea31cbb225c1a Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 28 Oct 2025 11:15:42 +0100 Subject: [PATCH 108/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 260 ++++-------------- .../simona/service/em/EmServiceBaseCore.scala | 169 +++++------- .../ie3/simona/service/em/EmServiceCore.scala | 31 +-- 3 files changed, 133 insertions(+), 327 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index e5f5744419..15b1836e12 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -10,7 +10,7 @@ import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent.Message import edu.ie3.simona.api.data.model.em -import edu.ie3.simona.api.data.model.em.{EmCommunicationMessage, EmData, EmSetPoint, ExtendedFlexOptionsResult, FlexOptionRequest, FlexOptions, GeneralFlexOptions} +import edu.ie3.simona.api.data.model.em.* import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration @@ -19,18 +19,16 @@ import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, Powe import edu.ie3.simona.service.em.EmCommunicationCore.EmAgentState import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} -import edu.ie3.simona.util.TickUtil.* import edu.ie3.simona.util.{ReceiveDataMap, ReceiveMultiDataMap} import edu.ie3.util.scala.quantities.QuantityConversionUtils.* import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger import squants.Power -import java.time.ZonedDateTime import java.util.UUID import scala.collection.mutable import scala.jdk.CollectionConverters.* -import scala.jdk.OptionConverters.{RichOptional, RichOption} +import scala.jdk.OptionConverters.{RichOption, RichOptional} import scala.math.max import scala.util.Try @@ -187,145 +185,15 @@ case class EmCommunicationCore( ) } - /* + case provideEmData: ProvideEmData => log.warn(s"Handling ext message: $provideEmData") - - // provide em data val extTick = provideEmData.tick - if extTick != tick then { - throw new CriticalFailureException( - s"Received request for tick '$extTick', while being in tick '$tick'." - ) - } else { - // handle flex requests - val requests = provideEmData.flexRequests.asScala - val activated = requests.flatMap { - case (uuid, _) if emStates(uuid).isWaitingForActivation => - uuidToAgent.get(uuid).map { agent => - // update the em state - emStates(uuid).setReceivedRequest() - - agent ! FlexShiftActivation( - tick, - requestedFlexType.getOrElse(uuid, FlexType.PowerLimit), - ) - - val count = max( - Try { - uuidToInferior(uuid).count { id => - nextActivation(id) <= tick - } - }.getOrElse(1), - 1, - ) - - // uuid -> number of sent flex requests - uuid -> count - } - case _ => - None - }.toMap - - val updatedDisaggregated = disaggregated ++ requests.map { - case (uuid, request) => uuid -> request.disaggregated - } - - // handle flex options - val expectFlexOptions = provideEmData.flexOptions.asScala.flatMap { - case (receiver, options) if emStates(receiver).isWaitingForExtern => - val agent = uuidToAgent(receiver) + // handling of requests + val flexRequest = provideEmData.flexRequests.asScala - val emState = emStates(receiver) - - // send flex options to agent - options.asScala.foreach { option => - val sender = option.sender - - // update the em state - emState.handleReceivedFlexOption(sender) - - agent ! ProvideFlexOptions( - sender, - PowerLimitFlexOptions( - option.pRef.toSquants, - option.pMin.toSquants, - option.pMax.toSquants, - ), - ) - } - - // receiver -> number of received flex options - Some(receiver -> 1) - case _ => None - }.toMap - - // handle set points - val expectedSetPoints = provideEmData - .setPoints() - .asScala - .flatMap { - case (uuid, setPoint) if emStates(uuid).isWaitingForSetPoint => - val agent = uuidToAgent(uuid) - log.warn(s"Receiver of set point: $agent") - - // updates the em state - emStates(uuid).setReceivedSetPoint() - - setPoint.power.toScala.flatMap( - _.getP.toScala.map(_.toSquants) - ) match { - case Some(power) => - agent ! IssuePowerControl(extTick, power) - - case None => - agent ! IssueNoControl(extTick) - } - - val count = Try { - uuidToInferior(uuid).count { id => emStates(id).isActivated } - }.getOrElse(0) - - // sender -> number of set points to send - Some(uuid -> count) - case _ => None - } - .toMap - - // check if we need to wait for internal answers - val msgToExt = getMsgToExtOption - - val activatedKeys = activated.keySet - - val updatedExpectDataFrom = expectDataFrom - .addExpectedKeys(activated) - .addExpectedKeys(expectFlexOptions) - .addExpectedKeys(expectedSetPoints) - log.warn( - s"ExpectDataFrom: $updatedExpectDataFrom, Request: $activated, FlexOption: $expectFlexOptions, SetPoint: $expectedSetPoints" - ) - - // update state data - val newState = copy( - disaggregated = updatedDisaggregated, - activatedAgents = activatedAgents ++ activatedKeys, - expectDataFrom = updatedExpectDataFrom, - completions = completions.addExpectedKeys(activatedKeys), - ) - - log.warn(s"Activated: ${newState.activatedAgents}") - log.warn(s"EmStates: ${newState.emStates}") - log.warn(s"Message to ext: $msgToExt") - - (newState, msgToExt) - } - */ - case flexRequest: RequestEmFlexResults => - val disagg = flexRequest.disaggregated - val entities = flexRequest.emEntities.asScala - - val mapping = entities.flatMap { uuid => + val requestMapping = flexRequest.keys.flatMap { uuid => if emStates(uuid).isWaitingForActivation then { uuidToAgent.get(uuid).map { agent => @@ -352,12 +220,42 @@ case class EmCommunicationCore( } else None }.toMap - val updatedDisaggregated = - disaggregated ++ entities.map(uuid => uuid -> disagg).toMap + val updatedDisaggregated = disaggregated ++ flexRequest.map { case (uuid, request) => uuid -> request.disaggregated }.toMap - val updatedExpectDataFrom = expectDataFrom.addExpectedKeys(mapping) + // handling of set points + val setPointMapping = provideEmData.setPoints() + .asScala + .flatMap { case (receiver, setPoint) => + val agent = uuidToAgent(receiver) + log.warn(s"Receiver of set point: $agent") - log.warn(s"ExpectDataFrom: $updatedExpectDataFrom, Changes: $mapping") + // updates the em state + emStates(receiver).setReceivedSetPoint() + + setPoint.power.toScala.flatMap( + _.getP.toScala.map(_.toSquants) + ) match { + case Some(power) => + agent ! IssuePowerControl(extTick, power) + + case None => + agent ! IssueNoControl(extTick) + } + + val count = Try { + uuidToInferior(receiver).count { id => emStates(id).isActivated } + }.getOrElse(0) + + // sender -> number of set points to send + Some(receiver -> count) + } + .toMap + + /* update internal state */ + val mapping = requestMapping ++ setPointMapping + + val updatedExpectDataFrom = expectDataFrom.addExpectedKeys(mapping) + log.warn(s"ExpectDataFrom: $updatedExpectDataFrom") // check if we need to wait for internal answers val msgToExt = getMsgToExtOption @@ -385,7 +283,7 @@ case class EmCommunicationCore( val sender = msg.sender msg.content match { - case flexRequest: FlexOptionRequest => + case _: FlexOptionRequest => uuidToAgent.get(receiver) match { case Some(agent) => // update the em state @@ -503,56 +401,6 @@ case class EmCommunicationCore( (newState, msgToExt) - case provideEmSetPoints: ProvideEmSetPointData => - val extTick = provideEmSetPoints.tick - - val mapping = provideEmSetPoints - .emSetPoints() - .asScala - .flatMap { case (receiver, setPoint) => - val agent = uuidToAgent(receiver) - log.warn(s"Receiver of set point: $agent") - - // updates the em state - emStates(receiver).setReceivedSetPoint() - - setPoint.power.toScala.flatMap( - _.getP.toScala.map(_.toSquants) - ) match { - case Some(power) => - agent ! IssuePowerControl(extTick, power) - - case None => - agent ! IssueNoControl(extTick) - } - - val count = Try { - uuidToInferior(receiver).count { id => emStates(id).isActivated } - }.getOrElse(0) - - // sender -> number of set points to send - Some(receiver -> count) - } - .toMap - - val updatedExpectDataFrom = expectDataFrom.addExpectedKeys(mapping) - - log.warn(s"ExpectDataFrom: $updatedExpectDataFrom, Changes: $mapping") - - // check if we need to wait for internal answers - val msgToExt = getMsgToExtOption - - // update state data - val newState = copy( - expectDataFrom = updatedExpectDataFrom, - completions = completions.addExpectedKeys(mapping.keySet), - ) - - log.warn(s"EmStates: ${newState.emStates}") - log.warn(s"Message to ext: $msgToExt") - - (newState, msgToExt) - case other => log.warn(s"Deprecated message received! Message: $other") @@ -563,10 +411,7 @@ case class EmCommunicationCore( tick: Long, flexResponse: FlexResponse, receiver: Either[UUID, ActorRef[FlexResponse]], - )(using - startTime: ZonedDateTime, - log: Logger, - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { + )(using log: Logger): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { val receiverUuid = receiver match { case Left(value) => value @@ -603,8 +448,8 @@ case class EmCommunicationCore( // flex option to ext val (resultToExt, pRef) = flexOptions match { case PowerLimitFlexOptions(ref, min, max) => - val flexOptionResult = new ExtendedFlexOptionsResult( - tick.toDateTime(using startTime), + val flexOptionResult = new em.PowerLimitFlexOptions( + receiverUuid, sender, ref.toQuantity, min.toQuantity, @@ -616,8 +461,6 @@ case class EmCommunicationCore( .flatMap(allFlexOptions.get) .foreach { result => val model = result match { - case result: ExtendedFlexOptionsResult => - result.getInputModel case options: GeneralFlexOptions => options.model case options: em.PowerLimitFlexOptions => @@ -733,10 +576,7 @@ case class EmCommunicationCore( override def handleFlexRequest( flexRequest: FlexRequest, receiver: ActorRef[FlexRequest], - )(using - startTime: ZonedDateTime, - log: Logger, - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { + )(using log: Logger): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { val receiverUuid = refToUuid(receiver) // the controlled em val sender = uuidToParent(receiverUuid) // the controlling em @@ -766,17 +606,13 @@ case class EmCommunicationCore( s"Receiver $receiverUuid got flex control message from $sender" ) - val (_, power) = control match { + val power = control match { case IssueNoControl(tick) => log.warn(s"Set points: $currentSetPoint") - - ( - tick.toDateTime, - new PValue(currentSetPoint(receiverUuid).toQuantity), - ) + new PValue(currentSetPoint(receiverUuid).toQuantity) case IssuePowerControl(tick, setPower) => - (tick.toDateTime, new PValue(setPower.toQuantity)) + new PValue(setPower.toQuantity) case other => throw new CriticalFailureException( 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 e54d6a6d7f..f5ae120761 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -7,13 +7,15 @@ 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, ExtendedFlexOptionsResult, 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.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.TickUtil.TickLong @@ -29,6 +31,8 @@ import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsSca * The last tick that was completed. * @param uuidToAgent * Map: uuid to em agent reference. + * @param agentToUuid + * Map: em agent reference to uuid. * @param flexOptions * ReceiveDataMap: uuid to flex option result. * @param allFlexOptions @@ -51,20 +55,21 @@ import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsSca * 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 uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, - flexOptions: ReceiveDataMap[UUID, ExtendedFlexOptionsResult] = + override val lastFinishedTick: Long = PRE_INIT_TICK, + override val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, + agentToUuid: Map[ActorRef[EmAgent.Message] | ActorRef[FlexResponse], UUID] = Map.empty, + flexOptions: ReceiveDataMap[UUID, FlexOptions] = ReceiveDataMap.empty, - override val allFlexOptions: Map[UUID, ExtendedFlexOptionsResult] = + override val allFlexOptions: Map[UUID, FlexOptions] = Map.empty, - override val completions: ReceiveDataMap[UUID, FlexCompletion] = + override val completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, - structure: Map[UUID, Set[UUID]] = Map.empty, - disaggregated: Map[UUID, Boolean] = Map.empty, - sendOptionsToExt: Boolean = false, - canHandleSetPoints: Boolean = false, - setPointOption: Option[ProvideEmSetPointData] = None, - nextActivation: Map[UUID, Long] = Map.empty, + structure: Map[UUID, Set[UUID]] = Map.empty, + disaggregated: Map[UUID, Boolean] = Map.empty, + sendOptionsToExt: Boolean = false, + canHandleSetPoints: Boolean = false, + setPointOption: Option[Map[UUID, EmSetPoint]] = None, + nextActivation: Map[UUID, Long] = Map.empty, ) extends EmServiceCore { override def handleRegistration( @@ -95,6 +100,7 @@ final case class EmServiceBaseCore( copy( uuidToAgent = uuidToAgent + (modelUuid -> ref), + agentToUuid = agentToUuid + (ref -> modelUuid), completions = completions.addExpectedKeys(Set(modelUuid)), structure = updatedStructure ++ Map(modelUuid -> Set.empty[UUID]), nextActivation = nextActivation.updated(modelUuid, 0), @@ -104,37 +110,8 @@ final case class EmServiceBaseCore( 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 { - log.warn( - s"We received the following data '$provideEmData'. The base service can currently not handle the provided flex options and set points." - ) - } + 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.") } val tick = provideEmData.tick val flexRequests = provideEmData.flexRequests.asScala.flatMap { @@ -146,47 +123,45 @@ final case class EmServiceBaseCore( } }.toMap - ( - copy( - flexOptions = ReceiveDataMap(flexRequests.keySet), - disaggregated = disaggregated ++ flexRequests, - sendOptionsToExt = true, - ), - None, + val updatedState = copy( + flexOptions = ReceiveDataMap(flexRequests.keySet), + disaggregated = disaggregated ++ flexRequests, + sendOptionsToExt = true, ) - case provideEmSetPoints: ProvideEmSetPointData => - if canHandleSetPoints then { - log.warn(s"Handling of set points.") + // handle set points + val setPoints = provideEmData.setPoints().asScala.toMap - handleSetPoint(tick, provideEmSetPoints, log) + if setPoints.nonEmpty then { - (this, None) - } else { - log.warn(s"Cannot handle set points.") - - 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 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), + setPointOption = Some(setPoints), + ), + None, + ) } - ( - copy( - flexOptions = ReceiveDataMap(emEntities.toSet), - setPointOption = Some(provideEmSetPoints), - ), - None, - ) - } + } else (updatedState, None) case _ => throw new CriticalFailureException( @@ -198,16 +173,20 @@ final case class EmServiceBaseCore( tick: Long, flexResponse: FlexResponse, receiver: Either[UUID, ActorRef[FlexResponse]], - )(using - startTime: ZonedDateTime, - log: Logger, - ): (EmServiceBaseCore, Option[EmDataResponseMessageToExt]) = { - receiver.foreach(_ ! flexResponse) + )(using log: Logger): (EmServiceBaseCore, Option[EmDataResponseMessageToExt]) = { + + 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 @@ -231,8 +210,12 @@ final case class EmServiceBaseCore( ) 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 { @@ -316,10 +299,7 @@ final case class EmServiceBaseCore( override def handleFlexRequest( flexRequest: FlexRequest, receiver: ActorRef[FlexRequest], - )(using - startTime: ZonedDateTime, - log: Logger, - ): (EmServiceBaseCore, Option[EmDataResponseMessageToExt]) = { + )(using log: Logger): (EmServiceBaseCore, Option[EmDataResponseMessageToExt]) = { log.debug(s"$receiver: $flexRequest") receiver ! flexRequest @@ -329,26 +309,27 @@ final case class EmServiceBaseCore( /** 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, 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 d801ecb56c..a2bfabff6c 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -9,7 +9,7 @@ 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, FlexOptions} +import edu.ie3.simona.api.data.model.em.{EmSetPoint, ExtendedFlexOptionsResult, FlexOptions} import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.ontology.messages.ServiceMessage.{EmFlexMessage, EmServiceRegistration, ServiceResponseMessage} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* @@ -140,20 +140,19 @@ 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, - log: Logger, + tick: Long, + setPoints: Map[UUID, EmSetPoint], + log: Logger, ): Unit = { - log.info(s"Handling of: $provideEmSetPoints") + log.info(s"Handling of set points: $setPoints") - provideEmSetPoints.emSetPoints.asScala - .foreach { case (agent, setPoint) => + setPoints.foreach { case (agent, setPoint) => uuidToAgent.get(agent) match { case Some(receiver) => val (pOption, qOption) = setPoint.power.toScala match { @@ -186,8 +185,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 @@ -198,18 +195,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 @@ -219,10 +211,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 @@ -231,7 +220,7 @@ trait EmServiceCore { * To derive the needed disaggregated data. */ final def addDisaggregatingFlexOptions( - flexOption: ExtendedFlexOptionsResult, + flexOption: FlexOptions, inferiorAgents: Set[UUID], ): Unit = { inferiorAgents.foreach { inferior => From 6c1081a052725b214b8c03e9328b855904cbbd80 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 4 Nov 2025 11:46:04 +0100 Subject: [PATCH 109/125] Saving changes. --- .../scala/edu/ie3/simona/service/em/EmCommunicationCore.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 15b1836e12..8d2083563d 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -461,7 +461,7 @@ case class EmCommunicationCore( .flatMap(allFlexOptions.get) .foreach { result => val model = result match { - case options: GeneralFlexOptions => + case options: em.EnergyBoundariesFlexOptions => options.model case options: em.PowerLimitFlexOptions => options.model From a216a098506b78b6e086e91c6ef4c40c497e12e7 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 4 Nov 2025 12:10:48 +0100 Subject: [PATCH 110/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 14 +++- .../simona/service/em/EmServiceBaseCore.scala | 73 +++++++++++------- .../ie3/simona/service/em/EmServiceCore.scala | 75 ++++++++++--------- .../primary/ExtPrimaryDataService.scala | 2 +- .../simona/sim/setup/ExtSimSetupData.scala | 4 +- 5 files changed, 101 insertions(+), 67 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 8d2083563d..85a740a77d 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -15,7 +15,11 @@ 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.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} +import edu.ie3.simona.ontology.messages.flex.{ + FlexType, + FlexibilityMessage, + PowerLimitFlexOptions, +} import edu.ie3.simona.service.em.EmCommunicationCore.EmAgentState import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} @@ -185,7 +189,6 @@ case class EmCommunicationCore( ) } - case provideEmData: ProvideEmData => log.warn(s"Handling ext message: $provideEmData") val extTick = provideEmData.tick @@ -220,10 +223,13 @@ case class EmCommunicationCore( } else None }.toMap - val updatedDisaggregated = disaggregated ++ flexRequest.map { case (uuid, request) => uuid -> request.disaggregated }.toMap + val updatedDisaggregated = disaggregated ++ flexRequest.map { + case (uuid, request) => uuid -> request.disaggregated + }.toMap // handling of set points - val setPointMapping = provideEmData.setPoints() + val setPointMapping = provideEmData + .setPoints() .asScala .flatMap { case (receiver, setPoint) => val agent = uuidToAgent(receiver) 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 f5ae120761..b4cb91977b 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -8,7 +8,11 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.model.em -import edu.ie3.simona.api.data.model.em.{EmSetPoint, ExtendedFlexOptionsResult, FlexOptions} +import edu.ie3.simona.api.data.model.em.{ + EmSetPoint, + ExtendedFlexOptionsResult, + FlexOptions, +} import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration @@ -24,15 +28,20 @@ import org.slf4j.Logger import java.time.ZonedDateTime import java.util.UUID -import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala, SetHasAsScala} +import scala.jdk.CollectionConverters.{ + ListHasAsScala, + MapHasAsJava, + MapHasAsScala, + SetHasAsScala, +} /** Basic service core for an [[ExtEmDataService]]. * @param lastFinishedTick * The last tick that was completed. * @param uuidToAgent * Map: uuid to em agent reference. - * @param agentToUuid - * Map: em agent reference to uuid. + * @param agentToUuid + * Map: em agent reference to uuid. * @param flexOptions * ReceiveDataMap: uuid to flex option result. * @param allFlexOptions @@ -55,21 +64,20 @@ import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsSca * 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 uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, - agentToUuid: Map[ActorRef[EmAgent.Message] | ActorRef[FlexResponse], UUID] = Map.empty, - flexOptions: ReceiveDataMap[UUID, FlexOptions] = - ReceiveDataMap.empty, - override val allFlexOptions: Map[UUID, FlexOptions] = + override val lastFinishedTick: Long = PRE_INIT_TICK, + override val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, + agentToUuid: Map[ActorRef[EmAgent.Message] | ActorRef[FlexResponse], UUID] = Map.empty, - override val completions: ReceiveDataMap[UUID, FlexCompletion] = + flexOptions: ReceiveDataMap[UUID, FlexOptions] = ReceiveDataMap.empty, + override val allFlexOptions: Map[UUID, FlexOptions] = Map.empty, + override val completions: ReceiveDataMap[UUID, FlexCompletion] = ReceiveDataMap.empty, - structure: Map[UUID, Set[UUID]] = Map.empty, - disaggregated: Map[UUID, Boolean] = Map.empty, - sendOptionsToExt: Boolean = false, - canHandleSetPoints: Boolean = false, - setPointOption: Option[Map[UUID, EmSetPoint]] = None, - nextActivation: Map[UUID, Long] = Map.empty, + structure: Map[UUID, Set[UUID]] = Map.empty, + disaggregated: Map[UUID, Boolean] = Map.empty, + sendOptionsToExt: Boolean = false, + canHandleSetPoints: Boolean = false, + setPointOption: Option[Map[UUID, EmSetPoint]] = None, + nextActivation: Map[UUID, Long] = Map.empty, ) extends EmServiceCore { override def handleRegistration( @@ -111,7 +119,11 @@ final case class EmServiceBaseCore( log: Logger ): (EmServiceBaseCore, Option[EmDataResponseMessageToExt]) = extMsg match { case provideEmData: ProvideEmData => - 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.") } + 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." + ) + } val tick = provideEmData.tick val flexRequests = provideEmData.flexRequests.asScala.flatMap { @@ -173,7 +185,9 @@ final case class EmServiceBaseCore( tick: Long, flexResponse: FlexResponse, receiver: Either[UUID, ActorRef[FlexResponse]], - )(using log: Logger): (EmServiceBaseCore, Option[EmDataResponseMessageToExt]) = { + )(using + log: Logger + ): (EmServiceBaseCore, Option[EmDataResponseMessageToExt]) = { val receiverUuid = receiver match { case Right(ref) => @@ -256,14 +270,17 @@ final case class EmServiceBaseCore( if finished then { // the next activations - val updatedNextActivation = nextActivation ++ updated.receivedData.flatMap { - case (uuid, msg) => - msg.requestAtTick.map(uuid -> _) - } + 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 + val keys = updatedNextActivation.filter { + case (_, activation) => activation == t + }.keySet log.warn(s"Keys: $keys") ReceiveDataMap[UUID, FlexCompletion](keys) case None => @@ -299,7 +316,9 @@ final case class EmServiceBaseCore( override def handleFlexRequest( flexRequest: FlexRequest, receiver: ActorRef[FlexRequest], - )(using log: Logger): (EmServiceBaseCore, Option[EmDataResponseMessageToExt]) = { + )(using + log: Logger + ): (EmServiceBaseCore, Option[EmDataResponseMessageToExt]) = { log.debug(s"$receiver: $flexRequest") receiver ! flexRequest @@ -309,8 +328,8 @@ final case class EmServiceBaseCore( /** Method to handle flex options. * @param tick * Current tick of the service. - * @param receiver - * The receiver of the flex options. + * @param receiver + * The receiver of the flex options. * @param provideFlexOptions * The provided flex options. * @return 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 a2bfabff6c..0516a7b708 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -9,9 +9,17 @@ 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.{EmSetPoint, ExtendedFlexOptionsResult, FlexOptions} +import edu.ie3.simona.api.data.model.em.{ + EmSetPoint, + ExtendedFlexOptionsResult, + FlexOptions, +} import edu.ie3.simona.api.ontology.em.* -import edu.ie3.simona.ontology.messages.ServiceMessage.{EmFlexMessage, EmServiceRegistration, ServiceResponseMessage} +import edu.ie3.simona.ontology.messages.ServiceMessage.{ + EmFlexMessage, + EmServiceRegistration, + ServiceResponseMessage, +} import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK @@ -31,6 +39,7 @@ import scala.jdk.OptionConverters.{RichOption, RichOptional} /** Trait for all em service cores. */ trait EmServiceCore { + /** The last tick that was completed. */ val lastFinishedTick: Long @@ -146,36 +155,36 @@ trait EmServiceCore { * Logger for logging messages. */ final def handleSetPoint( - tick: Long, - setPoints: Map[UUID, EmSetPoint], - log: Logger, + tick: Long, + setPoints: Map[UUID, EmSetPoint], + log: Logger, ): Unit = { log.info(s"Handling of set points: $setPoints") 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!") - } + 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. @@ -252,10 +261,10 @@ trait EmServiceCore { val (extMsgOption, nextTickOption) = if tick != INIT_SIM_TICK then { // send completion message to external simulation, if we aren't in the INIT_SIM_TICK val option = getMaybeNextTick - + (Some(new EmCompletion(option.map(long2Long).toJava)), option) } else (None, None) - + // every em agent has sent a completion message (updated, extMsgOption, nextTickOption, true) @@ -267,9 +276,7 @@ trait EmServiceCore { * An option for the next activation tick. */ protected final def getMaybeNextTick: Option[Long] = - completions.receivedData - .flatMap { case (_, completion) => - completion.requestAtTick - } - .minOption + completions.receivedData.flatMap { case (_, completion) => + completion.requestAtTick + }.minOption } diff --git a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala index 6771615c6c..b3fa5a2c6a 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala @@ -147,7 +147,7 @@ object ExtPrimaryDataService extends SimonaService with ExtDataSupport { * the current state data of this service * @return * the service stata data that should be used in the next state (normally - * with updated values) together with the completion message that is send + * with updated values) together with the completion message that is sent * in response to the trigger that was sent to start this announcement */ override protected def announceInformation( diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index ff14358e2b..ab7a710bf5 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -92,7 +92,9 @@ final case class ExtSimSetupData( Seq( emDataService, evDataService, - ).flatten ++ resultListeners ++ resultProviders ++ primaryDataServices.map(_._2) + ).flatten ++ resultListeners ++ resultProviders ++ primaryDataServices.map( + _._2 + ) } object ExtSimSetupData { From bb20d92c0e7cd4d2d007355f84449d9069a12233 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 11 Nov 2025 09:25:58 +0100 Subject: [PATCH 111/125] Saving changes. --- .../participant/ParticipantGridAdapter.scala | 4 +- .../service/em/EmCommunicationCore.scala | 398 +++++++++--------- .../simona/service/em/EmServiceBaseCore.scala | 184 ++++---- .../ie3/simona/service/em/EmServiceCore.scala | 36 +- .../simona/service/em/ExtEmDataService.scala | 94 +++-- .../ie3/simona/service/em/InternalCore.scala | 174 ++++++++ .../util/scala/quantities/QuantityUtil.scala | 10 +- 7 files changed, 540 insertions(+), 360 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/service/em/InternalCore.scala diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantGridAdapter.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantGridAdapter.scala index 587aada4d2..db47d17029 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantGridAdapter.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantGridAdapter.scala @@ -17,7 +17,7 @@ import edu.ie3.util.scala.quantities.{Megavars, QuantityUtil, ReactivePower} import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger import squants.energy.Megawatts -import squants.{Dimensionless, Each, Energy, Power} +import squants.{Dimensionless, Each, Energy, Power, UnitOfMeasure} import scala.collection.immutable.SortedMap import scala.util.{Failure, Success} @@ -259,6 +259,8 @@ object ParticipantGridAdapter { ], log: Logger, ): ComplexPower = { + given UnitOfMeasure[Power] = Megawatts + val p = QuantityUtil.average[Power, Energy]( tickToPower.map { case (tick, pd) => tick -> pd.p diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 85a740a77d..3f5ec0672a 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -9,20 +9,16 @@ package edu.ie3.simona.service.em import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.models.value.PValue import edu.ie3.simona.agent.em.EmAgent.Message +import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode import edu.ie3.simona.api.data.model.em import edu.ie3.simona.api.data.model.em.* 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.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.flex.{ - FlexType, - FlexibilityMessage, - PowerLimitFlexOptions, -} +import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} import edu.ie3.simona.service.em.EmCommunicationCore.EmAgentState import edu.ie3.simona.util.CollectionUtils.asJava -import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} +import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK import edu.ie3.simona.util.{ReceiveDataMap, ReceiveMultiDataMap} import edu.ie3.util.scala.quantities.QuantityConversionUtils.* import org.apache.pekko.actor.typed.ActorRef @@ -38,11 +34,29 @@ import scala.util.Try object EmCommunicationCore { + def apply(core: EmServiceCore): EmCommunicationCore = { + val uuidToAgent = core.uuidToAgent + + EmCommunicationCore( + core.mode, + core.lastFinishedTick, + uuidToAgent, + core.agentToUuid, + core.uuidToInferior, + core.uuidToParent, + core.completions, + core.nextActivation, + core.allFlexOptions, + uuidToAgent.keys.map(uuid => uuid -> EmAgentState()).toMap, + ) + } + final case class EmAgentState( private var receivedActivation: Boolean = false, private val awaitedFlexOptions: mutable.Set[UUID] = mutable.Set.empty, private var awaitedSetPoint: Boolean = false, private var waitingForInternal: Boolean = false, + private var waitingForRelease: Boolean = false, ) { def setReceivedRequest(): Unit = { receivedActivation = true @@ -50,6 +64,16 @@ object EmCommunicationCore { awaitedSetPoint = true } + def setWaitingForRelease(): Unit = { + waitingForRelease = true + } + + def setReceivedRelease(): Unit = { + receivedActivation = true + waitingForInternal = false + waitingForRelease = false + } + def addSendRequest(request: UUID): Unit = { awaitedFlexOptions.add(request) } @@ -94,6 +118,8 @@ object EmCommunicationCore { def isWaitingForSetPoint: Boolean = awaitedSetPoint + def isWaitingForRelease: Boolean = waitingForRelease + def isWaitingForInternal: Boolean = waitingForInternal def isActivated: Boolean = receivedActivation @@ -108,59 +134,28 @@ object EmCommunicationCore { } case class EmCommunicationCore( - disaggregated: Map[UUID, Boolean] = Map.empty, + override val mode: EmMode, override val lastFinishedTick: Long = PRE_INIT_TICK, override val uuidToAgent: Map[UUID, ActorRef[Message]] = Map.empty, - refToUuid: Map[ActorRef[FlexResponse] | ActorRef[FlexRequest], UUID] = - Map.empty, - uuidToInferior: Map[UUID, Seq[UUID]] = Map.empty, - uuidToParent: Map[UUID, UUID] = Map.empty, + override val agentToUuid: Map[ + ActorRef[FlexRequest] | ActorRef[FlexResponse], + UUID, + ] = Map.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, + override val nextActivation: Map[UUID, Long] = Map.empty, + override val allFlexOptions: Map[UUID, FlexOptions] = Map.empty, + emStates: Map[UUID, EmAgentState] = Map.empty, + disaggregated: Map[UUID, Boolean] = Map.empty, requestedFlexType: Map[UUID, FlexType] = Map.empty, - allFlexOptions: Map[UUID, FlexOptions] = Map.empty, currentSetPoint: Map[UUID, Power] = Map.empty, activatedAgents: Set[UUID] = Set.empty, - emStates: Map[UUID, EmAgentState] = Map.empty, expectDataFrom: ReceiveMultiDataMap[UUID, EmData] = ReceiveMultiDataMap.empty, - nextActivation: Map[UUID, Long] = Map.empty, ) extends EmServiceCore { - override def handleRegistration( - emServiceRegistration: EmServiceRegistration - ): EmServiceCore = { - val uuid = emServiceRegistration.inputUuid - val ref = emServiceRegistration.requestingActor - - val (updatedInferior, updatedUuidToParent) = - emServiceRegistration.parentUuid match { - case Some(parent) => - val inferior = uuidToInferior.get(parent) match { - case Some(inferiorUuids) => - inferiorUuids.appended(uuid) - case None => - Seq(uuid) - } - - ( - uuidToInferior.updated(parent, inferior), - uuidToParent.updated(uuid, parent), - ) - case None => - (uuidToInferior, uuidToParent) - } - - copy( - uuidToAgent = uuidToAgent.updated(uuid, ref), - refToUuid = refToUuid.updated(ref, uuid), - uuidToInferior = updatedInferior, - uuidToParent = updatedUuidToParent, - emStates = emStates.updated(uuid, EmAgentState()), - nextActivation = nextActivation.updated(uuid, 0), - ) - } - override def handleExtMessage(tick: Long, extMsg: EmDataMessageFromExt)(using log: Logger ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMsg match { @@ -194,7 +189,23 @@ case class EmCommunicationCore( val extTick = provideEmData.tick // handling of requests - val flexRequest = provideEmData.flexRequests.asScala + val (flexRelease, flexRequest) = + provideEmData.flexRequests.asScala.partition { case (_, request) => + request.releaseControl() + } + + flexRelease.keys.foreach { uuid => + log.warn(s"Release control for: $uuid") + + val inferior = uuidToInferior(uuid) + + uuidToAgent.get(uuid).foreach { agent => + // update the em states of the inferior + inferior.flatMap(emStates.get).foreach(_.setWaitingForRelease()) + + agent ! IssueNoControl(tick) + } + } val requestMapping = flexRequest.keys.flatMap { uuid => if emStates(uuid).isWaitingForActivation then { @@ -208,6 +219,8 @@ case class EmCommunicationCore( requestedFlexType.getOrElse(uuid, FlexType.PowerLimit), ) + log.warn(s"Inferior: ${uuidToInferior.get(uuid)}") + val count = max( Try { uuidToInferior(uuid).count { id => @@ -224,7 +237,7 @@ case class EmCommunicationCore( }.toMap val updatedDisaggregated = disaggregated ++ flexRequest.map { - case (uuid, request) => uuid -> request.disaggregated + case (uuid, request) => uuid -> request.disaggregated.booleanValue }.toMap // handling of set points @@ -422,155 +435,131 @@ case class EmCommunicationCore( case Left(value) => value case Right(ref) => - refToUuid(ref) + agentToUuid(ref) } flexResponse match { - case scheduleFlexActivation @ ScheduleFlexActivation( - modelUuid, - _, - scheduleKey, - ) => - if tick == INIT_SIM_TICK then { - scheduleKey.foreach(_.unlock()) - - uuidToAgent(receiverUuid) ! FlexActivation( - INIT_SIM_TICK, - FlexType.PowerLimit, - ) - - } else { - log.warn(s"$scheduleFlexActivation not handled!") - } - + case scheduleFlexActivation@ScheduleFlexActivation( + modelUuid, + _, + scheduleKey, + ) => + log.warn(s"$scheduleFlexActivation not handled!") (this, None) - case provideFlexOptions @ ProvideFlexOptions(sender, flexOptions) => - if tick == INIT_SIM_TICK then { - uuidToAgent(receiverUuid) ! provideFlexOptions - - (this, None) - } else { - // flex option to ext - val (resultToExt, pRef) = flexOptions match { - case PowerLimitFlexOptions(ref, min, max) => - val flexOptionResult = new em.PowerLimitFlexOptions( - receiverUuid, - sender, - ref.toQuantity, - min.toQuantity, - max.toQuantity, - ) - - if disaggregated.contains(receiverUuid) then { - uuidToInferior(receiverUuid) - .flatMap(allFlexOptions.get) - .foreach { result => - val model = result match { - case options: em.EnergyBoundariesFlexOptions => - options.model - case options: em.PowerLimitFlexOptions => - options.model - } + case ProvideFlexOptions(sender, flexOptions) => + // flex option to ext + val (resultToExt, pRef) = flexOptions match { + case PowerLimitFlexOptions(ref, min, max) => + val flexOptionResult = new em.PowerLimitFlexOptions( + receiverUuid, + sender, + ref.toQuantity, + min.toQuantity, + max.toQuantity, + ) - flexOptionResult.addDisaggregated(model, result) + if disaggregated.contains(receiverUuid) then { + uuidToInferior(receiverUuid) + .flatMap(allFlexOptions.get) + .foreach { result => + val model = result match { + case options: em.EnergyBoundariesFlexOptions => + options.model + case options: em.PowerLimitFlexOptions => + options.model } - } - (flexOptionResult, ref) + flexOptionResult.addDisaggregated(model, result) + } + } - case other => - throw CriticalFailureException( - s"Flex option type '$other' is currently not supported!" - ) - } + (flexOptionResult, ref) - // wrap the result, if sender and receiver are not the same, since we want to use ext communication - val msg = if receiverUuid != sender then { - new EmCommunicationMessage(receiverUuid, sender, resultToExt) - } else resultToExt + case other => + throw CriticalFailureException( + s"Flex option type '$other' is currently not supported!" + ) + } - val updated = expectDataFrom.addData(sender, msg) + // wrap the result, if sender and receiver are not the same, since we want to use ext communication + val msg = if receiverUuid != sender then { + new EmCommunicationMessage(receiverUuid, sender, resultToExt) + } else resultToExt - if updated.isComplete || updated.hasCompleted then { - val (data, updatedExpectDataFrom) = updated.getFinished + val updated = expectDataFrom.addData(sender, msg) - // should no longer wait for internal data - data.keys.foreach(emStates(_).setWaitingForInternal(false)) - log.warn(s"Updated EmStates: $emStates") + if updated.isComplete || updated.hasCompleted then { + val (data, updatedExpectDataFrom) = updated.getFinished - ( - copy( - allFlexOptions = allFlexOptions.updated(sender, resultToExt), - currentSetPoint = currentSetPoint.updated(sender, pRef), - expectDataFrom = updatedExpectDataFrom, - ), - Some(new EmResultResponse(data.asJava)), - ) - } else { - ( - copy( - allFlexOptions = allFlexOptions.updated(sender, resultToExt), - currentSetPoint = currentSetPoint.updated(sender, pRef), - expectDataFrom = updated, - ), - None, - ) - } + // should no longer wait for internal data + data.keys.foreach(emStates(_).setWaitingForInternal(false)) + log.warn(s"Updated EmStates: $emStates") + + ( + copy( + allFlexOptions = allFlexOptions.updated(sender, resultToExt), + currentSetPoint = currentSetPoint.updated(sender, pRef), + expectDataFrom = updatedExpectDataFrom, + ), + Some(new EmResultResponse(data.asJava)), + ) + } else { + ( + copy( + allFlexOptions = allFlexOptions.updated(sender, resultToExt), + currentSetPoint = currentSetPoint.updated(sender, pRef), + expectDataFrom = updated, + ), + None, + ) } - case completion @ FlexCompletion( - sender, - requestAtNextActivation, - requestAtTick, - ) => + + case completion@FlexCompletion( + sender, + requestAtNextActivation, + requestAtTick, + ) => // the completion can be sent directly to the receiver, since it's not used by the external communication uuidToAgent(receiverUuid) ! completion emStates(sender).setWaitingForInternal(false) - if tick == INIT_SIM_TICK then { - receiver match { - case Left(value) => - (copy(lastFinishedTick = tick), None) - case Right(_) => - (this, None) - } - } else { - val updatedData = completions.addData(sender, completion) + val updatedData = completions.addData(sender, completion) - if updatedData.isComplete then { - emStates.foreach(_._2.clear()) - log.warn(s"Cleared EmStates: $emStates") + if updatedData.isComplete then { + emStates.foreach(_._2.clear()) + log.warn(s"Cleared EmStates: $emStates") - // the next activations - val additionalActivation = updatedData.receivedData.flatMap { - case (uuid, msg) => - msg.requestAtTick.map(uuid -> _) - } + // the next activations + val additionalActivation = updatedData.receivedData.flatMap { + case (uuid, msg) => + msg.requestAtTick.map(uuid -> _) + } - ( - copy( - lastFinishedTick = tick, - completions = ReceiveDataMap.empty, - requestedFlexType = Map.empty, - allFlexOptions = Map.empty, - currentSetPoint = Map.empty, - activatedAgents = Set.empty, - expectDataFrom = ReceiveMultiDataMap.empty, - nextActivation = nextActivation ++ additionalActivation, - ), - Some(new EmCompletion(getMaybeNextTick.map(long2Long).toJava)), - ) - } else { - val msgToExt = getMsgToExtOption - log.warn(s"Not finished! Expected: ${updatedData.getExpectedKeys}") - log.warn(s"EmStates: $emStates") - log.warn(s"Message to ext: $msgToExt") + ( + copy( + lastFinishedTick = tick, + completions = ReceiveDataMap.empty, + requestedFlexType = Map.empty, + allFlexOptions = Map.empty, + currentSetPoint = Map.empty, + activatedAgents = Set.empty, + expectDataFrom = ReceiveMultiDataMap.empty, + nextActivation = nextActivation ++ additionalActivation, + ), + Some(new EmCompletion(getMaybeNextTick.map(long2Long).toJava)), + ) + } else { + val msgToExt = getMsgToExtOption + log.warn(s"Not finished! Expected: ${updatedData.getExpectedKeys}") + log.warn(s"EmStates: $emStates") + log.warn(s"Message to ext: $msgToExt") - (copy(completions = updatedData), msgToExt) - } + (copy(completions = updatedData), msgToExt) } + // not supported case other => log.warn(s"Flex response $other is not supported!") @@ -583,7 +572,7 @@ case class EmCommunicationCore( flexRequest: FlexRequest, receiver: ActorRef[FlexRequest], )(using log: Logger): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { - val receiverUuid = refToUuid(receiver) // the controlled em + val receiverUuid = agentToUuid(receiver) // the controlled em val sender = uuidToParent(receiverUuid) // the controlling em val updated = flexRequest match { @@ -605,35 +594,48 @@ case class EmCommunicationCore( ) case control: IssueFlexControl => - emStates(receiverUuid).setWaitingForInternal(false) + val state = emStates(receiverUuid) - // send set point to ext - log.warn( - s"Receiver $receiverUuid got flex control message from $sender" - ) + if state.isWaitingForRelease then { + // we are waiting for release, therefore, we are not sending data to ext - val power = control match { - case IssueNoControl(tick) => - log.warn(s"Set points: $currentSetPoint") - new PValue(currentSetPoint(receiverUuid).toQuantity) + state.setReceivedRelease() + receiver ! control - case IssuePowerControl(tick, setPower) => - new PValue(setPower.toQuantity) + // since we don't expect data, we simply return this store + expectDataFrom - case other => - throw new CriticalFailureException( - s"Flex control $other is not supported!" - ) - } + } else { + state.setWaitingForInternal(false) - expectDataFrom.addData( - sender, - new EmCommunicationMessage( - receiverUuid, + // send set point to ext + log.warn( + s"Receiver $receiverUuid got flex control message from $sender" + ) + + val power = control match { + case IssueNoControl(tick) => + log.warn(s"Set points: $currentSetPoint") + new PValue(currentSetPoint(receiverUuid).toQuantity) + + case IssuePowerControl(tick, setPower) => + new PValue(setPower.toQuantity) + + case other => + throw new CriticalFailureException( + s"Flex control $other is not supported!" + ) + } + + expectDataFrom.addData( sender, - new EmSetPoint(receiverUuid, power), - ), - ) + new EmCommunicationMessage( + receiverUuid, + sender, + new EmSetPoint(receiverUuid, power), + ), + ) + } case other => log.warn(s"$other is not supported!") 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 b4cb91977b..775936cc2b 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -7,53 +7,48 @@ package edu.ie3.simona.service.em import edu.ie3.simona.agent.em.EmAgent +import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode import edu.ie3.simona.api.data.model.em -import edu.ie3.simona.api.data.model.em.{ - EmSetPoint, - ExtendedFlexOptionsResult, - FlexOptions, -} +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.{INIT_SIM_TICK, PRE_INIT_TICK} -import edu.ie3.simona.util.TickUtil.TickLong +import edu.ie3.simona.util.SimonaConstants.PRE_INIT_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 mode + * The em mode of the data connection. * @param lastFinishedTick * The last tick that was completed. * @param uuidToAgent * Map: uuid to em agent reference. * @param agentToUuid * Map: em agent reference to uuid. - * @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 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 @@ -64,57 +59,26 @@ import scala.jdk.CollectionConverters.{ * Option for em set points that needs to be handled at a later time. */ final case class EmServiceBaseCore( + override val mode: EmMode, override val lastFinishedTick: Long = PRE_INIT_TICK, override val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, - agentToUuid: Map[ActorRef[EmAgent.Message] | ActorRef[FlexResponse], UUID] = - Map.empty, - flexOptions: ReceiveDataMap[UUID, FlexOptions] = ReceiveDataMap.empty, - override val allFlexOptions: Map[UUID, FlexOptions] = Map.empty, + override val agentToUuid: Map[ + ActorRef[FlexRequest] | ActorRef[FlexResponse], + UUID, + ] = Map.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[Map[UUID, EmSetPoint]] = None, - nextActivation: Map[UUID, Long] = Map.empty, ) extends EmServiceCore { - override def handleRegistration( - emServiceRegistration: EmServiceRegistration - ): EmServiceBaseCore = { - 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 None if !structure.contains(modelUuid) => - structure ++ Map(modelUuid -> Set.empty[UUID]) - - 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 - } - - copy( - uuidToAgent = uuidToAgent + (modelUuid -> ref), - agentToUuid = agentToUuid + (ref -> modelUuid), - completions = completions.addExpectedKeys(Set(modelUuid)), - structure = updatedStructure ++ Map(modelUuid -> Set.empty[UUID]), - nextActivation = nextActivation.updated(modelUuid, 0), - ) - } - override def handleExtMessage(tick: Long, extMsg: EmDataMessageFromExt)(using log: Logger ): (EmServiceBaseCore, Option[EmDataResponseMessageToExt]) = extMsg match { @@ -212,7 +176,7 @@ final case class EmServiceBaseCore( // we add the disaggregated flex options addDisaggregatingFlexOptions( flexOption, - structure.getOrElse(uuid, Set.empty), + uuidToInferior.getOrElse(uuid, Set.empty), ) } } @@ -257,55 +221,45 @@ final case class EmServiceBaseCore( } case completion: FlexCompletion => - if tick == INIT_SIM_TICK then { - receiver match { - case Left(value) => - (copy(lastFinishedTick = tick), None) - case Right(_) => - (this, None) - } - } else { - 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 - log.warn(s"Keys: $keys") - ReceiveDataMap[UUID, FlexCompletion](keys) - case None => - updated + 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 -> _) } - log.warn(s"$updated") + val expectedCompletions = nextTick match { + case Some(t) => + val keys = updatedNextActivation.filter { case (_, activation) => + activation == t + }.keySet + log.warn(s"Keys: $keys") + ReceiveDataMap[UUID, FlexCompletion](keys) + case None => + updated + } - ( - copy( - lastFinishedTick = tick, - completions = expectedCompletions, - disaggregated = Map.empty, - sendOptionsToExt = false, - canHandleSetPoints = false, - nextActivation = updatedNextActivation, - ), - extMsgOption, - ) + log.warn(s"$updated") - } else { - log.warn(s"$updated") + ( + copy( + lastFinishedTick = tick, + completions = expectedCompletions, + disaggregated = Map.empty, + sendOptionsToExt = false, + canHandleSetPoints = false, + nextActivation = updatedNextActivation, + ), + extMsgOption, + ) - (copy(completions = updated), extMsgOption) - } + } else { + log.warn(s"$updated") + + (copy(completions = updated), extMsgOption) } case _ => @@ -379,5 +333,15 @@ final case class EmServiceBaseCore( object EmServiceBaseCore { - def empty: EmServiceBaseCore = EmServiceBaseCore() + def apply(core: EmServiceCore): EmServiceBaseCore = EmServiceBaseCore( + core.mode, + core.lastFinishedTick, + core.uuidToAgent, + core.agentToUuid, + 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 0516a7b708..bb14b8d122 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -6,14 +6,10 @@ 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.{ - EmSetPoint, - ExtendedFlexOptionsResult, - FlexOptions, -} +import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode +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, @@ -33,13 +29,14 @@ import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime import java.util.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. */ trait EmServiceCore { + val mode: EmMode + /** The last tick that was completed. */ val lastFinishedTick: Long @@ -48,6 +45,12 @@ trait EmServiceCore { */ val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] + val agentToUuid: Map[ActorRef[FlexRequest] | ActorRef[FlexResponse], UUID] + + val uuidToInferior: Map[UUID, Set[UUID]] + + val uuidToParent: Map[UUID, UUID] + /** Map: uuid to flex option result. */ val allFlexOptions: Map[UUID, FlexOptions] @@ -56,21 +59,20 @@ trait EmServiceCore { */ 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 } - /** Method to handle a registration message. - * @param emServiceRegistration - * The registration to handle. - * @return - * An updated service core. - */ - def handleRegistration( - emServiceRegistration: EmServiceRegistration - ): EmServiceCore + def toInternal: InternalCore = InternalCore(this) + + def toExternal: EmServiceCore = mode match { + case EmMode.BASE => EmServiceBaseCore(this) + case EmMode.EM_COMMUNICATION => EmCommunicationCore(this) + } /** Method to handle the received message from the external simulation. * @param tick @@ -275,7 +277,7 @@ trait EmServiceCore { * @return * An option for the next activation tick. */ - protected final def getMaybeNextTick: Option[Long] = + 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 86805fa51d..8e260a39fa 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -22,7 +22,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} @@ -95,6 +98,7 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { startTime: ZonedDateTime, serviceCore: EmServiceCore, tick: Long = INIT_SIM_TICK, + simulateUntil: Long = FIRST_TICK_IN_SIMULATION, extEmDataMessage: Option[EmDataMessageFromExt] = None, ) extends ServiceBaseStateData @@ -134,12 +138,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 - case EmMode.EM_COMMUNICATION => - EmCommunicationCore() - } + val serviceCore = InternalCore(extEmDataConnection.mode) val emDataInitializedStateData = ExtEmDataStateData(extEmDataConnection, startTime, serviceCore) @@ -165,8 +164,8 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { ): Try[ExtEmDataStateData] = registrationMessage match { case emServiceRegistration: EmServiceRegistration => - val updatedCore = - serviceStateData.serviceCore.handleRegistration(emServiceRegistration) + val updatedCore = serviceStateData.serviceCore.toInternal + .handleRegistration(emServiceRegistration) if emServiceRegistration.parentEm.isEmpty then { emServiceRegistration.requestingActor ! FlexActivation( @@ -210,27 +209,68 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { (updatedStateData, Some(tick)) } else { - val extMsg = serviceStateData.extEmDataMessage.getOrElse( - throw ServiceException( - "ExtEmDataService was triggered without ExtEmDataMessage available" - ) - ) + log.warn(s"Tick ($tick): ServiceCore -> ${serviceStateData.serviceCore.getClass}, msg -> ${serviceStateData.extEmDataMessage}") + + val ((updatedCore, msgToExt), until) = ( + serviceStateData.extEmDataMessage, + serviceStateData.serviceCore, + ) match { + case (Some(simulationUntil: EmSimulationUntil), core) => + ((core.toInternal, None), Some(simulationUntil.tick)) + + case (Some(extMsg), core: EmCommunicationCore) => + ( + core.handleExtMessage(tick, extMsg)(using ctx.log), + None, + ) - val (updatedCore, msgToExt) = - serviceStateData.serviceCore.handleExtMessage(tick, extMsg)(using - ctx.log - ) + case (Some(extMsg), core: EmServiceBaseCore) => + ( + core.handleExtMessage(tick, extMsg)(using ctx.log), + None, + ) - msgToExt.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) + case (_, core: InternalCore) if serviceStateData.simulateUntil > tick => + log.warn(s"Received external message with internal core!") - ( - serviceStateData.copy( - tick = tick, - serviceCore = updatedCore, - extEmDataMessage = None, - ), - None, - ) + ((core, None), Some(serviceStateData.simulateUntil)) + + case (Some(extMsg), core) => + ( + core.toExternal.handleExtMessage(tick, extMsg)(using ctx.log), + None, + ) + + case (None, _) => + throw ServiceException( + "ExtEmDataService was triggered without ExtEmDataMessage available" + ) + } + + until match { + case Some(lastInternalTick) => + ( + serviceStateData.copy( + tick = tick, + serviceCore = updatedCore, + simulateUntil = lastInternalTick, + extEmDataMessage = None, + ), + updatedCore.getMaybeNextTick, + ) + + case None => + msgToExt.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) + + ( + serviceStateData.copy( + tick = tick, + serviceCore = updatedCore, + extEmDataMessage = None, + ), + None, + ) + } } } diff --git a/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala b/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala new file mode 100644 index 0000000000..b48c8f9f6e --- /dev/null +++ b/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala @@ -0,0 +1,174 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service.em + +import edu.ie3.simona.agent.em.EmAgent +import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode +import edu.ie3.simona.api.data.model.em.{EmSetPoint, FlexOptions} +import edu.ie3.simona.api.ontology.em.{ + EmDataMessageFromExt, + EmDataResponseMessageToExt, +} +import edu.ie3.simona.ontology.messages.ServiceMessage.EmServiceRegistration +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ + FlexCompletion, + FlexRequest, + FlexResponse, + IssueNoControl, +} +import edu.ie3.simona.util.ReceiveDataMap +import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK +import org.apache.pekko.actor.typed.ActorRef +import org.slf4j.Logger + +import java.util.UUID + +case class InternalCore( + override val mode: EmMode, + override val lastFinishedTick: Long = PRE_INIT_TICK, + override val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, + override val agentToUuid: Map[ + ActorRef[FlexRequest] | ActorRef[FlexResponse], + UUID, + ] = Map.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, + override val nextActivation: Map[UUID, Long] = Map.empty, + override val allFlexOptions: Map[UUID, FlexOptions] = Map.empty, + disaggregated: Map[UUID, Boolean] = Map.empty, + flexOptions: ReceiveDataMap[UUID, FlexOptions] = ReceiveDataMap.empty, + sendOptionsToExt: Boolean = false, + canHandleSetPoints: Boolean = false, + setPointOption: Option[Map[UUID, EmSetPoint]] = None, +) extends EmServiceCore { + + /** Method to handle a registration message. + * + * @param emServiceRegistration + * The registration to handle. + * @return + * An updated service core. + */ + def handleRegistration( + emServiceRegistration: EmServiceRegistration + ): EmServiceCore = { + val uuid = emServiceRegistration.inputUuid + val ref = emServiceRegistration.requestingActor + + val (updatedInferior, updatedUuidToParent) = + emServiceRegistration.parentUuid match { + case Some(parent) => + val inferior = uuidToInferior.get(parent) match { + case Some(inferiorUuids) => + inferiorUuids ++ Seq(uuid) + case None => + Set(uuid) + } + + ( + uuidToInferior.updated(parent, inferior), + uuidToParent.updated(uuid, parent), + ) + case None => + (uuidToInferior, uuidToParent) + } + + copy( + uuidToAgent = uuidToAgent.updated(uuid, ref), + agentToUuid = agentToUuid.updated(ref, uuid), + uuidToInferior = updatedInferior, + uuidToParent = updatedUuidToParent, + nextActivation = nextActivation.updated(uuid, 0), + ) + } + + override def handleExtMessage(tick: Long, extMsg: EmDataMessageFromExt)(using + log: Logger + ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { + log.warn("Handling of external message not possible!") + + (this, None) + } + + override def handleFlexResponse( + tick: Long, + flexResponse: FlexResponse, + receiver: Either[UUID, ActorRef[FlexResponse]], + )(using log: Logger): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { + flexResponse match { + case FlexibilityMessage.ProvideFlexOptions(modelUuid, flexOptions) => + receiver match { + case Left(uuid) => uuidToAgent(uuid) ! IssueNoControl(tick) + case Right(ref) => ref ! flexResponse + } + + (this, None) + + case FlexCompletion(modelUuid, requestAtNextActivation, requestAtTick) => + (receiver, requestAtTick) match { + case (Left(_), Some(nextTick)) => + ( + copy( + lastFinishedTick = tick, + nextActivation = nextActivation.updated(modelUuid, nextTick), + ), + None, + ) + + case (Left(_), None) => + (copy(lastFinishedTick = tick), None) + + case (Right(ref), Some(nextTick)) => + ref ! flexResponse + + ( + copy(nextActivation = + nextActivation.updated(modelUuid, nextTick) + ), + None, + ) + + case (Right(ref), None) => + ref ! flexResponse + + (this, None) + } + } + } + + override def handleFlexRequest( + flexRequest: FlexRequest, + receiver: ActorRef[FlexRequest], + )(using log: Logger): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { + receiver ! flexRequest + + (this, None) + } +} + +object InternalCore { + def apply(core: EmServiceCore): InternalCore = core match { + case internal: InternalCore => + internal + case external => + InternalCore( + core.mode, + core.lastFinishedTick, + core.uuidToAgent, + core.agentToUuid, + core.uuidToInferior, + core.uuidToParent, + core.completions, + core.nextActivation, + core.allFlexOptions, + ) + } + +} diff --git a/src/main/scala/edu/ie3/util/scala/quantities/QuantityUtil.scala b/src/main/scala/edu/ie3/util/scala/quantities/QuantityUtil.scala index d2edeb36b8..3090112c87 100644 --- a/src/main/scala/edu/ie3/util/scala/quantities/QuantityUtil.scala +++ b/src/main/scala/edu/ie3/util/scala/quantities/QuantityUtil.scala @@ -73,7 +73,7 @@ object QuantityUtil { values: Map[Long, Q], windowStart: Long, windowEnd: Long, - ): Try[Q] = { + )(using defaultUnit: UnitOfMeasure[Q]): Try[Q] = { if windowStart == windowEnd then Failure( new IllegalArgumentException("Cannot average over trivial time window.") @@ -114,7 +114,7 @@ object QuantityUtil { values: Map[Long, Q], windowStart: Long, windowEnd: Long, - ): QI = { + )(using defaultUnit: UnitOfMeasure[Q]): QI = { /** Case class to hold current state of integration * @@ -136,11 +136,7 @@ object QuantityUtil { /* Determine the unit from the first best value */ val unit = sortedValues.values.headOption .map(_.unit) - .getOrElse( - throw new QuantityException( - "Unable to determine unit for dummy starting value." - ) - ) + .getOrElse(defaultUnit) val zeroValue = unit(0d) /* the first relevant value for integration is placed before or at windowStart */ From b17c30abeb989a0fa5dec4ab93f84121232e3523 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 11 Nov 2025 10:25:59 +0100 Subject: [PATCH 112/125] Saving changes. --- .../ie3/simona/service/em/ExtEmDataService.scala | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) 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 8e260a39fa..c7d1f674e0 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -219,16 +219,10 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { ((core.toInternal, None), Some(simulationUntil.tick)) case (Some(extMsg), core: EmCommunicationCore) => - ( - core.handleExtMessage(tick, extMsg)(using ctx.log), - None, - ) + (core.handleExtMessage(tick, extMsg), None) case (Some(extMsg), core: EmServiceBaseCore) => - ( - core.handleExtMessage(tick, extMsg)(using ctx.log), - None, - ) + (core.handleExtMessage(tick, extMsg), None) case (_, core: InternalCore) if serviceStateData.simulateUntil > tick => log.warn(s"Received external message with internal core!") @@ -236,10 +230,7 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { ((core, None), Some(serviceStateData.simulateUntil)) case (Some(extMsg), core) => - ( - core.toExternal.handleExtMessage(tick, extMsg)(using ctx.log), - None, - ) + (core.toExternal.handleExtMessage(tick, extMsg), None) case (None, _) => throw ServiceException( From dab92de8d52ff2ebdbe5e273dbfd19435114c3f6 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 17 Nov 2025 12:50:32 +0100 Subject: [PATCH 113/125] Saving changes. --- .../simona/model/participant/evcs/EvModelWrapper.scala | 2 +- .../ie3/simona/service/em/EmCommunicationCore.scala | 10 +++++----- 2 files changed, 6 insertions(+), 6 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..e47311f8f9 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 pRatedAc: 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/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 3f5ec0672a..8684ff67ba 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -84,19 +84,21 @@ object EmCommunicationCore { def handleReceivedFlexOption(flexOption: UUID): Unit = { awaitedFlexOptions.remove(flexOption) - waitingForInternal = false if awaitedFlexOptions.isEmpty then { waitingForInternal = true + } else { + waitingForInternal = false } } def handleReceivedFlexOptions(flexOptions: Seq[UUID]): Unit = { flexOptions.foreach(awaitedFlexOptions.remove) - waitingForInternal = false if awaitedFlexOptions.isEmpty then { waitingForInternal = true + } else { + waitingForInternal = false } } @@ -151,7 +153,6 @@ case class EmCommunicationCore( disaggregated: Map[UUID, Boolean] = Map.empty, requestedFlexType: Map[UUID, FlexType] = Map.empty, currentSetPoint: Map[UUID, Power] = Map.empty, - activatedAgents: Set[UUID] = Set.empty, expectDataFrom: ReceiveMultiDataMap[UUID, EmData] = ReceiveMultiDataMap.empty, ) extends EmServiceCore { @@ -174,7 +175,7 @@ case class EmCommunicationCore( // activatedAgents.map(uuidToAgent).foreach(_ ! IssueNoControl(tick)) val nextTick: java.util.Optional[java.lang.Long] = - if activatedAgents.nonEmpty then { + if emStates.exists(_._2.isActivated) then { requestEmCompletion.maybeNextTick } else getMaybeNextTick.map(long2Long).toJava @@ -544,7 +545,6 @@ case class EmCommunicationCore( requestedFlexType = Map.empty, allFlexOptions = Map.empty, currentSetPoint = Map.empty, - activatedAgents = Set.empty, expectDataFrom = ReceiveMultiDataMap.empty, nextActivation = nextActivation ++ additionalActivation, ), From ac275e84c061b9d330c890376c12a8857746125d Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 17 Nov 2025 13:29:45 +0100 Subject: [PATCH 114/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 30 +++++++++++-------- .../simona/service/em/EmServiceBaseCore.scala | 2 ++ .../ie3/simona/service/em/EmServiceCore.scala | 2 ++ .../simona/service/em/ExtEmDataService.scala | 18 ++++++----- .../ie3/simona/service/em/InternalCore.scala | 26 ++++++++++------ 5 files changed, 49 insertions(+), 29 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 8684ff67ba..13066e9e11 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -15,7 +15,11 @@ import edu.ie3.simona.api.data.model.em.* import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.exceptions.CriticalFailureException import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* -import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} +import edu.ie3.simona.ontology.messages.flex.{ + FlexType, + FlexibilityMessage, + PowerLimitFlexOptions, +} import edu.ie3.simona.service.em.EmCommunicationCore.EmAgentState import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK @@ -42,6 +46,7 @@ object EmCommunicationCore { core.lastFinishedTick, uuidToAgent, core.agentToUuid, + core.uncontrolled, core.uuidToInferior, core.uuidToParent, core.completions, @@ -143,6 +148,7 @@ case class EmCommunicationCore( 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] = @@ -440,11 +446,11 @@ case class EmCommunicationCore( } flexResponse match { - case scheduleFlexActivation@ScheduleFlexActivation( - modelUuid, - _, - scheduleKey, - ) => + case scheduleFlexActivation @ ScheduleFlexActivation( + modelUuid, + _, + scheduleKey, + ) => log.warn(s"$scheduleFlexActivation not handled!") (this, None) @@ -516,12 +522,11 @@ case class EmCommunicationCore( ) } - - case completion@FlexCompletion( - sender, - requestAtNextActivation, - requestAtTick, - ) => + case completion @ FlexCompletion( + sender, + requestAtNextActivation, + requestAtTick, + ) => // the completion can be sent directly to the receiver, since it's not used by the external communication uuidToAgent(receiverUuid) ! completion emStates(sender).setWaitingForInternal(false) @@ -559,7 +564,6 @@ case class EmCommunicationCore( (copy(completions = updatedData), msgToExt) } - // not supported case other => log.warn(s"Flex response $other is not supported!") 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 775936cc2b..e7da5031b3 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -66,6 +66,7 @@ final case class EmServiceBaseCore( 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] = @@ -338,6 +339,7 @@ object EmServiceBaseCore { core.lastFinishedTick, core.uuidToAgent, core.agentToUuid, + core.uncontrolled, core.uuidToInferior, core.uuidToParent, core.completions, 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 bb14b8d122..c2340bea30 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -47,6 +47,8 @@ trait EmServiceCore { val agentToUuid: Map[ActorRef[FlexRequest] | ActorRef[FlexResponse], UUID] + val uncontrolled: Set[UUID] + val uuidToInferior: Map[UUID, Set[UUID]] val uuidToParent: Map[UUID, UUID] 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 c7d1f674e0..465d1084d2 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -209,7 +209,9 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { (updatedStateData, Some(tick)) } else { - log.warn(s"Tick ($tick): ServiceCore -> ${serviceStateData.serviceCore.getClass}, msg -> ${serviceStateData.extEmDataMessage}") + log.warn( + s"Tick ($tick): ServiceCore -> ${serviceStateData.serviceCore.getClass}, msg -> ${serviceStateData.extEmDataMessage}" + ) val ((updatedCore, msgToExt), until) = ( serviceStateData.extEmDataMessage, @@ -218,16 +220,16 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { case (Some(simulationUntil: EmSimulationUntil), core) => ((core.toInternal, None), Some(simulationUntil.tick)) - case (Some(extMsg), core: EmCommunicationCore) => - (core.handleExtMessage(tick, extMsg), None) - - case (Some(extMsg), core: EmServiceBaseCore) => + case (Some(extMsg), core: (EmCommunicationCore | EmServiceBaseCore)) => (core.handleExtMessage(tick, extMsg), None) case (_, core: InternalCore) if serviceStateData.simulateUntil > tick => log.warn(s"Received external message with internal core!") - ((core, None), Some(serviceStateData.simulateUntil)) + ( + (core.sendActivations(tick), None), + Some(serviceStateData.simulateUntil), + ) case (Some(extMsg), core) => (core.toExternal.handleExtMessage(tick, extMsg), None) @@ -251,7 +253,9 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { ) case None => - msgToExt.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) + msgToExt.foreach( + serviceStateData.extEmDataConnection.queueExtResponseMsg + ) ( serviceStateData.copy( diff --git a/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala b/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala index b48c8f9f6e..77d5eb79ea 100644 --- a/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala @@ -14,13 +14,9 @@ import edu.ie3.simona.api.ontology.em.{ EmDataResponseMessageToExt, } 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.FlexibilityMessage.{ - FlexCompletion, - FlexRequest, - FlexResponse, - IssueNoControl, -} +import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK import org.apache.pekko.actor.typed.ActorRef @@ -36,6 +32,7 @@ case class InternalCore( 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] = @@ -62,7 +59,7 @@ case class InternalCore( val uuid = emServiceRegistration.inputUuid val ref = emServiceRegistration.requestingActor - val (updatedInferior, updatedUuidToParent) = + val (updatedUncontrolled, updatedInferior, updatedUuidToParent) = emServiceRegistration.parentUuid match { case Some(parent) => val inferior = uuidToInferior.get(parent) match { @@ -73,16 +70,18 @@ case class InternalCore( } ( + uncontrolled, uuidToInferior.updated(parent, inferior), uuidToParent.updated(uuid, parent), ) case None => - (uuidToInferior, uuidToParent) + (uncontrolled + uuid, uuidToInferior, uuidToParent) } copy( uuidToAgent = uuidToAgent.updated(uuid, ref), agentToUuid = agentToUuid.updated(ref, uuid), + uncontrolled = updatedUncontrolled, uuidToInferior = updatedInferior, uuidToParent = updatedUuidToParent, nextActivation = nextActivation.updated(uuid, 0), @@ -97,6 +96,14 @@ case class InternalCore( (this, None) } + def sendActivations(tick: Long): InternalCore = { + uncontrolled.filter(nextActivation(_) == tick).foreach { + uuidToAgent(_) ! FlexActivation(tick, PowerLimit) + } + + this + } + override def handleFlexResponse( tick: Long, flexResponse: FlexResponse, @@ -157,12 +164,13 @@ object InternalCore { def apply(core: EmServiceCore): InternalCore = core match { case internal: InternalCore => internal - case external => + case _ => InternalCore( core.mode, core.lastFinishedTick, core.uuidToAgent, core.agentToUuid, + core.uncontrolled, core.uuidToInferior, core.uuidToParent, core.completions, From 1c10d0c11b146256c3722882196a7fcd4783944f Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 18 Nov 2025 12:24:27 +0100 Subject: [PATCH 115/125] Saving changes. --- .../simona/service/em/EmServiceBaseCore.scala | 27 ++++++++++++++++++- .../edu/ie3/simona/util/ReceiveDataMap.scala | 3 +++ 2 files changed, 29 insertions(+), 1 deletion(-) 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 e7da5031b3..c4cf5e2935 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -23,6 +23,7 @@ import org.slf4j.Logger import java.util.UUID import scala.jdk.CollectionConverters.MapHasAsScala +import scala.jdk.OptionConverters.RichOption /** Basic service core for an [[ExtEmDataService]]. * @param mode @@ -102,6 +103,7 @@ final case class EmServiceBaseCore( val updatedState = copy( flexOptions = ReceiveDataMap(flexRequests.keySet), + completions = completions.addExpectedKeys(flexRequests.keySet), disaggregated = disaggregated ++ flexRequests, sendOptionsToExt = true, ) @@ -140,6 +142,28 @@ final case class EmServiceBaseCore( } 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.map(long2Long).toJava + + ( + copy(lastFinishedTick = tick), + Some(new EmCompletion(nextTick)), + ) + } + case _ => throw new CriticalFailureException( s"The EmServiceBaseCore is not able to handle the message: $extMsg" @@ -277,7 +301,8 @@ final case class EmServiceBaseCore( log.debug(s"$receiver: $flexRequest") receiver ! flexRequest - (this, None) + val uuid = agentToUuid(receiver) + (copy(completions = completions.addExpectedKey(uuid)), None) } /** Method to handle flex options. 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 } From cf17abafb603ffd9a0eac2e58b533b05f1e6fcc6 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 20 Nov 2025 13:41:57 +0100 Subject: [PATCH 116/125] Saving changes. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 15 +++ .../ie3/simona/service/ExtDataSupport.scala | 7 +- .../ie3/simona/service/SimonaService.scala | 29 +++-- .../service/em/EmCommunicationCore.scala | 4 +- .../simona/service/em/EmServiceBaseCore.scala | 8 +- .../ie3/simona/service/em/EmServiceCore.scala | 5 +- .../simona/service/em/ExtEmDataService.scala | 105 +++++++++++++++--- .../ie3/simona/service/em/InternalCore.scala | 12 +- .../simona/service/ev/ExtEvDataService.scala | 4 +- .../primary/ExtPrimaryDataService.scala | 4 +- 10 files changed, 158 insertions(+), 35 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 2893f55414..626fe6ed9e 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -226,6 +226,8 @@ object EmAgent { awaitingFlexCtrl(emData, modelShell, flexOptionsCore) + case (_, x) => + throw new CriticalFailureException(s"Inactive: Could not handle $x") } private def activate( @@ -325,6 +327,10 @@ object EmAgent { can schedule themselves with their completions and inactive agents should be sleeping right now */ + case x => + throw new CriticalFailureException( + s"AwaitingFlexOptions: Could not handle $x" + ) } /** Behavior of an [[EmAgent]] waiting for a flex control message to be @@ -369,6 +375,11 @@ object EmAgent { } awaitingCompletions(emData, modelShell, newCore) + + case x => + throw new CriticalFailureException( + s"AwaitingFlexControl: Could not handle $x" + ) } /** Behavior of an [[EmAgent]] waiting for completions messages to be received @@ -416,6 +427,10 @@ object EmAgent { ) } + case (_, x) => + throw new CriticalFailureException( + s"AwaitingCompletion: Could not handle $x" + ) } /** Completions have all been received, possibly send results and report to 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/SimonaService.scala b/src/main/scala/edu/ie3/simona/service/SimonaService.scala index 9487b38077..8b561158c4 100644 --- a/src/main/scala/edu/ie3/simona/service/SimonaService.scala +++ b/src/main/scala/edu/ie3/simona/service/SimonaService.scala @@ -188,21 +188,36 @@ abstract class SimonaService { val (updatedStateData, maybeNextTick) = announceInformation(tick)(using stateData, ctx) - maybeNextTick match { + val data = maybeNextTick match { case Some(nextTick) if nextTick == tick => // we need to do an additional activation of this service ctx.self ! Activation(tick) + None + case Some(nextTick) if nextTick == -1 => - // this indicated that no completion should be sent + // this indicated that no completion should be sent + None + case _ => - scheduler ! Completion( - ctx.self, - maybeNextTick, - ) + finishActivation(updatedStateData, scheduler, maybeNextTick, ctx) } - idle(using updatedStateData, scheduler) + idle(using data.getOrElse(updatedStateData), scheduler) + } + + protected def finishActivation( + stateData: S, + scheduler: ActorRef[SchedulerMessage], + maybeNextTick: Option[Long], + ctx: ActorContext[Message], + ): Option[S] = { + scheduler ! Completion( + ctx.self, + maybeNextTick, + ) + + None } private def unhandled diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 13066e9e11..4bd4c7c8e8 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -183,7 +183,7 @@ case class EmCommunicationCore( val nextTick: java.util.Optional[java.lang.Long] = if emStates.exists(_._2.isActivated) then { requestEmCompletion.maybeNextTick - } else getMaybeNextTick.map(long2Long).toJava + } else getMaybeNextExtTick ( copy(lastFinishedTick = tick), @@ -553,7 +553,7 @@ case class EmCommunicationCore( expectDataFrom = ReceiveMultiDataMap.empty, nextActivation = nextActivation ++ additionalActivation, ), - Some(new EmCompletion(getMaybeNextTick.map(long2Long).toJava)), + Some(new EmCompletion(getMaybeNextExtTick)), ) } else { val msgToExt = getMsgToExtOption 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 c4cf5e2935..e41376421b 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -105,7 +105,7 @@ final case class EmServiceBaseCore( flexOptions = ReceiveDataMap(flexRequests.keySet), completions = completions.addExpectedKeys(flexRequests.keySet), disaggregated = disaggregated ++ flexRequests, - sendOptionsToExt = true, + sendOptionsToExt = flexRequests.nonEmpty, ) // handle set points @@ -134,6 +134,7 @@ final case class EmServiceBaseCore( ( updatedState.copy( flexOptions = updatedState.flexOptions.addExpectedKeys(entities), + completions = updatedState.completions.addExpectedKeys(entities), setPointOption = Some(setPoints), ), None, @@ -156,7 +157,7 @@ final case class EmServiceBaseCore( // deactivate agents by sending an IssueNoControl message // activatedAgents.map(uuidToAgent).foreach(_ ! IssueNoControl(tick)) - val nextTick = getMaybeNextTick.map(long2Long).toJava + val nextTick = getMaybeNextExtTick ( copy(lastFinishedTick = tick), @@ -221,6 +222,7 @@ final case class EmServiceBaseCore( (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 @@ -234,7 +236,7 @@ final case class EmServiceBaseCore( } } else { - log.warn(s"Missing flex options for: ${updated.getExpectedKeys}") + log.debug(s"Missing flex options for: ${updated.getExpectedKeys}") ( copy( 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 c2340bea30..39672f424e 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -27,7 +27,7 @@ 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.OptionConverters.{RichOption, RichOptional} @@ -283,4 +283,7 @@ trait EmServiceCore { completions.receivedData.flatMap { case (_, completion) => completion.requestAtTick }.minOption + + final def getMaybeNextExtTick: Optional[java.lang.Long] = + getMaybeNextTick.map(long2Long).toJava } 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 465d1084d2..c3b517f269 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -13,7 +13,8 @@ import edu.ie3.simona.api.ontology.DataMessageFromExt import edu.ie3.simona.api.ontology.em.* import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.exceptions.{InitializationException, ServiceException} -import edu.ie3.simona.ontology.messages.ServiceMessage +import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion +import edu.ie3.simona.ontology.messages.{SchedulerMessage, ServiceMessage} import edu.ie3.simona.ontology.messages.ServiceMessage.* import edu.ie3.simona.ontology.messages.flex.FlexType.PowerLimit import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.* @@ -33,6 +34,7 @@ import org.slf4j.{Logger, LoggerFactory} import java.time.ZonedDateTime import java.util.UUID import scala.util.{Failure, Success, Try} +import scala.jdk.OptionConverters.RichOptional object ExtEmDataService extends SimonaService with ExtDataSupport { @@ -98,8 +100,10 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { startTime: ZonedDateTime, serviceCore: EmServiceCore, tick: Long = INIT_SIM_TICK, + simulateFrom: Long = FIRST_TICK_IN_SIMULATION, simulateUntil: Long = FIRST_TICK_IN_SIMULATION, extEmDataMessage: Option[EmDataMessageFromExt] = None, + scheduler: Option[ActorRef[SchedulerMessage]] = None, ) extends ServiceBaseStateData case class InitExtEmData( @@ -183,6 +187,23 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { ) } + override protected def finishActivation( + stateData: ExtEmDataStateData, + scheduler: ActorRef[SchedulerMessage], + maybeNextTick: Option[Long], + ctx: ActorContext[ExtEmDataService.Message], + ): Option[ExtEmDataStateData] = { + val tick = stateData.tick + val nextExtTick = stateData.simulateUntil + + if nextExtTick > tick && stateData.simulateFrom < tick then { + Some(stateData.copy(scheduler = Some(scheduler))) + } else { + scheduler ! Completion(ctx.self, maybeNextTick.filter(_ < nextExtTick)) + None + } + } + override protected def announceInformation(tick: Long)(using serviceStateData: ExtEmDataStateData, ctx: ActorContext[Message], @@ -223,16 +244,16 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { case (Some(extMsg), core: (EmCommunicationCore | EmServiceBaseCore)) => (core.handleExtMessage(tick, extMsg), None) - case (_, core: InternalCore) if serviceStateData.simulateUntil > tick => - log.warn(s"Received external message with internal core!") + case (Some(extMsg), core) => + (core.toExternal.handleExtMessage(tick, extMsg), None) - ( - (core.sendActivations(tick), None), - Some(serviceStateData.simulateUntil), + case (extMsg, core: InternalCore) + if serviceStateData.simulateUntil > tick => + extMsg.foreach(_ => + log.warn(s"Received external message with internal core!") ) - case (Some(extMsg), core) => - (core.toExternal.handleExtMessage(tick, extMsg), None) + ((core.sendActivations(tick), None), None) case (None, _) => throw ServiceException( @@ -241,18 +262,21 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { } until match { - case Some(lastInternalTick) => + case Some(nextExternalTick) => + log.warn(s"Simulate until tick $nextExternalTick.") + ( serviceStateData.copy( tick = tick, serviceCore = updatedCore, - simulateUntil = lastInternalTick, + simulateFrom = tick, + simulateUntil = nextExternalTick, extEmDataMessage = None, ), - updatedCore.getMaybeNextTick, + updatedCore.nextActivation.values.minOption, ) - case None => + case _ => msgToExt.foreach( serviceStateData.extEmDataConnection.queueExtResponseMsg ) @@ -283,7 +307,8 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { } override protected def handleDataResponseMessage( - extResponseMsg: ServiceResponseMessage + extResponseMsg: ServiceResponseMessage, + ctx: ActorContext[Message], )(using serviceStateData: ExtEmDataStateData ): ExtEmDataStateData = { @@ -294,8 +319,58 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { extResponseMsg, )(using serviceStateData.startTime, log) - extMsg.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) + (serviceStateData.scheduler, serviceStateData.simulateUntil) match { + case (Some(scheduler), nextExternalTick) + if nextExternalTick > serviceStateData.tick => + ctx.log.warn(s"Still activated!") + // service is still activated - serviceStateData.copy(serviceCore = updatedCore) + extMsg match { + case Some(_: EmCompletion) => + // every em agent is finished for this tick + // go to next activation + val nextTick = updatedCore.nextActivation.values.minOption + .filter(_ < nextExternalTick) + + log.warn(s"Next tick option: $nextTick") + + scheduler ! Completion( + ctx.self, + nextTick, + ) + + serviceStateData.copy( + serviceCore = updatedCore, + scheduler = None, + ) + + case _ => + // we are not finished yet + serviceStateData.copy(serviceCore = updatedCore) + } + + case (Some(scheduler), _) => + ctx.log.warn(s"Still activated!") + + // service is still activated, but every em agent is finished + + log.warn(s"Next tick option: None") + + scheduler ! Completion( + ctx.self, + None, + ) + + serviceStateData.copy( + serviceCore = updatedCore, + scheduler = None, + ) + + case _ => + ctx.log.warn(s"Deactivated!") + extMsg.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) + + serviceStateData.copy(serviceCore = updatedCore) + } } } diff --git a/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala b/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala index 77d5eb79ea..fec343985e 100644 --- a/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala @@ -10,6 +10,7 @@ import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode import edu.ie3.simona.api.data.model.em.{EmSetPoint, FlexOptions} import edu.ie3.simona.api.ontology.em.{ + EmCompletion, EmDataMessageFromExt, EmDataResponseMessageToExt, } @@ -126,11 +127,14 @@ case class InternalCore( lastFinishedTick = tick, nextActivation = nextActivation.updated(modelUuid, nextTick), ), - None, + Some(new EmCompletion(getMaybeNextExtTick)), ) case (Left(_), None) => - (copy(lastFinishedTick = tick), None) + ( + copy(lastFinishedTick = tick), + Some(new EmCompletion(getMaybeNextExtTick)), + ) case (Right(ref), Some(nextTick)) => ref ! flexResponse @@ -147,6 +151,10 @@ case class InternalCore( (this, None) } + + case other => + log.warn(s"Cannot handle: $other") + (this, None) } } 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..d463a91959 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -29,6 +29,7 @@ import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceBaseStateData, } +import edu.ie3.simona.service.em.ExtEmDataService.Message import edu.ie3.simona.service.{ExtDataSupport, ServiceStateData, SimonaService} import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK @@ -333,7 +334,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/service/primary/ExtPrimaryDataService.scala b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala index b3fa5a2c6a..5d9ecd919d 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/ExtPrimaryDataService.scala @@ -30,6 +30,7 @@ import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceBaseStateData, } +import edu.ie3.simona.service.em.ExtEmDataService.Message import edu.ie3.simona.service.{ExtDataSupport, ServiceStateData, SimonaService} import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext @@ -237,7 +238,8 @@ object ExtPrimaryDataService extends SimonaService with ExtDataSupport { } override protected def handleDataResponseMessage( - extResponseMsg: ServiceResponseMessage + extResponseMsg: ServiceResponseMessage, + ctx: ActorContext[Message], )(implicit serviceStateData: ExtPrimaryDataStateData ): ExtPrimaryDataStateData = serviceStateData From c4d6143fb596a8c1d9fbf739270da00a4b0fe5c5 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 20 Nov 2025 16:56:08 +0100 Subject: [PATCH 117/125] Saving changes. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 19 +++---------------- .../simona/service/em/ExtEmDataService.scala | 4 ---- .../ie3/simona/service/em/InternalCore.scala | 8 +++++--- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 626fe6ed9e..819c68b9c2 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -225,9 +225,6 @@ object EmAgent { ctx.self ! msg awaitingFlexCtrl(emData, modelShell, flexOptionsCore) - - case (_, x) => - throw new CriticalFailureException(s"Inactive: Could not handle $x") } private def activate( @@ -327,10 +324,6 @@ object EmAgent { can schedule themselves with their completions and inactive agents should be sleeping right now */ - case x => - throw new CriticalFailureException( - s"AwaitingFlexOptions: Could not handle $x" - ) } /** Behavior of an [[EmAgent]] waiting for a flex control message to be @@ -375,11 +368,6 @@ object EmAgent { } awaitingCompletions(emData, modelShell, newCore) - - case x => - throw new CriticalFailureException( - s"AwaitingFlexControl: Could not handle $x" - ) } /** Behavior of an [[EmAgent]] waiting for completions messages to be received @@ -427,10 +415,9 @@ object EmAgent { ) } - case (_, x) => - throw new CriticalFailureException( - s"AwaitingCompletion: Could not handle $x" - ) + case (ctx, x) => + ctx.log.warn(s"AwaitingCompletion: Could not handle $x") + Behaviors.same } /** Completions have all been received, possibly send results and report to 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 c3b517f269..076056d659 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -322,7 +322,6 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { (serviceStateData.scheduler, serviceStateData.simulateUntil) match { case (Some(scheduler), nextExternalTick) if nextExternalTick > serviceStateData.tick => - ctx.log.warn(s"Still activated!") // service is still activated extMsg match { @@ -350,8 +349,6 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { } case (Some(scheduler), _) => - ctx.log.warn(s"Still activated!") - // service is still activated, but every em agent is finished log.warn(s"Next tick option: None") @@ -367,7 +364,6 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { ) case _ => - ctx.log.warn(s"Deactivated!") extMsg.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) serviceStateData.copy(serviceCore = updatedCore) diff --git a/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala b/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala index fec343985e..dab26f2ef9 100644 --- a/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala @@ -98,11 +98,13 @@ case class InternalCore( } def sendActivations(tick: Long): InternalCore = { - uncontrolled.filter(nextActivation(_) == tick).foreach { - uuidToAgent(_) ! FlexActivation(tick, PowerLimit) + val activated = uncontrolled.filter(nextActivation(_) == tick).map { uuid => + uuidToAgent(uuid) ! FlexActivation(tick, PowerLimit) + + uuid } - this + copy(nextActivation = nextActivation.removedAll(activated)) } override def handleFlexResponse( From 8aa9be93a85414fc4d6e9f5f1cc82662bd3512b5 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 20 Nov 2025 17:39:33 +0100 Subject: [PATCH 118/125] Disabling internal core. --- .../ie3/simona/service/SimonaService.scala | 23 +-- .../service/em/EmCommunicationCore.scala | 58 ++++++- .../simona/service/em/EmServiceBaseCore.scala | 39 ++++- .../ie3/simona/service/em/EmServiceCore.scala | 13 ++ .../simona/service/em/ExtEmDataService.scala | 159 ++++-------------- 5 files changed, 140 insertions(+), 152 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/service/SimonaService.scala b/src/main/scala/edu/ie3/simona/service/SimonaService.scala index 8b561158c4..c562f34109 100644 --- a/src/main/scala/edu/ie3/simona/service/SimonaService.scala +++ b/src/main/scala/edu/ie3/simona/service/SimonaService.scala @@ -188,7 +188,7 @@ abstract class SimonaService { val (updatedStateData, maybeNextTick) = announceInformation(tick)(using stateData, ctx) - val data = maybeNextTick match { + maybeNextTick match { case Some(nextTick) if nextTick == tick => // we need to do an additional activation of this service ctx.self ! Activation(tick) @@ -200,24 +200,13 @@ abstract class SimonaService { None case _ => - finishActivation(updatedStateData, scheduler, maybeNextTick, ctx) + scheduler ! Completion( + ctx.self, + maybeNextTick, + ) } - idle(using data.getOrElse(updatedStateData), scheduler) - } - - protected def finishActivation( - stateData: S, - scheduler: ActorRef[SchedulerMessage], - maybeNextTick: Option[Long], - ctx: ActorContext[Message], - ): Option[S] = { - scheduler ! Completion( - ctx.self, - maybeNextTick, - ) - - None + idle(using updatedStateData, scheduler) } private def unhandled diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 4bd4c7c8e8..e3cc17f9b9 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -14,6 +14,7 @@ import edu.ie3.simona.api.data.model.em import edu.ie3.simona.api.data.model.em.* 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.FlexibilityMessage.* import edu.ie3.simona.ontology.messages.flex.{ FlexType, @@ -22,7 +23,7 @@ import edu.ie3.simona.ontology.messages.flex.{ } import edu.ie3.simona.service.em.EmCommunicationCore.EmAgentState import edu.ie3.simona.util.CollectionUtils.asJava -import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK +import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} import edu.ie3.simona.util.{ReceiveDataMap, ReceiveMultiDataMap} import edu.ie3.util.scala.quantities.QuantityConversionUtils.* import org.apache.pekko.actor.typed.ActorRef @@ -32,7 +33,7 @@ import squants.Power import java.util.UUID import scala.collection.mutable import scala.jdk.CollectionConverters.* -import scala.jdk.OptionConverters.{RichOption, RichOptional} +import scala.jdk.OptionConverters.RichOptional import scala.math.max import scala.util.Try @@ -141,7 +142,7 @@ object EmCommunicationCore { } case class EmCommunicationCore( - override val mode: EmMode, + override val mode: EmMode = EmMode.EM_COMMUNICATION, override val lastFinishedTick: Long = PRE_INIT_TICK, override val uuidToAgent: Map[UUID, ActorRef[Message]] = Map.empty, override val agentToUuid: Map[ @@ -163,6 +164,42 @@ case class EmCommunicationCore( ReceiveMultiDataMap.empty, ) extends EmServiceCore { + def handleRegistration( + emServiceRegistration: EmServiceRegistration + ): EmServiceCore = { + val uuid = emServiceRegistration.inputUuid + val ref = emServiceRegistration.requestingActor + + 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.updated(uuid, ref), + agentToUuid = agentToUuid.updated(ref, uuid), + uncontrolled = updatedUncontrolled, + uuidToInferior = updatedInferior, + uuidToParent = updatedUuidToParent, + nextActivation = nextActivation.updated(uuid, 0), + emStates = emStates.updated(uuid, EmAgentState()), + ) + } + override def handleExtMessage(tick: Long, extMsg: EmDataMessageFromExt)(using log: Logger ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = extMsg match { @@ -526,7 +563,9 @@ case class EmCommunicationCore( sender, requestAtNextActivation, requestAtTick, - ) => + ) if tick != INIT_SIM_TICK => + log.warn(s"Em states: $emStates") + // the completion can be sent directly to the receiver, since it's not used by the external communication uuidToAgent(receiverUuid) ! completion emStates(sender).setWaitingForInternal(false) @@ -564,6 +603,17 @@ case class EmCommunicationCore( (copy(completions = updatedData), msgToExt) } + case completion: FlexCompletion => + receiver match { + case Left(_) => + (copy(lastFinishedTick = tick), None) + + case Right(ref) => + ref ! completion + + (this, None) + } + // not supported case other => log.warn(s"Flex response $other is not supported!") 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 e41376421b..7fdea4b685 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -12,6 +12,7 @@ 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 @@ -60,7 +61,7 @@ import scala.jdk.OptionConverters.RichOption * Option for em set points that needs to be handled at a later time. */ final case class EmServiceBaseCore( - override val mode: EmMode, + override val mode: EmMode = EmMode.BASE, override val lastFinishedTick: Long = PRE_INIT_TICK, override val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, override val agentToUuid: Map[ @@ -81,6 +82,42 @@ final case class EmServiceBaseCore( setPointOption: Option[Map[UUID, EmSetPoint]] = None, ) extends EmServiceCore { + def handleRegistration( + emServiceRegistration: EmServiceRegistration + ): EmServiceCore = { + val uuid = emServiceRegistration.inputUuid + val ref = emServiceRegistration.requestingActor + + 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.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 { 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 39672f424e..374c3678bc 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -69,13 +69,26 @@ trait EmServiceCore { def toQuantity: ComparableQuantity[PsdmPower] = value.toMegawatts.asMegaWatt } + @deprecated def toInternal: InternalCore = InternalCore(this) + @deprecated def toExternal: EmServiceCore = mode match { case EmMode.BASE => EmServiceBaseCore(this) case EmMode.EM_COMMUNICATION => EmCommunicationCore(this) } + /** Method to handle a registration message. + * + * @param emServiceRegistration + * The registration to handle. + * @return + * An updated service core. + */ + def handleRegistration( + emServiceRegistration: EmServiceRegistration + ): EmServiceCore + /** Method to handle the received message from the external simulation. * @param tick * Current tick of the service. 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 076056d659..954b88c9b7 100644 --- a/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/em/ExtEmDataService.scala @@ -100,10 +100,7 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { startTime: ZonedDateTime, serviceCore: EmServiceCore, tick: Long = INIT_SIM_TICK, - simulateFrom: Long = FIRST_TICK_IN_SIMULATION, - simulateUntil: Long = FIRST_TICK_IN_SIMULATION, extEmDataMessage: Option[EmDataMessageFromExt] = None, - scheduler: Option[ActorRef[SchedulerMessage]] = None, ) extends ServiceBaseStateData case class InitExtEmData( @@ -123,17 +120,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") } } @@ -142,7 +135,10 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { )(using log: Logger): Try[(ExtEmDataStateData, Option[Long])] = initServiceData match { case InitExtEmData(extEmDataConnection, startTime) => - val serviceCore = InternalCore(extEmDataConnection.mode) + val serviceCore = extEmDataConnection.mode match { + case EmMode.BASE => EmServiceBaseCore() + case EmMode.EM_COMMUNICATION => EmCommunicationCore() + } val emDataInitializedStateData = ExtEmDataStateData(extEmDataConnection, startTime, serviceCore) @@ -168,8 +164,8 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { ): Try[ExtEmDataStateData] = registrationMessage match { case emServiceRegistration: EmServiceRegistration => - val updatedCore = serviceStateData.serviceCore.toInternal - .handleRegistration(emServiceRegistration) + val updatedCore = + serviceStateData.serviceCore.handleRegistration(emServiceRegistration) if emServiceRegistration.parentEm.isEmpty then { emServiceRegistration.requestingActor ! FlexActivation( @@ -187,23 +183,6 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { ) } - override protected def finishActivation( - stateData: ExtEmDataStateData, - scheduler: ActorRef[SchedulerMessage], - maybeNextTick: Option[Long], - ctx: ActorContext[ExtEmDataService.Message], - ): Option[ExtEmDataStateData] = { - val tick = stateData.tick - val nextExtTick = stateData.simulateUntil - - if nextExtTick > tick && stateData.simulateFrom < tick then { - Some(stateData.copy(scheduler = Some(scheduler))) - } else { - scheduler ! Completion(ctx.self, maybeNextTick.filter(_ < nextExtTick)) - None - } - } - override protected def announceInformation(tick: Long)(using serviceStateData: ExtEmDataStateData, ctx: ActorContext[Message], @@ -234,62 +213,25 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { s"Tick ($tick): ServiceCore -> ${serviceStateData.serviceCore.getClass}, msg -> ${serviceStateData.extEmDataMessage}" ) - val ((updatedCore, msgToExt), until) = ( - serviceStateData.extEmDataMessage, - serviceStateData.serviceCore, - ) match { - case (Some(simulationUntil: EmSimulationUntil), core) => - ((core.toInternal, None), Some(simulationUntil.tick)) - - case (Some(extMsg), core: (EmCommunicationCore | EmServiceBaseCore)) => - (core.handleExtMessage(tick, extMsg), None) - - case (Some(extMsg), core) => - (core.toExternal.handleExtMessage(tick, extMsg), None) - - case (extMsg, core: InternalCore) - if serviceStateData.simulateUntil > tick => - extMsg.foreach(_ => - log.warn(s"Received external message with internal core!") - ) - - ((core.sendActivations(tick), None), None) - - case (None, _) => - throw ServiceException( - "ExtEmDataService was triggered without ExtEmDataMessage available" - ) - } + val extMsg = serviceStateData.extEmDataMessage.getOrElse( + throw ServiceException( + "ExtEmDataService was triggered without ExtEmDataMessage available" + ) + ) - until match { - case Some(nextExternalTick) => - log.warn(s"Simulate until tick $nextExternalTick.") - - ( - serviceStateData.copy( - tick = tick, - serviceCore = updatedCore, - simulateFrom = tick, - simulateUntil = nextExternalTick, - extEmDataMessage = None, - ), - updatedCore.nextActivation.values.minOption, - ) + val (updatedCore, msgToExt) = + serviceStateData.serviceCore.handleExtMessage(tick, extMsg) - case _ => - msgToExt.foreach( - serviceStateData.extEmDataConnection.queueExtResponseMsg - ) + msgToExt.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) - ( - serviceStateData.copy( - tick = tick, - serviceCore = updatedCore, - extEmDataMessage = None, - ), - None, - ) - } + ( + serviceStateData.copy( + tick = tick, + serviceCore = updatedCore, + extEmDataMessage = None, + ), + None, + ) } } @@ -312,61 +254,18 @@ object ExtEmDataService extends SimonaService with ExtDataSupport { )(using serviceStateData: ExtEmDataStateData ): ExtEmDataStateData = { + val tick = serviceStateData.tick val (updatedCore, extMsg) = serviceStateData.serviceCore.handleDataResponseMessage( - serviceStateData.tick, + tick, extResponseMsg, )(using serviceStateData.startTime, log) - (serviceStateData.scheduler, serviceStateData.simulateUntil) match { - case (Some(scheduler), nextExternalTick) - if nextExternalTick > serviceStateData.tick => - // service is still activated - - extMsg match { - case Some(_: EmCompletion) => - // every em agent is finished for this tick - // go to next activation - val nextTick = updatedCore.nextActivation.values.minOption - .filter(_ < nextExternalTick) - - log.warn(s"Next tick option: $nextTick") - - scheduler ! Completion( - ctx.self, - nextTick, - ) - - serviceStateData.copy( - serviceCore = updatedCore, - scheduler = None, - ) - - case _ => - // we are not finished yet - serviceStateData.copy(serviceCore = updatedCore) - } - - case (Some(scheduler), _) => - // service is still activated, but every em agent is finished - - log.warn(s"Next tick option: None") - - scheduler ! Completion( - ctx.self, - None, - ) - - serviceStateData.copy( - serviceCore = updatedCore, - scheduler = None, - ) - - case _ => - extMsg.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) - - serviceStateData.copy(serviceCore = updatedCore) + if tick >= FIRST_TICK_IN_SIMULATION then { + extMsg.foreach(serviceStateData.extEmDataConnection.queueExtResponseMsg) } + + serviceStateData.copy(serviceCore = updatedCore) } } From b6d228b327db012cea535f0920371c6d89d60f0d Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 21 Nov 2025 09:54:34 +0100 Subject: [PATCH 119/125] Saving changes. --- .../simona/model/participant/storage/StorageModel.scala | 4 ++-- .../edu/ie3/simona/service/em/EmCommunicationCore.scala | 9 +-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/storage/StorageModel.scala b/src/main/scala/edu/ie3/simona/model/participant/storage/StorageModel.scala index 92979f831b..3c4ef024dc 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/storage/StorageModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/storage/StorageModel.scala @@ -213,8 +213,8 @@ class StorageModel private ( (isEmpty(state.storedEnergy) && setPower < zeroKW) || (isAtTarget && setPower != zeroKW) - val activateAtNextTick = - ((isEmptyOrFull || isAtTarget) && isChargingOrDischarging) || hasObsoleteFlexOptions + val activateAtNextTick = true +// ((isEmptyOrFull || isAtTarget) && isChargingOrDischarging) || hasObsoleteFlexOptions // when charging, calculate time until we're full or at target energy val chargingEnergyTarget = () => diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index e3cc17f9b9..3b79f23ccc 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -265,14 +265,7 @@ case class EmCommunicationCore( log.warn(s"Inferior: ${uuidToInferior.get(uuid)}") - val count = max( - Try { - uuidToInferior(uuid).count { id => - nextActivation(id) <= tick - } - }.getOrElse(1), - 1, - ) + val count = uuidToInferior(uuid).size // uuid -> number of sent flex requests uuid -> count From f91d58ce0ecc236a52dba8ac2a88e46e3a2f0693 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 21 Nov 2025 14:24:55 +0100 Subject: [PATCH 120/125] Saving changes. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 19 +++++++------------ .../edu/ie3/simona/agent/em/EmDataCore.scala | 11 +++++++++++ .../agent/participant/ParticipantAgent.scala | 4 ++-- .../participant/storage/StorageModel.scala | 4 ++-- .../messages/flex/FlexibilityMessage.scala | 8 ++++++-- .../service/em/EmCommunicationCore.scala | 8 +++++--- 6 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 819c68b9c2..5aae4ba691 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -198,20 +198,12 @@ object EmAgent { inactive(emData, modelShell, newCore) case (_, msg: Activation) => - activate(emData, modelShell, core, msg.tick) + activate(emData, modelShell, core, msg.tick, false) case (ctx, msg: FlexActivation) => val tick = msg.tick ctx.log.info(s"EmAgent (${modelShell.uuid}) activated for tick $tick") - - activate(emData, modelShell, core, tick) - - case (ctx, msg: FlexShiftActivation) => - val tick = msg.tick - ctx.log.info( - s"EmAgent (${modelShell.uuid}) activated by service for tick $tick" - ) - activate(emData, modelShell, core.gotoTick(tick), tick) + activate(emData, modelShell, core, tick, msg.force) case (ctx, msg: IssueFlexControl) => val flexOptionsCore = core.activate(msg.tick) @@ -232,12 +224,15 @@ object EmAgent { modelShell: EmModelShell[?], core: EmDataCore.Inactive, tick: Long, + force: Boolean, ) = { - val flexOptionsCore = core.activate(tick) + val flexOptionsCore = if force then { + core.gotoTick(tick).activateAll(tick) + } else core.activate(tick) val (toActivate, newCore) = flexOptionsCore.takeNewFlexRequests() toActivate.foreach { - _ ! FlexActivation(tick, modelShell.getFlexType) + _ ! FlexActivation(tick, modelShell.getFlexType, force) } newCore.fold( diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmDataCore.scala b/src/main/scala/edu/ie3/simona/agent/em/EmDataCore.scala index 1f05444ccb..1c91e8d1e9 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmDataCore.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmDataCore.scala @@ -93,6 +93,17 @@ object EmDataCore { this } + def activateAll(newTick: Long): AwaitingFlexOptions = { + activationQueue.set(newTick, modelToActor.keySet) + + AwaitingFlexOptions( + modelToActor, + activationQueue, + correspondences, + activeTick = newTick, + ) + } + /** Tries to handle an activation of the EmAgent for given tick. If the * activation for the tick is not valid, a [[CriticalFailureException]] is * thrown. If successful, an [[AwaitingFlexOptions]] data core is returned diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index c9100b695e..8278cdd1cf 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -395,9 +395,9 @@ object ParticipantAgent { ) (shellWithOP, gridAdapterWithResult) - case FlexActivation(tick, flexType) => + case FlexActivation(tick, flexType, force) => val shellWithFlex = - if isCalculationRequired( + if force || isCalculationRequired( shell, inputHandler, ) || !modelShell.hasFlexOptions diff --git a/src/main/scala/edu/ie3/simona/model/participant/storage/StorageModel.scala b/src/main/scala/edu/ie3/simona/model/participant/storage/StorageModel.scala index 3c4ef024dc..92979f831b 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/storage/StorageModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/storage/StorageModel.scala @@ -213,8 +213,8 @@ class StorageModel private ( (isEmpty(state.storedEnergy) && setPower < zeroKW) || (isAtTarget && setPower != zeroKW) - val activateAtNextTick = true -// ((isEmptyOrFull || isAtTarget) && isChargingOrDischarging) || hasObsoleteFlexOptions + val activateAtNextTick = + ((isEmptyOrFull || isAtTarget) && isChargingOrDischarging) || hasObsoleteFlexOptions // when charging, calculate time until we're full or at target energy val chargingEnergyTarget = () => diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala index fe4a48090f..593be5db16 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala @@ -79,8 +79,11 @@ object FlexibilityMessage { * The flexibility type to calculate [[FlexOptions]] in. Unused during * initialization. */ - final case class FlexActivation(override val tick: Long, flexType: FlexType) - extends FlexRequest + final case class FlexActivation( + override val tick: Long, + flexType: FlexType, + force: Boolean = false, + ) extends FlexRequest object FlexActivation { @@ -104,6 +107,7 @@ object FlexibilityMessage { } // shifts the activation for controlled asset agent to the given tick + @deprecated final case class FlexShiftActivation( override val tick: Long, flexType: FlexType, diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 3b79f23ccc..11f8638369 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -258,9 +258,10 @@ case class EmCommunicationCore( // update the em state emStates(uuid).setReceivedRequest() - agent ! FlexShiftActivation( + agent ! FlexActivation( tick, requestedFlexType.getOrElse(uuid, FlexType.PowerLimit), + true, ) log.warn(s"Inferior: ${uuidToInferior.get(uuid)}") @@ -345,9 +346,10 @@ case class EmCommunicationCore( // update the em state emStates(receiver).setReceivedRequest() - agent ! FlexShiftActivation( + agent ! FlexActivation( tick, requestedFlexType.getOrElse(receiver, FlexType.PowerLimit), + true, ) val count = max( @@ -623,7 +625,7 @@ case class EmCommunicationCore( val sender = uuidToParent(receiverUuid) // the controlling em val updated = flexRequest match { - case FlexActivation(tick, flexType) => + case FlexActivation(tick, flexType, _) => // update the em state => waiting for external flex option provision emStates(sender).addSendRequest(receiverUuid) From ebb03d018640f8f582a4335c4d049fd44c6d75ac Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 24 Nov 2025 12:57:41 +0100 Subject: [PATCH 121/125] Saving changes. --- .../service/em/EmCommunicationCore.scala | 4 +- .../simona/service/em/EmServiceBaseCore.scala | 38 +++- .../ie3/simona/service/em/EmServiceCore.scala | 16 +- .../ie3/simona/service/em/InternalCore.scala | 192 ------------------ 4 files changed, 41 insertions(+), 209 deletions(-) delete mode 100644 src/main/scala/edu/ie3/simona/service/em/InternalCore.scala diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 11f8638369..5db0edc5f5 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -220,7 +220,7 @@ case class EmCommunicationCore( val nextTick: java.util.Optional[java.lang.Long] = if emStates.exists(_._2.isActivated) then { requestEmCompletion.maybeNextTick - } else getMaybeNextExtTick + } else getMaybeNextTick ( copy(lastFinishedTick = tick), @@ -587,7 +587,7 @@ case class EmCommunicationCore( expectDataFrom = ReceiveMultiDataMap.empty, nextActivation = nextActivation ++ additionalActivation, ), - Some(new EmCompletion(getMaybeNextExtTick)), + Some(new EmCompletion(getMaybeNextTick)), ) } else { val msgToExt = getMsgToExtOption 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 7fdea4b685..a787a9c7f2 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -24,7 +24,6 @@ import org.slf4j.Logger import java.util.UUID import scala.jdk.CollectionConverters.MapHasAsScala -import scala.jdk.OptionConverters.RichOption /** Basic service core for an [[ExtEmDataService]]. * @param mode @@ -80,6 +79,7 @@ final case class EmServiceBaseCore( sendOptionsToExt: Boolean = false, canHandleSetPoints: Boolean = false, setPointOption: Option[Map[UUID, EmSetPoint]] = None, + internal: Set[UUID] = Set.empty, ) extends EmServiceCore { def handleRegistration( @@ -194,7 +194,7 @@ final case class EmServiceBaseCore( // deactivate agents by sending an IssueNoControl message // activatedAgents.map(uuidToAgent).foreach(_ ! IssueNoControl(tick)) - val nextTick = getMaybeNextExtTick + val nextTick = getMaybeNextTick ( copy(lastFinishedTick = tick), @@ -202,6 +202,26 @@ final case class EmServiceBaseCore( ) } + case internal: EmSimulationInternal => + // the service should simulate the tick internal + val internalTick = internal.tick + + val uuids = uncontrolled + .filter { uuid => nextActivation(uuid) == internalTick } + .map { uuid => + uuidToAgent(uuid) ! FlexActivation(tick, PowerLimit) + uuid + } + + ( + copy( + completions = completions.addExpectedKeys(uuids), + flexOptions = flexOptions.addExpectedKeys(uuids), + internal = uuids, + ), + None, + ) + case _ => throw new CriticalFailureException( s"The EmServiceBaseCore is not able to handle the message: $extMsg" @@ -250,7 +270,12 @@ 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) } @@ -308,6 +333,10 @@ final case class EmServiceBaseCore( log.warn(s"$updated") + val msgToExt = if internal.nonEmpty then { + Some(new EmCompletion(updatedNextActivation.values.minOption)) + } else extMsgOption + ( copy( lastFinishedTick = tick, @@ -316,8 +345,9 @@ final case class EmServiceBaseCore( sendOptionsToExt = false, canHandleSetPoints = false, nextActivation = updatedNextActivation, + internal = Set.empty, ), - extMsgOption, + msgToExt, ) } else { 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 374c3678bc..e4179f3e36 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -26,6 +26,7 @@ import org.slf4j.Logger import squants.Power import tech.units.indriya.ComparableQuantity +import java.lang import java.time.ZonedDateTime import java.util.{Optional, UUID} import javax.measure.quantity.Power as PsdmPower @@ -69,14 +70,10 @@ trait EmServiceCore { def toQuantity: ComparableQuantity[PsdmPower] = value.toMegawatts.asMegaWatt } - @deprecated - def toInternal: InternalCore = InternalCore(this) - - @deprecated - def toExternal: EmServiceCore = mode match { - case EmMode.BASE => EmServiceBaseCore(this) - case EmMode.EM_COMMUNICATION => EmCommunicationCore(this) - } + 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. * @@ -296,7 +293,4 @@ trait EmServiceCore { completions.receivedData.flatMap { case (_, completion) => completion.requestAtTick }.minOption - - final def getMaybeNextExtTick: Optional[java.lang.Long] = - getMaybeNextTick.map(long2Long).toJava } diff --git a/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala b/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala deleted file mode 100644 index dab26f2ef9..0000000000 --- a/src/main/scala/edu/ie3/simona/service/em/InternalCore.scala +++ /dev/null @@ -1,192 +0,0 @@ -/* - * © 2025. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.service.em - -import edu.ie3.simona.agent.em.EmAgent -import edu.ie3.simona.api.data.connection.ExtEmDataConnection.EmMode -import edu.ie3.simona.api.data.model.em.{EmSetPoint, FlexOptions} -import edu.ie3.simona.api.ontology.em.{ - EmCompletion, - EmDataMessageFromExt, - EmDataResponseMessageToExt, -} -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.FlexibilityMessage.* -import edu.ie3.simona.util.ReceiveDataMap -import edu.ie3.simona.util.SimonaConstants.PRE_INIT_TICK -import org.apache.pekko.actor.typed.ActorRef -import org.slf4j.Logger - -import java.util.UUID - -case class InternalCore( - override val mode: EmMode, - override val lastFinishedTick: Long = PRE_INIT_TICK, - override val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = 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, - override val nextActivation: Map[UUID, Long] = Map.empty, - override val allFlexOptions: Map[UUID, FlexOptions] = Map.empty, - disaggregated: Map[UUID, Boolean] = Map.empty, - flexOptions: ReceiveDataMap[UUID, FlexOptions] = ReceiveDataMap.empty, - sendOptionsToExt: Boolean = false, - canHandleSetPoints: Boolean = false, - setPointOption: Option[Map[UUID, EmSetPoint]] = None, -) extends EmServiceCore { - - /** Method to handle a registration message. - * - * @param emServiceRegistration - * The registration to handle. - * @return - * An updated service core. - */ - def handleRegistration( - emServiceRegistration: EmServiceRegistration - ): EmServiceCore = { - val uuid = emServiceRegistration.inputUuid - val ref = emServiceRegistration.requestingActor - - 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.updated(uuid, ref), - agentToUuid = agentToUuid.updated(ref, uuid), - uncontrolled = updatedUncontrolled, - uuidToInferior = updatedInferior, - uuidToParent = updatedUuidToParent, - nextActivation = nextActivation.updated(uuid, 0), - ) - } - - override def handleExtMessage(tick: Long, extMsg: EmDataMessageFromExt)(using - log: Logger - ): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { - log.warn("Handling of external message not possible!") - - (this, None) - } - - def sendActivations(tick: Long): InternalCore = { - val activated = uncontrolled.filter(nextActivation(_) == tick).map { uuid => - uuidToAgent(uuid) ! FlexActivation(tick, PowerLimit) - - uuid - } - - copy(nextActivation = nextActivation.removedAll(activated)) - } - - override def handleFlexResponse( - tick: Long, - flexResponse: FlexResponse, - receiver: Either[UUID, ActorRef[FlexResponse]], - )(using log: Logger): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { - flexResponse match { - case FlexibilityMessage.ProvideFlexOptions(modelUuid, flexOptions) => - receiver match { - case Left(uuid) => uuidToAgent(uuid) ! IssueNoControl(tick) - case Right(ref) => ref ! flexResponse - } - - (this, None) - - case FlexCompletion(modelUuid, requestAtNextActivation, requestAtTick) => - (receiver, requestAtTick) match { - case (Left(_), Some(nextTick)) => - ( - copy( - lastFinishedTick = tick, - nextActivation = nextActivation.updated(modelUuid, nextTick), - ), - Some(new EmCompletion(getMaybeNextExtTick)), - ) - - case (Left(_), None) => - ( - copy(lastFinishedTick = tick), - Some(new EmCompletion(getMaybeNextExtTick)), - ) - - case (Right(ref), Some(nextTick)) => - ref ! flexResponse - - ( - copy(nextActivation = - nextActivation.updated(modelUuid, nextTick) - ), - None, - ) - - case (Right(ref), None) => - ref ! flexResponse - - (this, None) - } - - case other => - log.warn(s"Cannot handle: $other") - (this, None) - } - } - - override def handleFlexRequest( - flexRequest: FlexRequest, - receiver: ActorRef[FlexRequest], - )(using log: Logger): (EmServiceCore, Option[EmDataResponseMessageToExt]) = { - receiver ! flexRequest - - (this, None) - } -} - -object InternalCore { - def apply(core: EmServiceCore): InternalCore = core match { - case internal: InternalCore => - internal - case _ => - InternalCore( - core.mode, - core.lastFinishedTick, - core.uuidToAgent, - core.agentToUuid, - core.uncontrolled, - core.uuidToInferior, - core.uuidToParent, - core.completions, - core.nextActivation, - core.allFlexOptions, - ) - } - -} From b03acc0c4cbde0162aa42115387cdcafd923dd88 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 25 Nov 2025 12:35:59 +0100 Subject: [PATCH 122/125] Saving changes. --- .../agent/participant/ParticipantAgent.scala | 13 ++++++++- .../ParticipantResultHandler.scala | 8 +++-- .../messages/flex/FlexibilityMessage.scala | 7 ----- .../service/em/EmCommunicationCore.scala | 4 +-- .../simona/service/em/EmServiceBaseCore.scala | 15 +++------- .../ie3/simona/service/em/EmServiceCore.scala | 5 ++-- .../service/results/ResultServiceProxy.scala | 29 +++++++++++++++---- 7 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index 8278cdd1cf..3709bc47a8 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -195,8 +195,19 @@ object ParticipantAgent { case (ctx, activation: ActivationRequest) => given ActorRef[Message] = ctx.self + // determines, if we need to wait for a set point + // we only wait if we received a flex activation + val waitForSetPoint = activation match { + case _: FlexActivation => true + case _ => false + } + // inform the result proxy that this grid agent will send new results - resultHandler.informProxy(modelShell.uuid, activation.tick) + resultHandler.informProxy( + modelShell.uuid, + activation.tick, + waitForSetPoint, + ) val coreWithActivation = inputHandler.handleActivation(activation) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala index f3803131e9..e9b604a7e4 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantResultHandler.scala @@ -67,7 +67,11 @@ final case class ParticipantResultHandler( resultProxy ! FlexOptionsResultEvent(result) } - def informProxy(uuid: UUID, tick: Long): Unit = - resultProxy ! ExpectResult(uuid, tick) + def informProxy( + uuid: UUID, + tick: Long, + waitForSetPoint: Boolean = false, + ): Unit = + resultProxy ! ExpectResult(uuid, tick, waitForSetPoint) } diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala index 593be5db16..15bdc18b06 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/FlexibilityMessage.scala @@ -106,13 +106,6 @@ object FlexibilityMessage { FlexActivation(tick, flexType) } - // shifts the activation for controlled asset agent to the given tick - @deprecated - final case class FlexShiftActivation( - override val tick: Long, - flexType: FlexType, - ) extends FlexRequest - /** Message that provides [[FlexOptions]] to an * [[edu.ie3.simona.agent.em.EmAgent]] after they have been requested via * [[FlexActivation]]. diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 5db0edc5f5..0389ff2b0f 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -143,7 +143,7 @@ object EmCommunicationCore { case class EmCommunicationCore( override val mode: EmMode = EmMode.EM_COMMUNICATION, - override val lastFinishedTick: Long = PRE_INIT_TICK, + override val lastFinishedTick: Long = INIT_SIM_TICK, override val uuidToAgent: Map[UUID, ActorRef[Message]] = Map.empty, override val agentToUuid: Map[ ActorRef[FlexRequest] | ActorRef[FlexResponse], @@ -498,7 +498,7 @@ case class EmCommunicationCore( max.toQuantity, ) - if disaggregated.contains(receiverUuid) then { + if disaggregated(receiverUuid) then { uuidToInferior(receiverUuid) .flatMap(allFlexOptions.get) .foreach { result => 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 a787a9c7f2..bd77691824 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -18,7 +18,7 @@ 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.SimonaConstants.INIT_SIM_TICK import org.apache.pekko.actor.typed.ActorRef import org.slf4j.Logger @@ -61,7 +61,7 @@ import scala.jdk.CollectionConverters.MapHasAsScala */ final case class EmServiceBaseCore( override val mode: EmMode = EmMode.BASE, - override val lastFinishedTick: Long = PRE_INIT_TICK, + override val lastFinishedTick: Long = INIT_SIM_TICK, override val uuidToAgent: Map[UUID, ActorRef[EmAgent.Message]] = Map.empty, override val agentToUuid: Map[ ActorRef[FlexRequest] | ActorRef[FlexResponse], @@ -255,7 +255,7 @@ final case class EmServiceBaseCore( val data = updated.receivedData data.foreach { case (uuid, flexOption) => - if disaggregated.contains(uuid) then { + if disaggregated(uuid) then { // we add the disaggregated flex options addDisaggregatingFlexOptions( flexOption, @@ -325,14 +325,11 @@ final case class EmServiceBaseCore( val keys = updatedNextActivation.filter { case (_, activation) => activation == t }.keySet - log.warn(s"Keys: $keys") ReceiveDataMap[UUID, FlexCompletion](keys) case None => updated } - log.warn(s"$updated") - val msgToExt = if internal.nonEmpty then { Some(new EmCompletion(updatedNextActivation.values.minOption)) } else extMsgOption @@ -351,7 +348,7 @@ final case class EmServiceBaseCore( ) } else { - log.warn(s"$updated") + log.warn(s"Missing completion for: ${updated.getExpectedKeys}") (copy(completions = updated), extMsgOption) } @@ -405,15 +402,11 @@ final case class EmServiceBaseCore( ) if flexOptions.expects(modelUuid) then { - println(s"Received expected: $modelUuid") - ( flexOptions.addData(modelUuid, result), allFlexOptions.updated(modelUuid, result), ) } else { - println(s"Received unexpected: $modelUuid") - ( flexOptions, allFlexOptions.updated(modelUuid, result), 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 e4179f3e36..6af102e6a4 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -268,15 +268,14 @@ trait EmServiceCore { Boolean, ) = { val updated = completions.addData(completion.modelUuid, completion) + println(s"Added $completion") if updated.isComplete then { - val allKeys = updated.receivedData.keySet - val (extMsgOption, nextTickOption) = if tick != INIT_SIM_TICK then { // send completion message to external simulation, if we aren't in the INIT_SIM_TICK val option = getMaybeNextTick - (Some(new EmCompletion(option.map(long2Long).toJava)), option) + (Some(new EmCompletion(option)), option) } else (None, None) // every em agent has sent a completion message diff --git a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala index b194e554c9..2988a8c62e 100644 --- a/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/results/ResultServiceProxy.scala @@ -36,7 +36,11 @@ object ResultServiceProxy { type Message = ResultEvent | RequestResult | ExpectResult | DelayedStopHelper.StoppingMsg - final case class ExpectResult(assets: UUID | Seq[UUID], tick: Long) + final case class ExpectResult( + assets: UUID | Seq[UUID], + tick: Long, + waitForSetPoint: Boolean = false, + ) private final case class ResultServiceStateData( listeners: Seq[ActorRef[ResultResponse]], @@ -49,6 +53,7 @@ object ResultServiceProxy { gridResults: Map[UUID, Iterable[ResultEntity]] = Map.empty, results: Map[UUID, Iterable[ResultEntity]] = Map.empty, waitingForResults: Map[UUID, Long] = Map.empty, + requiresSetPoint: Set[UUID] = Set.empty, ) extends ServiceBaseStateData { def notifyListener(results: Map[UUID, Iterable[ResultEntity]]): Unit = if results.nonEmpty then listeners.foreach(_ ! ResultResponse(results)) @@ -61,8 +66,9 @@ object ResultServiceProxy { def isWaiting(uuids: Iterable[UUID], tick: Long): Boolean = { uuids.exists { uuid => waitingForResults.get(uuid) match { - case Some(nextTick) if nextTick <= tick => true - case _ => false + case Some(nextTick) => + nextTick <= tick && !requiresSetPoint.contains(uuid) + case _ => false } } } @@ -70,19 +76,29 @@ object ResultServiceProxy { def updateTick(tick: Long): ResultServiceStateData = copy(currentTick = tick) - def waitForResult(expectResult: ExpectResult): ResultServiceStateData = + def waitForResult(expectResult: ExpectResult): ResultServiceStateData = { expectResult.assets match { case uuid: UUID => - copy(waitingForResults = + val updated = copy(waitingForResults = waitingForResults.updated(uuid, expectResult.tick) ) + + if expectResult.waitForSetPoint then { + updated.copy(requiresSetPoint = requiresSetPoint + uuid) + } else updated + case uuids: Seq[UUID] => val tick = expectResult.tick - copy(waitingForResults = + val updated = copy(waitingForResults = waitingForResults ++ uuids.map(uuid => uuid -> tick).toMap ) + + if expectResult.waitForSetPoint then { + updated.copy(requiresSetPoint = requiresSetPoint ++ uuids) + } else updated } + } def addResult(result: ResultEntity): ResultServiceStateData = { val uuid = result.getInputModel @@ -110,6 +126,7 @@ object ResultServiceProxy { copy( results = updatedResults, waitingForResults = updatedWaitingForResults, + requiresSetPoint = requiresSetPoint.excl(uuid), ) } From 65fbe7575146ff2ea764878a33c43570c5dd8f5b Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 26 Nov 2025 11:41:34 +0100 Subject: [PATCH 123/125] Saving changes. --- .../edu/ie3/simona/agent/em/EmAgent.scala | 6 ++--- .../service/em/EmCommunicationCore.scala | 22 +++---------------- .../simona/service/em/EmServiceBaseCore.scala | 4 ++-- .../ie3/simona/service/em/EmServiceCore.scala | 5 ++--- 4 files changed, 9 insertions(+), 28 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala index 5aae4ba691..16c77acb62 100644 --- a/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/em/EmAgent.scala @@ -202,7 +202,7 @@ object EmAgent { case (ctx, msg: FlexActivation) => val tick = msg.tick - ctx.log.info(s"EmAgent (${modelShell.uuid}) activated for tick $tick") + // ctx.log.info(s"EmAgent (${modelShell.uuid}) activated for tick $tick") activate(emData, modelShell, core, tick, msg.force) case (ctx, msg: IssueFlexControl) => @@ -395,9 +395,7 @@ object EmAgent { lastActiveTick = updatedCore.activeTick, )(using ctx.self) - ctx.log.info( - s"${modelShell.uuid} -> inactive, next tick ${completion.requestAtTick}" - ) + // ctx.log.info(s"${modelShell.uuid} -> inactive, next tick ${completion.requestAtTick}") inactive(emData, modelShell, inactiveCore) } diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index 0389ff2b0f..c45b7417a2 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -233,25 +233,9 @@ case class EmCommunicationCore( val extTick = provideEmData.tick // handling of requests - val (flexRelease, flexRequest) = - provideEmData.flexRequests.asScala.partition { case (_, request) => - request.releaseControl() - } - - flexRelease.keys.foreach { uuid => - log.warn(s"Release control for: $uuid") - - val inferior = uuidToInferior(uuid) - - uuidToAgent.get(uuid).foreach { agent => - // update the em states of the inferior - inferior.flatMap(emStates.get).foreach(_.setWaitingForRelease()) - - agent ! IssueNoControl(tick) - } - } + val flexRequests = provideEmData.flexRequests.asScala - val requestMapping = flexRequest.keys.flatMap { uuid => + val requestMapping = flexRequests.keys.flatMap { uuid => if emStates(uuid).isWaitingForActivation then { uuidToAgent.get(uuid).map { agent => @@ -274,7 +258,7 @@ case class EmCommunicationCore( } else None }.toMap - val updatedDisaggregated = disaggregated ++ flexRequest.map { + val updatedDisaggregated = disaggregated ++ flexRequests.map { case (uuid, request) => uuid -> request.disaggregated.booleanValue }.toMap 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 bd77691824..acbea77f78 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceBaseCore.scala @@ -255,7 +255,7 @@ final case class EmServiceBaseCore( val data = updated.receivedData data.foreach { case (uuid, flexOption) => - if disaggregated(uuid) then { + if disaggregated.getOrElse(uuid, false) then { // we add the disaggregated flex options addDisaggregatingFlexOptions( flexOption, @@ -348,7 +348,7 @@ final case class EmServiceBaseCore( ) } else { - log.warn(s"Missing completion for: ${updated.getExpectedKeys}") + log.debug(s"Missing completion for: ${updated.getExpectedKeys}") (copy(completions = updated), extMsgOption) } 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 6af102e6a4..d81095c748 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -125,7 +125,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] => @@ -144,7 +144,7 @@ trait EmServiceCore { } case EmFlexMessage(flexResponse: FlexResponse, receiver) => - log.warn(s"$receiver <- $flexResponse") + log.debug(s"$receiver <- $flexResponse") receiver match { case uuid: UUID => @@ -268,7 +268,6 @@ trait EmServiceCore { Boolean, ) = { val updated = completions.addData(completion.modelUuid, completion) - println(s"Added $completion") if updated.isComplete then { val (extMsgOption, nextTickOption) = if tick != INIT_SIM_TICK then { From 387bf86d24723486a8b77f89c1d4d18187283501 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 27 Nov 2025 09:24:40 +0100 Subject: [PATCH 124/125] Saving changes. --- .../agent/participant/ParticipantAgent.scala | 31 +++++++++---------- .../service/em/EmCommunicationCore.scala | 13 +++++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index 3709bc47a8..f4218cb8b4 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -195,20 +195,6 @@ object ParticipantAgent { case (ctx, activation: ActivationRequest) => given ActorRef[Message] = ctx.self - // determines, if we need to wait for a set point - // we only wait if we received a flex activation - val waitForSetPoint = activation match { - case _: FlexActivation => true - case _ => false - } - - // inform the result proxy that this grid agent will send new results - resultHandler.informProxy( - modelShell.uuid, - activation.tick, - waitForSetPoint, - ) - val coreWithActivation = inputHandler.handleActivation(activation) val (updatedShell, updatedInputHandler, updatedGridAdapter) = @@ -229,9 +215,6 @@ object ParticipantAgent { case (ctx, msg: DataInputMessage) => given ActorRef[Message] = ctx.self - // inform the result proxy that this grid agent will send new results - resultHandler.informProxy(modelShell.uuid, msg.tick) - val inputHandlerWithData = inputHandler.handleDataInputMessage(msg) val (updatedShell, updatedInputHandler, updatedGridAdapter) = @@ -364,6 +347,20 @@ object ParticipantAgent { ) ) + // determines, if we need to wait for a set point + // we only wait if we received a flex activation + val waitForSetPoint = activation match { + case _: FlexActivation => true + case _ => false + } + + // inform the result proxy that this grid agent will send new results + resultHandler.informProxy( + modelShell.uuid, + activation.tick, + waitForSetPoint, + ) + val (updatedShell, updatedGridAdapter) = Scope(modelShell) .map( _.updateInputData( diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index c45b7417a2..a43424c90a 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -15,12 +15,9 @@ import edu.ie3.simona.api.data.model.em.* 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.{ - FlexType, - FlexibilityMessage, - PowerLimitFlexOptions, -} +import edu.ie3.simona.ontology.messages.flex.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} import edu.ie3.simona.service.em.EmCommunicationCore.EmAgentState import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} @@ -228,6 +225,12 @@ case class EmCommunicationCore( ) } + case internal: EmSimulationInternal => + // will be handled by an internal core + log.warn(s"Forwarding message to base core. This should only happen, if the simulation shall be finished.") + + EmServiceBaseCore(this).handleExtMessage(tick, internal) + case provideEmData: ProvideEmData => log.warn(s"Handling ext message: $provideEmData") val extTick = provideEmData.tick From 6e1c162b193aa63e5026fc826224ae0eed326701 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 27 Nov 2025 12:24:15 +0100 Subject: [PATCH 125/125] Saving changes. --- .../agent/participant/ParticipantAgent.scala | 4 +-- .../service/em/EmCommunicationCore.scala | 28 ++++++++----------- .../ie3/simona/service/em/EmServiceCore.scala | 2 -- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index f4218cb8b4..d3402b0447 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -351,7 +351,7 @@ object ParticipantAgent { // we only wait if we received a flex activation val waitForSetPoint = activation match { case _: FlexActivation => true - case _ => false + case _ => false } // inform the result proxy that this grid agent will send new results @@ -360,7 +360,7 @@ object ParticipantAgent { activation.tick, waitForSetPoint, ) - + val (updatedShell, updatedGridAdapter) = Scope(modelShell) .map( _.updateInputData( diff --git a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala index a43424c90a..2bb8fff884 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmCommunicationCore.scala @@ -17,7 +17,11 @@ 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.{FlexType, FlexibilityMessage, PowerLimitFlexOptions} +import edu.ie3.simona.ontology.messages.flex.{ + FlexType, + FlexibilityMessage, + PowerLimitFlexOptions, +} import edu.ie3.simona.service.em.EmCommunicationCore.EmAgentState import edu.ie3.simona.util.CollectionUtils.asJava import edu.ie3.simona.util.SimonaConstants.{INIT_SIM_TICK, PRE_INIT_TICK} @@ -227,12 +231,14 @@ case class EmCommunicationCore( case internal: EmSimulationInternal => // will be handled by an internal core - log.warn(s"Forwarding message to base core. This should only happen, if the simulation shall be finished.") + log.warn( + s"Forwarding message to base core. This should only happen, if the simulation shall be finished." + ) EmServiceBaseCore(this).handleExtMessage(tick, internal) case provideEmData: ProvideEmData => - log.warn(s"Handling ext message: $provideEmData") + log.debug(s"Handling ext message: $provideEmData") val extTick = provideEmData.tick // handling of requests @@ -251,8 +257,6 @@ case class EmCommunicationCore( true, ) - log.warn(s"Inferior: ${uuidToInferior.get(uuid)}") - val count = uuidToInferior(uuid).size // uuid -> number of sent flex requests @@ -271,7 +275,6 @@ case class EmCommunicationCore( .asScala .flatMap { case (receiver, setPoint) => val agent = uuidToAgent(receiver) - log.warn(s"Receiver of set point: $agent") // updates the em state emStates(receiver).setReceivedSetPoint() @@ -299,7 +302,6 @@ case class EmCommunicationCore( val mapping = requestMapping ++ setPointMapping val updatedExpectDataFrom = expectDataFrom.addExpectedKeys(mapping) - log.warn(s"ExpectDataFrom: $updatedExpectDataFrom") // check if we need to wait for internal answers val msgToExt = getMsgToExtOption @@ -317,8 +319,6 @@ case class EmCommunicationCore( (newState, msgToExt) case comMsg: EmCommunicationMessages => - log.warn(s"Handling ext message: $comMsg") - val messages = comMsg.messages.asScala val extTick = comMsg.tick @@ -404,7 +404,6 @@ case class EmCommunicationCore( case setPoint: EmSetPoint => val agent = uuidToAgent(receiver) - log.warn(s"Receiver of set point: $agent") // updates the em state emStates(receiver).setReceivedSetPoint() @@ -430,7 +429,7 @@ case class EmCommunicationCore( val updatedExpectDataFrom = expectDataFrom.addExpectedKeys(mapping) - log.warn(s"ExpectDataFrom: $updatedExpectDataFrom, Changes: $mapping") + // log.warn(s"ExpectDataFrom: $updatedExpectDataFrom, Changes: $mapping") // check if we need to wait for internal answers val msgToExt = getMsgToExtOption @@ -447,7 +446,7 @@ case class EmCommunicationCore( (newState, msgToExt) case other => - log.warn(s"Deprecated message received! Message: $other") + log.warn(s"Unsupported message received! Message: $other") (this, None) } @@ -645,13 +644,8 @@ case class EmCommunicationCore( state.setWaitingForInternal(false) // send set point to ext - log.warn( - s"Receiver $receiverUuid got flex control message from $sender" - ) - val power = control match { case IssueNoControl(tick) => - log.warn(s"Set points: $currentSetPoint") new PValue(currentSetPoint(receiverUuid).toQuantity) case IssuePowerControl(tick, setPower) => 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 d81095c748..90fcf4ee7a 100644 --- a/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala +++ b/src/main/scala/edu/ie3/simona/service/em/EmServiceCore.scala @@ -173,8 +173,6 @@ trait EmServiceCore { setPoints: Map[UUID, EmSetPoint], log: Logger, ): Unit = { - log.info(s"Handling of set points: $setPoints") - setPoints.foreach { case (agent, setPoint) => uuidToAgent.get(agent) match { case Some(receiver) =>