📄 thermostatmodel.java
字号:
package net.sf.dz.device.model.impl;import java.util.Iterator;import java.util.HashSet;import java.util.Set;import java.util.StringTokenizer;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 org.freehold.jukebox.logger.Logger;import org.freehold.jukebox.service.PassiveService;import net.sf.dz.device.model.Thermostat;import net.sf.dz.device.model.ThermostatController;import net.sf.dz.device.model.Unit;import net.sf.dz.event.TemperatureSensorListener;import net.sf.dz.event.ThermostatListener;import net.sf.dz.device.sensor.TemperatureSensor;import net.sf.dz.controller.ProcessController;import net.sf.dz.util.ObjectFactory;/** * The virtual thermostat implementation. * * @author Copyright © <a href="mailto:vt@freehold.crocodile.org">Vadim Tkachenko</a> 2001-2002 * @version $Id: ThermostatModel.java,v 1.10 2004/01/20 02:40:34 vtt Exp $ */public final class ThermostatModel extends PassiveService implements TemperatureSensorListener, Thermostat, ThermostatController { /** * Log channel to use. */ protected LogChannel CH_TSTAT; /** * Thermostat name. */ private String name; /** * Is this zone allowed to initiate the A/C start. * * Default is true. * * @see #isVoting */ private boolean voting = true; /** * The current temperature. * * @see #currentTemperatureChanged */ private double currentTemperature; /** * Current control signal. */ private double controlSignal = 0; /** * The sensor. * * Never queried directly. This object is added to the sensor as a * listener, and the notifications are {@link #currentTemperatureChanged * processed} asynchronously. */ private TemperatureSensor sensor; /** * The controller defining this thermostat's behavior. */ private ProcessController controller; /** * Set of listeners. */ private Set listenerSet = new HashSet(); /** * Is this thermostat enabled. * * If the zone is set to "OFF", the thermostat will still accept the * notifications, but will not relay the signal to the controller. */ private boolean enabled = true; /** * Is this thermostat on hold. * * If it is set to "hold", the {@link #schedule scheduler} will not * change the settings when the period changes. */ private boolean hold = false; /** * Is it possible to use the zone controlled by this thermostat as a * dump zone when heating. * * <p> * * 0 (default) means that this zone shouldn't be used as a dump zone, * values greater than 0 mean priority, 1 being the highest. The higher * the priority, the sooner the zone will be used as a dump zone when * heating. */ private int dumpHeating = 0; /** * Is it possible to use the zone controlled by this thermostat as a * dump zone when cooling. * * <p> * * 0 (default) means that this zone shouldn't be used as a dump zone, * values greater than 0 mean priority, 1 being the highest. The higher * the priority, the sooner the zone will be used as a dump zone when * cooling. */ private int dumpCooling = 0; /** * This thermostat's unit. * * The reference is required so the schedule event gets a hold of the * current A/C mode through {@link * net.sf.dz.device.model.Unit#getAC() unit.getAC()}. */ private Unit unit; /** * Attach to the A/C unit. * * @param unit A/C unit to attach to. Can't be <code>null</code>. */ public void attach(Unit unit) { this.unit = unit; } public Unit getUnit() { if ( unit == null ) { throw new IllegalStateException("Unit must be attached first"); } return unit; } public void configure() throws Throwable { // Figure out the name. // Since the name is supplied not for the thermostat, but for the // zone, we'll have to walk a little bit back on the configuration // root and extract it from there. The configuration root will look // something like // "dz.unit.${unit-name}.zone.${zone-name}.thermostat", in other // words, the component we're looking for is the next to last. Since // this happens just once (at configuration time), some calculation // overhead in order to reduce the complexity is OK. String trailer = null; String cursor = null; for ( StringTokenizer st = new StringTokenizer(getConfigurationRoot(), "."); st.hasMoreTokens(); ) { trailer = cursor; cursor = st.nextToken(); } // At this point, the trailer contains the thermostat name. name = trailer; CH_TSTAT = new LogChannel("Thermostat/" + getName()); dumpHeating = getConfiguration().getInteger(getConfigurationRoot() + ".dump.heating", 0); dumpCooling = getConfiguration().getInteger(getConfigurationRoot() + ".dump.cooling", 0); voting = getConfiguration().getBoolean(getConfigurationRoot() + ".voting.enabled", true); complain(LOG_INFO, CH_TSTAT, "Dump zone (heat " + dumpHeating + ", cool " + dumpCooling + ")"); createController(); createSensor(); complain(LOG_INFO, CH_TSTAT, "configured"); } public String getName() { return name; } public void setVoting(boolean voting) { complain(LOG_INFO, CH_TSTAT, "setVoting: " + voting); this.voting = voting; for ( Iterator i = listenerSet.iterator(); i.hasNext(); ) { ((ThermostatListener)i.next()).votingStateChanged(this, voting); } } public boolean isVoting() { return voting; } public void setHold(boolean hold) { complain(LOG_INFO, CH_TSTAT, "setHold: " + hold); this.hold = hold; for ( Iterator i = listenerSet.iterator(); i.hasNext(); ) { ((ThermostatListener)i.next()).holdStateChanged(this, hold); } } public boolean isOnHold() { return hold; } public void setOn(boolean enabled) { complain(LOG_INFO, CH_TSTAT, "setOn: " + enabled); this.enabled = enabled; for ( Iterator i = listenerSet.iterator(); i.hasNext(); ) { ((ThermostatListener)i.next()).enabledStateChanged(this, enabled); } // VT: NOTE: With the current implementation of zone controllers, // the step below is redundant. Let's be paranoid, though. if ( !enabled ) { controlSignalChanged(new Double(0)); } else { controlSignalChanged(new Double(controlSignal)); } } public boolean isOn() { return enabled; } public int getDumpPriority(int mode) { if ( !isOn() ) { // Override. If the zone is shut off, it can be used as a dump // zone. return 1; } switch ( mode ) { case -1: // cooling return dumpCooling; case 1: // heating return dumpHeating; default: // off return 0; } } /** * Create the thermostat controller. * * The controller is instantiated and initialized based on the * configuration. */ private void createController() { String configurationRoot = getConfigurationRoot() + ".controller"; String controllerClass = getConfiguration().getString(configurationRoot + ".class"); try { controller = (ProcessController)ObjectFactory.instantiate(controllerClass, ProcessController.class); if ( controller instanceof LogAware ) { ((LogAware)controller).setLogger(getLogger()); } controller.configure(configurationRoot, getConfiguration()); complain(LOG_DEBUG, CH_TSTAT, "Instantiated controller"); } catch ( Throwable t ) { complain(LOG_ERR, CH_TSTAT, "Can't instantiate controller, root " + configurationRoot + ", class " + controllerClass + ", cause:", t); throw new IllegalArgumentException("Can't instantiate controller, see messages above"); } } /** * Create the temperature sensor driver. * * The driver is instantiated and initialized based on the * configuration. */ private void createSensor() { String configurationRoot = getConfigurationRoot() + ".temp_sensor"; String sensorClass = getConfiguration().getString(configurationRoot + ".class"); try { sensor = (TemperatureSensor)ObjectFactory.instantiate(sensorClass, TemperatureSensor.class); if ( sensor instanceof LogAware ) { ((LogAware)sensor).setLogger(getLogger()); } sensor.configure(configurationRoot, getConfiguration()); sensor.addListener(this); complain(LOG_DEBUG, CH_TSTAT, "Instantiated temperature sensor"); } catch ( Throwable t ) { complain(LOG_ERR, CH_TSTAT, "Can't instantiate temp sensor, root " + configurationRoot + ", class " + sensorClass); throw new IllegalArgumentException("Can't instantiate temp sensor, see messages above"); } } public void currentTemperatureChanged(TemperatureSensor source, Object currentTemperatureObject) { // VT: NOTE: The source is ignored because we have just one sensor // connected. More than one will never happen because if even // there's a need to have more than one sensor per zone, their // readings will be combined and presented by an entity like // AggregateTemperatureSensor, so the logic will not change. if ( source != sensor ) { throw new IllegalStateException("Got a notification from someone else's sensor"); } // VT: FIXME: At this point, we'll just complain if the reading is // not available. Later, we'll have to open the dumper for this zone // and exclude it from the zone controller logic. if ( currentTemperatureObject == null || !(currentTemperatureObject instanceof Double) ) { complain(LOG_WARNING, CH_TSTAT, "Sensor failure: " + currentTemperatureObject); controlSignalChanged(currentTemperatureObject); return; } currentTemperature = ((Double)currentTemperatureObject).doubleValue(); // VT: NOTE: Do not be tempted to remove compute() from the logic if // this zone is not enabled - it may seem a pointless calculation, // but it's not. Since the process controller is a time sensitive // entity, it will keep calculating the *correct* value of the // signal, which needs to be done even if it is not currently used. controlSignal = controller.compute(System.currentTimeMillis(), currentTemperature); if ( enabled ) { controlSignalChanged(new Double(controlSignal)); } else { controlSignalChanged(new Double(0)); } } /** * Broadcast the notification about the {@link #controlSignal control * signal} change to the {@link #listenerSet listener set}. * * This may be a result of receiving a {@link #currentTemperatureChanged * temperature change notification} or control action ({@link * #setOn setOn()}). * * @param enabled Current state of {@link #enabled enabled}. */ private void controlSignalChanged(Object signal) { // complain(LOG_INFO, CH_TSTAT, "CO: " + signal); for ( Iterator i = listenerSet.iterator(); i.hasNext(); ) { ((ThermostatListener)i.next()).controlSignalChanged(this, signal); } } /** * Get the adjusted {@link #controlSignal control signal} value. * * @return If {@link #enabled enabled}, the value of the {@link * #controlSignal control signal}, otherwise 0. */ public double getControlSignal() { return enabled ? controlSignal : 0; } public ProcessController getController() { return controller; } public void addListener(ThermostatListener l) { listenerSet.add(l); } public TemperatureSensor getSensor() { return sensor; } protected void startup() throws Throwable { if ( unit == null ) { throw new IllegalStateException("Unit must be attached first"); } if ( sensor instanceof PassiveService ) { ((PassiveService)sensor).start().waitFor(); } complain(LOG_INFO, CH_TSTAT, "Started"); } protected void shutdown(Throwable cause) { } public String toString() { return "Thermostat[" + getName() + ": " + controller.getSetpoint() + "]"; } public LogChannel getLogChannel() { return CH_TSTAT; } public int compareTo(Object other) { if ( !(other instanceof Thermostat) ) { if ( other == null ) { throw new ClassCastException("null argument"); } else { throw new ClassCastException("Expected " + getClass().getName() + ", got " + other.getClass().getName()); } } return getName().compareTo(((Thermostat)other).getName()); }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -