From b02a3b54d2cce2bdc9fd48e723bf921c8cb448e9 Mon Sep 17 00:00:00 2001 From: Vadim Tkachenko Date: Fri, 21 Jun 2024 22:46:26 -0700 Subject: [PATCH] Now instantiating the thermostat with sensitivity configuration (#322) Sensitivity controller is not yet active. --- docs/configuration/zones.md | 4 +-- .../config/model/ZoneConfigurationParser.java | 23 ++++++++++--- .../java/net/sf/dz3r/model/Thermostat.java | 33 +++++++++++++++++-- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/docs/configuration/zones.md b/docs/configuration/zones.md index 72ae6f15a..4d302092f 100644 --- a/docs/configuration/zones.md +++ b/docs/configuration/zones.md @@ -68,8 +68,8 @@ Positive `p` and `i` values are used for cooling mode, negative for heating. `li ### sensitivity Defines how strongly the zone will react to setpoint changes. The algorithm is explained [here](https://www.emathhelp.net/en/calculators/differential-equations/half-life-calculator). -* `half-life` defines for how long the setpoint change amplification will linger. -* `multiplier` defines +* `half-life` defines for how long the setpoint change amplification will linger. Value of `PT0S` disables sensitivity altogether. +* `multiplier` defines the amplitude of the kick the control pipeline receives when the setpoint is changed. This section is optional with system defined defaults (logged at startup at `DEBUG` level), but feel free to tinker with it if you think that the system is too sluggish or too trigger-happy reacting to setpoint changes. diff --git a/modules/hcc-bootstrap/src/main/java/net/sf/dz3r/runtime/config/model/ZoneConfigurationParser.java b/modules/hcc-bootstrap/src/main/java/net/sf/dz3r/runtime/config/model/ZoneConfigurationParser.java index 69eb5a7a5..fea29c4df 100644 --- a/modules/hcc-bootstrap/src/main/java/net/sf/dz3r/runtime/config/model/ZoneConfigurationParser.java +++ b/modules/hcc-bootstrap/src/main/java/net/sf/dz3r/runtime/config/model/ZoneConfigurationParser.java @@ -43,7 +43,16 @@ public void parse(Set source) { private Map.Entry createZone(ZoneConfig cf) { - var ts = createThermostat(cf.name(), cf.settings().setpoint(), cf.settings().setpointRange(), cf.controller()); + var ts = createThermostat( + cf.name(), + cf.settings().setpoint(), cf.settings().setpointRange(), + cf.controller(), + Optional.ofNullable(cf.sensitivity()).orElseGet(() -> { + var s = new HalfLifeConfig(Duration.ofSeconds(10), 1d); + logger.debug("{}: using default sensitivity configuration of (half-life={}, multiplier={})", cf.name(), s.halfLife(), s.multiplier()); + return s; + + })); var eco = createEconomizer(cf.name(), cf.economizer()); var ecoSettings = Optional.ofNullable(eco).map(v -> v.config.settings).orElse(null); var zone = new Zone(ts, map(cf.settings(), ecoSettings), eco); @@ -61,7 +70,7 @@ private EconomizerContext createEconomizer(String zoneName, net.sf.dz3r.runtime. var hvacDevice = HCCObjects.requireNonNull(getHvacDevice(cf.hvacDevice()), "can't resolve hvac-device=" + cf.hvacDevice()); var timeout = Optional.ofNullable(cf.timeout()).orElseGet(() -> { var t = Duration.ofSeconds(90); - logger.info("{}: using default stale timeout of {} for the economizer", zoneName, t); + logger.debug("{}: using default stale timeout of {} for the economizer", zoneName, t); return t; }); @@ -97,10 +106,14 @@ private EconomizerContext createEconomizer(String zoneName, net.sf.dz3r.runtime. timeout); } - private Thermostat createThermostat(String name, Double setpoint, RangeConfig rangeConfig, PidControllerConfig cf) { + private Thermostat createThermostat(String name, Double setpoint, RangeConfig rangeConfig, PidControllerConfig cf, HalfLifeConfig sensitivity) { - var range = map(Optional.ofNullable(rangeConfig).orElse(new RangeConfig(10.0, 40.0))); - return new Thermostat(Clock.systemUTC(), name, range, setpoint, cf.p(), cf.i(), cf.d(), cf.limit()); + var range = map(Optional.ofNullable(rangeConfig).orElseGet(() -> { + var rc = new RangeConfig(10.0, 40.0); + logger.debug("{}: using default setpoint range of ({}..{})", name, rc.min(), rc.max()); + return rc; + })); + return new Thermostat(Clock.systemUTC(), name, range, setpoint, cf.p(), cf.i(), cf.d(), cf.limit(), sensitivity.halfLife(), sensitivity.multiplier()); } private ZoneSettings map(ZoneSettingsConfig source, EconomizerSettings economizerSettings) { diff --git a/modules/hcc-model/src/main/java/net/sf/dz3r/model/Thermostat.java b/modules/hcc-model/src/main/java/net/sf/dz3r/model/Thermostat.java index cd17f9583..37c480b2c 100644 --- a/modules/hcc-model/src/main/java/net/sf/dz3r/model/Thermostat.java +++ b/modules/hcc-model/src/main/java/net/sf/dz3r/model/Thermostat.java @@ -1,6 +1,7 @@ package net.sf.dz3r.model; import net.sf.dz3r.common.HCCObjects; +import net.sf.dz3r.controller.HalfLifeController; import net.sf.dz3r.controller.HysteresisController; import net.sf.dz3r.controller.ProcessController.Status; import net.sf.dz3r.controller.pid.AbstractPidController; @@ -15,6 +16,7 @@ import reactor.core.publisher.Sinks; import java.time.Clock; +import java.time.Duration; /** * Thermostat. @@ -58,6 +60,16 @@ public class Thermostat implements Addressable { */ private final AbstractPidController controller; + /** + * Controller defining how trigger happy the thermostat is. + */ + private final HalfLifeController sensitivityController; + + /** + * Multiplier for {@link #sensitivityController}. + */ + private final double sensitivityMultiplier; + /** * Controller defining this thermostat's output signal. */ @@ -67,11 +79,11 @@ public class Thermostat implements Addressable { private final Flux>> stateFlux = stateSink.asFlux(); /** - * Create a thermostat with a default 10C..40C setpoint range and specified setpoint and PID values. + * Create a thermostat with a default 10C..40C setpoint range, specified setpoint and PID values, and no sensitivity adjustment. * */ public Thermostat(String name, Double setpoint, double p, double i, double d, double limit) { - this(Clock.systemUTC(), name, new Range<>(10d, 40d), setpoint, p, i, d, limit); + this(Clock.systemUTC(), name, new Range<>(10d, 40d), setpoint, p, i, d, limit, Duration.ZERO, 0); } /** @@ -86,16 +98,31 @@ public Thermostat(String name, Double setpoint, double p, double i, double d, do * @param d PID controller derivative weight. * @param limit PID controller saturation limit. */ - public Thermostat(Clock clock, String name, Range setpointRange, Double setpoint, double p, double i, double d, double limit) { + public Thermostat(Clock clock, String name, Range setpointRange, Double setpoint, double p, double i, double d, double limit, Duration halfLife, double multiplier) { this.clock = HCCObjects.requireNonNull(clock, "clock can't be null"); this.name = name; this.setpointRange = setpointRange; + this.sensitivityMultiplier = checkSensitivity(halfLife, multiplier); controller = new SimplePidController<>("(controller) " + name, setpoint, p, i, d, limit); + sensitivityController = new HalfLifeController<>("(sensitivity) " + name, halfLife); signalRenderer = new HysteresisController<>("(signalRenderer) " + name, 0, HYSTERESIS); } + private double checkSensitivity(Duration halfLife, double multiplier) { + + if (multiplier < 0) { + throw new IllegalArgumentException("multiplier cannot be negative"); + } + + if (multiplier == 0 && !halfLife.isZero()) { + throw new IllegalArgumentException("zero multiplier with non-zero half life will slow the system down, specify zero half life if you want to disable the sensitivity controller"); + } + + return multiplier; + } + /** * Get the human readable thermostat name. *