diff --git a/CHANGELOG.md b/CHANGELOG.md index 6344047499..be3de2d30c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Merged `HpModelTestData` with `HpTestData` to `HpInputTestData` [#872](https://github.com/ie3-institute/simona/issues/872) - Harmonised both methods that check the inner temperature of thermal house against the boundaries [#880](https://github.com/ie3-institute/simona/issues/880) - Convert all `eval-rst` instances in rtd to myst syntax [#901](https://github.com/ie3-institute/simona/issues/901) +- External simulation should provide information about next tick of MobSim [#776](https://github.com/ie3-institute/simona/issues/776) ### Fixed - Removed a repeated line in the documentation of vn_simona config [#658](https://github.com/ie3-institute/simona/issues/658) diff --git a/build.gradle b/build.gradle index 50c430f3d4..8974152e9b 100644 --- a/build.gradle +++ b/build.gradle @@ -89,7 +89,7 @@ dependencies { exclude group: 'edu.ie3' } - implementation('com.github.ie3-institute:simonaAPI:0.4.0') { + implementation('com.github.ie3-institute:simonaAPI:0.5.0') { exclude group: 'org.apache.logging.log4j' exclude group: 'org.slf4j' /* Exclude our own nested dependencies */ diff --git a/docs/uml/main/ExtEvSimulationClasses.puml b/docs/uml/main/ExtEvSimulationClasses.puml index e35c8e130a..9543697f03 100644 --- a/docs/uml/main/ExtEvSimulationClasses.puml +++ b/docs/uml/main/ExtEvSimulationClasses.puml @@ -27,7 +27,7 @@ package ev-simulation { package simona-api { ' MIDDLE PART class ExtEvData { - ~ LinkedBlockingQueue receiveTriggerQueue + ~ LinkedBlockingQueue receiveTriggerQueue - ActorRef dataService - ActorRef extSimAdapter + List requestAvailablePublicEvCs() @@ -119,33 +119,33 @@ package simona-api { EvModelImpl --|> EvModel - interface ExtEvMessage + interface EvDataMessageFromExt class EvMovementsMessage { - Map> movements } class RequestEvcsFreeLots - RequestEvcsFreeLots --|> ExtEvMessage - EvMovementsMessage --|> ExtEvMessage + RequestEvcsFreeLots --|> EvDataMessageFromExt + EvMovementsMessage --|> EvDataMessageFromExt RequestEvcsFreeLots -[hidden]> EvMovementsMessage - interface ExtEvResponseMessage + interface EvDataResponseMessageToExt class AllDepartedEvsRepsonse { - Map> movements } class ProvideEvcsFreeLots - ProvideEvcsFreeLots --|> ExtEvResponseMessage - AllDepartedEvsRepsonse --|> ExtEvResponseMessage - ExtEvData -> ExtEvMessage - ExtEvData -> ExtEvResponseMessage + ProvideEvcsFreeLots --|> EvDataResponseMessageToExt + AllDepartedEvsRepsonse --|> EvDataResponseMessageToExt + ExtEvData -> EvDataMessageFromExt + ExtEvData -> EvDataResponseMessageToExt EvMovement -[hidden]-> RequestEvcsFreeLots - ExtEvMessage -[hidden]> ExtEvResponseMessage + EvDataMessageFromExt -[hidden]> EvDataResponseMessageToExt EvMovementsMessage -[hidden]> ProvideEvcsFreeLots ProvideEvcsFreeLots -[hidden]> AllDepartedEvsRepsonse class ScheduleDataServiceMessage { - ExtEvDataService dataService } - ExtEvResponseMessage -[hidden]> ScheduleDataServiceMessage + EvDataResponseMessageToExt -[hidden]> ScheduleDataServiceMessage ExtEvData -> ScheduleDataServiceMessage @@ -158,10 +158,10 @@ package simona-api { ExtLink --|> ExtLinkInterface interface ExtTrigger - class ActivityStartTrigger { + class ActivationMessage { - Long tick } - ActivityStartTrigger --|> ExtTrigger + ActivationMessage --|> ExtTrigger interface ExtTriggerResponse class CompletionMessage { @@ -188,7 +188,7 @@ package simona { class SimonaSim - class SimScheduler + class Scheduler class SimonaStandaloneSetup @@ -198,12 +198,12 @@ package simona { class ExtEvDataService - SimScheduler -- SimonaSim + Scheduler -- SimonaSim SimonaSim *- SimonaStandaloneSetup SimonaStandaloneSetup *- ExtSimLoader - ExtSimAdapter -- SimScheduler - ExtEvDataService -- SimScheduler + ExtSimAdapter -- Scheduler + ExtEvDataService -- Scheduler SecondaryData <|-- EvMovementData diff --git a/docs/uml/protocol/ExtEvSimulationSequence.puml b/docs/uml/protocol/ExtEvSimulationSequence.puml index 470d49981e..dde2f869d6 100644 --- a/docs/uml/protocol/ExtEvSimulationSequence.puml +++ b/docs/uml/protocol/ExtEvSimulationSequence.puml @@ -2,97 +2,201 @@ !theme plain +participant Scheduler +participant ExtSimAdapter +participant ExtSimulation +participant ExtEvDataService +participant EvcsAgent1 +participant EvcsAgent2 + ==Init== -SimScheduler -> ExtSimAdapter: ! ActivityStartTrigger(-1L) +Scheduler -> EvcsAgent1: ! Activation(-1) +activate EvcsAgent1 + +EvcsAgent1 -> ExtEvDataService: ! RegisterForEvDataMessage +deactivate EvcsAgent1 + +Scheduler -> EvcsAgent2: ! Activation(-1) +activate EvcsAgent2 + +EvcsAgent2 -> ExtEvDataService: ! RegisterForEvDataMessage +deactivate EvcsAgent2 + +Scheduler -> ExtSimAdapter: ! Activation(-1) activate ExtSimAdapter -ExtSimAdapter -> ExtSimulation: queue(ActivityStartTrigger(-1L)) +ExtSimAdapter -> ExtSimulation: queue(ActivationMessage(-1)) deactivate ExtSimAdapter activate ExtSimulation + ... Initialize external mobility simulation ... +ExtSimulation -> ExtEvDataService: ProvideArrivingEvs(_, t1) -ExtSimulation -> ExtSimAdapter: ! CompletionMessage(newTriggers) -deactivate ExtSimulation +ExtSimulation -> ExtSimAdapter: ! ScheduleDataServiceMessage(\n\tdataServiceRef\n) activate ExtSimAdapter -ExtSimAdapter -> SimScheduler: ! CompletionMessage(newTriggers) +ExtSimAdapter -> Scheduler: ! ScheduleActivation(\n\t_, dataServiceRef) deactivate ExtSimAdapter +activate Scheduler + +Scheduler -> ExtEvDataService: ! Activation(t1) +deactivate Scheduler +activate ExtEvDataService -==Sim== -SimScheduler -> ExtSimAdapter: ! ActivityStartTrigger(tick) +ExtSimulation -> ExtSimAdapter: ! CompletionMessage(newTriggers) +deactivate ExtSimulation activate ExtSimAdapter -ExtSimAdapter -> ExtSimulation: queue(ActivityStartTrigger(tick)) +ExtSimAdapter -> Scheduler: ! Completion(newTriggers) deactivate ExtSimAdapter -activate ExtSimulation -ExtSimulation -> ExtEvDataService: ! RequestEvcsFreeLots -ExtSimulation -> ExtSimAdapter: ! ScheduleDataServiceMessage(\n\tdataServiceRef\n) - -activate ExtSimAdapter -ExtSimAdapter -> SimScheduler: ! ScheduleTriggerMessage(\n\t_, dataServiceRef) -deactivate ExtSimAdapter +ExtEvDataService -> EvcsAgent1: ! RegistrationSuccessfulMessage(t1) +activate EvcsAgent1 -activate SimScheduler -SimScheduler -> ExtEvDataService: ! ActivityStartTrigger(tick) -deactivate SimScheduler +EvcsAgent1 -> Scheduler: ! Completion(t1) +deactivate EvcsAgent1 -activate ExtEvDataService -ExtEvDataService -> EvcsAgent1: ! EvFreeLotsRequest(tick) -activate EvcsAgent1 -ExtEvDataService -> EvcsAgent2: ! EvFreeLotsRequest(tick) +ExtEvDataService -> EvcsAgent2: ! RegistrationSuccessfulMessage(t1) activate EvcsAgent2 -ExtEvDataService -> SimScheduler: ! CompletionMessage(None) - -EvcsAgent2 -> ExtEvDataService: ! FreeLotsResponse(_, _) +EvcsAgent2 -> Scheduler: ! Completion(t1) deactivate EvcsAgent2 -EvcsAgent1 -> ExtEvDataService: ! FreeLotsResponse(_, _) -deactivate EvcsAgent1 -ExtEvDataService -> ExtSimulation: queue(ProvideEvcsFreeLots(_)) + +ExtEvDataService -> Scheduler: ! Completion(None) deactivate ExtEvDataService -... Running external mobility simulation,\n determining EV positions ... -ExtSimulation -> ExtEvDataService: ! EvMovementsMessage(_) -ExtSimulation -> ExtSimAdapter: ! ScheduleDataServiceMessage(\n\tdataServiceRef\n) +==Simulation== +Scheduler -> EvcsAgent1: ! Activation(t1) +Scheduler -> EvcsAgent2: ! Activation(t1) +Scheduler -> ExtSimAdapter: ! Activation(t1) activate ExtSimAdapter -ExtSimAdapter -> SimScheduler: ! ScheduleTriggerMessage(\n\t_, dataServiceRef) + +ExtSimAdapter -> ExtSimulation: queue(ActivationMessage(t1)) deactivate ExtSimAdapter +activate ExtSimulation -activate SimScheduler -SimScheduler -> ExtEvDataService: ! ActivityStartTrigger(tick) -deactivate SimScheduler +group Request free lots + ExtSimulation -> ExtEvDataService: ! RequestEvcsFreeLots -activate ExtEvDataService -ExtEvDataService -> EvcsAgent1: ! ProvideEvDataMessage(\n\ttick, _) -ExtEvDataService -> EvcsAgent2: ! ProvideEvDataMessage(\n\ttick, _) -ExtEvDataService -> SimScheduler: ! CompletionMessage(evcsTriggers) -deactivate ExtEvDataService + ExtSimulation -> ExtSimAdapter: ! ScheduleDataServiceMessage(\n\tdataServiceRef\n) + activate ExtSimAdapter -activate SimScheduler -SimScheduler -> EvcsAgent1: ! ActivityStartTrigger(tick) -activate EvcsAgent1 -SimScheduler -> EvcsAgent2: ! ActivityStartTrigger(tick) -deactivate SimScheduler + ExtSimAdapter -> Scheduler: ! ScheduleActivation(\n\t_, dataServiceRef) + deactivate ExtSimAdapter + activate Scheduler -activate EvcsAgent2 -EvcsAgent1 -> SimScheduler: ! CompletionMessage(None) -deactivate EvcsAgent1 + Scheduler -> ExtEvDataService: ! Activation(t1) + deactivate Scheduler + activate ExtEvDataService -EvcsAgent2 -> ExtEvDataService: ! DepartedEvsResponse(_, _) -activate ExtEvDataService -EvcsAgent2 -> SimScheduler: ! CompletionMessage(None) -deactivate EvcsAgent2 + ExtEvDataService -> EvcsAgent1: ! EvFreeLotsRequest(t1) + activate EvcsAgent1 -ExtEvDataService -> ExtSimulation: queue(AllDepartedEvsResponse(_)) -deactivate ExtEvDataService + ExtEvDataService -> EvcsAgent2: ! EvFreeLotsRequest(t1) + activate EvcsAgent2 -ExtSimulation -> ExtSimAdapter: ! CompletionMessage(newTriggers) -deactivate ExtSimulation + ExtEvDataService -> Scheduler: ! Completion(None) + + EvcsAgent2 -> ExtEvDataService: ! FreeLotsResponse(_, _) + deactivate EvcsAgent2 + + EvcsAgent1 -> ExtEvDataService: ! FreeLotsResponse(_, _) + deactivate EvcsAgent1 + + ExtEvDataService -> ExtSimulation: queue(ProvideEvcsFreeLots(_)) + deactivate ExtEvDataService +end + +group Request current prices (dummy implementation) + ExtSimulation -> ExtEvDataService: ! RequestCurrentPrices + + ExtSimulation -> ExtSimAdapter: ! ScheduleDataServiceMessage(\n\tdataServiceRef\n) + activate ExtSimAdapter + + ExtSimAdapter -> Scheduler: ! ScheduleActivation(\n\t_, dataServiceRef) + deactivate ExtSimAdapter + activate Scheduler + + Scheduler -> ExtEvDataService: ! Activation(t1) + deactivate Scheduler + activate ExtEvDataService + + ExtEvDataService -> ExtSimulation: queue(ProvideCurrentPrices(_)) + + ExtEvDataService -> Scheduler: ! Completion(None) + + deactivate ExtEvDataService +end + +group Request departing EVs + ExtSimulation -> ExtEvDataService: ! RequestDepartingEvs + + ExtSimulation -> ExtSimAdapter: ! ScheduleDataServiceMessage(\n\tdataServiceRef\n) + activate ExtSimAdapter + + ExtSimAdapter -> Scheduler: ! ScheduleActivation(\n\t_, dataServiceRef) + deactivate ExtSimAdapter + activate Scheduler + + Scheduler -> ExtEvDataService: ! Activation(t1) + deactivate Scheduler + activate ExtEvDataService + ExtEvDataService -> EvcsAgent1: ! DepartingEvsRequest(t1) + activate EvcsAgent1 + + ExtEvDataService -> EvcsAgent2: ! DepartingEvsRequest(t1) + activate EvcsAgent2 + + ExtEvDataService -> Scheduler: ! Completion(None) + + EvcsAgent2 -> ExtEvDataService: ! DepartingEvsResponse(_, _) + deactivate EvcsAgent2 + + EvcsAgent1 -> ExtEvDataService: ! DepartingEvsResponse(_, _) + deactivate EvcsAgent1 + + ExtEvDataService -> ExtSimulation: queue(ProvideDepartingEvs(_)) + deactivate ExtEvDataService +end + +... Running external mobility simulation,\n determining EV positions ... + +group Provide arriving EVs + ExtSimulation -> ExtEvDataService: ! ProvideArrivingEvs(_) + ExtSimulation -> ExtSimAdapter: ! ScheduleDataServiceMessage(\n\tdataServiceRef\n) + activate ExtSimAdapter + + ExtSimAdapter -> Scheduler: ! ScheduleActivation(\n\t_, dataServiceRef) + deactivate ExtSimAdapter + activate Scheduler + + Scheduler -> ExtEvDataService: ! Activation(t1) + deactivate Scheduler + activate ExtEvDataService + + ExtEvDataService -> EvcsAgent1: ! ProvideEvDataMessage(evs, t2) + activate EvcsAgent1 + + ExtEvDataService -> EvcsAgent2: ! ProvideEvDataMessage(evs, t2) + activate EvcsAgent2 + + ExtEvDataService -> Scheduler: ! Completion(None) + deactivate ExtEvDataService + + EvcsAgent1 -> Scheduler: ! Completion(t2) + deactivate EvcsAgent1 + + EvcsAgent2 -> Scheduler: ! Completion(t2) + deactivate EvcsAgent2 +end + +ExtSimulation -> ExtSimAdapter: ! CompletionMessage(t2) +deactivate ExtSimulation activate ExtSimAdapter -ExtSimAdapter -> SimScheduler: ! CompletionMessage(newTriggers) + +ExtSimAdapter -> Scheduler: ! Completion(t2) deactivate ExtSimAdapter @enduml \ No newline at end of file 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 5ebfefa55b..f2e78f7c53 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala @@ -14,7 +14,7 @@ import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.participant.ParticipantAgent.ParticipantMessage import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.{ - ActorEvMovementsService, + ActorExtEvDataService, ActorWeatherService, } import edu.ie3.simona.agent.participant.evcs.EvcsAgent @@ -666,7 +666,7 @@ class GridAgentController( modelConfiguration, primaryServiceProxy, Iterable( - ActorEvMovementsService( + ActorExtEvDataService( evMovementsService ) ), diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index 36803ae3c3..8bfac96858 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -46,7 +46,6 @@ import edu.ie3.simona.model.participant.{ SystemParticipant, } import edu.ie3.simona.ontology.messages.Activation -import edu.ie3.simona.ontology.messages.SchedulerMessage.ScheduleActivation import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ FlexResponse, IssueFlexControl, @@ -60,7 +59,6 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ } import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.scala.quantities.ReactivePower -import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps import org.apache.pekko.actor.typed.{ActorRef => TypedActorRef} import org.apache.pekko.actor.{ActorRef, FSM} import squants.{Dimensionless, Power} @@ -356,28 +354,13 @@ abstract class ParticipantAgent[ isYetTriggered, ), ) => - /* We yet have received at least one data provision message. Handle all messages, that follow up for this tick, by - * adding the received data to the collection state data and checking, if everything is at its place */ - val unexpectedSender = baseStateData.foreseenDataTicks.exists { - case (ref, None) => msg.serviceRef == ref - case _ => false - } - - if (data.contains(msg.serviceRef) || unexpectedSender) { + if (data.contains(msg.serviceRef)) { /* Update the yet received information */ val updatedData = data + (msg.serviceRef -> Some(msg.data)) - /* If we have received unexpected data, we also have not been scheduled before */ - if (unexpectedSender) - scheduler ! ScheduleActivation( - self.toTyped, - msg.tick, - msg.unlockKey, - ) - /* Depending on if a next data tick can be foreseen, either update the entry in the base state data or remove * it */ - val foreSeenDataTicks = + val foreseenDataTicks = baseStateData.foreseenDataTicks + (msg.serviceRef -> msg.nextDataTick) val updatedBaseStateData = BaseStateData.updateBaseStateData( baseStateData, @@ -385,7 +368,7 @@ abstract class ParticipantAgent[ baseStateData.requestValueStore, baseStateData.voltageValueStore, baseStateData.additionalActivationTicks, - foreSeenDataTicks, + foreseenDataTicks, ) val updatedStateData: DataCollectionStateData[PD] = stateData .copy( @@ -612,16 +595,13 @@ abstract class ParticipantAgent[ actorRef -> None } - val unforeseenPossible = - baseStateData.foreseenDataTicks.exists(_._2.isEmpty) - val nextStateData = DataCollectionStateData( baseStateData, expectedSenders, yetTriggered = true, ) - if (expectedSenders.nonEmpty || unforeseenPossible) { + if (expectedSenders.nonEmpty) { /* Do await provision messages in HandleInformation */ goto(HandleInformation) using nextStateData } else { diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala index 8ac5f1b44a..e9fef1ad20 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala @@ -72,10 +72,7 @@ import edu.ie3.simona.model.participant.{ SystemParticipant, } import edu.ie3.simona.ontology.messages.Activation -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} +import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ @@ -514,36 +511,6 @@ protected trait ParticipantAgentFundamentals[ } } - val unexpectedSender = baseStateData.foreseenDataTicks.exists { - case (ref, None) => msg.serviceRef == ref - case _ => false - } - - /* If we have received unexpected data, we also have not been scheduled before */ - if (unexpectedSender) { - baseStateData match { - case modelStateData: ParticipantModelBaseStateData[_, _, _, _] => - val maybeEmAgent = modelStateData.flexStateData.map(_.emAgent) - - maybeEmAgent match { - case Some(emAgent) => - emAgent ! ScheduleFlexRequest( - modelStateData.model.getUuid, - msg.tick, - msg.unlockKey, - ) - case None => - scheduler ! ScheduleActivation( - self.toTyped, - msg.tick, - msg.unlockKey, - ) - } - case _ => - false - } - } - /* If the sender announces a new next tick, add it to the list of expected ticks, else remove the current entry */ val foreseenDataTicks = baseStateData.foreseenDataTicks + (msg.serviceRef -> msg.nextDataTick) @@ -1098,9 +1065,9 @@ protected trait ParticipantAgentFundamentals[ false } - // Only for completing initialization: - // if we are EM-managed, there is no new tick for the - // scheduler, since we are activated by the EmAgent from now on + // If we're completing initialization and we're EM-managed: + // There is no new tick for the scheduler, + // since we are activated by the EmAgent from now on scheduler ! Completion( self.toTyped, maybeNextTick.filterNot(_ => emManaged), 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 b8b2ef33ad..5308b4e768 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ServiceRegistration.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ServiceRegistration.scala @@ -12,7 +12,7 @@ import edu.ie3.simona.agent.participant.data.Data.PrimaryData.PrimaryDataWithApp import edu.ie3.simona.agent.participant.data.Data.SecondaryData import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.{ - ActorEvMovementsService, + ActorExtEvDataService, ActorPriceService, ActorWeatherService, } @@ -83,8 +83,8 @@ trait ServiceRegistration[ case ActorWeatherService(serviceRef) => registerForWeather(serviceRef, inputModel) Some(serviceRef) - case ActorEvMovementsService(serviceRef) => - registerForEvMovements(serviceRef, inputModel) + case ActorExtEvDataService(serviceRef) => + registerForEvData(serviceRef, inputModel) Some(serviceRef) } @@ -124,7 +124,7 @@ trait ServiceRegistration[ * Input model of the simulation mode * @return */ - private def registerForEvMovements( + private def registerForEvData( serviceRef: ActorRef, inputModel: I, ): Unit = { diff --git a/src/main/scala/edu/ie3/simona/agent/participant/data/secondary/SecondaryDataService.scala b/src/main/scala/edu/ie3/simona/agent/participant/data/secondary/SecondaryDataService.scala index 7d2646b87b..ebac2ad136 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/data/secondary/SecondaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/data/secondary/SecondaryDataService.scala @@ -24,6 +24,6 @@ object SecondaryDataService { final case class ActorWeatherService(override val actorRef: ActorRef) extends SecondaryDataService[WeatherData] - final case class ActorEvMovementsService(override val actorRef: ActorRef) + final case class ActorExtEvDataService(override val actorRef: ActorRef) extends SecondaryDataService[EvData] } diff --git a/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgent.scala index b2c428242d..cdab067fa7 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgent.scala @@ -12,15 +12,20 @@ import edu.ie3.simona.agent.participant.data.Data.PrimaryData.{ ZERO_POWER, } import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService -import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.ActorEvMovementsService +import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.ActorExtEvDataService import edu.ie3.simona.agent.participant.statedata.BaseStateData.ParticipantModelBaseStateData -import edu.ie3.simona.agent.participant.statedata.ParticipantStateData +import edu.ie3.simona.agent.participant.statedata.{ + BaseStateData, + DataCollectionStateData, + ParticipantStateData, +} import edu.ie3.simona.agent.participant.statedata.ParticipantStateData.ParticipantInitializeStateData import edu.ie3.simona.agent.participant.{ ParticipantAgent, ParticipantAgentFundamentals, } import edu.ie3.simona.agent.state.AgentState.Idle +import edu.ie3.simona.agent.state.ParticipantAgentState.HandleInformation import edu.ie3.simona.config.SimonaConfig.EvcsRuntimeConfig import edu.ie3.simona.model.participant.evcs.EvcsModel import edu.ie3.simona.model.participant.evcs.EvcsModel.{ @@ -54,7 +59,7 @@ object EvcsAgent { ) val neededServices: Vector[Class[_ <: SecondaryDataService[_]]] = Vector( - classOf[ActorEvMovementsService] + classOf[ActorExtEvDataService] ) } @@ -105,6 +110,50 @@ class EvcsAgent( stay() using updatedStateData } + when(HandleInformation) { + // FreeLotsRequest and DepartingEvsRequest also need to be handled + // in case the activation has arrived first + + case Event( + EvFreeLotsRequest(tick), + stateData: DataCollectionStateData[ApparentPower], + ) => + stateData.baseStateData match { + case modelStateData: BaseStateData.ParticipantModelBaseStateData[ + ApparentPower, + EvcsRelevantData, + EvcsState, + EvcsModel, + ] => + handleFreeLotsRequest(tick, modelStateData) + stay() + case x => + throw new IllegalStateException( + s"Unsupported base state data '$x' when receiving FreeLotsRequest" + ) + } + + case Event( + DepartingEvsRequest(tick, departingEvs), + stateData: DataCollectionStateData[ApparentPower], + ) => + stateData.baseStateData match { + case modelStateData: BaseStateData.ParticipantModelBaseStateData[ + ApparentPower, + EvcsRelevantData, + EvcsState, + EvcsModel, + ] => + val updatedStateData = + handleDepartingEvsRequest(tick, departingEvs, modelStateData) + stay() using stateData.copy(baseStateData = updatedStateData) + case x => + throw new IllegalStateException( + s"Unsupported base state data '$x' when receiving DepartingEvsRequest" + ) + } + } + /** Determine the average result within the given tick window * * @param tickToResults diff --git a/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgentFundamentals.scala index 37bbbae246..d98e837e48 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgentFundamentals.scala @@ -19,7 +19,7 @@ import edu.ie3.simona.agent.participant.ParticipantAgentFundamentals import edu.ie3.simona.agent.participant.data.Data.PrimaryData.ApparentPower import edu.ie3.simona.agent.participant.data.Data.SecondaryData import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService -import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.ActorEvMovementsService +import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.ActorExtEvDataService import edu.ie3.simona.agent.participant.evcs.EvcsAgent.neededServices import edu.ie3.simona.agent.participant.statedata.BaseStateData.{ FlexControlledData, @@ -212,7 +212,7 @@ protected trait EvcsAgentFundamentals .getOrElse(tick, Map.empty) .collectFirst { // filter secondary data for arriving EVs data - case (_, arrivingEvsData: ArrivingEvsData) => + case (_, arrivingEvsData: ArrivingEvs) => arrivingEvsData.arrivals } .getOrElse(Seq.empty) @@ -327,7 +327,7 @@ protected trait EvcsAgentFundamentals .values .collectFirst { // filter secondary data for arriving EVs data - case _: ArrivingEvsData => + case _: ArrivingEvs => handleArrivingEvsAndGoIdle( tick, scheduler, @@ -359,7 +359,7 @@ protected trait EvcsAgentFundamentals EvcsModel, ], ): Unit = { - val evServiceRef = getService[ActorEvMovementsService]( + val evServiceRef = getService[ActorExtEvDataService]( modelBaseStateData.services ) @@ -398,7 +398,7 @@ protected trait EvcsAgentFundamentals EvcsState, EvcsModel, ] = { - val evServiceRef = getService[ActorEvMovementsService]( + val evServiceRef = getService[ActorExtEvDataService]( baseStateData.services ) @@ -494,32 +494,39 @@ protected trait EvcsAgentFundamentals val relevantData = createCalcRelevantData(modelBaseStateData, tick) - val lastState = getLastOrInitialStateData(modelBaseStateData, tick) + val updatedBaseStateData = { + if (relevantData.arrivals.nonEmpty) { + val lastState = getLastOrInitialStateData(modelBaseStateData, tick) - val currentEvs = modelBaseStateData.model.determineCurrentEvs( - relevantData, - lastState, - ) + val currentEvs = modelBaseStateData.model.determineCurrentEvs( + relevantData, + lastState, + ) - // if new EVs arrived, a new scheduling must be calculated. - val newSchedule = modelBaseStateData.model.calculateNewScheduling( - relevantData, - currentEvs, - ) + // if new EVs arrived, a new scheduling must be calculated. + val newSchedule = modelBaseStateData.model.calculateNewScheduling( + relevantData, + currentEvs, + ) - // create new current state - val newState = EvcsState(currentEvs, newSchedule, tick) + // create new current state + val newState = EvcsState(currentEvs, newSchedule, tick) - val updatedStateDataStore = ValueStore.updateValueStore( - modelBaseStateData.stateDataStore, - tick, - newState, - ) + val updatedStateDataStore = ValueStore.updateValueStore( + modelBaseStateData.stateDataStore, + tick, + newState, + ) - /* Update the base state data with the updated result value store and relevant data store */ - val updatedBaseStateData = modelBaseStateData.copy( - stateDataStore = updatedStateDataStore - ) + /* Update the base state data with the updated state data store */ + modelBaseStateData.copy( + stateDataStore = updatedStateDataStore + ) + } else + // Empty arrivals means that there is no data for this EVCS at the current tick, + // thus we just return and wait for the next activation + modelBaseStateData + } // We're only here if we're not flex-controlled, thus sending a Completion is always right goToIdleReplyCompletionAndScheduleTriggerForNextAction( 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 05a5be6886..1e8ae341a9 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 @@ -12,7 +12,6 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ ProvisionMessage, ServiceRegistrationMessage, } -import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import org.apache.pekko.actor.ActorRef import java.util.UUID @@ -48,8 +47,7 @@ object EvMessage { override val tick: Long, override val serviceRef: ActorRef, override val data: EvData, - override val nextDataTick: Option[Long] = None, - override val unlockKey: Option[ScheduleKey] = None, + override val nextDataTick: Option[Long], ) extends EvMessage with ProvisionMessage[EvData] @@ -74,7 +72,7 @@ object EvMessage { * @param arrivals * EVs arriving at the charging station */ - final case class ArrivingEvsData( + final case class ArrivingEvs( arrivals: Seq[EvModelWrapper] ) extends EvData {} diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/PrimaryDataMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/PrimaryDataMessage.scala index 73aa482b64..4721af5f2f 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/PrimaryDataMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/PrimaryDataMessage.scala @@ -8,7 +8,6 @@ package edu.ie3.simona.ontology.messages.services import edu.ie3.simona.agent.participant.data.Data.PrimaryData.ApparentPower import edu.ie3.simona.ontology.messages.services.ServiceMessage.ProvisionMessage -import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import org.apache.pekko.actor.ActorRef sealed trait PrimaryDataMessage @@ -30,7 +29,6 @@ object PrimaryDataMessage { override val serviceRef: ActorRef, override val data: ApparentPower, override val nextDataTick: Option[Long], - override val unlockKey: Option[ScheduleKey] = None, ) extends ProvisionMessage[ApparentPower] with PrimaryDataMessage } 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 33946b669f..d7444454fd 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 @@ -17,7 +17,7 @@ import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey */ sealed trait ServiceMessage -case object ServiceMessage { +object ServiceMessage { /** Message used to register for a service */ @@ -75,7 +75,10 @@ case object ServiceMessage { val tick: Long val serviceRef: ActorRef val data: D + + /** Next tick at which data could arrive. If None, no data is expected for + * the rest of the simulation + */ val nextDataTick: Option[Long] - val unlockKey: Option[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 9d17284d63..cb9e8349ba 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 @@ -11,7 +11,6 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ ProvisionMessage, ServiceRegistrationMessage, } -import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.util.scala.quantities.Irradiance import org.apache.pekko.actor.ActorRef import squants.{Temperature, Velocity} @@ -54,7 +53,6 @@ object WeatherMessage { override val serviceRef: ActorRef, override val data: WeatherData, override val nextDataTick: Option[Long], - override val unlockKey: Option[ScheduleKey] = None, ) extends WeatherMessage with ProvisionMessage[WeatherData] diff --git a/src/main/scala/edu/ie3/simona/scheduler/core/PhaseSwitchCore.scala b/src/main/scala/edu/ie3/simona/scheduler/core/PhaseSwitchCore.scala deleted file mode 100644 index eff9128068..0000000000 --- a/src/main/scala/edu/ie3/simona/scheduler/core/PhaseSwitchCore.scala +++ /dev/null @@ -1,160 +0,0 @@ -/* - * © 2023. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.scheduler.core - -import edu.ie3.simona.exceptions.CriticalFailureException -import edu.ie3.simona.scheduler.core.Core.{ - ActiveCore, - Actor, - CoreFactory, - InactiveCore, -} -import edu.ie3.util.scala.collection.immutable.PrioritySwitchBiSet - -/** A scheduler core that activates actors in phases when active. This means - * that only one actor at any given time is activated and the completion of - * said actor is necessary before activating other actors scheduled for the - * same tick. - * - * When multiple actors are scheduled for the same tick, they are always - * activated in the same order, which is given by the initial scheduling of - * said actors. Thus, if e.g. actor 1 has been scheduled for initialization - * before actor 2, actor 1 will be activated before actor 2 for the - * initialization tick and all consecutive ticks. - */ -object PhaseSwitchCore extends CoreFactory { - - override def create(): PhaseSwitchInactive = - PhaseSwitchInactive(PrioritySwitchBiSet.empty, None) - - final case class PhaseSwitchInactive private ( - private val activationQueue: PrioritySwitchBiSet[Long, Actor], - private val lastActiveTick: Option[Long], - ) extends InactiveCore { - override def activate(newTick: Long): ActiveCore = { - val nextScheduledTick = activationQueue.headKeyOption.getOrElse( - throw new CriticalFailureException( - "No activation scheduled, cannot activate." - ) - ) - - if (nextScheduledTick != newTick) - throw new CriticalFailureException( - s"Cannot activate with new tick $newTick because $nextScheduledTick is the next scheduled tick." - ) - - PhaseSwitchActive(activationQueue, activeTick = newTick) - } - - override def handleSchedule( - actor: Actor, - newTick: Long, - ): (Option[Long], InactiveCore) = { - lastActiveTick.filter(newTick < _).foreach { lastActive => - throw new CriticalFailureException( - s"Cannot schedule an activation for $actor at tick $newTick because the last active tick is $lastActive" - ) - } - - val oldEarliestTick = activationQueue.headKeyOption - - val updatedQueue = activationQueue.set(newTick, actor) - val newEarliestTick = updatedQueue.headKeyOption - - val maybeScheduleTick = - Option - .when(newEarliestTick != oldEarliestTick)(newEarliestTick) - .flatten - - (maybeScheduleTick, copy(activationQueue = updatedQueue)) - } - - } - - private final case class PhaseSwitchActive( - private val activationQueue: PrioritySwitchBiSet[Long, Actor], - activeTick: Long, - private val phase: Int = 0, - private val activeActors: Set[Actor] = Set.empty, - ) extends ActiveCore { - - override def handleCompletion(actor: Actor): ActiveCore = { - if (!activeActors.contains(actor)) - throw new CriticalFailureException( - s"Actor $actor is not part of the expected completing actors" - ) - - copy(activeActors = activeActors.excl(actor)) - } - - override def maybeComplete(): Option[(Option[Long], InactiveCore)] = { - Option.when( - activeActors.isEmpty && activationQueue.headKeyOption.forall( - _ > activeTick - ) - ) { - ( - activationQueue.headKeyOption, - PhaseSwitchInactive(activationQueue, Some(activeTick)), - ) - } - } - - override def handleSchedule( - actor: Actor, - newTick: Long, - ): ActiveCore = { - if (newTick == activeTick) { - // what's done, is done: old phases are completed, - // thus they cannot handle new activation schedulings - if (activationQueue.indexOf(actor).exists(_ < phase)) { - val activeActor = activationQueue.values(phase) - throw new CriticalFailureException( - s"Cannot schedule an activation at tick $newTick for $actor while actor $activeActor is active now" - ) - } - } else { - if (newTick < activeTick) - throw new CriticalFailureException( - s"Cannot schedule an activation at tick $newTick for $actor while tick $activeTick is currently active" - ) - } - - copy(activationQueue = activationQueue.set(newTick, actor)) - } - - override def takeNewActivations(): (Iterable[Actor], ActiveCore) = { - Option - .when(activeActors.isEmpty) { - // only one actor can be active at a time, and only - // if the last actor of the current tick has completed - activationQueue.takeNextValueFor(activeTick) - } - .flatten - .map { case (actor, updatedQueue) => - val newPhase = activationQueue - .indexOf(actor) - .getOrElse( - throw new RuntimeException( - "Actor not scheduled, should not happen" - ) - ) - ( - Iterable.single(actor), - copy( - activationQueue = updatedQueue, - phase = newPhase, - activeActors = activeActors.incl(actor), - ), - ) - } - .getOrElse((Iterable.empty, this)) - } - - } - -} 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 052a19b479..66cfaa96f1 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -6,19 +6,20 @@ package edu.ie3.simona.service.ev -import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps -import org.apache.pekko.actor.{ActorContext, ActorRef, Props} import edu.ie3.simona.api.data.ev.ExtEvData import edu.ie3.simona.api.data.ev.model.EvModel import edu.ie3.simona.api.data.ev.ontology._ import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException -import edu.ie3.simona.exceptions.{InitializationException, ServiceException} +import edu.ie3.simona.exceptions.{ + CriticalFailureException, + InitializationException, + ServiceException, +} import edu.ie3.simona.model.participant.evcs.EvModelWrapper import edu.ie3.simona.ontology.messages.services.EvMessage._ import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegistrationResponseMessage.RegistrationSuccessfulMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.ServiceRegistrationMessage -import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceBaseStateData, @@ -29,9 +30,12 @@ import edu.ie3.simona.service.ev.ExtEvDataService.{ } 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 java.util.UUID import scala.jdk.CollectionConverters._ +import scala.jdk.OptionConverters._ import scala.util.{Failure, Success, Try} object ExtEvDataService { @@ -142,8 +146,8 @@ class ExtEvDataService(override val scheduler: ActorRef) serviceStateData.uuidToActorRef.get(evcs) match { case None => // Actor is not registered yet - agentToBeRegistered ! RegistrationSuccessfulMessage(self, None) - + // (not sending confirmation message yet, because we're waiting + // for MobSim to tell us what the first tick is going to be) serviceStateData.copy( uuidToActorRef = serviceStateData.uuidToActorRef + (evcs -> agentToBeRegistered) @@ -175,6 +179,10 @@ class ExtEvDataService(override val scheduler: ActorRef) ExtEvStateData, 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" @@ -185,11 +193,14 @@ class ExtEvDataService(override val scheduler: ActorRef) case _: RequestEvcsFreeLots => requestFreeLots(tick) case departingEvsRequest: RequestDepartingEvs => - requestDepartingEvs(tick, departingEvsRequest.departures) + requestDepartingEvs(tick, asScala(departingEvsRequest.departures)) case arrivingEvsProvision: ProvideArrivingEvs => - handleArrivingEvs(tick, arrivingEvsProvision.arrivals)( - serviceStateData, - ctx, + handleArrivingEvs( + tick, + asScala(arrivingEvsProvision.arrivals), + arrivingEvsProvision.maybeNextTick.toScala.map(Long2long), + )( + serviceStateData ) } } @@ -241,16 +252,16 @@ class ExtEvDataService(override val scheduler: ActorRef) private def requestDepartingEvs( tick: Long, - requestedDepartingEvs: java.util.Map[UUID, java.util.List[UUID]], + requestedDepartingEvs: Map[UUID, Seq[UUID]], )(implicit serviceStateData: ExtEvStateData ): (ExtEvStateData, Option[Long]) = { val departingEvResponses = - requestedDepartingEvs.asScala.flatMap { case (evcs, departingEvs) => + requestedDepartingEvs.flatMap { case (evcs, departingEvs) => serviceStateData.uuidToActorRef.get(evcs) match { case Some(evcsActor) => - evcsActor ! DepartingEvsRequest(tick, departingEvs.asScala.toSeq) + evcsActor ! DepartingEvsRequest(tick, departingEvs) Some(evcs) @@ -280,44 +291,46 @@ class ExtEvDataService(override val scheduler: ActorRef) private def handleArrivingEvs( tick: Long, - allArrivingEvs: java.util.Map[UUID, java.util.List[EvModel]], + allArrivingEvs: Map[UUID, Seq[EvModel]], + maybeNextTick: Option[Long], )(implicit - serviceStateData: ExtEvStateData, - ctx: ActorContext, + serviceStateData: ExtEvStateData ): (ExtEvStateData, Option[Long]) = { - val actorToEvs = allArrivingEvs.asScala.flatMap { - case (evcs, arrivingEvs) => - serviceStateData.uuidToActorRef - .get(evcs) - .map((_, arrivingEvs.asScala.map(EvModelWrapper.apply).toSeq)) - .orElse { - log.warning( - "A corresponding actor ref for UUID {} could not be found", - evcs, - ) - None - } - } - if (actorToEvs.nonEmpty) { - val keys = - ScheduleLock.multiKey(ctx, scheduler.toTyped, tick, actorToEvs.size) + if (tick == INIT_SIM_TICK) { + + maybeNextTick.getOrElse( + throw new CriticalFailureException( + s"After initialization, a first simulation tick needs to be provided by the external mobility simulation." + ) + ) + + serviceStateData.uuidToActorRef.foreach { case (_, actor) => + actor ! RegistrationSuccessfulMessage( + self, + maybeNextTick, + ) + } + + } else { + serviceStateData.uuidToActorRef.foreach { case (evcs, actor) => + val evs = + allArrivingEvs.getOrElse(evcs, Seq.empty) - actorToEvs.zip(keys).foreach { case ((actor, arrivingEvs), key) => actor ! ProvideEvDataMessage( tick, self, - ArrivingEvsData(arrivingEvs), - unlockKey = Some(key), + ArrivingEvs(evs.map(EvModelWrapper.apply)), + maybeNextTick, ) } - } ( serviceStateData.copy( extEvMessage = None ), + // We still don't return the next tick because departures might come earlier None, ) } diff --git a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala index b37c061d39..7ff05ccf1a 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala @@ -22,7 +22,6 @@ import edu.ie3.simona.exceptions.InitializationException import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegistrationResponseMessage.RegistrationSuccessfulMessage -import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceActivationBaseStateData, @@ -437,6 +436,5 @@ object PrimaryServiceWorker { override val serviceRef: ActorRef, override val data: PrimaryData, override val nextDataTick: Option[Long], - override val unlockKey: Option[ScheduleKey] = None, ) extends ServiceMessage.ProvisionMessage[PrimaryData] } diff --git a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala index 9126926147..c8196aaf30 100644 --- a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala +++ b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala @@ -12,8 +12,7 @@ import edu.ie3.simona.event.RuntimeEvent import edu.ie3.simona.event.listener.{DelayedStopHelper, RuntimeEventListener} import edu.ie3.simona.main.RunSimona.SimonaEnded import edu.ie3.simona.scheduler.TimeAdvancer -import edu.ie3.simona.scheduler.core.PhaseSwitchCore -import edu.ie3.simona.sim.setup.{ExtSimSetupData, SimonaSetup} +import edu.ie3.simona.sim.setup.SimonaSetup import edu.ie3.util.scala.Scope import org.apache.pekko.actor.typed.scaladsl.adapter._ import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} @@ -77,30 +76,23 @@ object SimonaSim { val timeAdvancer = simonaSetup.timeAdvancer(ctx, ctx.self, runtimeEventListener) - val rootPhaseSwitch = - simonaSetup.scheduler(ctx, timeAdvancer, PhaseSwitchCore) + val scheduler = simonaSetup.scheduler(ctx, timeAdvancer) // External simulations have to be scheduled for initialization first, // so that the phase switch permanently activates them first - val extSimulationData: ExtSimSetupData = - simonaSetup.extSimulations(ctx, rootPhaseSwitch) - - // scheduler for all actors besides external simulation, - // which come second in line with phase switch - val simScheduler = - simonaSetup.scheduler(ctx, rootPhaseSwitch) + val extSimulationData = simonaSetup.extSimulations(ctx, scheduler) /* start services */ // primary service proxy val primaryServiceProxy = - simonaSetup.primaryServiceProxy(ctx, simScheduler) + simonaSetup.primaryServiceProxy(ctx, scheduler) // weather service val weatherService = - simonaSetup.weatherService(ctx, simScheduler) + simonaSetup.weatherService(ctx, scheduler) val environmentRefs = EnvironmentRefs( - simScheduler, + scheduler, runtimeEventListener.toClassic, primaryServiceProxy, weatherService, @@ -116,14 +108,12 @@ object SimonaSim { val otherActors = Iterable[ActorRef[_]]( timeAdvancer, - rootPhaseSwitch, - simScheduler, + scheduler, primaryServiceProxy.toTyped, weatherService.toTyped, ) ++ gridAgents ++ - extSimulationData.extDataServices.values.map(_.toTyped) ++ - extSimulationData.extScheduler.toSeq + extSimulationData.extDataServices.values.map(_.toTyped) /* 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 40443cad5f..3209fb706d 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -6,15 +6,12 @@ package edu.ie3.simona.sim.setup -import edu.ie3.simona.ontology.messages.SchedulerMessage 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], - extScheduler: Option[ActorRef[SchedulerMessage]], ) { def evDataService: Option[ClassicRef] = 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 bfb4e16cee..9c6effa859 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -99,14 +99,14 @@ trait SimonaSetup { * * @param context * Actor context to use - * @param rootScheduler - * Actor reference to it's according scheduler to use + * @param scheduler + * Actor reference to the scheduler to use * @return * External simulations and their init data */ def extSimulations( context: ActorContext[_], - rootScheduler: ActorRef[SchedulerMessage], + scheduler: ActorRef[SchedulerMessage], ): ExtSimSetupData /** Creates the time advancer 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 29f2bb7240..65d47863cf 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -194,20 +194,19 @@ class SimonaStandaloneSetup( override def extSimulations( context: ActorContext[_], - rootScheduler: ActorRef[SchedulerMessage], + scheduler: ActorRef[SchedulerMessage], ): ExtSimSetupData = { val jars = ExtSimLoader.scanInputFolder() val extLinks = jars.flatMap(ExtSimLoader.loadExtLink).toSeq if (extLinks.nonEmpty) { - val extScheduler = scheduler(context, parent = rootScheduler) val (extSimAdapters, extDataServices) = extLinks.zipWithIndex.map { case (extLink, index) => // external simulation always needs at least an ExtSimAdapter val extSimAdapter = context.toClassic.simonaActorOf( - ExtSimAdapter.props(extScheduler.toClassic), + ExtSimAdapter.props(scheduler.toClassic), s"$index", ) val extSimAdapterData = new ExtSimAdapterData(extSimAdapter, args) @@ -215,7 +214,7 @@ class SimonaStandaloneSetup( // send init data right away, init activation is scheduled extSimAdapter ! ExtSimAdapter.Create( extSimAdapterData, - ScheduleLock.singleKey(context, extScheduler, INIT_SIM_TICK), + ScheduleLock.singleKey(context, scheduler, INIT_SIM_TICK), ) // setup data services that belong to this external simulation @@ -226,7 +225,7 @@ class SimonaStandaloneSetup( extLink.getExtDataSimulations.asScala.zipWithIndex.map { case (_: ExtEvSimulation, dIndex) => val extEvDataService = context.toClassic.simonaActorOf( - ExtEvDataService.props(extScheduler.toClassic), + ExtEvDataService.props(scheduler.toClassic), s"$index-$dIndex", ) val extEvData = new ExtEvData(extEvDataService, extSimAdapter) @@ -235,7 +234,7 @@ class SimonaStandaloneSetup( InitExtEvData(extEvData), ScheduleLock.singleKey( context, - extScheduler, + scheduler, INIT_SIM_TICK, ), ) @@ -258,10 +257,9 @@ class SimonaStandaloneSetup( ExtSimSetupData( extSimAdapters, extDataServices.flatten.toMap, - Some(extScheduler), ) } else { - ExtSimSetupData(Iterable.empty, Map.empty, None) + ExtSimSetupData(Iterable.empty, Map.empty) } } diff --git a/src/main/scala/edu/ie3/util/scala/collection/immutable/PrioritySwitchBiSet.scala b/src/main/scala/edu/ie3/util/scala/collection/immutable/PrioritySwitchBiSet.scala deleted file mode 100644 index 5aeaf9591a..0000000000 --- a/src/main/scala/edu/ie3/util/scala/collection/immutable/PrioritySwitchBiSet.scala +++ /dev/null @@ -1,213 +0,0 @@ -/* - * © 2023. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.util.scala.collection.immutable - -import scala.collection.immutable - -/** Queue that is specialized at holding many values of type [[V]] for the same - * key of type [[K]], while only allowing each value to be linked to one key. - * Mathematically, the relation between keys and values is thus not univalent - * (right-unique), but injective. - * - * In contrast to the [[PrioritySwitchBiSet]], this data structure always - * returns values stored at the same key in a fixed order that is determined by - * the first storage of a value for any key. Also, only one value can be - * returned at a time for some key, namely the value that is in order after the - * last returned value for the same key. - * - * @param orderedValues - * Vector that holds values in a fixed order, in which they are always - * returned for all keys. This vector is never cleared, thus there should be - * a limited number of values stored in this data structure only. - * @param valueToIndex - * A reverse map to [[orderedValues]], linking every value to the index it - * has been stored at. - * @param queue - * A sorted map that holds the keys and the indices to values stored in - * [[orderedValues]]. Value indices are also ordered numerically within - * sorted sets. - * @param back - * A map that links values back to keys. Used to fastly ensure every value is - * only stored once. - * @tparam K - * Type of the key - * @tparam V - * Type of the value - */ -final case class PrioritySwitchBiSet[K, V]( - private val orderedValues: immutable.Vector[V], - private val valueToIndex: Map[V, Int], - private val queue: immutable.SortedMap[K, immutable.SortedSet[Int]], - private val back: Map[V, K], -) { - - /** Get the first key of the queue, if the queue is not empty. - * - * @return - * The first key - */ - def headKeyOption: Option[K] = - queue.headOption.map { case (key, _) => key } - - /** Get the first index of the first key of the queue, if the queue is not - * empty. - * - * @return - * The first index of the first key - */ - def headKeyIndexOption: Option[Int] = - queue.headOption.flatMap { case (_, set) => set.headOption } - - /** Get the index that given value is stored at, if it exists. - * - * @param value - * Value to retrieve the index for - * @return - * The index - */ - def indexOf(value: V): Option[Int] = valueToIndex.get(value) - - /** Set given value to given key - * - * @param key - * The key to add the value for - * @param value - * The value to add - * @return - * The altered data structure - */ - def set(key: K, value: V): PrioritySwitchBiSet[K, V] = - dequeue(value).add(key, value) - - private def dequeue(value: V): PrioritySwitchBiSet[K, V] = - back - .get(value) - .map { key => - val updatedBack = back.removed(value) - - val updatedQueue = valueToIndex - .get(value) - .map { i => - val newSet = - queue - .get(key) - .map(_.excl(i)) - .getOrElse(immutable.SortedSet.empty[Int]) - - if (newSet.isEmpty) - queue.removed(key) - else - queue.updated(key, newSet) - } - .getOrElse(queue) - - copy( - queue = updatedQueue, - back = updatedBack, - ) - } - .getOrElse(this) - - private def add(key: K, value: V): PrioritySwitchBiSet[K, V] = { - // add value to orderedValues and valueToIndex, if not present already - val (updatedStruct, i) = orderedValues.indexOf(value) match { - case -1 => - val newIndex = orderedValues.size - ( - copy( - orderedValues = orderedValues.appended(value), - valueToIndex = valueToIndex.updated(value, newIndex), - ), - newIndex, - ) - case i => - (this, i) - } - - // add value to key - val updatedSet = - queue.getOrElse(key, immutable.SortedSet.empty[Int]).incl(i) - - updatedStruct.copy( - queue = queue.updated(key, updatedSet), - back = back.updated(value, key), - ) - } - - /** Retrieves the first element in the list of given key. The returned element - * is also removed the queue here. - * - * If the list of values for given key is empty, the list is removed: There - * are no empty lists in the queue, thus also keys only exist for non-empty - * lists. - * - * @return - * The first element in the list of the first key and the changed data - * structure, if it is not empty. - */ - def takeNextValueFor(key: K): Option[(V, PrioritySwitchBiSet[K, V])] = { - queue - .get(key) - .flatMap { set => - set.headOption.map(orderedValues).map((set, _)) - } - .map { case (set, firstValue) => - val updatedQueue = - if (set.size == 1) - queue.removed(key) - else - // drop first value - queue.updated(key, set.drop(1)) - - ( - firstValue, - copy(queue = updatedQueue, back = back.removed(firstValue)), - ) - } - } - - /** Tests whether there is no value for any key in the queue. - * - * @return - * True if the queue is empty - */ - def isEmpty: Boolean = queue.isEmpty - - /** Tests whether there is any value for any key in the queue. - * - * @return - * True if the queue is non-empty - */ - def nonEmpty: Boolean = queue.nonEmpty - - /** Returns all values in order - * - * @return - * The value vector - */ - def values: Vector[V] = orderedValues - -} - -object PrioritySwitchBiSet { - - /** Creates and returns an empty PrioritySwitchBiSet for given types. - * - * @tparam K - * Type of the key, which needs to be sortable by means of [[Ordering]] - * @tparam V - * Type of the value - * @return - * An empty PrioritySwitchBiSet - */ - def empty[K: Ordering, V]: PrioritySwitchBiSet[K, V] = PrioritySwitchBiSet( - Vector.empty, - Map.empty, - immutable.SortedMap.empty, - Map.empty, - ) -} 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 08420edfbe..3e3a9c7a2c 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala @@ -17,7 +17,7 @@ import edu.ie3.simona.agent.grid.GridAgentMessages.{ } import edu.ie3.simona.agent.participant.ParticipantAgent.RequestAssetPowerMessage import edu.ie3.simona.agent.participant.data.Data.PrimaryData.ApparentPower -import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.ActorEvMovementsService +import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.ActorExtEvDataService import edu.ie3.simona.agent.participant.evcs.EvcsAgent import edu.ie3.simona.agent.participant.statedata.BaseStateData.ParticipantModelBaseStateData import edu.ie3.simona.agent.participant.statedata.DataCollectionStateData @@ -36,10 +36,7 @@ import edu.ie3.simona.model.participant.evcs.EvcsModel.{ ScheduleEntry, } import edu.ie3.simona.ontology.messages.Activation -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} +import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EvMessage._ @@ -48,7 +45,6 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegistrationResp RegistrationFailedMessage, RegistrationSuccessfulMessage, } -import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.simona.test.ParticipantAgentSpec import edu.ie3.simona.test.common.input.EvcsInputTestData import edu.ie3.simona.test.common.{EvTestData, TestSpawnerClassic} @@ -65,7 +61,6 @@ import squants.energy._ import squants.{Each, Energy, Power} import java.time.ZonedDateTime -import java.util.UUID import scala.collection.immutable.{SortedMap, SortedSet} class EvcsAgentModelCalculationSpec @@ -191,7 +186,7 @@ class EvcsAgentModelCalculationSpec inputModel = evcsInputModel, modelConfig = modelConfig, secondaryDataServices = Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ), simulationStartDate = simulationStartDate, simulationEndDate = simulationEndDate, @@ -253,7 +248,7 @@ class EvcsAgentModelCalculationSpec inputModel shouldBe SimpleInputContainer(evcsInputModel) modelConfig shouldBe modelConfig secondaryDataServices shouldBe Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ) simulationStartDate shouldBe simulationStartDate simulationEndDate shouldBe simulationEndDate @@ -301,7 +296,7 @@ class EvcsAgentModelCalculationSpec startDate shouldBe simulationStartDate endDate shouldBe simulationEndDate services shouldBe Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ) outputConfig shouldBe NotifierConfig( simulationResultInfo = false, @@ -412,8 +407,6 @@ class EvcsAgentModelCalculationSpec } "do correct transitions faced with new data in Idle" in { - val lock = TestProbe("lock") - val evcsAgent = TestFSMRef( new EvcsAgent( scheduler = scheduler.ref, @@ -438,7 +431,7 @@ class EvcsAgentModelCalculationSpec evService.expectMsgType[RegisterForEvDataMessage] evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(0)), ) /* I'm not interested in the content of the CompletionMessage */ @@ -448,19 +441,17 @@ class EvcsAgentModelCalculationSpec /* Send out new data */ val arrivingEvsData = - ArrivingEvsData(Seq(EvModelWrapper(evA), EvModelWrapper(evB))) + ArrivingEvs(Seq(EvModelWrapper(evA), EvModelWrapper(evB))) - val key1 = Some(ScheduleKey(lock.ref.toTyped, UUID.randomUUID())) evService.send( evcsAgent, ProvideEvDataMessage( 0L, evService.ref, arrivingEvsData, - unlockKey = key1, + Some(900), ), ) - scheduler.expectMsg(ScheduleActivation(evcsAgent.toTyped, 0, key1)) /* Find yourself in corresponding state and state data */ evcsAgent.stateName shouldBe HandleInformation @@ -471,7 +462,9 @@ class EvcsAgentModelCalculationSpec isYetTriggered, ) => /* The next data tick is already registered */ - baseStateData.foreseenDataTicks shouldBe Map(evService.ref -> None) + baseStateData.foreseenDataTicks shouldBe Map( + evService.ref -> Some(900) + ) /* The yet sent data is also registered */ expectedSenders shouldBe Map( @@ -495,7 +488,7 @@ class EvcsAgentModelCalculationSpec /* The agent will notice, that all expected information are apparent, switch to Calculate and trigger itself * for starting the calculation */ scheduler.expectMsg( - Completion(evcsAgent.toTyped, None) + Completion(evcsAgent.toTyped, Some(900)) ) evcsAgent.stateName shouldBe Idle evcsAgent.stateData match { @@ -548,8 +541,6 @@ class EvcsAgentModelCalculationSpec } "do correct transitions triggered for activation in idle" in { - val lock = TestProbe("lock") - val evcsAgent = TestFSMRef( new EvcsAgent( scheduler = scheduler.ref, @@ -574,7 +565,7 @@ class EvcsAgentModelCalculationSpec evService.expectMsgType[RegisterForEvDataMessage] evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(0)), ) /* I'm not interested in the content of the CompletionMessage */ @@ -597,10 +588,10 @@ class EvcsAgentModelCalculationSpec isYetTriggered, ) => /* The next data tick is already registered */ - baseStateData.foreseenDataTicks shouldBe Map(evService.ref -> None) + baseStateData.foreseenDataTicks shouldBe Map(evService.ref -> Some(0)) /* The yet sent data is also registered */ - expectedSenders shouldBe Map.empty + expectedSenders shouldBe Map(evService.ref -> None) /* It is not yet triggered */ isYetTriggered shouldBe true @@ -612,24 +603,22 @@ class EvcsAgentModelCalculationSpec /* Send out new data */ val arrivingEvsData = - ArrivingEvsData(Seq(EvModelWrapper(evA), EvModelWrapper(evB))) + ArrivingEvs(Seq(EvModelWrapper(evA), EvModelWrapper(evB))) - val key1 = Some(ScheduleKey(lock.ref.toTyped, UUID.randomUUID())) evService.send( evcsAgent, ProvideEvDataMessage( 0L, evService.ref, arrivingEvsData, - unlockKey = key1, + Some(900), ), ) - scheduler.expectMsg(ScheduleActivation(evcsAgent.toTyped, 0, key1)) /* The agent will notice, that all expected information are apparent, switch to Calculate and trigger itself * for starting the calculation */ scheduler.expectMsg( - Completion(evcsAgent.toTyped, None) + Completion(evcsAgent.toTyped, Some(900)) ) evcsAgent.stateName shouldBe Idle evcsAgent.stateData match { @@ -707,7 +696,7 @@ class EvcsAgentModelCalculationSpec evService.expectMsgType[RegisterForEvDataMessage] evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(10800)), ) /* I'm not interested in the content of the CompletionMessage */ @@ -715,7 +704,7 @@ class EvcsAgentModelCalculationSpec awaitAssert(evcsAgent.stateName shouldBe Idle) evcsAgent ! RequestAssetPowerMessage( - 7200L, + 7200, Each(1.0), Each(0.0), ) @@ -728,8 +717,6 @@ class EvcsAgentModelCalculationSpec } "provide number of free lots when asked to" in { - val lock = TestProbe("lock") - val evcsAgent = TestFSMRef( new EvcsAgent( scheduler = scheduler.ref, @@ -754,7 +741,7 @@ class EvcsAgentModelCalculationSpec evService.expectMsgType[RegisterForEvDataMessage] evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(0)), ) /* I'm not interested in the content of the CompletionMessage */ @@ -777,28 +764,26 @@ class EvcsAgentModelCalculationSpec scheduler.expectNoMessage() /* Send ev for this tick */ - val key1 = Some(ScheduleKey(lock.ref.toTyped, UUID.randomUUID())) evService.send( evcsAgent, ProvideEvDataMessage( - 0L, + 0, evService.ref, - ArrivingEvsData(Seq(EvModelWrapper(evA))), - unlockKey = key1, + ArrivingEvs(Seq(EvModelWrapper(evA))), + Some(900), ), ) - scheduler.expectMsg(ScheduleActivation(evcsAgent.toTyped, 0, key1)) scheduler.send( evcsAgent, Activation(0), ) - scheduler.expectMsg(Completion(evcsAgent.toTyped)) + scheduler.expectMsg(Completion(evcsAgent.toTyped, Some(900))) /* Ask for public evcs lot count again with a later tick */ evService.send( evcsAgent, - EvFreeLotsRequest(3600L), + EvFreeLotsRequest(3600), ) // this time, only one is still free @@ -812,6 +797,75 @@ class EvcsAgentModelCalculationSpec scheduler.expectNoMessage() } + "handle empty arrivals" in { + val evcsAgent = TestFSMRef( + new EvcsAgent( + scheduler = scheduler.ref, + initStateData = initStateData, + listener = Iterable.empty, + ) + ) + + scheduler.send( + evcsAgent, + Activation(INIT_SIM_TICK), + ) + + /* Refuse registration with primary service */ + primaryServiceProxy.expectMsgType[PrimaryServiceRegistrationMessage] + primaryServiceProxy.send( + evcsAgent, + RegistrationFailedMessage(primaryServiceProxy.ref), + ) + + /* I'm not interested in the content of the RegistrationMessage */ + evService.expectMsgType[RegisterForEvDataMessage] + evService.send( + evcsAgent, + RegistrationSuccessfulMessage(evService.ref, Some(0)), + ) + + /* I'm not interested in the content of the CompletionMessage */ + scheduler.expectMsgType[Completion] + awaitAssert(evcsAgent.stateName shouldBe Idle) + + /* Send ev for this tick */ + evService.send( + evcsAgent, + ProvideEvDataMessage( + 0, + evService.ref, + ArrivingEvs(Seq(EvModelWrapper(evA))), + Some(900), + ), + ) + + scheduler.send( + evcsAgent, + Activation(0), + ) + scheduler.expectMsg(Completion(evcsAgent.toTyped, Some(900))) + + /* Send empty EV list for this tick */ + evService.send( + evcsAgent, + ProvideEvDataMessage( + 900, + evService.ref, + ArrivingEvs(Seq.empty), + Some(1800), + ), + ) + + scheduler.send( + evcsAgent, + Activation(900), + ) + scheduler.expectMsg(Completion(evcsAgent.toTyped, Some(1800))) + + scheduler.expectNoMessage() + } + val evcsAgent = TestFSMRef( new EvcsAgent( scheduler = scheduler.ref, @@ -819,7 +873,7 @@ class EvcsAgentModelCalculationSpec inputModel = evcsInputModelQv, modelConfig = modelConfig, secondaryDataServices = Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ), simulationStartDate = simulationStartDate, simulationEndDate = simulationEndDate, @@ -833,8 +887,6 @@ class EvcsAgentModelCalculationSpec ) "provide correct average power after three data ticks are available" in { - val lock = TestProbe("lock") - scheduler.send(evcsAgent, Activation(INIT_SIM_TICK)) /* Refuse registration with primary service */ @@ -848,7 +900,7 @@ class EvcsAgentModelCalculationSpec evService.expectMsgType[RegisterForEvDataMessage] evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(0)), ) /* I'm not interested in the content of the CompletionMessage */ @@ -857,26 +909,26 @@ class EvcsAgentModelCalculationSpec /* Send out the three data points */ /* ... for tick 0 */ - val key1 = Some(ScheduleKey(lock.ref.toTyped, UUID.randomUUID())) evService.send( evcsAgent, ProvideEvDataMessage( - 0L, + 0, evService.ref, - ArrivingEvsData(Seq(EvModelWrapper(evA.copyWithDeparture(3600L)))), - unlockKey = key1, + ArrivingEvs( + Seq(EvModelWrapper(evA.copyWithDeparture(3600))) + ), + Some(3600), ), ) - scheduler.expectMsg(ScheduleActivation(evcsAgent.toTyped, 0, key1)) scheduler.send(evcsAgent, Activation(0)) - scheduler.expectMsg(Completion(evcsAgent.toTyped)) + scheduler.expectMsg(Completion(evcsAgent.toTyped, Some(3600))) /* ... for tick 3600 */ // departures first evService.send( evcsAgent, - DepartingEvsRequest(3600L, Seq(evA.getUuid)), + DepartingEvsRequest(3600, Seq(evA.getUuid)), ) evService.expectMsgType[DepartingEvsResponse] match { case DepartingEvsResponse(evcs, evModels) => @@ -891,27 +943,27 @@ class EvcsAgentModelCalculationSpec } // arrivals second - val key2 = Some(ScheduleKey(lock.ref.toTyped, UUID.randomUUID())) evService.send( evcsAgent, ProvideEvDataMessage( - 3600L, + 3600, evService.ref, - ArrivingEvsData(Seq(EvModelWrapper(evB.copyWithDeparture(7200L)))), - unlockKey = key2, + ArrivingEvs( + Seq(EvModelWrapper(evB.copyWithDeparture(7200))) + ), + Some(7200), ), ) - scheduler.expectMsg(ScheduleActivation(evcsAgent.toTyped, 3600, key2)) scheduler.send(evcsAgent, Activation(3600)) - scheduler.expectMsg(Completion(evcsAgent.toTyped)) + scheduler.expectMsg(Completion(evcsAgent.toTyped, Some(7200))) /* ... for tick 7200 */ // departures first evService.send( evcsAgent, - DepartingEvsRequest(7200L, Seq(evB.getUuid)), + DepartingEvsRequest(7200, Seq(evB.getUuid)), ) evService.expectMsgType[DepartingEvsResponse] match { case DepartingEvsResponse(evcs, evModels) => @@ -925,21 +977,21 @@ class EvcsAgentModelCalculationSpec } } - val key3 = Some(ScheduleKey(lock.ref.toTyped, UUID.randomUUID())) evService.send( evcsAgent, ProvideEvDataMessage( - 7200L, + 7200, evService.ref, - ArrivingEvsData(Seq(EvModelWrapper(evA.copyWithDeparture(10800L)))), - unlockKey = key3, + ArrivingEvs( + Seq(EvModelWrapper(evA.copyWithDeparture(10800))) + ), + None, ), ) - scheduler.expectMsg(ScheduleActivation(evcsAgent.toTyped, 7200, key3)) scheduler.send(evcsAgent, Activation(7200)) - scheduler.expectMsg(Completion(evcsAgent.toTyped)) + scheduler.expectMsg(Completion(evcsAgent.toTyped, None)) /* Ask the agent for average power in tick 7500 */ evcsAgent ! RequestAssetPowerMessage( @@ -1003,7 +1055,7 @@ class EvcsAgentModelCalculationSpec inputModel = evcsInputModelQv, modelConfig = modelConfig, secondaryDataServices = Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ), simulationStartDate = simulationStartDate, simulationEndDate = simulationEndDate, @@ -1040,7 +1092,7 @@ class EvcsAgentModelCalculationSpec inputModel shouldBe SimpleInputContainer(evcsInputModelQv) modelConfig shouldBe modelConfig secondaryDataServices shouldBe Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ) simulationStartDate shouldBe simulationStartDate simulationEndDate shouldBe simulationEndDate @@ -1071,7 +1123,7 @@ class EvcsAgentModelCalculationSpec evService.expectMsg(RegisterForEvDataMessage(evcsInputModelQv.getUuid)) evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(0)), ) emAgent.expectMsg( @@ -1103,11 +1155,11 @@ class EvcsAgentModelCalculationSpec startDate shouldBe simulationStartDate endDate shouldBe simulationEndDate services shouldBe Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ) outputConfig shouldBe defaultOutputConfig additionalActivationTicks shouldBe empty - foreseenDataTicks shouldBe Map(evService.ref -> None) + foreseenDataTicks shouldBe Map(evService.ref -> Some(0)) voltageValueStore shouldBe ValueStore( resolution, SortedMap(0L -> Each(1.0)), @@ -1137,7 +1189,7 @@ class EvcsAgentModelCalculationSpec inputModel = SimpleInputContainer(evcsInputModelQv), modelConfig = modelConfig, secondaryDataServices = Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ), simulationStartDate = simulationStartDate, simulationEndDate = simulationEndDate, @@ -1178,7 +1230,7 @@ class EvcsAgentModelCalculationSpec inputModel shouldBe SimpleInputContainer(evcsInputModelQv) modelConfig shouldBe modelConfig secondaryDataServices shouldBe Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ) simulationStartDate shouldBe simulationStartDate simulationEndDate shouldBe simulationEndDate @@ -1212,11 +1264,11 @@ class EvcsAgentModelCalculationSpec evService.expectMsg(RegisterForEvDataMessage(evcsInputModelQv.getUuid)) evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(900)), ) emAgent.expectMsg( - ScheduleFlexRequest(evcsInputModelQv.getUuid, 0) + ScheduleFlexRequest(evcsInputModelQv.getUuid, 900) ) scheduler.expectMsg(Completion(evcsAgent.toTyped)) @@ -1267,7 +1319,7 @@ class EvcsAgentModelCalculationSpec result.p should approximate(Kilowatts(0)) result.q should approximate(Megavars(0)) requestAtNextActivation shouldBe false - requestAtTick shouldBe None + requestAtTick shouldBe Some(900) } // results arrive after next activation @@ -1285,11 +1337,11 @@ class EvcsAgentModelCalculationSpec ProvideEvDataMessage( 900, evService.ref, - ArrivingEvsData(Seq(ev900)), + ArrivingEvs(Seq(ev900)), + Some(4500), ), ) - emAgent.expectMsg(ScheduleFlexRequest(evcsInputModelQv.getUuid, 900)) emAgent.send(evcsAgent, RequestFlexOptions(900)) emAgent.expectMsgType[ProvideFlexOptions] match { @@ -1403,11 +1455,11 @@ class EvcsAgentModelCalculationSpec ProvideEvDataMessage( 4500, evService.ref, - ArrivingEvsData(Seq(ev4500)), + ArrivingEvs(Seq(ev4500)), + Some(11700), ), ) - emAgent.expectMsg(ScheduleFlexRequest(evcsInputModelQv.getUuid, 4500)) emAgent.send(evcsAgent, RequestFlexOptions(4500)) emAgent.expectMsgType[ProvideFlexOptions] match { @@ -1501,7 +1553,7 @@ class EvcsAgentModelCalculationSpec result.q should approximate(Megavars(0)) // since battery is still below lowest soc, it's still considered empty requestAtNextActivation shouldBe true - requestAtTick shouldBe Some(32776) + requestAtTick shouldBe Some(11700) } resultListener.expectMsgPF() { @@ -1536,11 +1588,11 @@ class EvcsAgentModelCalculationSpec ProvideEvDataMessage( 11700, evService.ref, - ArrivingEvsData(Seq(ev11700)), + ArrivingEvs(Seq(ev11700)), + None, ), ) - emAgent.expectMsg(ScheduleFlexRequest(evcsInputModelQv.getUuid, 11700)) emAgent.send(evcsAgent, RequestFlexOptions(11700)) val combinedChargingPower = diff --git a/src/test/scala/edu/ie3/simona/scheduler/PhaseSwitchSchedulerSpec.scala b/src/test/scala/edu/ie3/simona/scheduler/PhaseSwitchSchedulerSpec.scala deleted file mode 100644 index 447a2d2a44..0000000000 --- a/src/test/scala/edu/ie3/simona/scheduler/PhaseSwitchSchedulerSpec.scala +++ /dev/null @@ -1,502 +0,0 @@ -/* - * © 2023. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.scheduler - -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} -import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} -import edu.ie3.simona.scheduler.core.PhaseSwitchCore -import edu.ie3.simona.util.ActorUtils.RichActivatedActor -import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK -import org.apache.pekko.actor.testkit.typed.scaladsl.{ - ScalaTestWithActorTestKit, - TestProbe, -} -import org.scalatest.matchers.should -import org.scalatest.wordspec.AnyWordSpecLike - -/** A lot of functions are already tested in [[SchedulerSpec]] and don't need to - * be repeated here - */ -class PhaseSwitchSchedulerSpec - extends ScalaTestWithActorTestKit - with AnyWordSpecLike - with should.Matchers { - - "The Scheduler with PhaseSwitchCore should work correctly" when { - - "receiving initial activation scheduling before activation" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - val agent2 = TestProbe[Activation]("agent_2") - - scheduler ! ScheduleActivation(agent1.ref, INIT_SIM_TICK) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe INIT_SIM_TICK - val schedulerActivation = sa1.actor - - scheduler ! ScheduleActivation(agent2.ref, INIT_SIM_TICK) - - agent1.expectNoMessage() - agent2.expectNoMessage() - parent.expectNoMessage() - - // TICK -1, phase 0 - schedulerActivation ! Activation(INIT_SIM_TICK) - - agent1.expectMessage(Activation(INIT_SIM_TICK)) - agent2.expectNoMessage() - - // TICK -1, phase 1 - scheduler ! Completion(agent1.ref) - - agent1.expectNoMessage() - agent2.expectMessage(Activation(INIT_SIM_TICK)) - - parent.expectNoMessage() - - scheduler ! Completion(agent2.ref) - - parent.expectMessage(Completion(schedulerActivation)) - } - - "receiving activation scheduling in a different order than initially" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - val agent2 = TestProbe[Activation]("agent_2") - - scheduler ! ScheduleActivation(agent1.ref, INIT_SIM_TICK) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe INIT_SIM_TICK - val schedulerActivation = sa1.actor - - scheduler ! ScheduleActivation(agent2.ref, INIT_SIM_TICK) - - // TICK -1, phase 0 - schedulerActivation ! Activation(INIT_SIM_TICK) - agent1.expectMessage(Activation(INIT_SIM_TICK)) - - // TICK -1, phase 1 - scheduler ! Completion(agent1.ref) - agent2.expectMessage(Activation(INIT_SIM_TICK)) - - scheduler ! Completion(agent2.ref, Some(0)) - parent.expectMessage(Completion(schedulerActivation, Some(0))) - - // scheduling first actor after the second actor for the same tick - scheduler ! ScheduleActivation(agent1.ref, 0) - parent.expectNoMessage() - - // TICK 0, phase 0 - schedulerActivation ! Activation(0) - - agent1.expectMessage(Activation(0)) - agent2.expectNoMessage() - - // TICK -1, phase 1 - scheduler ! Completion(agent1.ref) - - agent1.expectNoMessage() - agent2.expectMessage(Activation(0)) - - scheduler ! Completion(agent2.ref) - - parent.expectMessage(Completion(schedulerActivation)) - } - - "receiving activation scheduling after init activation" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - val agent2 = TestProbe[Activation]("agent_2") - - scheduler ! ScheduleActivation(agent1.ref, INIT_SIM_TICK) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe INIT_SIM_TICK - val schedulerActivation = sa1.actor - - agent1.expectNoMessage() - - // TICK -1, phase 0 - schedulerActivation ! Activation(INIT_SIM_TICK) - - agent1.expectMessage(Activation(INIT_SIM_TICK)) - agent2.expectNoMessage() - - scheduler ! ScheduleActivation( - agent2.ref, - INIT_SIM_TICK, - ) - - // waiting for next phase - agent2.expectNoMessage() - parent.expectNoMessage() - - // TICK -1, phase 1 - scheduler ! Completion(agent1.ref) - - agent2.expectMessage(Activation(INIT_SIM_TICK)) - - scheduler ! Completion(agent2.ref) - - parent.expectMessage(Completion(schedulerActivation)) - } - - "scheduling with parent when earliest tick changes" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - val agent2 = TestProbe[Activation]("agent_2") - - scheduler ! ScheduleActivation(agent1.ref, 10) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe 10 - val schedulerActivation = sa1.actor - - scheduler ! ScheduleActivation(agent2.ref, INIT_SIM_TICK) - parent.expectMessage( - ScheduleActivation(schedulerActivation, INIT_SIM_TICK) - ) - - scheduler ! ScheduleActivation(agent2.ref, 5) - parent.expectMessage(ScheduleActivation(schedulerActivation, 5)) - - scheduler ! ScheduleActivation(agent2.ref, 11) - // expect activation for earliest tick (of agent 1) - parent.expectMessage(ScheduleActivation(schedulerActivation, 10)) - - scheduler ! ScheduleActivation(agent2.ref, 20) - // no update, 10 is still earliest - parent.expectNoMessage() - - scheduler ! ScheduleActivation(agent2.ref, 10) - parent.expectNoMessage() - - agent1.expectNoMessage() - - // TICK 10, phase 0 - schedulerActivation ! Activation(10) - - agent1.expectMessage(Activation(10)) - agent2.expectNoMessage() - - // TICK 10, phase 1 - scheduler ! Completion(agent1.ref) - - agent2.expectMessage(Activation(10)) - } - - "scheduling two actors for different ticks" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - val agent2 = TestProbe[Activation]("agent_2") - - scheduler ! ScheduleActivation( - agent1.ref, - INIT_SIM_TICK, - ) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe INIT_SIM_TICK - val schedulerActivation = sa1.actor - - scheduler ! ScheduleActivation( - agent2.ref, - INIT_SIM_TICK, - ) - - // TICK -1 - schedulerActivation ! Activation(INIT_SIM_TICK) - - agent2.expectNoMessage() - agent1.expectActivationAndComplete( - scheduler, - INIT_SIM_TICK, - Some(0), - ) - - parent.expectNoMessage() - - agent2.expectActivationAndComplete( - scheduler, - INIT_SIM_TICK, - Some(0), - ) - - parent.expectMessage(Completion(schedulerActivation, Some(0))) - - agent1.expectNoMessage() - agent2.expectNoMessage() - - // TICK 0 - schedulerActivation ! Activation(0) - - agent1.expectActivationAndComplete( - scheduler, - 0, - Some(900), - ) - - parent.expectNoMessage() - - agent2.expectActivationAndComplete( - scheduler, - 0, - Some(300), - ) - - parent.expectMessage(Completion(schedulerActivation, Some(300))) - - // TICK 300 - schedulerActivation ! Activation(300) - - agent2.expectActivationAndComplete( - scheduler, - 300, - Some(900), - ) - - parent.expectMessage(Completion(schedulerActivation, Some(900))) - - agent1.expectNoMessage() - agent2.expectNoMessage() - - // TICK 900 - schedulerActivation ! Activation(900) - - agent1.expectActivationAndComplete( - scheduler, - 900, - Some(3600), - ) - - parent.expectNoMessage() - - agent2.expectActivationAndComplete( - scheduler, - 900, - Some(1800), - ) - - parent.expectMessage(Completion(schedulerActivation, Some(1800))) - - parent.expectNoMessage() - agent1.expectNoMessage() - agent2.expectNoMessage() - } - - } - - "The Scheduler with PhaseSwitchCore should fail and stop" when { - - "activated if no activation is scheduled" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - - scheduler ! ScheduleActivation(agent1.ref, INIT_SIM_TICK) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe INIT_SIM_TICK - val schedulerActivation = sa1.actor - - // TICK -1 - schedulerActivation ! Activation(INIT_SIM_TICK) - agent1.expectActivationAndComplete(scheduler, INIT_SIM_TICK, None) - parent.expectMessage(Completion(schedulerActivation, None)) - - // No activation scheduled, this should fail now - schedulerActivation ! Activation(0) - - // scheduler stopped - parent.expectTerminated(scheduler) - } - - "activated with wrong tick" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - - scheduler ! ScheduleActivation(agent1.ref, INIT_SIM_TICK) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe INIT_SIM_TICK - val schedulerActivation = sa1.actor - - // TICK 0 - schedulerActivation ! Activation(0) - - // agent does not receive activation - agent1.expectNoMessage() - parent.expectNoMessage() - - // scheduler stopped - parent.expectTerminated(scheduler) - } - - "asked to schedule activation for a past tick while inactive" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - - scheduler ! ScheduleActivation(agent1.ref, 900) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe 900 - val schedulerActivation = sa1.actor - - // TICK 900 - schedulerActivation ! Activation(900) - - agent1.expectActivationAndComplete( - scheduler, - 900, - Some(1800), - ) - - parent.expectMessage(Completion(schedulerActivation, Some(1800))) - - // now inactive again - // can't schedule activation with earlier tick than last tick (900) -> error - scheduler ! ScheduleActivation(agent1.ref, INIT_SIM_TICK) - - // agent does not receive activation - agent1.expectNoMessage() - parent.expectNoMessage() - - // scheduler stopped - parent.expectTerminated(scheduler) - } - - "asked to schedule activation for a past tick while active" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - - scheduler ! ScheduleActivation(agent1.ref, 0) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe 0 - val schedulerActivation = sa1.actor - - // TICK 0 - schedulerActivation ! Activation(0) - agent1.expectMessage(Activation(0)) - - // can't schedule activation for earlier tick than current active -> error - scheduler ! ScheduleActivation(agent1.ref, INIT_SIM_TICK) - - // agent does not receive activation - agent1.expectNoMessage() - parent.expectNoMessage() - - // scheduler stopped - parent.expectTerminated(scheduler) - } - - "asked to schedule activation for a past phase in the current tick" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - val agent2 = TestProbe[Activation]("agent_2") - - scheduler ! ScheduleActivation(agent1.ref, 0) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe 0 - val schedulerActivation = sa1.actor - - scheduler ! ScheduleActivation(agent2.ref, 0) - - // TICK 0, phase 0 - schedulerActivation ! Activation(0) - agent1.expectMessage(Activation(0)) - - // TICK 0, phase 1 - scheduler ! Completion(agent1.ref) - agent2.expectMessage(Activation(0)) - - // schedule first actor again, this is not allowed - scheduler ! ScheduleActivation(agent1.ref, 0) - - // agent does not receive activation - agent1.expectNoMessage() - parent.expectNoMessage() - - // scheduler stopped - parent.expectTerminated(scheduler) - } - - "receiving unexpected completion message" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - val agent2 = TestProbe[Activation]("agent_1") - - scheduler ! ScheduleActivation(agent1.ref, 0) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe 0 - val schedulerActivation = sa1.actor - - // TICK 0 - schedulerActivation ! Activation(0) - agent1.expectMessage(Activation(0)) - - // receiving completion for wrong actor - scheduler ! Completion(agent2.ref) - - // parent does not receive completion - parent.expectNoMessage() - - // scheduler stopped - parent.expectTerminated(scheduler) - } - } -} 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 62c062c86e..8e6cf9e07d 100644 --- a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala @@ -6,9 +6,6 @@ package edu.ie3.simona.service.ev -import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps -import org.apache.pekko.actor.{ActorRef, ActorSystem} -import org.apache.pekko.testkit.{TestActorRef, TestProbe} import com.typesafe.config.ConfigFactory import edu.ie3.simona.api.data.ev.ExtEvData import edu.ie3.simona.api.data.ev.model.EvModel @@ -33,12 +30,16 @@ import edu.ie3.simona.test.common.{ } 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.scalatest.wordspec.AnyWordSpecLike import tech.units.indriya.quantity.Quantities import java.util.UUID import scala.concurrent.duration.DurationInt import scala.jdk.CollectionConverters._ +import scala.jdk.OptionConverters._ class ExtEvDataServiceSpec extends TestKitWithShutdown( @@ -55,15 +56,6 @@ class ExtEvDataServiceSpec with EvTestData with TestSpawnerClassic { - private val scheduler = TestProbe("scheduler") - private val extSimAdapter = TestProbe("extSimAdapter") - - private val extEvData = (dataService: ActorRef) => - new ExtEvData( - dataService, - extSimAdapter.ref, - ) - private val evcs1UUID = UUID.fromString("06a14909-366e-4e94-a593-1016e1455b30") private val evcs2UUID = @@ -71,7 +63,11 @@ class ExtEvDataServiceSpec "An uninitialized ev movement service" must { "send correct completion message after initialisation" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -79,7 +75,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extEvData(evService)), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsg( ScheduleActivation(evService.toTyped, INIT_SIM_TICK, Some(key)) @@ -90,7 +86,11 @@ class ExtEvDataServiceSpec } "stash registration request and handle it correctly once initialized" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val evcs1 = TestProbe("evcs1") @@ -106,7 +106,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extEvData(evService)), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsg( ScheduleActivation(evService.toTyped, INIT_SIM_TICK, Some(key)) @@ -114,15 +114,17 @@ class ExtEvDataServiceSpec scheduler.send(evService, Activation(INIT_SIM_TICK)) scheduler.expectMsg(Completion(evService.toTyped)) - - evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, None)) } } "An idle ev movements service" must { "handle duplicate registrations correctly" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -130,7 +132,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extEvData(evService)), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -141,19 +143,35 @@ class ExtEvDataServiceSpec val evcs2 = TestProbe("evcs2") evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) - evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, None)) + evcs1.expectNoMessage() evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) - evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, None)) + evcs2.expectNoMessage() // register first one again evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) evcs1.expectNoMessage() - evcs2.expectNoMessage() + + extEvData.sendExtMsg( + new ProvideArrivingEvs( + Map.empty[UUID, java.util.List[EvModel]].asJava, + Some(long2Long(0L)).toJava, + ) + ) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + scheduler.send(evService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(evService.toTyped)) + + evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) + evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) } "fail when activated without having received ExtEvMessage" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -161,7 +179,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extEvData(evService)), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsg( ScheduleActivation(evService.toTyped, INIT_SIM_TICK, Some(key)) @@ -182,9 +200,11 @@ class ExtEvDataServiceSpec } "handle free lots requests correctly and forward them to the correct evcs" in { - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - val extData = extEvData(evService) + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -192,7 +212,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -203,12 +223,25 @@ class ExtEvDataServiceSpec val evcs2 = TestProbe("evcs2") evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) - evcs1.expectMsgType[RegistrationSuccessfulMessage] + evcs1.expectNoMessage() evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) - evcs2.expectMsgType[RegistrationSuccessfulMessage] + evcs2.expectNoMessage() + + extEvData.sendExtMsg( + new ProvideArrivingEvs( + Map.empty[UUID, java.util.List[EvModel]].asJava, + Some(long2Long(0L)).toJava, + ) + ) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + scheduler.send(evService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(evService.toTyped)) - extData.sendExtMsg( + evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) + evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) + + extEvData.sendExtMsg( new RequestEvcsFreeLots() ) @@ -231,7 +264,7 @@ class ExtEvDataServiceSpec scheduler.expectMsg(Completion(evService.toTyped)) - extData.receiveTriggerQueue shouldBe empty + extEvData.receiveTriggerQueue shouldBe empty // return free lots to ev service evcs1.send( @@ -243,7 +276,7 @@ class ExtEvDataServiceSpec ) // nothing should happen yet, waiting for second departed ev - extData.receiveTriggerQueue shouldBe empty + extEvData.receiveTriggerQueue shouldBe empty evcs2.send( evService, @@ -256,21 +289,23 @@ class ExtEvDataServiceSpec // ev service should recognize that all evcs that are expected are returned, // thus should send ProvideEvcsFreeLots awaitCond( - !extData.receiveTriggerQueue.isEmpty, + !extEvData.receiveTriggerQueue.isEmpty, max = 3.seconds, message = "No message received", ) - extData.receiveTriggerQueue.size() shouldBe 1 + extEvData.receiveTriggerQueue.size() shouldBe 1 // only evcs 1 should be included, the other one is full - extData.receiveTriggerQueue.take() shouldBe new ProvideEvcsFreeLots( + extEvData.receiveTriggerQueue.take() shouldBe new ProvideEvcsFreeLots( Map(evcs1UUID -> int2Integer(2)).asJava ) } "handle price requests correctly by returning dummy values" in { - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - val extData = extEvData(evService) + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -278,7 +313,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -289,12 +324,25 @@ class ExtEvDataServiceSpec val evcs2 = TestProbe("evcs2") evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) - evcs1.expectMsgType[RegistrationSuccessfulMessage] + evcs1.expectNoMessage() evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) - evcs2.expectMsgType[RegistrationSuccessfulMessage] + evcs2.expectNoMessage() - extData.sendExtMsg(new RequestCurrentPrices()) + extEvData.sendExtMsg( + new ProvideArrivingEvs( + Map.empty[UUID, java.util.List[EvModel]].asJava, + Some(long2Long(0L)).toJava, + ) + ) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + scheduler.send(evService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(evService.toTyped)) + + evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) + evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) + + extEvData.sendExtMsg(new RequestCurrentPrices()) // ev service should receive request at this moment // scheduler should receive schedule msg @@ -311,13 +359,13 @@ class ExtEvDataServiceSpec // ev service should recognize that all evcs that are expected are returned, // thus should send ProvideEvcsFreeLots awaitCond( - !extData.receiveTriggerQueue.isEmpty, + !extEvData.receiveTriggerQueue.isEmpty, max = 3.seconds, message = "No message received", ) - extData.receiveTriggerQueue.size() shouldBe 1 + extEvData.receiveTriggerQueue.size() shouldBe 1 // only evcs 1 should be included, the other one is full - extData.receiveTriggerQueue.take() shouldBe new ProvideCurrentPrices( + extEvData.receiveTriggerQueue.take() shouldBe new ProvideCurrentPrices( Map( evcs1UUID -> double2Double(0d), evcs2UUID -> double2Double(0d), @@ -328,9 +376,11 @@ class ExtEvDataServiceSpec } "return free lots requests right away if there are no evcs registered" in { - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - val extData = extEvData(evService) + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -338,14 +388,14 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] scheduler.send(evService, Activation(INIT_SIM_TICK)) scheduler.expectMsg(Completion(evService.toTyped)) - extData.sendExtMsg(new RequestEvcsFreeLots()) + extEvData.sendExtMsg(new RequestEvcsFreeLots()) // ev service should receive movements msg at this moment // scheduler receives schedule msg @@ -360,18 +410,20 @@ class ExtEvDataServiceSpec // ev service should send ProvideEvcsFreeLots right away awaitCond( - !extData.receiveTriggerQueue.isEmpty, + !extEvData.receiveTriggerQueue.isEmpty, max = 3.seconds, message = "No message received", ) - extData.receiveTriggerQueue.size() shouldBe 1 - extData.receiveTriggerQueue.take() shouldBe new ProvideEvcsFreeLots() + extEvData.receiveTriggerQueue.size() shouldBe 1 + extEvData.receiveTriggerQueue.take() shouldBe new ProvideEvcsFreeLots() } "handle ev departure requests correctly and return departed evs" in { - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - val extData = extEvData(evService) + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -379,7 +431,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -390,17 +442,30 @@ class ExtEvDataServiceSpec val evcs2 = TestProbe("evcs1") evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) - evcs1.expectMsgType[RegistrationSuccessfulMessage] + evcs1.expectNoMessage() evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) - evcs2.expectMsgType[RegistrationSuccessfulMessage] + evcs2.expectNoMessage() + + extEvData.sendExtMsg( + new ProvideArrivingEvs( + Map.empty[UUID, java.util.List[EvModel]].asJava, + Some(long2Long(0L)).toJava, + ) + ) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + scheduler.send(evService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(evService.toTyped)) + + evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) + evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) val departures = Map( evcs1UUID -> List(evA.getUuid).asJava, evcs2UUID -> List(evB.getUuid).asJava, ).asJava - extData.sendExtMsg( + extEvData.sendExtMsg( new RequestDepartingEvs(departures) ) @@ -433,7 +498,7 @@ class ExtEvDataServiceSpec ) // nothing should happen yet, waiting for second departed ev - extData.receiveTriggerQueue shouldBe empty + extEvData.receiveTriggerQueue shouldBe empty val updatedEvB = evB.copyWith( Quantities.getQuantity(4.0, PowerSystemUnits.KILOWATTHOUR) @@ -447,20 +512,22 @@ class ExtEvDataServiceSpec // ev service should recognize that all evs that are expected are returned, // thus should send ProvideDepartingEvs awaitCond( - !extData.receiveTriggerQueue.isEmpty, + !extEvData.receiveTriggerQueue.isEmpty, max = 3.seconds, message = "No message received", ) - extData.receiveTriggerQueue.size() shouldBe 1 - extData.receiveTriggerQueue.take() shouldBe new ProvideDepartingEvs( + extEvData.receiveTriggerQueue.size() shouldBe 1 + extEvData.receiveTriggerQueue.take() shouldBe new ProvideDepartingEvs( List[EvModel](updatedEvA, updatedEvB).asJava ) } "return ev departure requests right away if request list is empty" in { - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - val extData = extEvData(evService) + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -468,14 +535,14 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] scheduler.send(evService, Activation(INIT_SIM_TICK)) scheduler.expectMsg(Completion(evService.toTyped)) - extData.sendExtMsg( + extEvData.sendExtMsg( new RequestDepartingEvs(Map.empty[UUID, java.util.List[UUID]].asJava) ) @@ -492,20 +559,22 @@ class ExtEvDataServiceSpec // ev service should send ProvideDepartingEvs right away awaitCond( - !extData.receiveTriggerQueue.isEmpty, + !extEvData.receiveTriggerQueue.isEmpty, max = 3.seconds, message = "No message received", ) - extData.receiveTriggerQueue.size() shouldBe 1 - extData.receiveTriggerQueue.take() shouldBe new ProvideDepartingEvs( + extEvData.receiveTriggerQueue.size() shouldBe 1 + extEvData.receiveTriggerQueue.take() shouldBe new ProvideDepartingEvs( List.empty[EvModel].asJava ) } "handle ev arrivals correctly and forward them to the correct evcs" in { - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - val extData = extEvData(evService) + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -513,7 +582,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -524,18 +593,31 @@ class ExtEvDataServiceSpec val evcs2 = TestProbe("evcs2") evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) - evcs1.expectMsgType[RegistrationSuccessfulMessage] + evcs1.expectNoMessage() evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) - evcs2.expectMsgType[RegistrationSuccessfulMessage] + evcs2.expectNoMessage() + + extEvData.sendExtMsg( + new ProvideArrivingEvs( + Map.empty[UUID, java.util.List[EvModel]].asJava, + Some(long2Long(0L)).toJava, + ) + ) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + scheduler.send(evService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(evService.toTyped)) + + evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) + evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) val arrivals = Map( evcs1UUID -> List[EvModel](evA).asJava, evcs2UUID -> List[EvModel](evB).asJava, ).asJava - extData.sendExtMsg( - new ProvideArrivingEvs(arrivals) + extEvData.sendExtMsg( + new ProvideArrivingEvs(arrivals, None.toJava) ) // ev service should receive movements msg at this moment @@ -546,29 +628,31 @@ class ExtEvDataServiceSpec // we trigger ev service scheduler.send(evService, Activation(tick)) - // schedule lock is scheduled - scheduler.expectMsgType[ScheduleActivation].tick shouldBe tick val evsMessage1 = evcs1.expectMsgType[ProvideEvDataMessage] evsMessage1.tick shouldBe tick - evsMessage1.data shouldBe ArrivingEvsData(Seq(EvModelWrapper(evA))) - evsMessage1.unlockKey should not be empty + evsMessage1.data shouldBe ArrivingEvs( + Seq(EvModelWrapper(evA)) + ) val evsMessage2 = evcs2.expectMsgType[ProvideEvDataMessage] evsMessage2.tick shouldBe tick - evsMessage2.data shouldBe ArrivingEvsData(Seq(EvModelWrapper(evB))) - evsMessage2.unlockKey should not be empty + evsMessage2.data shouldBe ArrivingEvs( + Seq(EvModelWrapper(evB)) + ) scheduler.expectMsg(Completion(evService.toTyped)) // no response expected - extData.receiveTriggerQueue shouldBe empty + extEvData.receiveTriggerQueue shouldBe empty } "skip a movements provision from an evcs that is not registered" in { - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - val extData = extEvData(evService) + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -576,7 +660,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -586,15 +670,27 @@ class ExtEvDataServiceSpec val evcs1 = TestProbe("evcs1") evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) - evcs1.expectMsgType[RegistrationSuccessfulMessage] + evcs1.expectNoMessage() + + extEvData.sendExtMsg( + new ProvideArrivingEvs( + Map.empty[UUID, java.util.List[EvModel]].asJava, + Some(long2Long(0L)).toJava, + ) + ) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + scheduler.send(evService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(evService.toTyped)) + + evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) val arrivals = Map( evcs1UUID -> List[EvModel](evA).asJava, evcs2UUID -> List[EvModel](evB).asJava, ).asJava - extData.sendExtMsg( - new ProvideArrivingEvs(arrivals) + extEvData.sendExtMsg( + new ProvideArrivingEvs(arrivals, None.toJava) ) // ev service should receive movements msg at this moment @@ -605,18 +701,17 @@ class ExtEvDataServiceSpec // we trigger ev service scheduler.send(evService, Activation(tick)) - // schedule lock is scheduled - scheduler.expectMsgType[ScheduleActivation].tick shouldBe tick val evsMessage1 = evcs1.expectMsgType[ProvideEvDataMessage] evsMessage1.tick shouldBe tick - evsMessage1.data shouldBe ArrivingEvsData(Seq(EvModelWrapper(evA))) - evsMessage1.unlockKey should not be empty + evsMessage1.data shouldBe ArrivingEvs( + Seq(EvModelWrapper(evA)) + ) scheduler.expectMsg(Completion(evService.toTyped)) // no response expected - extData.receiveTriggerQueue shouldBe empty + 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 1ae6457b8e..db7e9abdea 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSpec.scala @@ -251,13 +251,11 @@ class PrimaryServiceWorkerSpec actualServiceRef, actualData, actualNextDataTick, - unlockKey, ) => actualTick shouldBe 0L actualServiceRef shouldBe serviceRef actualData shouldBe primaryData actualNextDataTick shouldBe Some(900L) - unlockKey shouldBe None } } @@ -356,7 +354,6 @@ class PrimaryServiceWorkerSpec actualServiceRef, data, nextDataTick, - unlockKey, ) => tick shouldBe 900L actualServiceRef shouldBe serviceRef @@ -366,7 +363,6 @@ class PrimaryServiceWorkerSpec case _ => fail("Expected to get active power only.") } nextDataTick shouldBe None - unlockKey shouldBe None } } } diff --git a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala index ad028070d2..63be126c0f 100644 --- a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala @@ -60,7 +60,7 @@ class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { ) { override def extSimulations( context: ActorContext[_], - rootScheduler: ActorRef[SchedulerMessage], + scheduler: ActorRef[SchedulerMessage], ): ExtSimSetupData = { // We cannot return a TestProbe ref here, // needs to be a proper actor created by context @@ -68,7 +68,7 @@ class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { forwardMessage(Some(extSimAdapter.ref)), uniqueName("extSimAdapterForwarder"), ) - ExtSimSetupData(Iterable(extSim.toClassic), Map.empty, None) + ExtSimSetupData(Iterable(extSim.toClassic), Map.empty) } } ), @@ -453,8 +453,8 @@ object SimonaSimSpec { override def extSimulations( context: ActorContext[_], - rootScheduler: ActorRef[SchedulerMessage], + scheduler: ActorRef[SchedulerMessage], ): ExtSimSetupData = - ExtSimSetupData(Iterable.empty, Map.empty, None) + ExtSimSetupData(Iterable.empty, Map.empty) } } 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 55e7310266..7fa3ab3469 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala @@ -58,7 +58,7 @@ class SimonaSetupSpec extends UnitSpec with SimonaSetup with SubGridGateMokka { override def extSimulations( context: ActorContext[_], - rootScheduler: ActorRef[SchedulerMessage], + scheduler: ActorRef[SchedulerMessage], ): ExtSimSetupData = throw new NotImplementedException( "This is a dummy setup" ) diff --git a/src/test/scala/edu/ie3/util/scala/collection/immutable/PrioritySwitchBiSetSpec.scala b/src/test/scala/edu/ie3/util/scala/collection/immutable/PrioritySwitchBiSetSpec.scala deleted file mode 100644 index 125cc24c5f..0000000000 --- a/src/test/scala/edu/ie3/util/scala/collection/immutable/PrioritySwitchBiSetSpec.scala +++ /dev/null @@ -1,174 +0,0 @@ -/* - * © 2023. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.util.scala.collection.immutable - -import edu.ie3.simona.test.common.UnitSpec - -class PrioritySwitchBiSetSpec extends UnitSpec { - private type Key = Int - private type Value = String - - private val item1: Value = "test1" - private val item2: Value = "test2" - private val item3: Value = "test3" - private val item4: Value = "test4" - private val item5: Value = "test5" - private val item6: Value = "test6" - - "A PriorityMultiBiSet" should { - "be created correctly emptily" in { - val prioSet = PrioritySwitchBiSet.empty[Key, Value] - - prioSet.isEmpty shouldBe true - prioSet.nonEmpty shouldBe false - prioSet.headKeyOption shouldBe None - prioSet.headKeyIndexOption shouldBe None - prioSet.values shouldBe Vector.empty - prioSet.takeNextValueFor(0) shouldBe None - } - - "behave correctly when adding to an empty map" in { - val prioSet00 = PrioritySwitchBiSet.empty[Key, Value] - - val prioSet01 = prioSet00.set(1, item1) - prioSet01.isEmpty shouldBe false - prioSet01.nonEmpty shouldBe true - prioSet01.headKeyOption shouldBe Some(1) - prioSet01.headKeyIndexOption shouldBe Some(0) - prioSet01.values shouldBe Vector(item1) - prioSet01.indexOf(item1) shouldBe Some(0) - prioSet01.indexOf(item2) shouldBe None - prioSet01.takeNextValueFor(0) shouldBe None - - val (val02, prioSet02) = prioSet01.takeNextValueFor(1).value - val02 shouldBe item1 - prioSet02.isEmpty shouldBe true - prioSet02.headKeyOption shouldBe None - prioSet02.headKeyIndexOption shouldBe None - prioSet02.values shouldBe Vector(item1) - } - - "behave correctly when adding and retrieving multiple values" in { - val prioSet00 = PrioritySwitchBiSet.empty[Key, Value] - - val prioSet01 = prioSet00.set(10, item1) - prioSet01.nonEmpty shouldBe true - prioSet01.headKeyOption shouldBe Some(10) - prioSet01.headKeyIndexOption shouldBe Some(0) - prioSet01.values shouldBe Vector(item1) - prioSet01.indexOf(item1) shouldBe Some(0) - - val prioSet02 = prioSet01.set(2, item2) - prioSet02.nonEmpty shouldBe true - prioSet02.headKeyOption shouldBe Some(2) - prioSet02.headKeyIndexOption shouldBe Some(1) - prioSet02.values shouldBe Vector(item1, item2) - prioSet02.indexOf(item2) shouldBe Some(1) - - // moving item2 to 5 - val prioSet03 = prioSet02.set(5, item2) - prioSet03.nonEmpty shouldBe true - prioSet03.headKeyOption shouldBe Some(5) - prioSet03.headKeyIndexOption shouldBe Some(1) - prioSet03.values shouldBe Vector(item1, item2) - prioSet03.indexOf(item2) shouldBe Some(1) - prioSet03.takeNextValueFor(2) shouldBe None - - val prioSet04 = prioSet03.set(5, item4) - prioSet04.nonEmpty shouldBe true - prioSet04.headKeyOption shouldBe Some(5) - prioSet04.headKeyIndexOption shouldBe Some(1) - prioSet04.values shouldBe Vector(item1, item2, item4) - prioSet04.indexOf(item4) shouldBe Some(2) - - val prioSet05 = prioSet04.set(5, item3) - prioSet05.headKeyOption shouldBe Some(5) - prioSet05.headKeyIndexOption shouldBe Some(1) - prioSet05.values shouldBe Vector(item1, item2, item4, item3) - prioSet05.indexOf(item3) shouldBe Some(3) - - val prioSet06 = prioSet05.set(15, item5) - prioSet06.headKeyOption shouldBe Some(5) - prioSet06.headKeyIndexOption shouldBe Some(1) - prioSet06.values shouldBe Vector(item1, item2, item4, item3, item5) - prioSet06.indexOf(item5) shouldBe Some(4) - - // moving item4 to 15 - val prioSet07 = prioSet06.set(15, item4) - prioSet07.headKeyOption shouldBe Some(5) - prioSet07.headKeyIndexOption shouldBe Some(1) - prioSet07.values shouldBe Vector(item1, item2, item4, item3, item5) - - // moving item1 to 15 - val prioSet08 = prioSet07.set(15, item1) - prioSet08.headKeyOption shouldBe Some(5) - prioSet08.headKeyIndexOption shouldBe Some(1) - prioSet08.values shouldBe Vector(item1, item2, item4, item3, item5) - - // priority indices should not have changed - prioSet08.indexOf(item1) shouldBe Some(0) - prioSet08.indexOf(item2) shouldBe Some(1) - prioSet08.indexOf(item3) shouldBe Some(3) - prioSet08.indexOf(item4) shouldBe Some(2) - prioSet08.indexOf(item5) shouldBe Some(4) - prioSet08.indexOf(item6) shouldBe None - - // retrieving values now. They should come in order: - // 5 -> (item2, item3), 15 -> (item1, item4, item5) - - val (val09, prioSet09) = prioSet08.takeNextValueFor(5).value - val09 shouldBe item2 - prioSet09.isEmpty shouldBe false - prioSet09.headKeyOption shouldBe Some(5) - prioSet09.headKeyIndexOption shouldBe Some(3) - - val (val10, prioSet10) = prioSet09.takeNextValueFor(5).value - val10 shouldBe item3 - prioSet10.isEmpty shouldBe false - prioSet10.headKeyOption shouldBe Some(15) - prioSet10.headKeyIndexOption shouldBe Some(0) - - val (val11, prioSet11) = prioSet10.takeNextValueFor(15).value - val11 shouldBe item1 - prioSet11.isEmpty shouldBe false - prioSet11.headKeyOption shouldBe Some(15) - prioSet11.headKeyIndexOption shouldBe Some(2) - - val (val12, prioSet12) = prioSet11.takeNextValueFor(15).value - val12 shouldBe item4 - prioSet12.isEmpty shouldBe false - prioSet12.headKeyOption shouldBe Some(15) - prioSet12.headKeyIndexOption shouldBe Some(4) - - // moving item5 to 10 - val prioSet13 = prioSet12.set(10, item5) - prioSet13.isEmpty shouldBe false - prioSet13.headKeyOption shouldBe Some(10) - prioSet13.headKeyIndexOption shouldBe Some(4) - prioSet13.takeNextValueFor(15) shouldBe None - - val (val14, prioSet14) = prioSet13.takeNextValueFor(10).value - val14 shouldBe item5 - prioSet14.isEmpty shouldBe true - prioSet14.nonEmpty shouldBe false - prioSet14.headKeyOption shouldBe None - prioSet14.headKeyIndexOption shouldBe None - - // priority indices should not have changed - prioSet14.indexOf(item1) shouldBe Some(0) - prioSet14.indexOf(item2) shouldBe Some(1) - prioSet14.indexOf(item3) shouldBe Some(3) - prioSet14.indexOf(item4) shouldBe Some(2) - prioSet14.indexOf(item5) shouldBe Some(4) - prioSet14.indexOf(item6) shouldBe None - - prioSet14.values shouldBe Vector(item1, item2, item4, item3, item5) - } - - } - -}