📄 abstractzonecontroller.java
字号:
package net.sf.dz.device.model.impl;import java.io.IOException;import java.util.Iterator;import java.util.Map;import java.util.HashMap;import java.util.Set;import java.util.HashSet;import org.freehold.jukebox.conf.Configurable;import org.freehold.jukebox.conf.Configuration;import org.freehold.jukebox.logger.LogAware;import org.freehold.jukebox.logger.LogChannel;import net.sf.dz.device.actuator.AC;import net.sf.dz.device.actuator.Damper;import net.sf.dz.device.model.DamperController;import net.sf.dz.device.model.Thermostat;import net.sf.dz.device.model.Unit;import net.sf.dz.device.model.ZoneController;/** * The zone controller abstraction. * * Implements the behavior common for all the zone controller, and provides * the template methods for the rest. * * @author Copyright © <a href="mailto:vt@freehold.crocodile.org">Vadim Tkachenko</a> 2001-2002 * @version $Id: AbstractZoneController.java,v 1.12 2004/01/20 02:40:34 vtt Exp $ */abstract public class AbstractZoneController extends LogAware implements ZoneController { public static final LogChannel CH_AZC = new LogChannel("ZC"); /** * The unit to serve. */ protected Unit unit; /** * The HVAC unit to control. */ protected AC ac; /** * The damper controller. */ protected DamperController damperController; /** * "Unhappy" controller signal. * * If the controller signal goes above this value, the signal source is * considered "unhappy". */ protected double ctl_unhappy; /** * "Happy" controller signal. * * If the controller signal goes below this value, the signal source is * considered "happy". */ protected double ctl_happy; /** * Mapping from the thermostat to its last known output signal. * * Required so we know whether the value changed since the last * iteration. Error conditions are not registered in this map, but in * {@link #tsFailureMap tsFailureMap}. */ protected Map tsSignalMap = new HashMap(); /** * Thermostats that aren't satisfied with the temperature in their * rooms. * * Having this allows better control over starting and stopping the A/C. */ protected Set unhappyTs = new HashSet(); /** * Mapping from the thermostat to its current failure condition. * * As soon as a valid signal arrives from the thermostat, its entry is * removed from this map. */ protected Map tsFailureMap = new HashMap(); /** * When do we start thinking about dumping excess static pressure. * * <p> * * If the total area of open dampers vs. all the dampers is more than * this, no big deal. Otherwise, we start opening the dump zone dampers * and otherwise fiddle with them. * * <p> * * The closer is this value to 1.0, the better from the A/C unit point * of view. However, this has to be modified for the case of the * variable speed fan, it is not yet clear how, though. * * <p> * * Rough model shows that the dump threshold more than 0.4 is * unacceptable from the comfort point of view. In real life, the dump * threshold may be even lower because the dampers are not ideal and * leak a lot. */ protected double dumpThreshold = 0.3; public double getDumpThreshold() { return dumpThreshold; } public void setDumpThreshold(double dumpThreshold) { if ( dumpThreshold < 0 || dumpThreshold > 1 ) { throw new IllegalArgumentException("Invalid value " + dumpThreshold + " (should be in 0..1 range)"); } this.dumpThreshold = dumpThreshold; // VT: FIXME: Propagate immediately? } protected void configure() throws Throwable { if ( unit == null ) { throw new IllegalStateException("Unit must be attached first"); } dumpThreshold = getConfiguration().getDouble(getConfigurationRoot() + ".dump_threshold", 0.3); ctl_unhappy = getConfiguration().getDouble(getConfigurationRoot() + ".unhappy", 1); ctl_happy = getConfiguration().getDouble(getConfigurationRoot() + ".happy", -ctl_unhappy); // Let's see that they're not out of their mind if ( ctl_unhappy <= ctl_happy ) { throw new IllegalArgumentException("Bad values for ctl_unhappy and ctl_happy (the former must be greater than latter): " + ctl_unhappy + ", " + ctl_happy); } } public void attach(Unit unit) { this.unit = unit; this.ac = unit.getAC(); this.damperController = unit.getDamperController(); if ( unit == null || ac == null || damperController == null ) { throw new IllegalStateException("Unit being connected is not completely initialized"); } } /** * Accept the notification about the change in the thermostat process * controller output. * * @param source Thermostat whose signal has changed. * * @param signal The current value of the thermostat process controller signal. */ public final synchronized void controlSignalChanged(Thermostat source, Object signalObject) { // First of all, let's figure if the signal is a temperature value // or an error condition. if ( signalObject instanceof Double ) { clearFailure(source); controlSignalChanged(source, ((Double)signalObject).doubleValue()); } else { // Since there's no signal from this thermostat, it will not // participate in calculating the demand - with any luck, the // damper will just stay open tsSignalMap.remove(source); processFailure(source, signalObject); } } private final synchronized void controlSignalChanged(Thermostat source, double signal) { //complain(LOG_DEBUG, CH_AZC, "Control signal changed: " + source.getName() + ": " + signal); int mode = unit.getAC().getMode(); if ( mode == 0 ) { // A/C is off, nothing to do return; } // Adjust the signal according to the A/C running mode. Happy is // negative, unhappy is positive, regardless of the mode. signal *= -mode; if ( !source.isOn() ) { // Fake the happy signal so they shut up // VT: FIXME: If we get smart about happy/unhappy limits, the // signal value here has to be faked keeping those values in // mind. signal = ctl_happy; // At this point, the signal is such that the rest of the // controller logic believes that this room is happy and // processes the rest of the framework accordingly, including // shutting the dampers and switching off the A/C, if // required. Simple ;) } // Check if the signal value has changed Double old = (Double)tsSignalMap.get(source); if ( old != null ) { if ( old.compareTo(new Double(signal)) == 0 ) { // Nothing happened //complain(LOG_DEBUG, CH_AZC, source.getName() + ": signal not changed"); return; } } // Record the value so it is available in the later calculations tsSignalMap.put(source, new Double(signal)); Double demand = new Double(computeDemand()); // Adjust the demand according to the signal value. demand = controlSignalChanged(source, signal, demand); if ( demand != null ) { if ( demand.doubleValue() < 0 ) { throw new IllegalStateException("Negative demand???"); } ac.demand(demand.doubleValue()); } } public void enabledStateChanged(Thermostat source, boolean enabled) { if ( enabled ) { reset(source); } } public void votingStateChanged(Thermostat source, boolean voting) { if ( source.isOn() ) { reset(source); } } private void reset(Thermostat source) { // Regardless of actual signal value, mark this thermostat as // unhappy. It's quite possible that it is within the controller // hysteresis zone, which will cause the HVAC to be turned on, but // the damper will stay closed. unhappyTs.add(source); //complain(LOG_DEBUG, CH_AZC, source.getName() + ": made unhappy"); // Also, the old signal value has to be expired from the signal // cache, lest controlSignalChanged() thinks that nothing happened tsSignalMap.remove(source); // Let's now force the signal to be processed controlSignalChanged(source, source.getControlSignal()); } public void holdStateChanged(Thermostat source, boolean hold) { controlSignalChanged(source, source.getControlSignal()); } /** * Make a decision about the A/C operating mode. * * The processing done here will determine whether the A/C has to be * switched on, off, or the stage has to be changed. * * @param source Thermostat whose signal has changed. * * @param signal The signal, adjusted according to current A/C mode * (happy is negative, unhappy is positive). * * @param currentDemand Total heating/cooling demand from all the rooms * (see {@link #computeDemand computeDemand()}). * * @return Adjusted demand. Null value means that there will be no * changes to the current A/C operation, anything else will be passed on * to the A/C unit to carry out. */ abstract protected Double controlSignalChanged(Thermostat source, double signal, Double currentDemand); /** * Compute the total demand for A/C. * * @return Sum of all non-negative signal values taken from each * thermostat. This may not be appropriate for all subclasses. */ protected double computeDemand() { int mode = unit.getAC().getMode(); double demand = 0; for ( Iterator i = tsSignalMap.keySet().iterator(); i.hasNext(); ) { double signal = ((Double)tsSignalMap.get(i.next())).doubleValue() * ((mode == 0) ? 0 : 1); if ( signal > 0 ) { demand += signal; } } //complain(LOG_DEBUG, CH_AZC, "Total demand: " + demand); return demand; } private synchronized void clearFailure(Thermostat source) { if ( tsFailureMap.containsKey(source) ) { tsFailureMap.remove(source); complain(LOG_NOTICE, CH_AZC, "Cleared failure condition for " + source.getName()); // VT: FIXME: Make sure the system is put back in order. // Possibly, this will be achieved automatically since right // after this call the normal control flow will be executed if ( tsFailureMap.isEmpty() ) { complain(LOG_NOTICE, CH_AZC, "Normal operation restored: all zones operable"); } } } private synchronized void processFailure(Thermostat source, Object failureData) { if ( !tsFailureMap.containsKey(source) ) { tsFailureMap.put(source, failureData); complain(LOG_WARNING, CH_AZC, "Registered failure condition for " + source.getName() + ": " + failureData); // VT: FIXME: Send a notification to the user, whether this is // all of the zones or just one. Think about how not to make // ourselves an annoyance for the case of things like 1-Wire // glitches. // VT: NOTE: This is the only place where the dampers for faulty // zones are handled. No subclasses should be bothered with // that, because the logic is identical. // VT: NOTE: The dampers should be taken care of before the HVAC // because it is possible that the dampers will stay operable // even if the HVAC controls are not - for example, the case // when both the sensors and the HVAC actuators are located on // the same 1-Wire network. In this case, the 1-Wire network // failure may make HVAC controls inoperable and the unit will // stay on, however, the possible damage will be minimized since // all the dampers will be fully open. Damper d = damperController.getDamper(source); try { d.set(1); } catch ( IOException ioex ) { complain(LOG_WARNING, CH_AZC, "Can set the throttle, cause:", ioex); } if ( tsFailureMap.size() == unit.getZoneCount() ) { // Oh shit, all the zones have failed complain(LOG_ALERT, CH_AZC, "ALL ZONES REPORTED FAILURE"); // Take care of the HVAC, if possible. ac.demand(0); // VT: FIXME: Tell the user (via notification) what the last // known HVAC state was, and whether an attempt to shut it // off (if any) was successful or not) - this will make // their life easier. // // Currently, this is not possible because the HVAC control // is asynchronous, so the code handling this condition will // have to be moved there, or, as alternative, a special // synchronous piece of code must be added to provide // emergency shutoff. } } } protected final synchronized boolean isFailing(Thermostat source) { return tsFailureMap.containsKey(source); }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -