diff --git a/modules/hcc-model/src/test/java/net/sf/dz3r/model/ThermostatTest.java b/modules/hcc-model/src/test/java/net/sf/dz3r/model/ThermostatTest.java index 91f8d93f..0d1f7b75 100644 --- a/modules/hcc-model/src/test/java/net/sf/dz3r/model/ThermostatTest.java +++ b/modules/hcc-model/src/test/java/net/sf/dz3r/model/ThermostatTest.java @@ -10,15 +10,20 @@ import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Sinks; +import reactor.core.scheduler.Schedulers; import reactor.test.StepVerifier; import reactor.tools.agent.ReactorDebugAgent; +import java.time.Clock; +import java.time.Duration; import java.time.Instant; import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeoutException; import java.util.function.Predicate; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; /** * Test cases for {@link Thermostat}. @@ -183,6 +188,7 @@ private void assertPartial(Signal, Void> assertThat(s.isOK()).isTrue(); assertThat(s.isError()).isFalse(); } + @Test void setpointChangeEmitsSignal() { @@ -223,4 +229,69 @@ void setpointChangeEmitsSignal() { out.dispose(); } + + /** + * Make sure setpoint change causes a trail of adjusted signals according to its half-life - in cooling mode. + */ + @Test + void setpointChangeHalfLifeCooling() throws InterruptedException { + assertThatCode(() -> { + setpointChangeHalfLife( + new Thermostat(Clock.systemUTC(), "ts", new Range<>(10d, 40d), 25.0, -1.0, 0, 0, 0, Duration.ofSeconds(10), 1), + 20d, + 30d); + }) + .doesNotThrowAnyException(); + } + + /** + * Make sure setpoint change causes a trail of adjusted signals according to its half-life - in heating mode. + */ + @Test + void setpointChangeHalfLifeHeating() throws InterruptedException { + assertThatCode(() -> { + setpointChangeHalfLife( + new Thermostat(Clock.systemUTC(), "ts", new Range<>(10d, 40d), 25.0, 1.0, 0, 0, 0, Duration.ofSeconds(10), 1), + 30d, + 10d); + }) + .doesNotThrowAnyException(); + } + + private void setpointChangeHalfLife(Thermostat ts, Double changedSetpoint, Double temperature) throws InterruptedException { + var count = 20; + var deltaT = 1; + var offset = 0; + var start = Instant.now(); + Sinks.Many> sourceSink = Sinks.many().multicast().onBackpressureBuffer(); + var source = sourceSink.asFlux(); + + var changeGate = new CountDownLatch(1); + var stopGate = new CountDownLatch(1); + + ts + .compute(source) + .subscribeOn(Schedulers.boundedElastic()) + .doOnNext(ignored -> changeGate.countDown()) + .doOnComplete(stopGate::countDown) + + // The data structure passing the sensitivity controller output here is not yet in place, so let's just watch the logs until it is + .subscribe(s -> logger.info("output: {}", s)); + + // Prime the controller + sourceSink.tryEmitNext(new Signal(start, temperature)); + + changeGate.await(); + + // Introduce a change increasing the demand + ts.setSetpoint(changedSetpoint); + + // Feed the rest + while (offset++ < count) { + sourceSink.tryEmitNext(new Signal(start.plus(Duration.ofSeconds(offset * deltaT)), temperature)); + } + + sourceSink.tryEmitComplete(); + stopGate.await(); + } }