diff --git a/CHANGELOG.md b/CHANGELOG.md index 62299f2d3e..61882126f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implementation of StorageAgent [#309](https://github.com/ie3-institute/simona/issues/309) - Enhanced Newton-Raphson-PowerFlow failures with more information [#815](https://github.com/ie3-institute/simona/issues/815) - Update RTD references and bibliography [#868](https://github.com/ie3-institute/simona/issues/868) +- Add gradle application plugin for command line execution with gradle run [#890](https://github.com/ie3-institute/simona/issues/890) ### Changed - Adapted to changed data source in PSDM [#435](https://github.com/ie3-institute/simona/issues/435) @@ -65,6 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Rewrote SystemComponentTest from groovy to scala [#646](https://github.com/ie3-institute/simona/issues/646) - Converting remaining rst files to markdown [#838](https://github.com/ie3-institute/simona/issues/838) - Merging both `FixedFeedInModelSpec` tests [#870](https://github.com/ie3-institute/simona/issues/870) +- Prepare ThermalStorageTestData for Storage without storageVolumeLvlMin [#894](https://github.com/ie3-institute/simona/issues/894) ### Fixed - Removed a repeated line in the documentation of vn_simona config [#658](https://github.com/ie3-institute/simona/issues/658) @@ -88,6 +90,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed result output for thermal houses and cylindrical storages [#844](https://github.com/ie3-institute/simona/issues/844) - Fixed FixedFeedModelSpec [#861](https://github.com/ie3-institute/simona/issues/861) - Fixing duration calculation in result events [#801](https://github.com/ie3-institute/simona/issues/801) +- Handle MobSim requests for current prices [#892](https://github.com/ie3-institute/simona/issues/892) - Fixed Hp results leading to overheating house and other effects [#827](https://github.com/ie3-institute/simona/issues/827) - Fixed thermal storage getting recharged when empty [#827](https://github.com/ie3-institute/simona/issues/827) diff --git a/build.gradle b/build.gradle index ba28ebd6c4..dac93ee8d2 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,7 @@ plugins { id "com.github.maiflai.scalatest" version "0.32" // run scalatest without specific spec task id 'org.hidetake.ssh' version '2.11.2' id 'net.thauvin.erik.gradle.semver' version '1.0.4' // semantic versioning + id "application" } ext { @@ -166,6 +167,11 @@ jar { } } +// Run with ./gradlew run --args='--config /path/to/simona.conf' +application { + mainClassName = jar.manifest.attributes.get('Main-Class') +} + ////////////////////////////////////////////////////////////////////// // Build pekko'able fat jar using the gradle shadow plugin // see http://www.sureshpw.com/2015/10/building-akka-bundle-with-all.html diff --git a/docs/readthedocs/requirements.txt b/docs/readthedocs/requirements.txt index ddb172e349..34b2f0762a 100644 --- a/docs/readthedocs/requirements.txt +++ b/docs/readthedocs/requirements.txt @@ -1,7 +1,7 @@ Sphinx==7.3.7 sphinx-rtd-theme==2.0.0 sphinxcontrib-plantuml==0.30 -myst-parser==3.0.1 +myst-parser==4.0.0 markdown-it-py==3.0.0 sphinx-hoverxref==1.4.0 sphinxcontrib-bibtex==2.6.2 diff --git a/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala b/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala index bd51151454..cf694fb05f 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala @@ -22,7 +22,7 @@ import edu.ie3.util.quantities.PowerSystemUnits import edu.ie3.util.scala.OperationInterval import edu.ie3.util.scala.quantities.DefaultQuantities import edu.ie3.util.scala.quantities.DefaultQuantities._ -import squants.energy.Kilowatts +import squants.energy.{Energy, KilowattHours, Kilowatts} import squants.{Power, Temperature} import java.time.ZonedDateTime @@ -153,18 +153,22 @@ final case class HpModel( state: HpState, relevantData: HpRelevantData, ): (Boolean, ThermalEnergyDemand, ThermalEnergyDemand) = { - val (demandHouse, demandStorage) = thermalGrid.energyDemand( - relevantData.currentTick, - relevantData.ambientTemperature, - state.thermalGridState, - ) + val (demandHouse, demandStorage, updatedState) = + thermalGrid.energyDemandAndUpdatedState( + relevantData.currentTick, + relevantData.ambientTemperature, + state.thermalGridState, + ) + implicit val tolerance: Energy = KilowattHours(1e-3) + val noStorageOrStorageIsEmpty: Boolean = + updatedState.storageState.isEmpty || updatedState.storageState.exists( + _.storedEnergy =~ zeroKWH + ) - val storedEnergy = state.thermalGridState.storageState - .map(storageState => storageState.storedEnergy) - .getOrElse(zeroKWH) + val turnHpOn: Boolean = { + (demandHouse.hasRequiredDemand && noStorageOrStorageIsEmpty) || demandStorage.hasRequiredDemand || (state.isRunning && demandHouse.hasAdditionalDemand) || (state.isRunning && demandStorage.hasAdditionalDemand) - val turnHpOn = - (demandHouse.hasRequiredDemand && demandHouse.required > storedEnergy) || demandStorage.hasRequiredDemand || (state.isRunning && demandHouse.hasAdditionalDemand) || (state.isRunning && demandStorage.hasAdditionalDemand) + } ( turnHpOn, @@ -197,10 +201,16 @@ final case class HpModel( houseDemand: ThermalEnergyDemand, storageDemand: ThermalEnergyDemand, ): HpState = { + val lastStateStorageqDot = state.thermalGridState.storageState + .map(_.qDot) + .getOrElse(zeroKW) + val (newActivePower, newThermalPower) = if (isRunning) (pRated, pThermal) - else (DefaultQuantities.zeroKW, DefaultQuantities.zeroKW) + else if (lastStateStorageqDot < zeroKW) + (zeroKW, lastStateStorageqDot * (-1)) + else (zeroKW, zeroKW) /* Push thermal energy to the thermal grid and get its updated state in return */ val (thermalGridState, maybeThreshold) = @@ -229,26 +239,30 @@ final case class HpModel( lastState: HpState, ): ProvideFlexOptions = { /* Determine the operating state in the given tick */ - val updatedState = determineState(lastState, data) + val updatedHpState: HpState = determineState(lastState, data) /* Determine the options we have */ - val (thermalEnergyDemandHouse, thermalEnergyDemandStorge) = - thermalGrid.energyDemand( + val ( + thermalEnergyDemandHouse, + thermalEnergyDemandStorage, + updatedThermalGridState, + ) = + thermalGrid.energyDemandAndUpdatedState( data.currentTick, data.ambientTemperature, lastState.thermalGridState, ) val canOperate = thermalEnergyDemandHouse.hasRequiredDemand || thermalEnergyDemandHouse.hasAdditionalDemand || - thermalEnergyDemandStorge.hasRequiredDemand || thermalEnergyDemandStorge.hasAdditionalDemand + thermalEnergyDemandStorage.hasRequiredDemand || thermalEnergyDemandStorage.hasAdditionalDemand val canBeOutOfOperation = - !thermalEnergyDemandHouse.hasRequiredDemand && !thermalEnergyDemandStorge.hasRequiredDemand + !thermalEnergyDemandHouse.hasRequiredDemand && !thermalEnergyDemandStorage.hasRequiredDemand val lowerBoundary = if (canBeOutOfOperation) zeroKW else - updatedState.activePower + updatedHpState.activePower val upperBoundary = if (canOperate) sRated * cosPhiRated @@ -257,7 +271,7 @@ final case class HpModel( ProvideMinMaxFlexOptions( uuid, - updatedState.activePower, + updatedHpState.activePower, lowerBoundary, upperBoundary, ) @@ -288,14 +302,18 @@ final case class HpModel( /* If the setpoint value is above 50 % of the electrical power, turn on the heat pump otherwise turn it off */ val turnOn = setPower > (sRated * cosPhiRated * 0.5) - val (thermalEnergyDemandHouse, thermalEnergyDemandStorage) = - thermalGrid.energyDemand( + val ( + thermalEnergyDemandHouse, + thermalEnergyDemandStorage, + updatedThermalGridState, + ) = + thermalGrid.energyDemandAndUpdatedState( data.currentTick, data.ambientTemperature, lastState.thermalGridState, ) - val updatedState = + val updatedHpState: HpState = calcState( lastState, data, @@ -305,10 +323,10 @@ final case class HpModel( ) ( - updatedState, + updatedHpState, FlexChangeIndicator( changesAtNextActivation = true, - updatedState.maybeThermalThreshold.map(_.tick), + updatedHpState.maybeThermalThreshold.map(_.tick), ), ) } diff --git a/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala b/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala index 6f6656c9ad..817531838f 100644 --- a/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala +++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala @@ -44,7 +44,7 @@ final case class ThermalGrid( ) extends LazyLogging { /** Determine the energy demand of the total grid at the given instance in - * time + * time and returns it including the updatedState * * @param tick * Questioned instance in time @@ -53,47 +53,48 @@ final case class ThermalGrid( * @param state * Currently applicable state of the thermal grid * @return - * The total energy demand of the house and the storage + * The total energy demand of the house and the storage and an updated + * [[ThermalGridState]] */ - def energyDemand( + def energyDemandAndUpdatedState( tick: Long, ambientTemperature: Temperature, state: ThermalGridState, - ): (ThermalEnergyDemand, ThermalEnergyDemand) = { + ): (ThermalEnergyDemand, ThermalEnergyDemand, ThermalGridState) = { /* First get the energy demand of the houses but only if inner temperature is below target temperature */ - val houseDemand = + val (houseDemand, updatedHouseState) = house.zip(state.houseState).headOption match { case Some((thermalHouse, lastHouseState)) => - val updatedState = thermalHouse.determineState( - tick, - lastHouseState, - ambientTemperature, - lastHouseState.qDot, - ) + val (updatedHouseState, updatedStorageState) = + thermalHouse.determineState( + tick, + lastHouseState, + ambientTemperature, + lastHouseState.qDot, + ) if ( - updatedState._1.innerTemperature < thermalHouse.targetTemperature + updatedHouseState.innerTemperature < thermalHouse.targetTemperature ) { - house - .zip(state.houseState) - .map { case (house, state) => - house.energyDemandHeating( - tick, - ambientTemperature, - state, - ) - } - .getOrElse(ThermalEnergyDemand.noDemand) + ( + thermalHouse.energyDemandHeating( + tick, + ambientTemperature, + updatedHouseState, + ), + Some(updatedHouseState), + ) + } else { - ThermalEnergyDemand.noDemand + (ThermalEnergyDemand.noDemand, Some(updatedHouseState)) } case None => - ThermalEnergyDemand.noDemand + (ThermalEnergyDemand.noDemand, None) } /* Then go over the storages, see what they can provide and what they might be able to charge */ - val storageDemand = { + val (storageDemand, updatedStorageState) = { storage .zip(state.storageState) @@ -102,21 +103,28 @@ final case class ThermalGrid( storage.updateState(tick, state.qDot, state)._1 val storedEnergy = updatedStorageState.storedEnergy val soc = storedEnergy / storage.getMaxEnergyThreshold - val storageRequired = - if (soc < 0.5) { - storage.getMaxEnergyThreshold * 0.5 - storedEnergy + val storageRequired = { + if (soc == 0d) { + storage.getMaxEnergyThreshold - storedEnergy + } else { zeroMWH } + } val storagePossible = storage.getMaxEnergyThreshold - storedEnergy - ThermalEnergyDemand( - storageRequired, - storagePossible, + ( + ThermalEnergyDemand( + storageRequired, + storagePossible, + ), + Some(updatedStorageState), ) + } .getOrElse( - ThermalEnergyDemand(zeroMWH, zeroMWH) + ThermalEnergyDemand(zeroMWH, zeroMWH), + None, ) } @@ -129,6 +137,7 @@ final case class ThermalGrid( storageDemand.required, storageDemand.possible, ), + ThermalGridState(updatedHouseState, updatedStorageState), ) } @@ -270,11 +279,15 @@ final case class ThermalGrid( ) } - if (qDotHouseLastState > zeroKW | qDotStorageLastState > zeroKW) { + if ( + (qDotHouseLastState > zeroKW && qDotHouseLastState == qDot) | qDotStorageLastState > zeroKW + ) { val (updatedHouseState, thermalHouseThreshold, remainingQDotHouse) = handleInfeedHouse(tick, ambientTemperature, state, qDotHouseLastState) val (updatedStorageState, thermalStorageThreshold) = - if (remainingQDotHouse > qDotStorageLastState) { + if ( + qDotStorageLastState >= zeroKW && remainingQDotHouse > qDotStorageLastState + ) { handleInfeedStorage( tick, ambientTemperature, @@ -391,8 +404,9 @@ final case class ThermalGrid( } } - /** Handles the case, when the storage has heat demand and will be filled up - * here. + /** Handles the cases, when the storage has heat demand and will be filled up + * here (positive qDot) or will be return its stored energy into the thermal + * grid (negative qDot). * @param tick * Current tick * @param ambientTemperature 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 c827cb52fb..052a19b479 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -180,6 +180,8 @@ class ExtEvDataService(override val scheduler: ActorRef) "ExtEvDataService was triggered without ExtEvMessage available" ) ) match { + case _: RequestCurrentPrices => + requestCurrentPrices() case _: RequestEvcsFreeLots => requestFreeLots(tick) case departingEvsRequest: RequestDepartingEvs => @@ -192,6 +194,26 @@ class ExtEvDataService(override val scheduler: ActorRef) } } + private def requestCurrentPrices()(implicit + serviceStateData: ExtEvStateData + ): (ExtEvStateData, Option[Long]) = { + // currently not supported, return dummy + val dummyPrice = double2Double(0d) + val prices = serviceStateData.uuidToActorRef.map { case (evcs, _) => + evcs -> dummyPrice + } + serviceStateData.extEvData.queueExtResponseMsg( + new ProvideCurrentPrices(prices.asJava) + ) + + ( + serviceStateData.copy( + extEvMessage = None + ), + None, + ) + } + private def requestFreeLots(tick: Long)(implicit serviceStateData: ExtEvStateData ): (ExtEvStateData, Option[Long]) = { @@ -351,7 +373,7 @@ class ExtEvDataService(override val scheduler: ActorRef) freeLotsCount > 0 } .map { case (evcs, freeLotsCount) => - evcs -> Integer.valueOf(freeLotsCount) + evcs -> int2Integer(freeLotsCount) } serviceStateData.extEvData.queueExtResponseMsg( diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala index ba82893e6f..58dc68b2d8 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala @@ -17,12 +17,12 @@ import java.util.UUID trait ThermalGridTestData { protected val thermalBusInput = new ThermalBusInput( - UUID.randomUUID(), + UUID.fromString("48fa6e8d-c07f-45cd-9ad7-094a1f2a7489"), "Thermal Bus", OperatorInput.NO_OPERATOR_ASSIGNED, OperationTime.notLimited(), ) - protected val testGridambientTemperature: Temperature = Celsius(12d) + protected val testGridAmbientTemperature: Temperature = Celsius(12d) protected val testGridQDotInfeed: Power = Kilowatts(15d) protected val testGridQDotConsumption: Power = Kilowatts(-42d) protected val testGridQDotConsumptionHigh: Power = Kilowatts(-200d) diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala index 0b756af29c..6d48c4712b 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala @@ -98,36 +98,49 @@ class ThermalGridWithHouseAndStorageSpec "deliver the house demand (no demand) with added flexibility by storage" in { val tick = 10800 // after three hours - val (houseDemand, storageDemand) = thermalGrid.energyDemand( - tick, - testGridambientTemperature, - ThermalGrid.startingState(thermalGrid), - ) - + val (houseDemand, storageDemand, updatedThermalGridState) = + thermalGrid.energyDemandAndUpdatedState( + tick, + testGridAmbientTemperature, + ThermalGrid.startingState(thermalGrid), + ) houseDemand.required should approximate(KilowattHours(0d)) houseDemand.possible should approximate(KilowattHours(31.05009722d)) - storageDemand.required should approximate(KilowattHours(345d)) - storageDemand.possible should approximate(KilowattHours(920d)) + storageDemand.required should approximate(KilowattHours(1150d)) + storageDemand.possible should approximate(KilowattHours(1150d)) + updatedThermalGridState.houseState shouldBe Some( + ThermalHouseState(10800, Kelvin(292.0799935185185), Kilowatts(0d)) + ) + updatedThermalGridState.storageState shouldBe Some( + ThermalStorageState(10800, KilowattHours(0d), Kilowatts(0d)) + ) } "deliver the correct house and storage demand" in { val tick = 10800 // after three hours val startingState = ThermalGrid.startingState(thermalGrid) - val (houseDemand, storageDemand) = thermalGrid.energyDemand( - tick, - testGridambientTemperature, - startingState.copy(houseState = - startingState.houseState.map( - _.copy(innerTemperature = Celsius(16d)) - ) - ), - ) + val (houseDemand, storageDemand, updatedThermalGridState) = + thermalGrid.energyDemandAndUpdatedState( + tick, + testGridAmbientTemperature, + startingState.copy(houseState = + startingState.houseState.map( + _.copy(innerTemperature = Celsius(16d)) + ) + ), + ) houseDemand.required should approximate(KilowattHours(45.6000555)) houseDemand.possible should approximate(KilowattHours(75.600055555)) - storageDemand.required should approximate(KilowattHours(345d)) - storageDemand.possible should approximate(KilowattHours(920d)) + storageDemand.required should approximate(KilowattHours(1150d)) + storageDemand.possible should approximate(KilowattHours(1150d)) + updatedThermalGridState.houseState shouldBe Some( + ThermalHouseState(10800, Celsius(15.959996296296296), Kilowatts(0d)) + ) + updatedThermalGridState.storageState shouldBe Some( + ThermalStorageState(10800, KilowattHours(0d), Kilowatts(0d)) + ) } } @@ -151,7 +164,7 @@ class ThermalGridWithHouseAndStorageSpec val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleConsumption( tick, - testGridambientTemperature, + testGridAmbientTemperature, gridState, externalQDot, ) @@ -177,7 +190,7 @@ class ThermalGridWithHouseAndStorageSpec "take energy from storage, if there is actual consumption" in { val tick = 0L val initialGridState = ThermalGrid.startingState(thermalGrid) - val initialLoading = KilowattHours(430d) + val initialLoading = KilowattHours(200d) val gridState = initialGridState.copy(storageState = initialGridState.storageState.map(storageState => storageState.copy(storedEnergy = initialLoading) @@ -188,7 +201,7 @@ class ThermalGridWithHouseAndStorageSpec val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleConsumption( tick, - testGridambientTemperature, + testGridAmbientTemperature, gridState, externalQDot, ) @@ -242,7 +255,7 @@ class ThermalGridWithHouseAndStorageSpec maybeStorageState, maybeHouseState.map(_._1), None, - testGridambientTemperature, + testGridAmbientTemperature, testGridQDotConsumption, ) match { case (maybeRevisedHouseState, maybeRevisedStorageState) => @@ -355,7 +368,7 @@ class ThermalGridWithHouseAndStorageSpec ( ThermalStorageState( tick, - KilowattHours(50d), + KilowattHours(0d), testGridQDotInfeed, ), Some(StorageEmpty(tick)), @@ -397,7 +410,7 @@ class ThermalGridWithHouseAndStorageSpec ( ThermalStorageState( tick, - KilowattHours(250d), + KilowattHours(20d), testGridQDotInfeed, ), None, @@ -418,7 +431,7 @@ class ThermalGridWithHouseAndStorageSpec val formerStorageState = Some( ThermalStorageState( 0L, - KilowattHours(300d), + KilowattHours(70d), Kilowatts(-50d), ) ) @@ -454,8 +467,8 @@ class ThermalGridWithHouseAndStorageSpec thermalStorage.chargingPower * (-1) ) - houseColdTick shouldBe 3718L - storageEmptyTick shouldBe 3678L + houseColdTick shouldBe 3695L + storageEmptyTick shouldBe 3663L case _ => fail("Revision of states failed") } } @@ -475,7 +488,7 @@ class ThermalGridWithHouseAndStorageSpec val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleInfeed( tick, - testGridambientTemperature, + testGridAmbientTemperature, initialGridState, externalQDot, thermalDemand, @@ -523,7 +536,7 @@ class ThermalGridWithHouseAndStorageSpec val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleInfeed( tick, - testGridambientTemperature, + testGridAmbientTemperature, gridState, externalQDot, noThermalDemand, @@ -552,7 +565,7 @@ class ThermalGridWithHouseAndStorageSpec case _ => fail("Thermal grid state has been calculated wrong.") } reachedThreshold shouldBe Some( - StorageFull(220800L) + StorageFull(276000L) ) } } diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala index 68ef86d28a..ef19a034e3 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala @@ -17,9 +17,9 @@ import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseThreshold.{ HouseTemperatureUpperBoundaryReached, } import edu.ie3.simona.test.common.UnitSpec -import squants.energy.{KilowattHours, Kilowatts, Megawatts, WattHours, Watts} +import squants.energy._ import squants.thermal.Celsius -import squants.{Energy, Power, Temperature} +import squants.{Energy, Kelvin, Power, Temperature} import scala.jdk.CollectionConverters._ @@ -80,20 +80,25 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { val tick = 10800 // after three hours val expectedHouseDemand = thermalHouse.energyDemandHeating( tick, - testGridambientTemperature, + testGridAmbientTemperature, expectedHouseStartingState, ) - val (houseDemand, storageDemand) = thermalGrid.energyDemand( - tick, - testGridambientTemperature, - ThermalGrid.startingState(thermalGrid), - ) + val (houseDemand, storageDemand, updatedThermalGridState) = + thermalGrid.energyDemandAndUpdatedState( + tick, + testGridAmbientTemperature, + ThermalGrid.startingState(thermalGrid), + ) houseDemand.required should approximate(expectedHouseDemand.required) houseDemand.possible should approximate(expectedHouseDemand.possible) storageDemand.required should approximate(KilowattHours(0d)) storageDemand.possible should approximate(KilowattHours(0d)) + updatedThermalGridState.houseState shouldBe Some( + ThermalHouseState(10800, Kelvin(292.0799935185185), Kilowatts(0d)) + ) + updatedThermalGridState.storageState shouldBe None } } @@ -135,7 +140,7 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleConsumption( tick, - testGridambientTemperature, + testGridAmbientTemperature, gridState, externalQDot, ) @@ -163,7 +168,7 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleConsumption( tick, - testGridambientTemperature, + testGridAmbientTemperature, gridState, testGridQDotConsumption, ) @@ -198,7 +203,7 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleInfeed( tick, - testGridambientTemperature, + testGridAmbientTemperature, gridState, testGridQDotInfeed, thermalDemand, @@ -227,7 +232,7 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { thermalGrid.updateState( 0L, ThermalGrid.startingState(thermalGrid), - testGridambientTemperature, + testGridAmbientTemperature, testGridQDotInfeed, thermalDemand, noThermalDemand, @@ -252,7 +257,7 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { thermalGrid.updateState( 0L, ThermalGrid.startingState(thermalGrid), - testGridambientTemperature, + testGridAmbientTemperature, testGridQDotConsumption, thermalDemand, noThermalDemand, @@ -277,7 +282,7 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { thermalGrid.updateState( 0L, ThermalGrid.startingState(thermalGrid), - testGridambientTemperature, + testGridAmbientTemperature, Megawatts(0d), thermalDemand, noThermalDemand, diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala index 1592e38c7a..39f93cec59 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala @@ -81,16 +81,44 @@ class ThermalGridWithStorageOnlySpec "deliver the capabilities of the storage" in { val tick = 10800 // after three hours - val (houseDemand, storageDemand) = thermalGrid.energyDemand( - tick, - testGridambientTemperature, - ThermalGrid.startingState(thermalGrid), + val (houseDemand, storageDemand, updatedThermalGridState) = + thermalGrid.energyDemandAndUpdatedState( + tick, + testGridAmbientTemperature, + ThermalGrid.startingState(thermalGrid), + ) + + houseDemand.required should approximate(KilowattHours(0d)) + houseDemand.possible should approximate(KilowattHours(0d)) + storageDemand.required should approximate(KilowattHours(1150d)) + storageDemand.possible should approximate(KilowattHours(1150d)) + updatedThermalGridState.houseState shouldBe None + updatedThermalGridState.storageState shouldBe Some( + ThermalStorageState(10800, KilowattHours(0d), Kilowatts(0d)) ) + } + + "deliver the capabilities of a half full storage" in { + val tick = 10800 // after three hours + + val (houseDemand, storageDemand, updatedThermalGridState) = + thermalGrid.energyDemandAndUpdatedState( + tick, + testGridAmbientTemperature, + ThermalGridState( + None, + Some(ThermalStorageState(0L, KilowattHours(575d), Kilowatts(0d))), + ), + ) houseDemand.required should approximate(KilowattHours(0d)) houseDemand.possible should approximate(KilowattHours(0d)) - storageDemand.required should approximate(KilowattHours(345d)) - storageDemand.possible should approximate(KilowattHours(920d)) + storageDemand.required should approximate(KilowattHours(0d)) + storageDemand.possible should approximate(KilowattHours(575d)) + updatedThermalGridState.houseState shouldBe None + updatedThermalGridState.storageState shouldBe Some( + ThermalStorageState(10800L, KilowattHours(575d), Kilowatts(0d)) + ) } } @@ -108,7 +136,7 @@ class ThermalGridWithStorageOnlySpec Some( ThermalStorageState( 0L, - KilowattHours(430d), + KilowattHours(200d), Kilowatts(0d), ) ) @@ -117,7 +145,7 @@ class ThermalGridWithStorageOnlySpec val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleConsumption( tick, - testGridambientTemperature, + testGridAmbientTemperature, gridState, testGridQDotConsumptionHigh, ) @@ -129,7 +157,7 @@ class ThermalGridWithStorageOnlySpec None, ) => tick shouldBe 0L - storedEnergy should approximate(KilowattHours(430d)) + storedEnergy should approximate(KilowattHours(200d)) qDot should approximate(testGridQDotConsumptionHigh) case _ => fail("Thermal grid state has been calculated wrong.") } @@ -150,7 +178,7 @@ class ThermalGridWithStorageOnlySpec val (updatedGridState, reachedThreshold) = thermalGrid invokePrivate handleInfeed( tick, - testGridambientTemperature, + testGridAmbientTemperature, gridState, testGridQDotInfeed, noThermalDemand, @@ -164,11 +192,11 @@ class ThermalGridWithStorageOnlySpec None, ) => tick shouldBe 0L - storedEnergy should approximate(KilowattHours(230d)) + storedEnergy should approximate(KilowattHours(0d)) qDot should approximate(testGridQDotInfeed) case _ => fail("Thermal grid state has been calculated wrong.") } - reachedThreshold shouldBe Some(StorageFull(220800L)) + reachedThreshold shouldBe Some(StorageFull(276000L)) } } @@ -177,13 +205,13 @@ class ThermalGridWithStorageOnlySpec val (updatedState, nextThreshold) = thermalGrid.updateState( 0L, ThermalGrid.startingState(thermalGrid), - testGridambientTemperature, + testGridAmbientTemperature, testGridQDotInfeed, noThermalDemand, thermalDemand, ) - nextThreshold shouldBe Some(StorageFull(220800L)) + nextThreshold shouldBe Some(StorageFull(276000L)) updatedState match { case ThermalGridState( @@ -192,7 +220,7 @@ class ThermalGridWithStorageOnlySpec None, ) => tick shouldBe 0L - storedEnergy should approximate(KilowattHours(230d)) + storedEnergy should approximate(KilowattHours(0d)) qDot should approximate(testGridQDotInfeed) case _ => fail("Thermal grid state updated failed") } @@ -207,12 +235,12 @@ class ThermalGridWithStorageOnlySpec Some( ThermalStorageState( 0L, - KilowattHours(430d), + KilowattHours(200d), Kilowatts(0d), ) ) ), - testGridambientTemperature, + testGridAmbientTemperature, testGridQDotConsumptionHigh, thermalDemand, noThermalDemand, @@ -226,7 +254,7 @@ class ThermalGridWithStorageOnlySpec Some(StorageEmpty(thresholdTick)), ) => tick shouldBe 0L - storedEnergy should approximate(KilowattHours(430d)) + storedEnergy should approximate(KilowattHours(200d)) qDot should approximate(testGridQDotConsumptionHigh) thresholdTick shouldBe 3600L case _ => fail("Thermal grid state updated failed") @@ -237,7 +265,7 @@ class ThermalGridWithStorageOnlySpec val updatedState = thermalGrid.updateState( 0L, ThermalGrid.startingState(thermalGrid), - testGridambientTemperature, + testGridAmbientTemperature, Kilowatts(0d), noThermalDemand, noThermalDemand, @@ -252,7 +280,7 @@ class ThermalGridWithStorageOnlySpec None, ) => tick shouldBe 0L - storedEnergy should approximate(KilowattHours(230d)) + storedEnergy should approximate(KilowattHours(0d)) qDot should approximate(Megawatts(0d)) case _ => fail("Thermal grid state updated failed") diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalStorageTestData.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalStorageTestData.scala index b5434bbb17..2360fb6efc 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalStorageTestData.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalStorageTestData.scala @@ -25,7 +25,7 @@ trait ThermalStorageTestData extends ThermalGridTestData { "TestThermalBus", ), getQuantity(100, StandardUnits.VOLUME), - getQuantity(20, StandardUnits.VOLUME), + getQuantity(0, StandardUnits.VOLUME), getQuantity(30, StandardUnits.TEMPERATURE), getQuantity(40, StandardUnits.TEMPERATURE), getQuantity(1.15, StandardUnits.SPECIFIC_HEAT_CAPACITY), 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 5eed45eb46..62c062c86e 100644 --- a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala @@ -263,10 +263,70 @@ class ExtEvDataServiceSpec extData.receiveTriggerQueue.size() shouldBe 1 // only evcs 1 should be included, the other one is full extData.receiveTriggerQueue.take() shouldBe new ProvideEvcsFreeLots( - Map(evcs1UUID -> Integer.valueOf(2)).asJava + Map(evcs1UUID -> int2Integer(2)).asJava ) } + "handle price requests correctly by returning dummy values" in { + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + + val extData = extEvData(evService) + + val key = + ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) + scheduler.expectMsgType[ScheduleActivation] // lock activation scheduled + + scheduler.send( + evService, + SimonaService.Create(InitExtEvData(extData), key), + ) + scheduler.expectMsgType[ScheduleActivation] + + scheduler.send(evService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(evService.toTyped)) + + val evcs1 = TestProbe("evcs1") + val evcs2 = TestProbe("evcs2") + + evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) + evcs1.expectMsgType[RegistrationSuccessfulMessage] + + evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) + evcs2.expectMsgType[RegistrationSuccessfulMessage] + + extData.sendExtMsg(new RequestCurrentPrices()) + + // ev service should receive request at this moment + // scheduler should receive schedule msg + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + + val tick = 0L + + // we trigger ev service + scheduler.send(evService, Activation(tick)) + + evcs1.expectNoMessage() + evcs2.expectNoMessage() + + // ev service should recognize that all evcs that are expected are returned, + // thus should send ProvideEvcsFreeLots + awaitCond( + !extData.receiveTriggerQueue.isEmpty, + max = 3.seconds, + message = "No message received", + ) + extData.receiveTriggerQueue.size() shouldBe 1 + // only evcs 1 should be included, the other one is full + extData.receiveTriggerQueue.take() shouldBe new ProvideCurrentPrices( + Map( + evcs1UUID -> double2Double(0d), + evcs2UUID -> double2Double(0d), + ).asJava + ) + + scheduler.expectMsg(Completion(evService.toTyped)) + } + "return free lots requests right away if there are no evcs registered" in { val evService = TestActorRef(new ExtEvDataService(scheduler.ref))