Skip to content

Commit

Permalink
Augmented ZoneStatus with EconomizerStatus (#239)
Browse files Browse the repository at this point in the history
  • Loading branch information
climategadgets committed Oct 21, 2022
1 parent 442f780 commit 59167b4
Show file tree
Hide file tree
Showing 21 changed files with 197 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ private Flux<ZoneSnapshot> convert(Signal<ZoneStatus, String> source) {
source.timestamp.toEpochMilli(),
zoneName,
modeMap.get(hvacMode),
renderState(status.settings.enabled, status.thermostatStatus.calling),
status.thermostatStatus.demand,
renderState(status.settings.enabled, status.callingStatus.calling),
status.callingStatus.demand,
sensorSignal.getValue(),
status.settings.setpoint,
status.settings.enabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ private Point convert(Signal<ZoneStatus, String> signal) {
b.addField("hold", status.settings.hold);
b.addField("dumpPriority", status.settings.dumpPriority);

b.addField("calling", status.thermostatStatus.calling);
b.addField("demand", status.thermostatStatus.demand);
b.addField("calling", status.callingStatus.calling);
b.addField("demand", status.callingStatus.demand);
}

if (signal.error != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ protected Map<Damper<?>, Double> compute(Map<String, Signal<ZoneStatus, String>>
}

// Negative demand counts as 0, otherwise damper positions will go below 0 and go boom
var demand = Math.max(0.0, zoneSignal.getValue().thermostatStatus.demand);
var demand = Math.max(0.0, zoneSignal.getValue().callingStatus.demand);
var zoneSet = demand2zone.computeIfAbsent(demand, k -> new TreeSet<>());

zoneSet.add(zoneName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ protected Map<Damper<?>, Double> compute(Map<String, Signal<ZoneStatus, String>>
zone2signal.forEach((key, value) -> {

var damper = getDamperFor(key);
var position = value.getValue().thermostatStatus.calling ? 1d : 0d;
var position = value.getValue().callingStatus.calling ? 1d : 0d;

result.put(damper, position);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import net.sf.dz3r.model.Zone;
import net.sf.dz3r.signal.Signal;
import net.sf.dz3r.signal.SignalProcessor;
import net.sf.dz3r.signal.hvac.ThermostatStatus;
import net.sf.dz3r.signal.hvac.CallingStatus;
import net.sf.dz3r.signal.hvac.EconomizerStatus;
import net.sf.dz3r.signal.hvac.ZoneStatus;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
Expand Down Expand Up @@ -43,6 +44,13 @@ public abstract class AbstractEconomizer <A extends Comparable<A>> implements Si
*/
private Signal<Double, Void> ambient;

/**
* Economizer status.
*
* Can't be {@code null}, that will throw off {@link Zone#compute(Flux)} reporting.
*/
private EconomizerStatus economizerStatus;

/**
* Mirrors the state of {@link #targetDevice} to avoid expensive operations.
*/
Expand All @@ -66,6 +74,7 @@ protected AbstractEconomizer(

this.settings = settings;
this.targetDevice = targetDevice;
this.economizerStatus = new EconomizerStatus(new EconomizerTransientSettings(settings), 0, false, null);

// Don't forget to connect fluxes; this can only be done in subclasses after all the
// necessary components were initialized
Expand Down Expand Up @@ -129,6 +138,8 @@ private void connectCombined(FluxSink<Pair<Signal<Double, String>, Signal<Double

private Boolean recordDeviceState(Signal<Boolean, ProcessController.Status<Double>> stateSignal) {

economizerStatus = new EconomizerStatus(new EconomizerTransientSettings(settings), stateSignal.payload.signal, stateSignal.getValue(), ambient);

var newState = stateSignal.getValue();

if ((actuatorState == null && newState != null) || newState.compareTo(actuatorState) != 0) {
Expand Down Expand Up @@ -237,35 +248,47 @@ private Signal<Double, Void> computeCombined(Pair<Signal<Double, String>, Signal
* Figure out whether the HVAC needs to be suppressed and adjust the signal if so.
*
* @param source Signal computed by {@link Zone}.
* @return Signal with {@link ZoneStatus#thermostatStatus} possibly adjusted to shut off the HVAC if the economizer is active.
* @return Signal with {@link ZoneStatus#callingStatus} possibly adjusted to shut off the HVAC if the economizer is active.
*/
public Signal<ZoneStatus, String> computeHvacSuppression(Signal<ZoneStatus, String> source) {

if (actuatorState == null || actuatorState.equals(Boolean.FALSE)) {
if (source.isError()) {

// Economizer inactive, no change required
// How did it get here? This log message is likely a dupe, need to keep an eye on it
logger.warn("error signal in economizer pipeline? {}", source);

// Can't do anything meaningful with it here
return source;
}

if (settings.keepHvacOn) {
// Augment the source with the economizer status
var augmentedSource = new Signal<>(
source.timestamp,
new ZoneStatus(
source.getValue().settings,
source.getValue().callingStatus,
economizerStatus),
source.payload,
source.status,
source.error);

// We're feeding indoor air to HVAC air return, right?
return source;
}
if (actuatorState == null || actuatorState.equals(Boolean.FALSE)) {

if (source.isError()) {
// Economizer inactive, no change required
return augmentedSource;
}

// How did it get here? This log message is likely a dupe, need to keep an eye on it
logger.warn("error signal in economizer pipeline? {}", source);
if (settings.keepHvacOn) {

// Can't do anything meaningful with it here
return source;
// We're feeding indoor air to HVAC air return, right?
return augmentedSource;
}

// Need to suppress demand and keep the HVAC off while the economizer is on
var adjusted = new ZoneStatus(
source.getValue().settings,
new ThermostatStatus(0, false));
new CallingStatus(0, false),
economizerStatus);

return new Signal<>(
source.timestamp,
Expand All @@ -279,10 +302,10 @@ public Signal<ZoneStatus, String> computeHvacSuppression(Signal<ZoneStatus, Stri
public void close() throws Exception {
ThreadContext.push("close");
try {
logger.info("Shutting down");
logger.info("Shutting down: {}", getAddress());
targetDevice.setState(false).block();
} finally {
logger.info("Shut down");
logger.info("Shut down: {}", getAddress());
ThreadContext.pop();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

import net.sf.dz3r.model.HvacMode;

public class EconomizerSettings {
/**
* Full set of economizer settings.
*
* @author Copyright &copy; <a href="mailto:[email protected]">Vadim Tkachenko</a> 2001-2022
*/
public class EconomizerSettings extends EconomizerTransientSettings {

public final String name;

Expand All @@ -11,16 +16,6 @@ public class EconomizerSettings {
*/
public final HvacMode mode;

/**
* Temperature difference between indoor and outdoor temperature necessary to turn the device on.
*/
public final double changeoverDelta;

/**
* When this temperature is reached, the device is shut off.
*/
public final double targetTemperature;

/**
* {@code true} means that turning on the device will NOT turn the HVAC off.
* You probably want to keep this at {@code false}, unless the indoor temperature is measured at HVAC return
Expand All @@ -38,7 +33,7 @@ public class EconomizerSettings {
* Primary constructor with just the {@link #mode}, {@link #changeoverDelta}, and {@link #targetTemperature} values provided,
* {@link #keepHvacOn} set to {@code false}, and PI controller with default settings.
*/
public EconomizerSettings( String name, HvacMode mode, double changeoverDelta, double targetTemperature) {
public EconomizerSettings(String name, HvacMode mode, double changeoverDelta, double targetTemperature) {
// VT: FIXME: I and saturationLimit of 0 are bad defaults, will need to be adjusted when deployed to production
this(name, mode, changeoverDelta, targetTemperature, false, 1, 0, 0);
}
Expand All @@ -55,11 +50,10 @@ public EconomizerSettings(String name,
HvacMode mode, double changeoverDelta, double targetTemperature,
boolean keepHvacOn,
double P, double I, double saturationLimit) {
super(changeoverDelta, targetTemperature);
this.name = name;

this.mode = mode;
this.changeoverDelta = changeoverDelta;
this.targetTemperature = targetTemperature;
this.keepHvacOn = keepHvacOn;

this.P = P;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package net.sf.dz3r.device.actuator.economizer;

/**
* Set of economizer settings that can change over time by the user.
*
* @author Copyright &copy; <a href="mailto:[email protected]">Vadim Tkachenko</a> 2001-2022
*/
public class EconomizerTransientSettings {

/**
* Temperature difference between indoor and outdoor temperature necessary to turn the device on.
*/
public final double changeoverDelta;

/**
* When this temperature is reached, the device is shut off.
*/
public final double targetTemperature;

public EconomizerTransientSettings(double changeoverDelta, double targetTemperature) {
this.changeoverDelta = changeoverDelta;
this.targetTemperature = targetTemperature;
}

public EconomizerTransientSettings(EconomizerSettings source) {
this.changeoverDelta = source.changeoverDelta;
this.targetTemperature = source.targetTemperature;
}

@Override
public String toString() {
return "{changeoverDelta=" + changeoverDelta
+ ", targetTemperature=" + targetTemperature
+ "}";
}
}
10 changes: 5 additions & 5 deletions dz3r-model/src/main/java/net/sf/dz3r/model/Thermostat.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import net.sf.dz3r.controller.pid.SimplePidController;
import net.sf.dz3r.device.Addressable;
import net.sf.dz3r.signal.Signal;
import net.sf.dz3r.signal.hvac.ThermostatStatus;
import net.sf.dz3r.signal.hvac.CallingStatus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import reactor.core.publisher.Flux;
Expand All @@ -28,7 +28,7 @@
*
* @author Copyright &copy; <a href="mailto:[email protected]">Vadim Tkachenko</a> 2001-2021
*/
public class Thermostat implements ProcessController<Double, ThermostatStatus, Void>, Addressable<String> {
public class Thermostat implements ProcessController<Double, CallingStatus, Void>, Addressable<String> {

private final Logger logger = LogManager.getLogger();

Expand Down Expand Up @@ -147,7 +147,7 @@ public double getError() {
* @see net.sf.dz3r.device.actuator.economizer.v2.PidEconomizer#computeDeviceState(Flux)
*/
@Override
public Flux<Signal<Status<ThermostatStatus>, Void>> compute(Flux<Signal<Double, Void>> pv) {
public Flux<Signal<Status<CallingStatus>, Void>> compute(Flux<Signal<Double, Void>> pv) {

logger.debug("compute()");

Expand All @@ -171,14 +171,14 @@ public Flux<Signal<Status<ThermostatStatus>, Void>> compute(Flux<Signal<Double,
.map(this::mapOutput);
}

private Signal<Status<ThermostatStatus>, Void> mapOutput(Signal<Status<Double>, Status<Double>> source) {
private Signal<Status<CallingStatus>, Void> mapOutput(Signal<Status<Double>, Status<Double>> source) {

var demand = source.payload.signal - signalRenderer.getThresholdLow();
var calling = Double.compare(source.getValue().signal, 1.0) == 0;

return new Signal<>(
source.timestamp,
new Status<>(source.payload.setpoint, source.payload.error, new ThermostatStatus(demand, calling)),
new Status<>(source.payload.setpoint, source.payload.error, new CallingStatus(demand, calling)),
null,
source.status,
source.error);
Expand Down
12 changes: 6 additions & 6 deletions dz3r-model/src/main/java/net/sf/dz3r/model/Zone.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import net.sf.dz3r.device.actuator.economizer.v2.PidEconomizer;
import net.sf.dz3r.signal.Signal;
import net.sf.dz3r.signal.SignalProcessor;
import net.sf.dz3r.signal.hvac.ThermostatStatus;
import net.sf.dz3r.signal.hvac.CallingStatus;
import net.sf.dz3r.signal.hvac.ZoneStatus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand Down Expand Up @@ -121,11 +121,11 @@ public Flux<Signal<ZoneStatus, String>> compute(Flux<Signal<Double, String>> in)
return stage3.map(this::suppressEconomizer);
}

private Signal<ZoneStatus, String> translate(Signal<ProcessController.Status<ThermostatStatus>, Void> source) {
private Signal<ZoneStatus, String> translate(Signal<ProcessController.Status<CallingStatus>, Void> source) {

return new Signal<>(
source.timestamp,
new ZoneStatus(settings, source.getValue().signal),
new ZoneStatus(settings, source.getValue().signal, null),
getAddress(),
source.status,
source.error);
Expand All @@ -139,7 +139,7 @@ private Signal<ZoneStatus, String> suppressIfNotEnabled(Signal<ZoneStatus, Strin

return new Signal<>(
source.timestamp,
new ZoneStatus(new ZoneSettings(source.getValue().settings, false), new ThermostatStatus(0, false)),
new ZoneStatus(new ZoneSettings(source.getValue().settings, false), new CallingStatus(0, false), null),
source.payload,
source.status,
source.error);
Expand Down Expand Up @@ -181,14 +181,14 @@ public void close() throws Exception {
ThreadContext.push("close");
try {

logger.info("Shutting down");
logger.info("Shutting down: {}", getAddress());

if (economizer != null) {
economizer.close();

}
} finally {
logger.info("Shut down");
logger.info("Shut down: {}", getAddress());
ThreadContext.pop();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ private Signal<UnitControlSignal, String> process(Signal<ZoneStatus, String> sig
.filter(kv -> kv.getValue().getValue().settings.enabled);

var unhappy = enabled
.filter(kv -> kv.getValue().getValue().thermostatStatus.calling)
.filter(kv -> kv.getValue().getValue().callingStatus.calling)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

var unhappyVoting = unhappy
Expand Down Expand Up @@ -212,7 +212,7 @@ private double computeDemand(Map<String, Signal<ZoneStatus, String>> source) {
return source
.values()
.stream()
.map(e -> e.getValue().thermostatStatus.demand)
.map(e -> e.getValue().callingStatus.demand)
.reduce(Double::sum).orElse(0d);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package net.sf.dz3r.signal.hvac;

/**
* Calling status.
*
* This object defines the actual thermostat or economizer status in real time.
*
* @author Copyright &copy; <a href="mailto:[email protected]">Vadim Tkachenko</a> 2001-2022
*/
public class CallingStatus {

public final double demand;
public final boolean calling;

public CallingStatus(double demand, boolean calling) {
this.demand = demand;
this.calling = calling;
}

@Override
public String toString() {
return "{demand=" + demand + ", calling=" + calling + "}";
}
}
Loading

0 comments on commit 59167b4

Please sign in to comment.