diff --git a/CHANGELOG.md b/CHANGELOG.md index 2206139ac9..f0d5ad75c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,6 +143,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Typo and format of `ThermalGrid` and `ThermalHouse` ScalaDocs [#1196](https://github.com/ie3-institute/simona/issues/1196) - Refactor `EmRuntimeConfig` [#1181](https://github.com/ie3-institute/simona/issues/1181) - Based `PvModel` calculations on irradiance (power per area) instead of irradiation (energy per area) [#1212](https://github.com/ie3-institute/simona/issues/1212) +- Converting `ExtEvDataService` to pekko typed [#1214](https://github.com/ie3-institute/simona/issues/1214) +- Converting `WeatherService` to pekko typed [#1216](https://github.com/ie3-institute/simona/issues/1216) ### 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/agent/EnvironmentRefs.scala b/src/main/scala/edu/ie3/simona/agent/EnvironmentRefs.scala index 4b5c6c920d..9cbc12cc1e 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 +import edu.ie3.simona.ontology.messages.services.{EvMessage, WeatherMessage} import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.{ActorRef => ClassicRef} @@ -29,6 +30,6 @@ final case class EnvironmentRefs( scheduler: ActorRef[SchedulerMessage], runtimeEventListener: ActorRef[RuntimeEvent], primaryServiceProxy: ClassicRef, - weather: ClassicRef, - evDataService: Option[ClassicRef], + weather: ActorRef[WeatherMessage], + evDataService: Option[ActorRef[EvMessage]], ) 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 750fd89681..3d53a9d325 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala @@ -38,6 +38,11 @@ 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.{ + EvMessage, + ServiceMessage, + WeatherMessage, +} import edu.ie3.simona.service.ServiceType import edu.ie3.simona.util.ConfigUtil import edu.ie3.simona.util.ConfigUtil._ @@ -334,7 +339,7 @@ class GridAgentController( maybeControllingEm: Option[ActorRef[FlexResponse]], ): ActorRef[ParticipantAgent.Request] = { - val serviceMap: Map[ServiceType, ClassicRef] = + val serviceMap: Map[ServiceType, ActorRef[_ >: ServiceMessage]] = Seq( Some(ServiceType.WeatherService -> environmentRefs.weather), environmentRefs.evDataService.map(ref => @@ -527,7 +532,7 @@ class GridAgentController( evcsInput: EvcsInput, modelConfiguration: EvcsRuntimeConfig, primaryServiceProxy: ClassicRef, - evMovementsService: ClassicRef, + evMovementsService: ActorRef[EvMessage], simulationStartDate: ZonedDateTime, simulationEndDate: ZonedDateTime, resolution: Long, @@ -545,7 +550,7 @@ class GridAgentController( primaryServiceProxy, Iterable( ActorExtEvDataService( - evMovementsService + evMovementsService.toClassic ) ), simulationStartDate, @@ -591,7 +596,7 @@ class GridAgentController( thermalGrid: ThermalGrid, modelConfiguration: HpRuntimeConfig, primaryServiceProxy: ClassicRef, - weatherService: ClassicRef, + weatherService: ActorRef[WeatherMessage], requestVoltageDeviationThreshold: Double, outputConfig: NotifierConfig, maybeControllingEm: Option[ActorRef[FlexResponse]], @@ -605,7 +610,7 @@ class GridAgentController( thermalGrid, modelConfiguration, primaryServiceProxy, - Iterable(ActorWeatherService(weatherService)), + Iterable(ActorWeatherService(weatherService.toClassic)), simulationStartDate, simulationEndDate, resolution, diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ServiceRegistration.scala b/src/main/scala/edu/ie3/simona/agent/participant/ServiceRegistration.scala index 727d2b787a..809be7666f 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ServiceRegistration.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ServiceRegistration.scala @@ -26,6 +26,7 @@ import edu.ie3.simona.model.participant.{ } import edu.ie3.simona.ontology.messages.services.EvMessage.RegisterForEvDataMessage import edu.ie3.simona.ontology.messages.services.WeatherMessage.RegisterForWeatherMessage +import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps trait ServiceRegistration[ PD <: PrimaryDataWithComplexPower[PD], @@ -122,7 +123,7 @@ trait ServiceRegistration[ s"is invalid." ) } - serviceRef ! RegisterForWeatherMessage(participantRef, lat, lon) + serviceRef ! RegisterForWeatherMessage(participantRef.toTyped, lat, lon) } /** Register for the EV movement service @@ -139,7 +140,7 @@ trait ServiceRegistration[ ): Unit = { inputModel match { case evcsInput: EvcsInput => - serviceRef ! RegisterForEvDataMessage(evcsInput.getUuid) + serviceRef ! RegisterForEvDataMessage(self.toTyped, evcsInput.getUuid) case _ => throw new ServiceRegistrationException( s"Cannot register for EV movements information at node ${inputModel.getNode.getId} " + diff --git a/src/main/scala/edu/ie3/simona/agent/participant2/ParticipantAgentInit.scala b/src/main/scala/edu/ie3/simona/agent/participant2/ParticipantAgentInit.scala index c3b55d9b23..5bcda65c81 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant2/ParticipantAgentInit.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant2/ParticipantAgentInit.scala @@ -20,12 +20,13 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ } import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.services.EvMessage.RegisterForEvDataMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.PrimaryServiceRegistrationMessage import edu.ie3.simona.ontology.messages.services.WeatherMessage.RegisterForWeatherMessage import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.service.ServiceType import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK -import org.apache.pekko.actor.typed.scaladsl.Behaviors +import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import org.apache.pekko.actor.typed.{ActorRef, Behavior} import org.apache.pekko.actor.{ActorRef => ClassicRef} @@ -55,7 +56,7 @@ object ParticipantAgentInit { final case class ParticipantRefs( gridAgent: ActorRef[GridAgent.Request], primaryServiceProxy: ClassicRef, - services: Map[ServiceType, ClassicRef], + services: Map[ServiceType, ActorRef[_ >: ServiceMessage]], resultListener: Iterable[ActorRef[ResultEvent]], ) @@ -241,12 +242,14 @@ object ParticipantAgentInit { // requiring at least one secondary service, thus send out registrations and wait for replies val requiredServices = requiredServiceTypes .map(serviceType => - serviceType -> participantRefs.services.getOrElse( - serviceType, - throw new CriticalFailureException( - s"${modelShell.identifier}: Service of type $serviceType is not available." - ), - ) + serviceType -> participantRefs.services + .getOrElse( + serviceType, + throw new CriticalFailureException( + s"${modelShell.identifier}: Service of type $serviceType is not available." + ), + ) + .toClassic ) .toMap @@ -257,7 +260,7 @@ object ParticipantAgentInit { modelShell, serviceType, serviceRef, - ) + )(ctx) } waitingForServices( @@ -277,18 +280,14 @@ object ParticipantAgentInit { modelShell: ParticipantModelShell[_, _], serviceType: ServiceType, serviceRef: ClassicRef, - ): Unit = + )(implicit ctx: ActorContext[ParticipantAgent.Request]): Unit = serviceType match { case ServiceType.WeatherService => val geoPosition = participantInput.getNode.getGeoPosition Option(geoPosition.getY).zip(Option(geoPosition.getX)) match { case Some((lat, lon)) => - serviceRef ! RegisterForWeatherMessage( - participantRef.toClassic, - lat, - lon, - ) + serviceRef ! RegisterForWeatherMessage(ctx.self, lat, lon) case _ => throw new CriticalFailureException( s"${modelShell.identifier} cannot register for weather information at " + @@ -304,7 +303,7 @@ object ParticipantAgentInit { ) case ServiceType.EvMovementService => - serviceRef ! RegisterForEvDataMessage(modelShell.uuid) + serviceRef ! RegisterForEvDataMessage(ctx.self, modelShell.uuid) } /** Waiting for replies from secondary services. If all replies have been 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..d8a3b2c0df 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 @@ -7,8 +7,11 @@ package edu.ie3.simona.ontology.messages.services import edu.ie3.simona.agent.participant.data.Data.SecondaryData +import edu.ie3.simona.agent.participant2.ParticipantAgent +import edu.ie3.simona.agent.participant2.ParticipantAgent.ParticipantRequest import edu.ie3.simona.model.participant.evcs.EvModelWrapper import edu.ie3.simona.ontology.messages.services.ServiceMessage.ServiceRegistrationMessage +import org.apache.pekko.actor.typed.ActorRef import java.util.UUID @@ -16,14 +19,19 @@ sealed trait EvMessage object EvMessage { + private[services] trait EvInternal extends EvMessage + /** Indicate the [[edu.ie3.simona.service.ev.ExtEvDataService]] that the * requesting agent wants to receive EV movements * + * @param actorRef + * actor ref for the agent to be registered * @param evcs * the charging station */ final case class RegisterForEvDataMessage( - evcs: UUID + actorRef: ActorRef[ParticipantAgent.Request], + evcs: UUID, ) extends EvMessage with ServiceRegistrationMessage @@ -35,6 +43,8 @@ object EvMessage { * The latest tick that the data is requested for */ final case class EvFreeLotsRequest(tick: Long) + extends EvMessage + with ParticipantRequest /** Requests EV models of departing EVs with given UUIDs * @@ -44,6 +54,8 @@ object EvMessage { * The UUIDs of EVs that are requested */ final case class DepartingEvsRequest(tick: Long, departingEvs: Seq[UUID]) + extends EvMessage + with ParticipantRequest /** Holds arrivals for one charging station * 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 7d77380dd4..fab296c86c 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,19 +6,45 @@ package edu.ie3.simona.ontology.messages.services -import org.apache.pekko.actor.ActorRef +import org.apache.pekko.actor.{ActorRef => ClassicRef} import java.util.UUID -import edu.ie3.simona.agent.participant.data.Data +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.ontology.messages.services.WeatherMessage.WeatherInternal import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey +import edu.ie3.simona.service.ServiceStateData.InitializeServiceStateData /** Collections of all messages, that are send to and from the different * services */ -sealed trait ServiceMessage +sealed trait ServiceMessage extends EvInternal with WeatherInternal object ServiceMessage { + final case class WrappedActivation(activation: Activation) + extends ServiceMessage + + final case class WrappedExternalMessage( + extMsg: DataMessageFromExt + ) extends ServiceMessage + + /** Service initialization data can sometimes only be constructed once the + * service actor is created (e.g. + * [[edu.ie3.simona.service.ev.ExtEvDataService]]). Thus, we need an extra + * initialization message. + */ + final case class Create[+I <: InitializeServiceStateData]( + initializeStateData: I, + unlockKey: ScheduleKey, + ) extends ServiceMessage + + final case class ScheduleServiceActivation( + tick: Long, + unlockKey: ScheduleKey, + ) extends ServiceMessage + /** Message used to register for a service */ trait ServiceRegistrationMessage extends ServiceMessage @@ -31,7 +57,7 @@ object ServiceMessage { * Identifier of the input model */ final case class PrimaryServiceRegistrationMessage( - requestingActor: ActorRef, + requestingActor: ClassicRef, inputModelUuid: UUID, ) extends ServiceRegistrationMessage @@ -42,12 +68,6 @@ 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 ScheduleServiceActivation( - tick: Long, - unlockKey: ScheduleKey, - ) - } diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/WeatherMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/WeatherMessage.scala index e45baf14a9..4f7bead24d 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/WeatherMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/WeatherMessage.scala @@ -7,9 +7,10 @@ package edu.ie3.simona.ontology.messages.services import edu.ie3.simona.agent.participant.data.Data.SecondaryData +import edu.ie3.simona.agent.participant2.ParticipantAgent import edu.ie3.simona.ontology.messages.services.ServiceMessage.ServiceRegistrationMessage import edu.ie3.util.scala.quantities.Irradiance -import org.apache.pekko.actor.ActorRef +import org.apache.pekko.actor.typed.ActorRef import squants.{Temperature, Velocity} sealed trait WeatherMessage @@ -22,6 +23,8 @@ sealed trait WeatherMessage */ object WeatherMessage { + private[services] trait WeatherInternal extends WeatherMessage + /** Indicate the [[edu.ie3.simona.service.weather.WeatherService]] that the * requesting agent wants to receive weather for the provided coordinates * @@ -33,7 +36,7 @@ object WeatherMessage { * Longitude of the requested location */ final case class RegisterForWeatherMessage( - requestingActor: ActorRef, + requestingActor: ActorRef[ParticipantAgent.Request], latitude: Double, longitude: Double, ) extends WeatherMessage diff --git a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala index 31800da210..bf8c97368d 100644 --- a/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala +++ b/src/main/scala/edu/ie3/simona/service/ExtDataSupport.scala @@ -8,22 +8,41 @@ 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.service.ServiceStateData.ServiceBaseStateData +import edu.ie3.simona.ontology.messages.services.ServiceMessage +import edu.ie3.simona.ontology.messages.services.ServiceMessage.WrappedExternalMessage +import edu.ie3.simona.service.ServiceStateData.ServiceConstantStateData +import org.apache.pekko.actor.typed.Behavior +import org.apache.pekko.actor.typed.scaladsl.{Behaviors, StashBuffer} +/** Trait that enables handling of external data. + * @tparam T + * the type of messages this service accepts + */ trait ExtDataSupport[ - S <: ServiceBaseStateData + T >: ServiceMessage ] { - this: SimonaService[S] => + this: TypedSimonaService[T] => - override def idleExternal(implicit stateData: S): Receive = { - case extMsg: DataMessageFromExt => + override private[service] def idleExternal(implicit + stateData: S, + constantData: ServiceConstantStateData, + buffer: StashBuffer[T], + ): Behavior[T] = Behaviors.receive { + case (_, WrappedExternalMessage(extMsg)) => val updatedStateData = handleDataMessage(extMsg)(stateData) - context become idle(updatedStateData) - case extResponseMsg: EvResponseMessage => + buffer.unstashAll(idle(updatedStateData, constantData, buffer)) + + case (_, extResponseMsg: EvResponseMessage) => val updatedStateData = handleDataResponseMessage(extResponseMsg)(stateData) - context become idle(updatedStateData) + + buffer.unstashAll(idle(updatedStateData, constantData, buffer)) + + case (ctx, unsupported) => + ctx.log.warn(s"Received unsupported message: $unsupported!") + buffer.stash(unsupported) + buffer.unstashAll(idleInternal) } /** Handle a message from outside the simulation diff --git a/src/main/scala/edu/ie3/simona/service/ServiceStateData.scala b/src/main/scala/edu/ie3/simona/service/ServiceStateData.scala index b8075a6e87..2ca34fbef0 100644 --- a/src/main/scala/edu/ie3/simona/service/ServiceStateData.scala +++ b/src/main/scala/edu/ie3/simona/service/ServiceStateData.scala @@ -6,7 +6,9 @@ package edu.ie3.simona.service +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.util.scala.collection.immutable.SortedDistinctSeq +import org.apache.pekko.actor.typed.ActorRef trait ServiceStateData @@ -18,6 +20,11 @@ object ServiceStateData { trait ServiceBaseStateData extends ServiceStateData + case class ServiceConstantStateData( + scheduler: ActorRef[SchedulerMessage], + activationAdapter: ActorRef[Activation], + ) extends ServiceStateData + /** Indicate that the service is initialized */ trait ServiceActivationBaseStateData extends ServiceBaseStateData { diff --git a/src/main/scala/edu/ie3/simona/service/TypedSimonaService.scala b/src/main/scala/edu/ie3/simona/service/TypedSimonaService.scala new file mode 100644 index 0000000000..68510f52e5 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/service/TypedSimonaService.scala @@ -0,0 +1,275 @@ +/* + * © 2020. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.service + +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, + ScheduleServiceActivation, + ServiceRegistrationMessage, + WrappedActivation, +} +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey +import edu.ie3.simona.service.ServiceStateData.{ + InitializeServiceStateData, + ServiceBaseStateData, + ServiceConstantStateData, +} +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import org.apache.pekko.actor.typed.scaladsl.{ + ActorContext, + Behaviors, + StashBuffer, +} +import org.apache.pekko.actor.typed.{ActorRef, Behavior} + +import scala.language.implicitConversions +import scala.util.{Failure, Success, Try} + +/** Abstract description of a service agent, that is able to announce new + * information to registered participants + * + * @tparam T + * the type of messages this service accepts + */ +abstract class TypedSimonaService[ + T >: ServiceMessage +] { + + /** The service specific type of the [[ServiceStateData]] + */ + type S <: ServiceBaseStateData + + def apply( + scheduler: ActorRef[SchedulerMessage], + bufferSize: Int = 100, + ): Behavior[T] = Behaviors.withStash(bufferSize) { buffer => + Behaviors.setup { ctx => + val activationAdapter: ActorRef[Activation] = + ctx.messageAdapter[Activation](msg => WrappedActivation(msg)) + + val constantData: ServiceConstantStateData = + ServiceConstantStateData( + scheduler, + activationAdapter, + ) + + uninitialized(constantData, buffer) + } + } + + /** Receive method that is used before the service is initialized. Represents + * the state "Uninitialized". + * + * @return + * idleInternal methods for the uninitialized state + */ + def uninitialized(implicit + constantData: ServiceConstantStateData, + buffer: StashBuffer[T], + ): Behavior[T] = Behaviors.receive { + case ( + _, + Create( + initializeStateData: InitializeServiceStateData, + unlockKey: ScheduleKey, + ), + ) => + constantData.scheduler ! ScheduleActivation( + constantData.activationAdapter, + INIT_SIM_TICK, + Some(unlockKey), + ) + + initializing(initializeStateData) + + // not ready yet to handle registrations, stash request away + case (_, msg: ServiceRegistrationMessage) => + buffer.stash(msg) + Behaviors.same + + } + + private def initializing( + initializeStateData: InitializeServiceStateData + )(implicit + constantData: ServiceConstantStateData, + buffer: StashBuffer[T], + ): Behavior[T] = Behaviors.receive { + case (ctx, WrappedActivation(Activation(INIT_SIM_TICK))) => + // init might take some time and could go wrong if invalid initialize service data is received + // execute complete and unstash only if init is carried out successfully + init( + initializeStateData + ) match { + case Success((serviceStateData, maybeNewTick)) => + constantData.scheduler ! Completion( + constantData.activationAdapter, + maybeNewTick, + ) + buffer.unstashAll(idle(serviceStateData, constantData, buffer)) + case Failure(exception) => + // initialize service trigger with invalid data + ctx.log.error( + "Error during service initialization." + + s"\nReceivedData: {}" + + s"\nException: {}", + initializeStateData, + exception, + ) + throw exception // if a service fails startup we don't want to go on with the simulation + } + + // not ready yet to handle registrations, stash request away + case (_, msg: ServiceRegistrationMessage) => + buffer.stash(msg) + Behaviors.same + + case (_, msg: WrappedActivation) => + buffer.stash(msg) + Behaviors.same + + // unhandled message + case (ctx, x) => + ctx.log.error(s"Received unhandled message: $x") + Behaviors.unhandled + } + + /** Default receive method when the service is initialized. Requires the + * actual state data of this service to be ready to be used. + * + * @param stateData + * the state data of this service + * @return + * default idleInternal method when the service is initialized + */ + final protected def idle(implicit + stateData: S, + constantData: ServiceConstantStateData, + buffer: StashBuffer[T], + ): Behavior[T] = idleExternal + + private[service] def idleInternal(implicit + stateData: S, + constantData: ServiceConstantStateData, + buffer: StashBuffer[T], + ): Behavior[T] = Behaviors.receive { + // agent registration process + case (ctx, registrationMsg: ServiceRegistrationMessage) => + /* Someone asks to register for information from the service */ + handleRegistrationRequest(registrationMsg)(stateData, ctx) match { + case Success(stateData) => idle(stateData, constantData, buffer) + case Failure(exception) => + ctx.log.error( + "Error during registration." + + "\nMsg: {}" + + "\nException: {}", + registrationMsg, + exception, + ) + Behaviors.unhandled + } + + case (_, ScheduleServiceActivation(tick, unlockKey)) => + constantData.scheduler ! ScheduleActivation( + constantData.activationAdapter, + tick, + Some(unlockKey), + ) + + buffer.unstashAll(idleInternal) + + // activity start trigger for this service + case (ctx, WrappedActivation(Activation(tick))) => + /* The scheduler sends out an activity start trigger. Announce new data to all registered recipients. */ + val (updatedStateData, maybeNewTriggers) = + announceInformation(tick)(stateData, ctx) + constantData.scheduler ! Completion( + constantData.activationAdapter, + maybeNewTriggers, + ) + + buffer.unstashAll(idle(updatedStateData, constantData, buffer)) + + // unhandled message + case (ctx, x) => + ctx.log.error("Unhandled message received:{}", x) + Behaviors.unhandled + } + + /** Internal api method that allows handling incoming messages from external + * simulations + * + * @param stateData + * the state data of this service + * @return + * the [[idleExternal()]] behavior as default, to override extend + * [[ExtDataSupport]] + */ + private[service] def idleExternal(implicit + stateData: S, + constantData: ServiceConstantStateData, + buffer: StashBuffer[T], + ): Behavior[T] = idleInternal + + /** 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 sent to + * the [[edu.ie3.simona.scheduler.Scheduler]] together with the completion + * message that is sent in response to the trigger that is sent 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 + */ + def init( + initServiceData: InitializeServiceStateData + ): Try[(S, Option[Long])] + + /** 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) + */ + protected def handleRegistrationRequest( + registrationMessage: ServiceRegistrationMessage + )(implicit + serviceStateData: S, + ctx: ActorContext[T], + ): Try[S] + + /** 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 sent + * in response to the trigger that was sent to start this announcement + */ + protected def announceInformation(tick: Long)(implicit + serviceStateData: S, + ctx: ActorContext[T], + ): (S, Option[Long]) + +} 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..8cb4db1596 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.service.ev +import edu.ie3.simona.agent.participant2.ParticipantAgent import edu.ie3.simona.agent.participant2.ParticipantAgent.{ DataProvision, RegistrationSuccessfulMessage, @@ -21,36 +22,42 @@ import edu.ie3.simona.exceptions.{ ServiceException, } import edu.ie3.simona.model.participant.evcs.EvModelWrapper +import edu.ie3.simona.ontology.messages.services.EvMessage 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.{ + ServiceRegistrationMessage, + WrappedExternalMessage, +} import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceBaseStateData, } -import edu.ie3.simona.service.ev.ExtEvDataService.{ - ExtEvStateData, - InitExtEvData, +import edu.ie3.simona.service.{ + ExtDataSupport, + ServiceStateData, + TypedSimonaService, } -import edu.ie3.simona.service.{ExtDataSupport, ServiceStateData, SimonaService} import edu.ie3.simona.util.ReceiveDataMap import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK -import org.apache.pekko.actor.{ActorContext, ActorRef, Props} +import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps +import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} +import org.apache.pekko.actor.typed.{ActorRef, Behavior} +import org.slf4j.Logger import java.util.UUID import scala.jdk.CollectionConverters._ import scala.jdk.OptionConverters._ import scala.util.{Failure, Success, Try} -object ExtEvDataService { +object ExtEvDataService + extends TypedSimonaService[EvMessage] + with ExtDataSupport[EvMessage] { - def props(scheduler: ActorRef): Props = - Props( - new ExtEvDataService(scheduler: ActorRef) - ) + override type S = ExtEvStateData final case class ExtEvStateData( extEvData: ExtEvDataConnection, - uuidToActorRef: Map[UUID, ActorRef] = Map.empty[UUID, ActorRef], + uuidToActorRef: Map[UUID, ActorRef[ParticipantAgent.Request]] = Map.empty, extEvMessage: Option[EvDataMessageFromExt] = None, freeLots: ReceiveDataMap[UUID, Int] = ReceiveDataMap.empty, departingEvResponses: ReceiveDataMap[UUID, Seq[EvModelWrapper]] = @@ -61,12 +68,26 @@ object ExtEvDataService { extEvData: ExtEvDataConnection ) extends InitializeServiceStateData -} - -class ExtEvDataService(override val scheduler: ActorRef) - extends SimonaService[ExtEvStateData](scheduler) - with ExtDataSupport[ExtEvStateData] { + def adapter(evService: ActorRef[EvMessage]): Behavior[DataMessageFromExt] = + Behaviors.receiveMessagePartial { extMsg => + evService ! WrappedExternalMessage(extMsg) + Behaviors.same + } + /** 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 sent to + * the [[edu.ie3.simona.scheduler.Scheduler]] together with the completion + * message that is sent in response to the trigger that is sent 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: ServiceStateData.InitializeServiceStateData ): Try[ @@ -107,11 +128,12 @@ class ExtEvDataService(override val scheduler: ActorRef) override def handleRegistrationRequest( registrationMessage: ServiceRegistrationMessage )(implicit - serviceStateData: ExtEvStateData - ): Try[ExtEvStateData] = + serviceStateData: S, + ctx: ActorContext[EvMessage], + ): Try[S] = registrationMessage match { - case RegisterForEvDataMessage(evcs) => - Success(handleRegistrationRequest(sender(), evcs)) + case RegisterForEvDataMessage(actorRef, evcs) => + Success(handleRegistrationRequest(actorRef, evcs)) case invalidMessage => Failure( InvalidRegistrationRequestException( @@ -135,12 +157,13 @@ class ExtEvDataService(override val scheduler: ActorRef) * information if the registration has been carried out successfully */ private def handleRegistrationRequest( - agentToBeRegistered: ActorRef, + agentToBeRegistered: ActorRef[ParticipantAgent.Request], evcs: UUID, )(implicit - serviceStateData: ExtEvStateData + serviceStateData: ExtEvStateData, + ctx: ActorContext[EvMessage], ): ExtEvStateData = { - log.debug( + ctx.log.debug( "Received ev movement service registration from {} for [Evcs:{}]", agentToBeRegistered.path.name, evcs, @@ -157,7 +180,7 @@ class ExtEvDataService(override val scheduler: ActorRef) ) case Some(_) => // actor is already registered, do nothing - log.warning( + ctx.log.warn( "Sending actor {} is already registered", agentToBeRegistered, ) @@ -173,39 +196,47 @@ class ExtEvDataService(override val scheduler: ActorRef) * 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 sent + * with updated values) together with the message that is sent * in response to the trigger that was sent to start this announcement */ override protected def announceInformation( tick: Long - )(implicit serviceStateData: ExtEvStateData, ctx: ActorContext): ( - ExtEvStateData, + )(implicit + serviceStateData: S, + ctx: ActorContext[EvMessage], + ): ( + S, Option[Long], ) = { def asScala[E] : java.util.Map[UUID, java.util.List[E]] => Map[UUID, Seq[E]] = map => map.asScala.view.mapValues(_.asScala.toSeq).toMap - serviceStateData.extEvMessage.getOrElse( - throw ServiceException( - "ExtEvDataService was triggered without ExtEvMessage available" - ) - ) match { - case _: RequestCurrentPrices => - requestCurrentPrices() - case _: RequestEvcsFreeLots => - requestFreeLots(tick) - case departingEvsRequest: RequestDepartingEvs => - requestDepartingEvs(tick, asScala(departingEvsRequest.departures)) - case arrivingEvsProvision: ProvideArrivingEvs => - handleArrivingEvs( - tick, - asScala(arrivingEvsProvision.arrivals), - arrivingEvsProvision.maybeNextTick.toScala.map(Long2long), - )( - serviceStateData + implicit val log: Logger = ctx.log + + serviceStateData.extEvMessage + .map { + case _: RequestCurrentPrices => + requestCurrentPrices() + case _: RequestEvcsFreeLots => + requestFreeLots(tick) + case departingEvsRequest: RequestDepartingEvs => + requestDepartingEvs(tick, asScala(departingEvsRequest.departures)) + case arrivingEvsProvision: ProvideArrivingEvs => + handleArrivingEvs( + tick, + asScala(arrivingEvsProvision.arrivals), + arrivingEvsProvision.maybeNextTick.toScala.map(Long2long), + )( + serviceStateData, + ctx, + ) + } + .getOrElse( + throw ServiceException( + "ExtEvDataService was triggered without ExtEvMessage available" ) - } + ) } private def requestCurrentPrices()(implicit @@ -257,7 +288,8 @@ class ExtEvDataService(override val scheduler: ActorRef) tick: Long, requestedDepartingEvs: Map[UUID, Seq[UUID]], )(implicit - serviceStateData: ExtEvStateData + serviceStateData: ExtEvStateData, + log: Logger, ): (ExtEvStateData, Option[Long]) = { val departingEvResponses = @@ -269,7 +301,7 @@ class ExtEvDataService(override val scheduler: ActorRef) Some(evcs) case None => - log.warning( + log.warn( "A corresponding actor ref for UUID {} could not be found", evcs, ) @@ -297,12 +329,11 @@ class ExtEvDataService(override val scheduler: ActorRef) allArrivingEvs: Map[UUID, Seq[EvModel]], maybeNextTick: Option[Long], )(implicit - serviceStateData: ExtEvStateData + serviceStateData: ExtEvStateData, + ctx: ActorContext[EvMessage], ): (ExtEvStateData, Option[Long]) = { if (tick == INIT_SIM_TICK) { - // During initialization, an empty ProvideArrivingEvs message - // is sent, which includes the first relevant tick val nextTick = maybeNextTick.getOrElse( throw new CriticalFailureException( @@ -312,7 +343,7 @@ class ExtEvDataService(override val scheduler: ActorRef) serviceStateData.uuidToActorRef.foreach { case (_, actor) => actor ! RegistrationSuccessfulMessage( - self, + ctx.self.toClassic, nextTick, ) } @@ -324,7 +355,7 @@ class ExtEvDataService(override val scheduler: ActorRef) actor ! DataProvision( tick, - self, + ctx.self.toClassic, ArrivingEvs(evs.map(EvModelWrapper.apply)), maybeNextTick, ) @@ -342,7 +373,7 @@ class ExtEvDataService(override val scheduler: ActorRef) override protected def handleDataMessage( extMsg: DataMessageFromExt - )(implicit serviceStateData: ExtEvStateData): ExtEvStateData = + )(implicit serviceStateData: S): S = extMsg match { case extEvMessage: EvDataMessageFromExt => serviceStateData.copy( 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 70d0bd1ff9..03c1697b3b 100644 --- a/src/main/scala/edu/ie3/simona/service/weather/WeatherService.scala +++ b/src/main/scala/edu/ie3/simona/service/weather/WeatherService.scala @@ -6,29 +6,23 @@ package edu.ie3.simona.service.weather +import edu.ie3.simona.agent.participant2.ParticipantAgent +import edu.ie3.simona.config.SimonaConfig +import edu.ie3.simona.exceptions.InitializationException +import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.agent.participant2.ParticipantAgent.{ DataProvision, RegistrationFailedMessage, RegistrationSuccessfulMessage, } -import org.apache.pekko.actor.{ActorContext, ActorRef, Props} -import edu.ie3.simona.exceptions.{ - CriticalFailureException, - InitializationException, -} -import edu.ie3.simona.config.SimonaConfig -import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.ontology.messages.services.ServiceMessage.ServiceRegistrationMessage +import edu.ie3.simona.ontology.messages.services.WeatherMessage import edu.ie3.simona.ontology.messages.services.WeatherMessage._ -import edu.ie3.simona.service.SimonaService import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, - ServiceActivationBaseStateData, -} -import edu.ie3.simona.service.weather.WeatherService.{ - InitWeatherServiceStateData, - WeatherInitializedStateData, + ServiceBaseStateData, } +import edu.ie3.simona.service.TypedSimonaService import edu.ie3.simona.service.weather.WeatherSource.{ AgentCoordinates, WeightedCoordinates, @@ -36,26 +30,22 @@ import edu.ie3.simona.service.weather.WeatherSource.{ import edu.ie3.simona.util.SimonaConstants import edu.ie3.simona.util.TickUtil.RichZonedDateTime import edu.ie3.util.scala.collection.immutable.SortedDistinctSeq +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.time.ZonedDateTime import scala.util.{Failure, Success, Try} -object WeatherService { +/** Weather Service is responsible to register other actors that require weather + * information and provide weather information when requested + * + * @version 0.1 + * @since 2019-07-28 + */ +object WeatherService extends TypedSimonaService[WeatherMessage] { - def props( - scheduler: ActorRef, - startDateTime: ZonedDateTime, - simulationEnd: ZonedDateTime, - amountOfInterpolationCoordinates: Int = 4, - ): Props = - Props( - new WeatherService( - scheduler, - startDateTime, - simulationEnd, - amountOfInterpolationCoordinates, - ) - ) + override type S = WeatherInitializedStateData /** @param weatherSource * weather source to receive information from @@ -72,14 +62,15 @@ object WeatherService { */ final case class WeatherInitializedStateData( weatherSource: WeatherSource, - coordsToActorRefMap: Map[AgentCoordinates, Vector[ActorRef]] = - Map.empty[AgentCoordinates, Vector[ActorRef]], + coordsToActorRefMap: Map[AgentCoordinates, Vector[ + ActorRef[ParticipantAgent.Request] + ]] = Map.empty, weightedWeatherCoordinates: Map[AgentCoordinates, WeightedCoordinates] = Map.empty[AgentCoordinates, WeightedCoordinates], - override val maybeNextActivationTick: Option[Long], - override val activationTicks: SortedDistinctSeq[Long] = - SortedDistinctSeq.empty, - ) extends ServiceActivationBaseStateData + maybeNextActivationTick: Option[Long], + activationTicks: SortedDistinctSeq[Long] = SortedDistinctSeq.empty, + amountOfInterpolationCoords: Int = 4, + ) extends ServiceBaseStateData /** Weather service state data used for initialization of the weather service * @@ -87,26 +78,11 @@ object WeatherService { * the definition of the source to use */ final case class InitWeatherServiceStateData( - sourceDefinition: SimonaConfig.Simona.Input.Weather.Datasource + sourceDefinition: SimonaConfig.Simona.Input.Weather.Datasource, + startDateTime: ZonedDateTime, + simulationEnd: ZonedDateTime, ) extends InitializeServiceStateData -} - -/** Weather Service is responsible to register other actors that require weather - * information and provide weather information when requested - * - * @version 0.1 - * @since 2019-07-28 - */ -final case class WeatherService( - override val scheduler: ActorRef, - private implicit val simulationStart: ZonedDateTime, - simulationEnd: ZonedDateTime, - private val amountOfInterpolationCoords: Int, -) extends SimonaService[ - WeatherInitializedStateData - ](scheduler) { - /** 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 @@ -125,7 +101,13 @@ final case class WeatherService( initServiceData: InitializeServiceStateData ): Try[(WeatherInitializedStateData, Option[Long])] = initServiceData match { - case InitWeatherServiceStateData(sourceDefinition) => + case InitWeatherServiceStateData( + sourceDefinition, + startDateTime, + simulationEnd, + ) => + implicit val simulationStart: ZonedDateTime = startDateTime + val weatherSource = WeatherSource(sourceDefinition) /* What is the first tick to be triggered for? And what are further activation ticks */ @@ -170,11 +152,18 @@ final case class WeatherService( override def handleRegistrationRequest( registrationMessage: ServiceRegistrationMessage )(implicit - serviceStateData: WeatherInitializedStateData + serviceStateData: WeatherInitializedStateData, + ctx: ActorContext[WeatherMessage], ): Try[WeatherInitializedStateData] = registrationMessage match { - case RegisterForWeatherMessage(actor, latitude, longitude) => - Success(handleRegistrationRequest(actor, latitude, longitude)) + case RegisterForWeatherMessage( + agentToBeRegistered, + latitude, + longitude, + ) => + Success( + handleRegistrationRequest(agentToBeRegistered, latitude, longitude) + ) case invalidMessage => Failure( InvalidRegistrationRequestException( @@ -200,15 +189,16 @@ final case class WeatherService( * information if the registration has been carried out successfully */ private def handleRegistrationRequest( - agentToBeRegistered: ActorRef, + agentToBeRegistered: ActorRef[ParticipantAgent.Request], latitude: Double, longitude: Double, )(implicit - serviceStateData: WeatherInitializedStateData + serviceStateData: WeatherInitializedStateData, + ctx: ActorContext[_], ): WeatherInitializedStateData = { - log.debug( + ctx.log.debug( "Received weather registration from {} for [Lat:{}, Long:{}]", - agentToBeRegistered.path.name, + agentToBeRegistered, latitude, longitude, ) @@ -219,22 +209,22 @@ final case class WeatherService( longitude, ) + val RegistrationMessage = serviceStateData.maybeNextActivationTick match { + case Some(nextActivationTick) => + RegistrationSuccessfulMessage(_, nextActivationTick) + case None => + RegistrationFailedMessage + } + serviceStateData.coordsToActorRefMap.get(agentCoord) match { case None => /* The coordinate itself is not known yet. Try to figure out, which weather coordinates are relevant */ serviceStateData.weatherSource.getWeightedCoordinates( agentCoord, - amountOfInterpolationCoords, + serviceStateData.amountOfInterpolationCoords, ) match { case Success(weightedCoordinates) => - agentToBeRegistered ! RegistrationSuccessfulMessage( - self, - serviceStateData.maybeNextActivationTick.getOrElse( - throw new CriticalFailureException( - "No first data tick for weather service" - ) - ), - ) + agentToBeRegistered ! RegistrationMessage(ctx.self.toClassic) /* Enhance the mapping from agent coordinate to requesting actor's ActorRef as well as the necessary * weather coordinates for later averaging. */ @@ -247,24 +237,17 @@ final case class WeatherService( serviceStateData.weightedWeatherCoordinates + (agentCoord -> weightedCoordinates), ) case Failure(exception) => - log.error( - exception, + ctx.log.error( s"Unable to obtain necessary information to register for coordinate $agentCoord.", + exception, ) - agentToBeRegistered ! RegistrationFailedMessage(self) + agentToBeRegistered ! RegistrationFailedMessage(ctx.self.toClassic) serviceStateData } case Some(actorRefs) if !actorRefs.contains(agentToBeRegistered) => // coordinate is already known (= we have data for it), but this actor is not registered yet - agentToBeRegistered ! RegistrationSuccessfulMessage( - self, - serviceStateData.maybeNextActivationTick.getOrElse( - throw new CriticalFailureException( - "No first data tick for weather service" - ) - ), - ) + agentToBeRegistered ! RegistrationMessage(ctx.self.toClassic) serviceStateData.copy( coordsToActorRefMap = @@ -273,7 +256,7 @@ final case class WeatherService( case Some(actorRefs) if actorRefs.contains(agentToBeRegistered) => // actor is already registered, do nothing - log.warning( + ctx.log.warn( "Sending actor {} is already registered", agentToBeRegistered, ) @@ -282,7 +265,7 @@ final case class WeatherService( case _ => // actor is not registered, and we don't have data for it // inform the agentToBeRegistered that the registration failed as we don't have data for it - agentToBeRegistered ! RegistrationFailedMessage(self) + agentToBeRegistered ! RegistrationFailedMessage(ctx.self.toClassic) serviceStateData } } @@ -300,7 +283,7 @@ final case class WeatherService( */ override protected def announceInformation(tick: Long)(implicit serviceStateData: WeatherInitializedStateData, - ctx: ActorContext, + ctx: ActorContext[WeatherMessage], ): (WeatherInitializedStateData, Option[Long]) = { /* Pop the next activation tick and update the state data */ @@ -324,7 +307,7 @@ final case class WeatherService( recipients.foreach( _ ! DataProvision( tick, - self, + ctx.self.toClassic, weatherResult, maybeNextTick, ) @@ -337,5 +320,4 @@ final case class WeatherService( maybeNextTick, ) } - } diff --git a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala index c8196aaf30..d6ba5b5a1f 100644 --- a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala +++ b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala @@ -110,10 +110,10 @@ object SimonaSim { timeAdvancer, scheduler, primaryServiceProxy.toTyped, - weatherService.toTyped, + weatherService, ) ++ gridAgents ++ - extSimulationData.extDataServices.values.map(_.toTyped) + extSimulationData.extDataServices.values /* watch all actors */ resultEventListeners.foreach(ctx.watch) 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 3209fb706d..bbd88061ca 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -6,14 +6,18 @@ package edu.ie3.simona.sim.setup +import edu.ie3.simona.ontology.messages.services.{EvMessage, ServiceMessage} import org.apache.pekko.actor.{ActorRef => ClassicRef} import edu.ie3.simona.service.ev.ExtEvDataService +import org.apache.pekko.actor.typed.ActorRef final case class ExtSimSetupData( extSimAdapters: Iterable[ClassicRef], - extDataServices: Map[Class[_], ClassicRef], + extDataServices: Map[Class[_], ActorRef[_ >: ServiceMessage]], ) { - def evDataService: Option[ClassicRef] = - extDataServices.get(classOf[ExtEvDataService]) + def evDataService: Option[ActorRef[EvMessage]] = + extDataServices + .get(classOf[ExtEvDataService.type]) + .map { case service: ActorRef[EvMessage] => service } } 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 4c452a4c3c..1ffe56e569 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -13,6 +13,7 @@ import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.ontology.messages.SchedulerMessage +import edu.ie3.simona.ontology.messages.services.WeatherMessage import edu.ie3.simona.scheduler.TimeAdvancer import edu.ie3.simona.scheduler.core.Core.CoreFactory import edu.ie3.simona.scheduler.core.RegularSchedulerCore @@ -95,7 +96,7 @@ trait SimonaSetup { def weatherService( context: ActorContext[_], scheduler: ActorRef[SchedulerMessage], - ): ClassicRef + ): ActorRef[WeatherMessage] /** Loads external simulations and provides corresponding actors and init data * 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 c90b3177c9..a3a721604b 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -25,10 +25,13 @@ import edu.ie3.simona.exceptions.agent.GridAgentInitializationException import edu.ie3.simona.io.grid.GridProvider import edu.ie3.simona.ontology.messages.SchedulerMessage import edu.ie3.simona.ontology.messages.SchedulerMessage.ScheduleActivation +import edu.ie3.simona.ontology.messages.services.{ + ServiceMessage, + WeatherMessage, +} import edu.ie3.simona.scheduler.core.Core.CoreFactory import edu.ie3.simona.scheduler.core.RegularSchedulerCore import edu.ie3.simona.scheduler.{ScheduleLock, Scheduler, TimeAdvancer} -import edu.ie3.simona.service.SimonaService import edu.ie3.simona.service.ev.ExtEvDataService import edu.ie3.simona.service.ev.ExtEvDataService.InitExtEvData import edu.ie3.simona.service.primary.PrimaryServiceProxy @@ -180,21 +183,19 @@ class SimonaStandaloneSetup( override def weatherService( context: ActorContext[_], scheduler: ActorRef[SchedulerMessage], - ): ClassicRef = { - val weatherService = context.toClassic.simonaActorOf( - WeatherService.props( - scheduler.toClassic, + ): ActorRef[WeatherMessage] = { + val weatherService = context.spawn( + WeatherService(scheduler), + "weatherAgent", + ) + weatherService ! ServiceMessage.Create( + InitWeatherServiceStateData( + simonaConfig.simona.input.weather.datasource, TimeUtil.withDefaults .toZonedDateTime(simonaConfig.simona.time.startDateTime), TimeUtil.withDefaults .toZonedDateTime(simonaConfig.simona.time.endDateTime), ), - "weatherAgent", - ) - weatherService ! SimonaService.Create( - InitWeatherServiceStateData( - simonaConfig.simona.input.weather.datasource - ), ScheduleLock.singleKey(context, scheduler, INIT_SIM_TICK), ) @@ -230,17 +231,19 @@ class SimonaStandaloneSetup( extLink.setup(extSimAdapterData) val extSim = extLink.getExtSimulation - val extDataInit - : Iterable[(Class[_ <: SimonaService[_]], ClassicRef)] = + val extDataInit: Iterable[(Class[_], ActorRef[_ >: ServiceMessage])] = extSim.getDataConnections.asScala.zipWithIndex.map { case (evConnection: ExtEvDataConnection, dIndex) => - val extEvDataService = context.toClassic.simonaActorOf( - ExtEvDataService.props(scheduler.toClassic), - s"$index-$dIndex", + val extEvDataService = context.spawn( + ExtEvDataService(scheduler), + s"ExtEvDataService-$index-$dIndex", + ) + evConnection.setActorRefs( + extEvDataService.toClassic, + extSimAdapter, ) - evConnection.setActorRefs(extEvDataService, extSimAdapter) - extEvDataService ! SimonaService.Create( + extEvDataService ! ServiceMessage.Create( InitExtEvData(evConnection), ScheduleLock.singleKey( context, @@ -249,7 +252,7 @@ class SimonaStandaloneSetup( ), ) - (classOf[ExtEvDataService], extEvDataService) + (classOf[ExtEvDataService.type], extEvDataService) } // starting external simulation 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 fd98cc642d..bf6bc713bd 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -209,7 +209,7 @@ class EmAgentIT // deal with weather service registration weatherService.expectMessage( RegisterForWeatherMessage( - pvAgent.toClassic, + pvAgent, pvInput.getNode.getGeoPosition.getY, pvInput.getNode.getGeoPosition.getX, ) @@ -473,7 +473,7 @@ class EmAgentIT // deal with weather service registration weatherService.expectMessage( RegisterForWeatherMessage( - pvAgent.toClassic, + pvAgent, pvInput.getNode.getGeoPosition.getY, pvInput.getNode.getGeoPosition.getX, ) 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..0c8ee43e9f 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala @@ -21,6 +21,7 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, } +import edu.ie3.simona.ontology.messages.services.WeatherMessage import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.test.common.model.grid.DbfsTestGrid @@ -56,7 +57,7 @@ class DBFSAlgorithmCenGridSpec private val runtimeEvents: TestProbe[RuntimeEvent] = TestProbe("runtimeEvents") private val primaryService = TestProbe("primaryService") - private val weatherService = TestProbe("weatherService") + private val weatherService = TestProbe[WeatherMessage]("weatherService") private val superiorGridAgent = SuperiorGA( TestProbe("superiorGridAgent_1000"), @@ -78,7 +79,7 @@ class DBFSAlgorithmCenGridSpec scheduler = scheduler.ref, runtimeEventListener = runtimeEvents.ref, primaryServiceProxy = primaryService.ref.toClassic, - weather = weatherService.ref.toClassic, + weather = weatherService.ref, 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..df1f6cf102 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala @@ -20,6 +20,7 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, } +import edu.ie3.simona.ontology.messages.services.WeatherMessage import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.test.common.model.grid.DbfsTestGrid @@ -49,7 +50,7 @@ class DBFSAlgorithmFailedPowerFlowSpec private val runtimeEvents: TestProbe[RuntimeEvent] = TestProbe("runtimeEvents") private val primaryService = TestProbe("primaryService") - private val weatherService = TestProbe("weatherService") + private val weatherService = TestProbe[WeatherMessage]("weatherService") private val superiorGridAgent = SuperiorGA( TestProbe("superiorGridAgent_1000"), @@ -63,7 +64,7 @@ class DBFSAlgorithmFailedPowerFlowSpec scheduler = scheduler.ref, runtimeEventListener = runtimeEvents.ref, primaryServiceProxy = primaryService.ref.toClassic, - weather = weatherService.ref.toClassic, + weather = weatherService.ref, 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..c6bc1b9f70 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala @@ -21,7 +21,10 @@ 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, + WeatherMessage, +} import edu.ie3.simona.ontology.messages.services.ServiceMessage.PrimaryServiceRegistrationMessage import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock @@ -52,13 +55,13 @@ class DBFSAlgorithmParticipantSpec TestProbe("runtimeEvents") private val primaryService: TestProbe[ServiceMessage] = TestProbe("primaryService") - private val weatherService = TestProbe("weatherService") + private val weatherService = TestProbe[WeatherMessage]("weatherService") private val environmentRefs = EnvironmentRefs( scheduler = scheduler.ref, runtimeEventListener = runtimeEvents.ref, primaryServiceProxy = primaryService.ref.toClassic, - weather = weatherService.ref.toClassic, + weather = weatherService.ref, 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..08bc4b7e26 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala @@ -19,7 +19,10 @@ 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, + WeatherMessage, +} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.test.common.model.grid.DbfsTestGrid @@ -55,14 +58,14 @@ class DBFSAlgorithmSupGridSpec TestProbe("runtimeEvents") private val primaryService: TestProbe[ServiceMessage] = TestProbe("primaryService") - private val weatherService = TestProbe("weatherService") + private val weatherService = TestProbe[WeatherMessage]("weatherService") private val hvGrid: TestProbe[GridAgent.Request] = TestProbe("hvGrid") private val environmentRefs = EnvironmentRefs( scheduler = scheduler.ref, runtimeEventListener = runtimeEvents.ref, primaryServiceProxy = primaryService.ref.toClassic, - weather = weatherService.ref.toClassic, + weather = weatherService.ref, evDataService = None, ) diff --git a/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala index 1f225a7878..bc6bb96253 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala @@ -266,7 +266,9 @@ class EvcsAgentModelCalculationSpec ) /* Expect a registration message */ - evService.expectMsg(RegisterForEvDataMessage(evcsInputModel.getUuid)) + evService.expectMsg( + RegisterForEvDataMessage(evcsAgent.ref.toTyped, evcsInputModel.getUuid) + ) /* ... as well as corresponding state and state data */ evcsAgent.stateName shouldBe HandleInformation @@ -361,7 +363,9 @@ class EvcsAgentModelCalculationSpec ) /* Expect a registration message */ - evService.expectMsg(RegisterForEvDataMessage(evcsInputModel.getUuid)) + evService.expectMsg( + RegisterForEvDataMessage(evcsAgent.ref.toTyped, evcsInputModel.getUuid) + ) evService.send( evcsAgent, RegistrationSuccessfulMessage(evService.ref, 900L), @@ -1123,7 +1127,12 @@ class EvcsAgentModelCalculationSpec // only receive registration message. ScheduleFlexRequest after secondary service initialized emAgent.expectNoMessage() - evService.expectMsg(RegisterForEvDataMessage(evcsInputModelQv.getUuid)) + evService.expectMsg( + RegisterForEvDataMessage( + evcsAgent.ref.toTyped, + evcsInputModelQv.getUuid, + ) + ) evService.send( evcsAgent, RegistrationSuccessfulMessage(evService.ref, 0), @@ -1263,7 +1272,12 @@ class EvcsAgentModelCalculationSpec ) emAgent.expectNoMessage() - evService.expectMsg(RegisterForEvDataMessage(evcsInputModelQv.getUuid)) + evService.expectMsg( + RegisterForEvDataMessage( + evcsAgent.ref.toTyped, + evcsInputModelQv.getUuid, + ) + ) evService.send( evcsAgent, RegistrationSuccessfulMessage(evService.ref, 900), @@ -2086,7 +2100,9 @@ class EvcsAgentModelCalculationSpec RegistrationFailedMessage(primaryServiceProxy.ref), ) - evService.expectMsg(RegisterForEvDataMessage(evcsInputModel.getUuid)) + evService.expectMsg( + RegisterForEvDataMessage(evcsAgent.ref.toTyped, evcsInputModel.getUuid) + ) evService.send( evcsAgent, RegistrationSuccessfulMessage(evService.ref, 0), diff --git a/src/test/scala/edu/ie3/simona/agent/participant/HpAgentModelCalculationSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/HpAgentModelCalculationSpec.scala index 66436dd11b..c26df81177 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/HpAgentModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/HpAgentModelCalculationSpec.scala @@ -274,7 +274,7 @@ class HpAgentModelCalculationSpec /* Expect a registration message */ weatherService.expectMsg( - RegisterForWeatherMessage(hpAgent.ref, 52.02083574, 7.40110716) + RegisterForWeatherMessage(hpAgent.ref.toTyped, 52.02083574, 7.40110716) ) /* ... as well as corresponding state and state data */ @@ -375,7 +375,7 @@ class HpAgentModelCalculationSpec /* Expect a registration message */ weatherService.expectMsg( - RegisterForWeatherMessage(hpAgent.ref, 52.02083574, 7.40110716) + RegisterForWeatherMessage(hpAgent.ref.toTyped, 52.02083574, 7.40110716) ) weatherService.send( hpAgent, diff --git a/src/test/scala/edu/ie3/simona/agent/participant2/ParticipantAgentInitSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant2/ParticipantAgentInitSpec.scala index 628b34f76a..d231f3e628 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant2/ParticipantAgentInitSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant2/ParticipantAgentInitSpec.scala @@ -41,6 +41,7 @@ import edu.ie3.simona.test.common.input.{LoadInputTestData, PvInputTestData} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.simona.util.TickUtil.TickLong import org.apache.pekko.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import squants.Each @@ -325,7 +326,7 @@ class ParticipantAgentInitSpec val refs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryService.ref.toClassic, - services = Map(ServiceType.WeatherService -> service.ref.toClassic), + services = Map(ServiceType.WeatherService -> service.ref), resultListener = Iterable(resultListener.ref), ) @@ -359,7 +360,7 @@ class ParticipantAgentInitSpec service.expectMessage( RegisterForWeatherMessage( - participantAgent.toClassic, + participantAgent, mockInput.getNode.getGeoPosition.getY, mockInput.getNode.getGeoPosition.getX, ) @@ -386,7 +387,7 @@ class ParticipantAgentInitSpec val refs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryService.ref.toClassic, - services = Map(ServiceType.WeatherService -> service.ref.toClassic), + services = Map(ServiceType.WeatherService -> service.ref), resultListener = Iterable(resultListener.ref), ) @@ -444,7 +445,7 @@ class ParticipantAgentInitSpec val refs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryService.ref.toClassic, - services = Map(ServiceType.WeatherService -> service.ref.toClassic), + services = Map(ServiceType.WeatherService -> service.ref), resultListener = Iterable(resultListener.ref), ) @@ -483,7 +484,7 @@ class ParticipantAgentInitSpec service.expectMessage( RegisterForWeatherMessage( - participantAgent.toClassic, + participantAgent, mockInput.getNode.getGeoPosition.getY, mockInput.getNode.getGeoPosition.getX, ) @@ -511,7 +512,7 @@ class ParticipantAgentInitSpec val refs = ParticipantRefs( gridAgent = gridAgent.ref, primaryServiceProxy = primaryService.ref.toClassic, - services = Map(ServiceType.WeatherService -> service.ref.toClassic), + services = Map(ServiceType.WeatherService -> service.ref), resultListener = Iterable(resultListener.ref), ) 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 06d9ef07c9..77415e8159 100644 --- a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala @@ -6,7 +6,7 @@ package edu.ie3.simona.service.ev -import com.typesafe.config.ConfigFactory +import edu.ie3.simona.agent.participant2.ParticipantAgent import edu.ie3.simona.agent.participant2.ParticipantAgent.{ DataProvision, RegistrationSuccessfulMessage, @@ -15,27 +15,29 @@ 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.exceptions.ServiceException import edu.ie3.simona.model.participant.evcs.EvModelWrapper -import edu.ie3.simona.ontology.messages.Activation 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, + WrappedActivation, + WrappedExternalMessage, +} +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.ev.ExtEvDataService.InitExtEvData -import edu.ie3.simona.test.common.{ - EvTestData, - TestKitWithShutdown, - TestSpawnerClassic, -} +import edu.ie3.simona.test.common.{EvTestData, TestSpawnerTyped} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.quantities.PowerSystemUnits -import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps -import org.apache.pekko.actor.ActorSystem -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 tech.units.indriya.quantity.Quantities @@ -43,21 +45,19 @@ import java.util.UUID import scala.concurrent.duration.DurationInt import scala.jdk.CollectionConverters._ import scala.jdk.OptionConverters._ +import scala.language.implicitConversions class ExtEvDataServiceSpec - extends TestKitWithShutdown( - ActorSystem( - "ExtEvDataServiceSpec", - ConfigFactory - .parseString(""" - |pekko.loggers = ["org.apache.pekko.testkit.TestEventListener"] - |pekko.loglevel = "INFO" - |""".stripMargin), - ) - ) + extends ScalaTestWithActorTestKit with AnyWordSpecLike with EvTestData - with TestSpawnerClassic { + with TestSpawnerTyped { + + implicit def wrap(msg: Activation): WrappedActivation = + WrappedActivation(msg) + + implicit def wrap(msg: EvDataMessageFromExt): WrappedExternalMessage = + WrappedExternalMessage(msg) private val evcs1UUID = UUID.fromString("06a14909-366e-4e94-a593-1016e1455b30") @@ -66,96 +66,95 @@ class ExtEvDataServiceSpec "An uninitialized ev movement 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 evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val evService = testKit.spawn(ExtEvDataService(scheduler.ref)) + val adapter = testKit.spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(evService, extSimAdapter.ref) + extEvData.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( - evService, - SimonaService.Create(InitExtEvData(extEvData), key), - ) - scheduler.expectMsg( - ScheduleActivation(evService.toTyped, INIT_SIM_TICK, Some(key)) - ) + evService ! Create(InitExtEvData(extEvData), key) + + val activationMsg = scheduler.expectMessageType[ScheduleActivation] + activationMsg.tick shouldBe INIT_SIM_TICK + activationMsg.unlockKey shouldBe Some(key) - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.toTyped)) + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(activationMsg.actor)) } "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 evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val evService = testKit.spawn(ExtEvDataService(scheduler.ref)) + val adapter = testKit.spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(evService, extSimAdapter.ref) + extEvData.setActorRefs(adapter.toClassic, extSimAdapter.ref.toClassic) - val evcs1 = TestProbe("evcs1") + val evcs1 = TestProbe[ParticipantAgent.Request]("evcs1") // this one should be stashed - evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) + evService ! RegisterForEvDataMessage(evcs1.ref, evcs1UUID) evcs1.expectNoMessage() 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( - evService, - SimonaService.Create(InitExtEvData(extEvData), key), - ) - scheduler.expectMsg( - ScheduleActivation(evService.toTyped, INIT_SIM_TICK, Some(key)) - ) + evService ! Create(InitExtEvData(extEvData), key) - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.toTyped)) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] + activationMsg.tick shouldBe INIT_SIM_TICK + activationMsg.unlockKey shouldBe Some(key) + + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(activationMsg.actor)) } } "An idle ev movements service" must { "handle duplicate registrations correctly" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val evService = testKit.spawn(ExtEvDataService(scheduler.ref)) + val adapter = testKit.spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(evService, extSimAdapter.ref) + extEvData.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( - evService, - SimonaService.Create(InitExtEvData(extEvData), key), - ) - scheduler.expectMsgType[ScheduleActivation] + evService ! Create(InitExtEvData(extEvData), key) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.toTyped)) + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(activationMsg.actor)) - val evcs1 = TestProbe("evcs1") - val evcs2 = TestProbe("evcs2") + val evcs1 = TestProbe[ParticipantAgent.Request]("evcs1") + val evcs2 = TestProbe[ParticipantAgent.Request]("evcs2") - evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) + evService ! RegisterForEvDataMessage(evcs1.ref, evcs1UUID) evcs1.expectNoMessage() - evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) + evService ! RegisterForEvDataMessage(evcs2.ref, evcs2UUID) evcs2.expectNoMessage() // register first one again - evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) + evService ! RegisterForEvDataMessage(evcs1.ref, evcs1UUID) evcs1.expectNoMessage() extEvData.sendExtMsg( @@ -164,76 +163,77 @@ class ExtEvDataServiceSpec Some(long2Long(0L)).toJava, ) ) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.toTyped)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) + scheduler.expectNoMessage() + + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(activationMsg.actor)) - evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, 0L)) - evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, 0L)) + evcs1.expectMessage( + RegistrationSuccessfulMessage(evService.ref.toClassic, 0L) + ) + evcs2.expectMessage( + RegistrationSuccessfulMessage(evService.ref.toClassic, 0L) + ) } "fail when activated without having received ExtEvMessage" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val evService = testKit.spawn(ExtEvDataService(scheduler.ref)) + val adapter = testKit.spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(evService, extSimAdapter.ref) + extEvData.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( - evService, - SimonaService.Create(InitExtEvData(extEvData), key), - ) - scheduler.expectMsg( - ScheduleActivation(evService.toTyped, INIT_SIM_TICK, Some(key)) - ) + evService ! Create(InitExtEvData(extEvData), key) - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.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] { - evService.receive( - Activation(0), - scheduler.ref, - ) - } + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(activationMsg.actor)) + // we trigger ev service and expect an exception + evService ! Activation(0) scheduler.expectNoMessage() } "handle free lots requests correctly and forward them to the correct evcs" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val evService = testKit.spawn(ExtEvDataService(scheduler.ref)) + val adapter = testKit.spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(evService, extSimAdapter.ref) + extEvData.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( - evService, - SimonaService.Create(InitExtEvData(extEvData), key), - ) - scheduler.expectMsgType[ScheduleActivation] + evService ! Create(InitExtEvData(extEvData), key) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.toTyped)) + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(activationMsg.actor)) - val evcs1 = TestProbe("evcs1") - val evcs2 = TestProbe("evcs2") + val evcs1 = TestProbe[ParticipantAgent.Request]("evcs1") + val evcs2 = TestProbe[ParticipantAgent.Request]("evcs2") - evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) + evService ! RegisterForEvDataMessage(evcs1.ref, evcs1UUID) evcs1.expectNoMessage() - evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) + evService ! RegisterForEvDataMessage(evcs2.ref, evcs2UUID) evcs2.expectNoMessage() extEvData.sendExtMsg( @@ -242,12 +242,21 @@ class ExtEvDataServiceSpec Some(long2Long(0L)).toJava, ) ) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.toTyped)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) + + scheduler.expectNoMessage() - evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, 0L)) - evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, 0L)) + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(activationMsg.actor)) + + evcs1.expectMessage( + RegistrationSuccessfulMessage(evService.ref.toClassic, 0L) + ) + evcs2.expectMessage( + RegistrationSuccessfulMessage(evService.ref.toClassic, 0L) + ) extEvData.sendExtMsg( new RequestEvcsFreeLots() @@ -255,51 +264,40 @@ class ExtEvDataServiceSpec // ev service should receive request at this moment // scheduler should receive schedule msg - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) val tick = 0L // we trigger ev service - scheduler.send(evService, Activation(tick)) + evService ! Activation(tick) - evcs1.expectMsg( + evcs1.expectMessage( EvFreeLotsRequest(tick) ) - evcs2.expectMsg( + evcs2.expectMessage( EvFreeLotsRequest(tick) ) - scheduler.expectMsg(Completion(evService.toTyped)) + scheduler.expectMessage(Completion(activationMsg.actor)) extEvData.receiveTriggerQueue shouldBe empty // return free lots to ev service - evcs1.send( - evService, - FreeLotsResponse( - evcs1UUID, - 2, - ), - ) + evService ! FreeLotsResponse(evcs1UUID, 2) // nothing should happen yet, waiting for second departed ev extEvData.receiveTriggerQueue shouldBe empty - evcs2.send( - evService, - FreeLotsResponse( - evcs2UUID, - 0, - ), - ) + evService ! FreeLotsResponse(evcs2UUID, 0) // ev service should recognize that all evcs that are expected are returned, // thus should send ProvideEvcsFreeLots awaitCond( !extEvData.receiveTriggerQueue.isEmpty, max = 3.seconds, - message = "No message received", ) extEvData.receiveTriggerQueue.size() shouldBe 1 // only evcs 1 should be included, the other one is full @@ -309,33 +307,32 @@ class ExtEvDataServiceSpec } "handle price requests correctly by returning dummy values" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val evService = testKit.spawn(ExtEvDataService(scheduler.ref)) + val adapter = testKit.spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(evService, extSimAdapter.ref) + extEvData.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( - evService, - SimonaService.Create(InitExtEvData(extEvData), key), - ) - scheduler.expectMsgType[ScheduleActivation] + evService ! Create(InitExtEvData(extEvData), key) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.toTyped)) + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(activationMsg.actor)) - val evcs1 = TestProbe("evcs1") - val evcs2 = TestProbe("evcs2") + val evcs1 = TestProbe[ParticipantAgent.Request]("evcs1") + val evcs2 = TestProbe[ParticipantAgent.Request]("evcs2") - evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) + evService ! RegisterForEvDataMessage(evcs1.ref, evcs1UUID) evcs1.expectNoMessage() - evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) + evService ! RegisterForEvDataMessage(evcs2.ref, evcs2UUID) evcs2.expectNoMessage() extEvData.sendExtMsg( @@ -344,23 +341,33 @@ class ExtEvDataServiceSpec Some(long2Long(0L)).toJava, ) ) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.toTyped)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(10.seconds, Completion(activationMsg.actor)) - evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, 0L)) - evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, 0L)) + evcs1.expectMessage( + 10.seconds, + RegistrationSuccessfulMessage(evService.ref.toClassic, 0L), + ) + evcs2.expectMessage( + 10.seconds, + RegistrationSuccessfulMessage(evService.ref.toClassic, 0L), + ) extEvData.sendExtMsg(new RequestCurrentPrices()) // ev service should receive request at this moment // scheduler should receive schedule msg - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) val tick = 0L // we trigger ev service - scheduler.send(evService, Activation(tick)) + evService ! Activation(tick) evcs1.expectNoMessage() evcs2.expectNoMessage() @@ -369,8 +376,7 @@ class ExtEvDataServiceSpec // thus should send ProvideEvcsFreeLots awaitCond( !extEvData.receiveTriggerQueue.isEmpty, - max = 3.seconds, - message = "No message received", + max = 30.seconds, ) extEvData.receiveTriggerQueue.size() shouldBe 1 // only evcs 1 should be included, the other one is full @@ -381,81 +387,81 @@ class ExtEvDataServiceSpec ).asJava ) - scheduler.expectMsg(Completion(evService.toTyped)) + scheduler.expectMessage(Completion(activationMsg.actor)) } "return free lots requests right away if there are no evcs registered" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val evService = testKit.spawn(ExtEvDataService(scheduler.ref)) + val adapter = testKit.spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(evService, extSimAdapter.ref) + extEvData.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( - evService, - SimonaService.Create(InitExtEvData(extEvData), key), - ) - scheduler.expectMsgType[ScheduleActivation] + evService ! Create(InitExtEvData(extEvData), key) + + val activationMsg = scheduler.expectMessageType[ScheduleActivation] - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.toTyped)) + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(activationMsg.actor)) extEvData.sendExtMsg(new RequestEvcsFreeLots()) // ev service should receive movements msg at this moment // scheduler receives schedule msg - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) val tick = 0L // we trigger ev service - scheduler.send(evService, Activation(tick)) + evService ! Activation(tick) - scheduler.expectMsg(Completion(evService.toTyped)) + scheduler.expectMessage(Completion(activationMsg.actor)) // ev service should send ProvideEvcsFreeLots right away awaitCond( !extEvData.receiveTriggerQueue.isEmpty, - max = 3.seconds, - message = "No message received", + max = 10.seconds, ) extEvData.receiveTriggerQueue.size() shouldBe 1 extEvData.receiveTriggerQueue.take() shouldBe new ProvideEvcsFreeLots() } "handle ev departure requests correctly and return departed evs" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val evService = testKit.spawn(ExtEvDataService(scheduler.ref)) + val adapter = testKit.spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(evService, extSimAdapter.ref) + extEvData.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( - evService, - SimonaService.Create(InitExtEvData(extEvData), key), - ) - scheduler.expectMsgType[ScheduleActivation] + evService ! Create(InitExtEvData(extEvData), key) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.toTyped)) + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(activationMsg.actor)) - val evcs1 = TestProbe("evcs1") - val evcs2 = TestProbe("evcs1") + val evcs1 = TestProbe[ParticipantAgent.Request]("evcs1") + val evcs2 = TestProbe[ParticipantAgent.Request]("evcs1") - evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) + evService ! RegisterForEvDataMessage(evcs1.ref, evcs1UUID) evcs1.expectNoMessage() - evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) + evService ! RegisterForEvDataMessage(evcs2.ref, evcs2UUID) evcs2.expectNoMessage() extEvData.sendExtMsg( @@ -464,12 +470,18 @@ class ExtEvDataServiceSpec Some(long2Long(0L)).toJava, ) ) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.toTyped)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(activationMsg.actor)) - evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, 0L)) - evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, 0L)) + evcs1.expectMessage( + RegistrationSuccessfulMessage(evService.ref.toClassic, 0L) + ) + evcs2.expectMessage( + RegistrationSuccessfulMessage(evService.ref.toClassic, 0L) + ) val departures = Map( evcs1UUID -> List(evA.getUuid).asJava, @@ -482,30 +494,34 @@ class ExtEvDataServiceSpec // ev service should receive departure msg at this moment // scheduler should receive schedule msg - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) val tick = 0L // we trigger ev service - scheduler.send(evService, Activation(tick)) + evService ! Activation(tick) - evcs1.expectMsg( - DepartingEvsRequest(tick, scala.collection.immutable.Seq(evA.getUuid)) + evcs1.expectMessage( + 10.seconds, + DepartingEvsRequest(tick, scala.collection.immutable.Seq(evA.getUuid)), ) - evcs2.expectMsg( - DepartingEvsRequest(tick, scala.collection.immutable.Seq(evB.getUuid)) + evcs2.expectMessage( + 10.seconds, + DepartingEvsRequest(tick, scala.collection.immutable.Seq(evB.getUuid)), ) - scheduler.expectMsg(Completion(evService.toTyped)) + scheduler.expectMessage(Completion(activationMsg.actor)) // return evs to ev service val updatedEvA = evA.copyWith( Quantities.getQuantity(6.0, PowerSystemUnits.KILOWATTHOUR) ) - evcs1.send( - evService, - DepartingEvsResponse(evcs1UUID, Seq(EvModelWrapper(updatedEvA))), + evService ! DepartingEvsResponse( + evcs1UUID, + Seq(EvModelWrapper(updatedEvA)), ) // nothing should happen yet, waiting for second departed ev @@ -515,9 +531,9 @@ class ExtEvDataServiceSpec Quantities.getQuantity(4.0, PowerSystemUnits.KILOWATTHOUR) ) - evcs2.send( - evService, - DepartingEvsResponse(evcs2UUID, Seq(EvModelWrapper(updatedEvB))), + evService ! DepartingEvsResponse( + evcs2UUID, + Seq(EvModelWrapper(updatedEvB)), ) // ev service should recognize that all evs that are expected are returned, @@ -525,7 +541,6 @@ class ExtEvDataServiceSpec awaitCond( !extEvData.receiveTriggerQueue.isEmpty, max = 3.seconds, - message = "No message received", ) extEvData.receiveTriggerQueue.size() shouldBe 1 extEvData.receiveTriggerQueue.take() shouldBe new ProvideDepartingEvs( @@ -534,25 +549,24 @@ class ExtEvDataServiceSpec } "return ev departure requests right away if request list is empty" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val evService = testKit.spawn(ExtEvDataService(scheduler.ref)) + val adapter = testKit.spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(evService, extSimAdapter.ref) + extEvData.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( - evService, - SimonaService.Create(InitExtEvData(extEvData), key), - ) - scheduler.expectMsgType[ScheduleActivation] + evService ! Create(InitExtEvData(extEvData), key) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.toTyped)) + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(activationMsg.actor)) extEvData.sendExtMsg( new RequestDepartingEvs(Map.empty[UUID, java.util.List[UUID]].asJava) @@ -560,20 +574,21 @@ class ExtEvDataServiceSpec // ev service should receive departure msg at this moment // scheduler should receive schedule msg - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) val tick = 0L // we trigger ev service - scheduler.send(evService, Activation(tick)) + evService ! Activation(tick) - scheduler.expectMsg(Completion(evService.toTyped)) + scheduler.expectMessage(Completion(activationMsg.actor)) // ev service should send ProvideDepartingEvs right away awaitCond( !extEvData.receiveTriggerQueue.isEmpty, max = 3.seconds, - message = "No message received", ) extEvData.receiveTriggerQueue.size() shouldBe 1 extEvData.receiveTriggerQueue.take() shouldBe new ProvideDepartingEvs( @@ -582,33 +597,32 @@ class ExtEvDataServiceSpec } "handle ev arrivals correctly and forward them to the correct evcs" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val evService = testKit.spawn(ExtEvDataService(scheduler.ref)) + val adapter = testKit.spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(evService, extSimAdapter.ref) + extEvData.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( - evService, - SimonaService.Create(InitExtEvData(extEvData), key), - ) - scheduler.expectMsgType[ScheduleActivation] + evService ! Create(InitExtEvData(extEvData), key) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsgType[Completion] + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessageType[Completion] - val evcs1 = TestProbe("evcs1") - val evcs2 = TestProbe("evcs2") + val evcs1 = TestProbe[ParticipantAgent.Request]("evcs1") + val evcs2 = TestProbe[ParticipantAgent.Request]("evcs2") - evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) + evService ! RegisterForEvDataMessage(evcs1.ref, evcs1UUID) evcs1.expectNoMessage() - evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) + evService ! RegisterForEvDataMessage(evcs2.ref, evcs2UUID) evcs2.expectNoMessage() extEvData.sendExtMsg( @@ -617,12 +631,18 @@ class ExtEvDataServiceSpec Some(long2Long(0L)).toJava, ) ) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.toTyped)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(10.seconds, Completion(activationMsg.actor)) - evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, 0L)) - evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, 0L)) + evcs1.expectMessage( + RegistrationSuccessfulMessage(evService.ref.toClassic, 0L) + ) + evcs2.expectMessage( + RegistrationSuccessfulMessage(evService.ref.toClassic, 0L) + ) val arrivals = Map( evcs1UUID -> List[EvModel](evA).asJava, @@ -635,55 +655,58 @@ class ExtEvDataServiceSpec // ev service should receive movements msg at this moment // scheduler receive schedule msg - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) val tick = 0L // we trigger ev service - scheduler.send(evService, Activation(tick)) + evService ! Activation(tick) - val evsMessage1 = evcs1.expectMsgType[DataProvision[_]] + val evsMessage1 = + evcs1.expectMessageType[DataProvision[EvData]](10.seconds) evsMessage1.tick shouldBe tick evsMessage1.data shouldBe ArrivingEvs( Seq(EvModelWrapper(evA)) ) - val evsMessage2 = evcs2.expectMsgType[DataProvision[_]] + val evsMessage2 = + evcs2.expectMessageType[DataProvision[EvData]](10.seconds) evsMessage2.tick shouldBe tick evsMessage2.data shouldBe ArrivingEvs( Seq(EvModelWrapper(evB)) ) - scheduler.expectMsg(Completion(evService.toTyped)) + scheduler.expectMessage(Completion(activationMsg.actor)) // no response expected extEvData.receiveTriggerQueue shouldBe empty } "skip a movements provision from an evcs that is not registered" in { - val scheduler = TestProbe("scheduler") - val extSimAdapter = TestProbe("extSimAdapter") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val extSimAdapter = TestProbe[ScheduleDataServiceMessage]("extSimAdapter") - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val evService = testKit.spawn(ExtEvDataService(scheduler.ref)) + val adapter = testKit.spawn(ExtEvDataService.adapter(evService)) val extEvData = new ExtEvDataConnection() - extEvData.setActorRefs(evService, extSimAdapter.ref) + extEvData.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( - evService, - SimonaService.Create(InitExtEvData(extEvData), key), - ) - scheduler.expectMsgType[ScheduleActivation] + evService ! Create(InitExtEvData(extEvData), key) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsgType[Completion] + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessageType[Completion] - val evcs1 = TestProbe("evcs1") + val evcs1 = TestProbe[ParticipantAgent.Request]("evcs1") - evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) + evService ! RegisterForEvDataMessage(evcs1.ref, evcs1UUID) evcs1.expectNoMessage() extEvData.sendExtMsg( @@ -692,11 +715,15 @@ class ExtEvDataServiceSpec Some(long2Long(0L)).toJava, ) ) - extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) - scheduler.send(evService, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(evService.toTyped)) + extSimAdapter.expectMessage( + new ScheduleDataServiceMessage(adapter.toClassic) + ) + evService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(activationMsg.actor)) - evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, 0L)) + evcs1.expectMessage( + RegistrationSuccessfulMessage(evService.ref.toClassic, 0L) + ) val arrivals = Map( evcs1UUID -> List[EvModel](evA).asJava, @@ -709,20 +736,20 @@ class ExtEvDataServiceSpec // ev service should receive movements msg at this moment // scheduler should receive schedule msg - extSimAdapter.expectMsgType[ScheduleDataServiceMessage] + extSimAdapter.expectMessageType[ScheduleDataServiceMessage] val tick = 0L // we trigger ev service - scheduler.send(evService, Activation(tick)) + evService ! Activation(tick) - val evsMessage1 = evcs1.expectMsgType[DataProvision[_]] + val evsMessage1 = evcs1.expectMessageType[DataProvision[EvData]] evsMessage1.tick shouldBe tick evsMessage1.data shouldBe ArrivingEvs( Seq(EvModelWrapper(evA)) ) - scheduler.expectMsg(Completion(evService.toTyped)) + scheduler.expectMessage(Completion(activationMsg.actor)) // no response expected extEvData.receiveTriggerQueue shouldBe empty diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSpec.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSpec.scala index a11282b466..24f17cb483 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSpec.scala @@ -205,7 +205,12 @@ class PrimaryServiceWorkerSpec } "refuse registration for wrong registration request" in { - serviceRef ! RegisterForWeatherMessage(self, 51.4843281, 7.4116482) + val agent = TestProbe() + serviceRef ! RegisterForWeatherMessage( + agent.ref.toTyped, + 51.4843281, + 7.4116482, + ) expectNoMessage() } diff --git a/src/test/scala/edu/ie3/simona/service/weather/WeatherServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/weather/WeatherServiceSpec.scala index 8de4bdfd92..36b47d0851 100644 --- a/src/test/scala/edu/ie3/simona/service/weather/WeatherServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/weather/WeatherServiceSpec.scala @@ -14,54 +14,45 @@ import edu.ie3.simona.agent.participant2.ParticipantAgent.{ RegistrationSuccessfulMessage, } import edu.ie3.simona.config.SimonaConfig -import edu.ie3.simona.ontology.messages.Activation 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, + WrappedActivation, +} import edu.ie3.simona.ontology.messages.services.WeatherMessage._ +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.weather.WeatherService.InitWeatherServiceStateData import edu.ie3.simona.service.weather.WeatherSource.AgentCoordinates -import edu.ie3.simona.test.common.{ - ConfigTestData, - TestKitWithShutdown, - TestSpawnerClassic, -} +import edu.ie3.simona.test.common.{ConfigTestData, TestSpawnerTyped} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.TimeUtil import edu.ie3.util.scala.quantities.WattsPerSquareMeter -import org.apache.pekko.actor.ActorSystem -import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps -import org.apache.pekko.testkit.{ - EventFilter, - ImplicitSender, - TestActorRef, +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.wordspec.AnyWordSpecLike import squants.motion.MetersPerSecond import squants.thermal.Celsius +import scala.language.implicitConversions + class WeatherServiceSpec - extends TestKitWithShutdown( - ActorSystem( - "WeatherServiceSpec", - ConfigFactory - .parseString(""" - |pekko.loggers = ["org.apache.pekko.testkit.TestEventListener"] - |pekko.loglevel = "INFO" - """.stripMargin), - ) - ) - with ImplicitSender + extends ScalaTestWithActorTestKit with AnyWordSpecLike with PrivateMethodTester with LazyLogging with ConfigTestData - with TestSpawnerClassic { + with TestSpawnerTyped { + + implicit def wrap(msg: Activation): ServiceMessage = WrappedActivation(msg) // setup config for scheduler private val config = ConfigFactory @@ -92,101 +83,88 @@ class WeatherServiceSpec private val validCoordinate: AgentCoordinates = AgentCoordinates(52.02083574, 7.40110716) - private val scheduler = TestProbe("scheduler") + private val scheduler = TestProbe[SchedulerMessage]("scheduler") + + private val agent = TestProbe[Any]("agent") // build the weather service - private val weatherActor: TestActorRef[WeatherService] = TestActorRef( - new WeatherService( - scheduler.ref, - TimeUtil.withDefaults.toZonedDateTime( - simonaConfig.simona.time.startDateTime - ), - TimeUtil.withDefaults.toZonedDateTime( - simonaConfig.simona.time.endDateTime - ), - 4, - ) + private val weatherService = testKit.spawn( + WeatherService(scheduler.ref) ) "A weather service" must { "receive correct completion message after initialisation" in { val key = - ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) - scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled - - scheduler.send( - weatherActor, - SimonaService.Create( - InitWeatherServiceStateData( - simonaConfig.simona.input.weather.datasource + ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) + scheduler + .expectMessageType[ScheduleActivation] // lock activation scheduled + + weatherService ! Create( + InitWeatherServiceStateData( + simonaConfig.simona.input.weather.datasource, + TimeUtil.withDefaults.toZonedDateTime( + simonaConfig.simona.time.startDateTime + ), + TimeUtil.withDefaults.toZonedDateTime( + simonaConfig.simona.time.endDateTime ), - key, ), + key, ) - scheduler.expectMsg( - ScheduleActivation(weatherActor.toTyped, INIT_SIM_TICK, Some(key)) - ) - scheduler.send(weatherActor, Activation(INIT_SIM_TICK)) - scheduler.expectMsg(Completion(weatherActor.toTyped, Some(0))) + val activationMsg = scheduler.expectMessageType[ScheduleActivation] + activationMsg.tick shouldBe INIT_SIM_TICK + activationMsg.unlockKey shouldBe Some(key) + + weatherService ! Activation(INIT_SIM_TICK) + scheduler.expectMessage(Completion(activationMsg.actor, Some(0))) } "announce failed weather registration on invalid coordinate" in { - EventFilter - .error( - pattern = - "\\[.*] Unable to obtain necessary information to register for coordinate AgentCoordinates\\(180\\.5,90\\.5\\)\\.", - occurrences = 1, - ) - .intercept { - weatherActor ! RegisterForWeatherMessage( - self, - invalidCoordinate.latitude, - invalidCoordinate.longitude, - ) - } - - expectMsg(RegistrationFailedMessage(weatherActor)) + weatherService ! RegisterForWeatherMessage( + agent.ref, + invalidCoordinate.latitude, + invalidCoordinate.longitude, + ) + + agent.expectMessage(RegistrationFailedMessage(weatherService.toClassic)) } "announce, that a valid coordinate is registered" in { /* The successful registration stems from the test above */ - weatherActor ! RegisterForWeatherMessage( - self, + weatherService ! RegisterForWeatherMessage( + agent.ref, validCoordinate.latitude, validCoordinate.longitude, ) - expectMsg(RegistrationSuccessfulMessage(weatherActor.ref, 0L)) + agent.expectMessage( + RegistrationSuccessfulMessage(weatherService.toClassic, 0L) + ) } "recognize, that a valid coordinate yet is registered" in { /* The successful registration stems from the test above */ - EventFilter - .warning( - pattern = "Sending actor Actor\\[.*] is already registered", - occurrences = 1, - ) - .intercept { - weatherActor ! RegisterForWeatherMessage( - self, - validCoordinate.latitude, - validCoordinate.longitude, - ) - } - expectNoMessage() + weatherService ! RegisterForWeatherMessage( + agent.ref, + validCoordinate.latitude, + validCoordinate.longitude, + ) + + agent.expectNoMessage() } "send out correct weather information upon activity start trigger and request the triggering for the next tick" in { /* Send out an activity start trigger as the scheduler */ - scheduler.send(weatherActor, Activation(0)) + weatherService ! Activation(0) - scheduler.expectMsg(Completion(weatherActor.toTyped, Some(3600))) + val activationMsg = scheduler.expectMessageType[Completion] + activationMsg.newTick shouldBe Some(3600) - expectMsg( + agent.expectMessage( DataProvision( 0, - weatherActor, + weatherService.toClassic, WeatherData( WattsPerSquareMeter(0d), WattsPerSquareMeter(0d), @@ -201,14 +179,15 @@ class WeatherServiceSpec "sends out correct weather information when triggered again and does not as for triggering, if the end is reached" in { /* Send out an activity start trigger as the scheduler */ - scheduler.send(weatherActor, Activation(3600)) + weatherService ! Activation(3600) - scheduler.expectMsg(Completion(weatherActor.toTyped)) + val activationMsg = scheduler.expectMessageType[Completion] + activationMsg.newTick shouldBe None - expectMsg( + agent.expectMessage( DataProvision( 3600, - weatherActor, + weatherService.toClassic, WeatherData( WattsPerSquareMeter(0d), WattsPerSquareMeter(0d), diff --git a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala index 9d1140be17..94eaff05d6 100644 --- a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala @@ -18,6 +18,7 @@ import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.main.RunSimona.SimonaEnded import edu.ie3.simona.ontology.messages.SchedulerMessage import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion +import edu.ie3.simona.ontology.messages.services.WeatherMessage import edu.ie3.simona.scheduler.TimeAdvancer import edu.ie3.simona.scheduler.core.Core.CoreFactory import edu.ie3.simona.scheduler.core.RegularSchedulerCore @@ -426,8 +427,8 @@ object SimonaSimSpec { override def weatherService( context: ActorContext[_], scheduler: ActorRef[SchedulerMessage], - ): ClassicRef = - context.spawn(empty, uniqueName("weatherService")).toClassic + ): ActorRef[WeatherMessage] = + context.spawn(empty, uniqueName("weatherService")) override def timeAdvancer( context: ActorContext[_], 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 0428ed765b..00ddeb90ad 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala @@ -16,6 +16,7 @@ import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.ontology.messages.SchedulerMessage +import edu.ie3.simona.ontology.messages.services.WeatherMessage import edu.ie3.simona.scheduler.TimeAdvancer import edu.ie3.simona.scheduler.core.Core.CoreFactory import edu.ie3.simona.scheduler.core.RegularSchedulerCore @@ -55,7 +56,9 @@ class SimonaSetupSpec extends UnitSpec with SimonaSetup with SubGridGateMokka { override def weatherService( context: ActorContext[_], scheduler: ActorRef[SchedulerMessage], - ): ClassicRef = throw new NotImplementedException("This is a dummy setup") + ): ActorRef[WeatherMessage] = throw new NotImplementedException( + "This is a dummy setup" + ) override def extSimulations( context: ActorContext[_],