Concurrent Programming in Java
The requirements for the system it implements are described in the following lightly edited excerpts from Booch's Object-Oriented Design (first edition) text. (Booch's version was in turn taken from other sources. They allegedly originated from an actual set of requirements for a real system. The implementation is fairly faithful even to some of the stranger-sounding aspects of the requirements.)
The basic function of the system is to regulate the flow of heat to individual rooms in a home in an attempt to maintain a working temperature tw established for each room. A given room needs heat to be turned on whenever the room temperature is <= tw-2 degrees, and does not need heat when the room temperature is >= tw+2 degrees. The working temperature for each room is calculated by the system as a function of a desired temperature, td (set by the user through a manual input device) and whether or not the room is occupied. If the room is occupied, the working temperature is set to the desired temperature. If the room is vacant, the working temperature is set to td-5 degrees Fahrenheit. Additionally, the system maintains a weekly living pattern, and attempts to raise room temperatures thirty minutes before occupancy is anticipated for a given room. The weekly living pattern is updated when variations to the established patterns occur two weeks in a row.There are lots of ways to design such a system. (See, for example the versions in Booch's book, as well as DeChampeaux's OO Development Process and Metrics). We'll take a pull-driven approach. Pull-driven designs are uncommon for control systems, but work pretty well here, because most of the information flow across stages takes a function-like transformational form, that can be computed in a demand-driven manner.... The following input devices exist: Main switch, Desired temperature input, and Fault Reset. ... The following display devices are provided: Furnace Status, Fault indicator.
Heat is provided to each room of the home by hot water, which is heated by the furnace. Each room is equipped with a valve that controls the flow of hot water into the room. The valve can only be commanded to be fully open or fully closed.
The furnace consists of a boiler, an oil valve, an ignator, a blower, and a water temperature sensor. The furnace activates when the main switch is on and at least one room needs heat. The activation procedure is:
The furnace deactivates on a fault or when no rooms need heat. The deactivation procedure is:
- The system activates the blower
- When the blower reaches a predetermined RPM value the system opens the oil valve and ignites the oil
- When the water reaches a predermined value, the system opens the appropriate water valves.
- The Furnace indicator is turned on.
A fuel flow status sensor and an optical combustion sensor signal the system if abnormalities occur, in which case the system deactivates the furnace. The minimum time for the furnace to restart after prior operation is five minutes.
- The system closes the oil valve and then after five seconds stops the blower motor.
- The system turns the furnace indicator off.
- The system closes all the room water valves.
The overall flow is illustrated in the following diagram, where solid lines represent (most of the) information flow (not control flow) implemented via software connections, and dashed lines represent (only some of the) ``external'' feedback in the system; for example turning on a RoomValve switch should cause a Room Valve Sensor to report that the valve is on. Since this is a simulation, all of these are also implemented as software connections.
Note: All source code described in this example can be obtained as the single file HHS.java.
From a pull-driven approach, the principal functioning of the system is for Room Valve Switches to decide whether to turn on or off. So the Room Valve Switches (one per room) play the role of sinks in this design, pulling information from other components. In the process of doing so, information passes through and is transformed by a lot of other components.
floats): temperatures, RPMs.
longs).
interface BoolStage { public boolean boolValue(); }
interface RealStage { public float realValue(); }
interface Timer { public long timeValue(); }
While most components in this system are pull-driven value-reporters,
there are also some purely passive Effectors serving as
push-driven sinks activated as by-products of other
actions. Plus a few (like the Furnace Indicator) that both accept and
report values, so act like ``variables''. These can all be captured
by adding a few more interfaces:
interface BoolEffector { public void set(boolean newval); }
interface BoolVar extends BoolStage, BoolEffector {}
interface RealEffector { public void set(float newval); }
interface RealVar extends RealStage, RealEffector {}
class Bool implements BoolVar {
protected boolean value_;
public Bool(boolean initial) { value_ = initial; }
public Bool() { value_ = false; }
public synchronized boolean boolValue() { return value_; }
public synchronized void set(boolean newVal) { value_ = newVal; }
}
class Real implements RealVar {
protected float value_;
public Real(float initial) { value_ = initial; }
public Real() { value_ = 0.0f; }
public synchronized float realValue() { return value_; }
public synchronized void set(float newVal) { value_ = newVal; }
}
class SystemTimer implements Timer {
public long timeValue() { return System.currentTimeMillis(); }
};
For example, a Bool is used instead of an actual physical
Valve Sensor. Rather than physically sensing whether a valve is
open, we simply arrange that Room Valve Switches set the Bool to true
or false. (Depending on the exact hardware available, we might have to
do this in a real system. If there were not any way to sense the
valves, this would be the best we could do. And even if there were, we
might want to use a Bool as well, and build a stage that dealt with
differences between assumed versus sensed status.)
Similarly we need a few arbitrary classes to simulate sensed values. Among other unshown classes, here is one that randomly reports true or false:
class RandomBoolStage implements BoolStage {
protected float prob_;
public RandomBoolStage(float prob) { prob_ = prob; }
public boolean boolValue() { return Math.random() <= prob_; }
};
Several other classes dress up these raw simulated stages using AWT widgets, for viewing in the Applet. For example, among other similar (unshown) classes, here is a wrapper around a BoolVar:
class LabelledBoolVar extends java.awt.Label implements BoolVar {
static final Color offColor = Color.lightGray;
static final Color onColor = Color.green;
protected BoolVar s_;
public LabelledBoolVar(String label, BoolVar s) {
super(label);
s_ = s;
display();
}
public LabelledBoolVar(String label) { this(label, new Bool()); }
protected void display() {
if (s_.boolValue()) setBackground(onColor); else setBackground(offColor);
}
public synchronized void set(boolean newVal) {
boolean old = s_.boolValue();
s_.set(newVal);
if (newVal != old) display();
}
public synchronized boolean boolValue() { return s_.boolValue(); }
}
Most of these functions are highly specialized to this system, but we
do need a few generic, reusable components. For example, we need
``OR-gates'', that report true if any of their inputs report
true. These are used when OR'ing the Valve Sensors from
the rooms to determine whether any of them are on. While most stages
in this system have fixed links to predecessors, ORers can be
connected to just about anything, using a list of
attachable BoolStages:
class Orer implements BoolStage {
protected collections.UpdatableBag inputs_;
public Orer() { inputs_ = new collections.LinkedBuffer(); }
public synchronized void attach(BoolStage s) { inputs_.addIfAbsent(s); }
public synchronized void detach(BoolStage s) { inputs_.exclude(s); }
public synchronized boolean boolValue() {
for (Enumeration e = inputs_.elements(); e.hasMoreElements(); )
if (((BoolStage)(e.nextElement())).boolValue()) return true;
return false;
}
};
A more typical stage is the RoomHeatNeedCalculator, that
combines information about the current actual temperature, desired temperature,
(actual or expected) current occupancy, and current valve status
(which is used to prevent hysteresis effects) to decide whether
a room needs heat. Like several other Stages, this component
is immutable, simply computing this
function whenever asked, without ever changing its state. (Among
other consequences, no methods need be synchronized.)
Also, like most components of control systems, the details are
slightly messy:
class RoomHeatNeedCalculator implements BoolStage {
// Room update parameters
static final float offsetWhenUnoccupied = 5.0f;
static final float roomHeatThreshold = 2.0f;
// Safety overrides for desired temperature settings
static final float minimumDesiredRoomTemperature = 40.0f;
static final float maximumDesiredRoomTemperature = 120.0f;
protected BoolStage valveSensor_;
protected BoolStage occupancyReporter_;
protected RealStage actualTemperatureSensor_;
protected RealStage desiredTemperatureSensor_;
public RoomHeatNeedCalculator(BoolStage valveSensor,
BoolStage occupancyReporter,
RealStage actualTemperatureSensor,
RealStage desiredTemperatureSensor) {
valveSensor_ = valveSensor;
occupancyReporter_ = occupancyReporter;
actualTemperatureSensor_ = actualTemperatureSensor;
desiredTemperatureSensor_ = desiredTemperatureSensor;
}
public boolean boolValue() {
float actual = actualTemperatureSensor_.realValue();
float desired = desiredTemperatureSensor_.realValue();
// Offset desired temp if not occupied or expected
boolean consideredOccupied = occupancyReporter_.boolValue();
if (!consideredOccupied) desired -= offsetWhenUnoccupied;
// check if desired is safe/sensible:
if (desired < minimumDesiredRoomTemperature)
desired = minimumDesiredRoomTemperature;
else if (desired > maximumDesiredRoomTemperature)
desired = maximumDesiredRoomTemperature;
boolean currentlyHeating = valveSensor_.boolValue();
if (!currentlyHeating && actual <= desired - roomHeatThreshold)
return true;
else if (currentlyHeating && actual >= desired + roomHeatThreshold)
return false;
else
return currentlyHeating;
}
};
synchronized. (The update rate is set to be a fairly
slow once per second just for demo purposes. Also, since this is a
simulation, this class does not actually cause any physical action,
its only effect is to turn on a simulated Valve Sensor):
class RoomValve implements Runnable {
static final int updateInterval = 1000;
protected BoolStage heatNeed_;
protected BoolStage furnaceOn_;
protected BoolEffector valveSensor_;
protected Thread me_;
public RoomValve(BoolStage heatNeed,
BoolStage furnaceOn,
BoolEffector valveSensor) {
heatNeed_ = heatNeed;
furnaceOn_ = furnaceOn;
valveSensor_ = valveSensor;
me_ = new Thread(this);
me_.start();
}
protected void update() {
boolean wantHeat = heatNeed_.boolValue();
boolean canHeat = furnaceOn_.boolValue();
valveSensor_.set(wantHeat && canHeat);
}
public void run() {
if (Thread.currentThread() != me_) return; // only one thread!
for (;;) {
update();
try { Thread.sleep(updateInterval); }
catch (InterruptedException ex) { return; }
}
}
}
class TimeOutSensor implements BoolStage {
protected long startTime_;
protected long duration_;
protected Timer time_;
public TimeOutSensor(Timer time, long duration) {
time_ = time;
duration_ = duration;
startTime_ = time_.timeValue();
}
public synchronized boolean boolValue() {
return time_.timeValue() - startTime_ >= duration_;
}
public synchronized void reset() { startTime_ = time_.timeValue(); }
}
The OccupancyReporter is a Stage that is attached to a raw
OccupancySensor, but ``transforms'' this value into one describing
whether a room is either currently occupied or is expected to be
occupied soon. To do this, it maintains occupancy history and smooths
it into the required living pattern.
Because occupancy histories must be updated periodically whether
or not the Reporter is asked about its current value, this update is
done in its own Thread, running independently of it being asked. (It
just so happens that under the parameters of the rest of the system,
OccupancyReporter.boolValue() is called
frequently enough for such purposes, but adding a thread here
guarantees proper functionality without having to make any further
assumptions about the rest of the system.)
class OccupancyReporter implements BoolStage, Runnable {
// Main time granularity constant.
// Occupancy is tracked in 5-minute (300 second) blocks;
static final int occBlockSize = 300 * 1000;
// Occupancy is checked every 1 second
static final int updateInterval = 1000;
// Derived constants:
// One week has 7 days * 24 hrs * 60 min * 60 sec / occBlockSize blocks
static final int blocksPerWeek = 7 * 24 * 60 * 60 * 1000 / occBlockSize;
static final int blocksPerTwoWeeks = 2 * blocksPerWeek;
// A half hour has 30 min * 60 sec / occBlockSize blocks
static final int blocksPer30Minutes = 30 * 60 * 1000 / occBlockSize;
// Utilities for indexing bit vectors recording occupancy
static int convertTimeToHistoryIndex(long t) {
return (int)((t / occBlockSize) % blocksPerTwoWeeks);
}
// index of livingPattern block corresponding to a history block
static int livingPatternIndexOf(int historyIndex) {
return historyIndex % blocksPerWeek;
}
// index of history block representing same time during other week
static int otherWeekIndexOf(int historyIndex) {
return (historyIndex + blocksPerWeek) % blocksPerTwoWeeks;
}
// offset a livingPattern index by a constant
static int livingPatternOffset(int base, int offset) {
return (base + offset + blocksPerWeek) % blocksPerWeek;
}
protected BoolStage occSensor_;
protected Timer time_;
protected BitSet history_;
protected BitSet livingPattern_;
protected int lastUpdatedPatternIndex_;
protected Thread me_;
public OccupancyReporter(BoolStage occSensor, Timer time) {
occSensor_ = occSensor;
time_ = time;
livingPattern_ = new BitSet(blocksPerWeek);
history_ = new BitSet(blocksPerTwoWeeks);
lastUpdatedPatternIndex_ = 0;
me_ = new Thread(this);
me_.start();
}
public void run() {
if (Thread.currentThread() != me_) return;
for (;;) {
update();
try { Thread.sleep(updateInterval); }
catch (InterruptedException ex) { return; }
}
}
// Report true if occupied or expected in 30 minutes
public synchronized boolean boolValue() {
// check current occupancy
int historyIndex = convertTimeToHistoryIndex(time_.timeValue());
if (history_.get(historyIndex))
return true;
// check livingPattern for expected occupancy within the next 30 minutes
int livingPatternIndex = livingPatternIndexOf(historyIndex);
for(int i = 0; i < blocksPer30Minutes; ++i)
if (livingPattern_.get(livingPatternOffset(livingPatternIndex, i)))
return true;
return false;
}
// Check occupancy; update history and livingPattern
protected synchronized void update() {
int historyIndex = convertTimeToHistoryIndex(time_.timeValue());
// Clear the current block, but only if this is the first
// of possibly many successive update calls during the same block.
// This way we end up OR'ing occupancy for the block.
// (This might be a bad choice if occupancy sensors false alarm.)
if (historyIndex != lastUpdatedPatternIndex_) {
history_.clear(historyIndex);
lastUpdatedPatternIndex_ = historyIndex;
// Only bother to update livingPattern when cross blocks.
int otherWeekIndex = otherWeekIndexOf(historyIndex);
int livingPatternIndex = livingPatternIndexOf(historyIndex);
boolean current = history_.get(historyIndex);
boolean otherWeek = history_.get(otherWeekIndex);
if (current == otherWeek) {
if (current) livingPattern_.set(livingPatternIndex);
else livingPattern_.clear(livingPatternIndex);
}
// Apply a simple smoother to get rid of strays.
// The following changes the middle of the previous group of 5 blocks
// if it is different from all its neighbors. (We cannot apply
// this to current index since we don't know future yet.)
int t1 = livingPatternOffset(livingPatternIndex, -4);
int t2 = livingPatternOffset(livingPatternIndex, -3);
int t3 = livingPatternOffset(livingPatternIndex, -2);
int t4 = livingPatternOffset(livingPatternIndex, -1);
int t5 = livingPatternIndex;
if (livingPattern_.get(t1) && livingPattern_.get(t2) &&
livingPattern_.get(t4) && livingPattern_.get(t5))
livingPattern_.set(t3);
else if (!livingPattern_.get(t1) && !livingPattern_.get(t2) &&
!livingPattern_.get(t4) && !livingPattern_.get(t5))
livingPattern_.clear(t3);
}
if (occSensor_.boolValue()) history_.set(historyIndex);
}
}
The code for this is very long, but not very interesting except
as an illustration of how to build a mostly-humanly comprehensible
state update function from such a messy set of inputs
and transitions, and also including many more Fault states, covering
cases that should never happen, than described in the original
requirements.
class Furnace implements BoolStage, Runnable {
// Bounds on water temperature in boiler
static final float minWaterTemp = 150.0f;
static final float maxWaterTemp = 200.0f;
// Time-out values for Furnace
static final long blowerTimeOutPeriod = 60000; // 1 minute
static final long roomValveTimeOutPeriod = 60000; // 1 minute
static final long blowerDelayPeriod = 5000; // 5 seconds
static final long waterTimeOutPeriod = 30 * 1000; // 30 sec
static final long furnaceRecyclePeriod = 10 * 1000; // 10 sec
// Threshhold value for Furnace blower
static final float minBlowerRPM = 300.0f;
// State update rate
static final int updateInterval = 1000;
// State to report in boolValue
protected boolean on_;
// Components
protected BoolStage blowerSensor_;
protected BoolStage oilValveSensor_;
protected BoolStage fuelFlowFaultSensor_;
protected BoolStage combustionFaultSensor_;
protected BoolStage heatRequest_;
protected BoolStage roomValveStatus_;
protected BoolStage mainSwitch_;
protected RealStage rpmSensor_;
protected RealStage waterTempSensor_;
protected Timer time_;
protected BoolEffector oilValveSwitch_;
protected BoolEffector blowerSwitch_;
protected BoolEffector ignatorSwitch_;
protected BoolEffector furnaceIndicator_;
protected BoolVar faultLight_;
protected BoolVar faultClearRequested_;
protected TimeOutSensor recycleTimeOut_;
protected TimeOutSensor roomValveTimeOut_;
protected TimeOutSensor blowerInitTimeOut_;
protected TimeOutSensor waterTimeOut_;
protected TimeOutSensor blowerDelayTimeOut_;
protected Thread me_;
public Furnace(BoolEffector blowerSwitch,
BoolStage blowerSensor,
BoolEffector oilValveSwitch,
BoolStage oilValveSensor,
BoolEffector ignatorSwitch,
BoolStage fuelFlowFaultSensor,
BoolStage combustionFaultSensor,
RealStage rpmSensor,
RealStage waterTempSensor,
Timer time,
BoolStage mainSwitch,
BoolVar faultLight,
BoolEffector furnaceIndicator,
BoolVar faultClearRequested,
BoolStage heatRequest,
BoolStage roomValveStatus) {
blowerSwitch_ = blowerSwitch;
blowerSensor_ = blowerSensor;
oilValveSwitch_ = oilValveSwitch;
oilValveSensor_ = oilValveSensor;
ignatorSwitch_ = ignatorSwitch;
fuelFlowFaultSensor_ = fuelFlowFaultSensor;
combustionFaultSensor_ = combustionFaultSensor;
rpmSensor_ = rpmSensor;
waterTempSensor_ = waterTempSensor;
time_ = time;
mainSwitch_ = mainSwitch;
faultLight_ = faultLight;
furnaceIndicator_ = furnaceIndicator;
faultClearRequested_ = faultClearRequested;
heatRequest_ = heatRequest;
roomValveStatus_ = roomValveStatus;
blowerSwitch_.set(false);
oilValveSwitch_.set(false);
ignatorSwitch_.set(false);
furnaceIndicator_.set(false);
on_ = false;
recycleTimeOut_ = new TimeOutSensor(time_, furnaceRecyclePeriod);
roomValveTimeOut_ = new TimeOutSensor(time_, roomValveTimeOutPeriod);
blowerInitTimeOut_ = new TimeOutSensor(time_, blowerTimeOutPeriod);
waterTimeOut_ = new TimeOutSensor(time_, waterTimeOutPeriod);
blowerDelayTimeOut_ = new TimeOutSensor(time_, blowerDelayPeriod);
me_ = new Thread(this);
me_.start();
}
// better names for Sensed States
boolean faulted() { return faultLight_.boolValue(); }
boolean systemOn() { return mainSwitch_.boolValue(); }
boolean clearRequested() { return faultClearRequested_.boolValue(); }
boolean wantHeat() { return heatRequest_.boolValue(); }
boolean blowerOn() { return blowerSensor_.boolValue(); }
boolean oilOn() { return oilValveSensor_.boolValue(); }
boolean roomValvesOpen() { return roomValveStatus_.boolValue(); }
boolean recycleTimedOut() { return recycleTimeOut_.boolValue(); }
boolean roomValvesTimedOut() { return roomValveTimeOut_.boolValue(); }
boolean blowerInitTimedOut() { return blowerInitTimeOut_.boolValue(); }
boolean waterTimedOut() { return waterTimeOut_.boolValue(); }
boolean blowerDelayTimedOut() { return blowerDelayTimeOut_.boolValue(); }
boolean combustionFault() { return combustionFaultSensor_.boolValue(); }
boolean fuelFlowFault() { return fuelFlowFaultSensor_.boolValue(); }
boolean rpmOK() { return rpmSensor_.realValue() >= minBlowerRPM; }
boolean waterHotEnough() { return waterTempSensor_.realValue()>=minWaterTemp;}
boolean waterTooHot() { return waterTempSensor_.realValue() > maxWaterTemp; }
// Action States -- aggregations of above
boolean readyForBlowerOn() {
return recycleTimedOut() && !blowerOn() && !oilOn();
}
boolean readyForOilOn() {
return blowerOn() && rpmOK() && !oilOn();
}
boolean readyToHeat() {
return blowerOn() && rpmOK() && oilOn() && waterHotEnough();
}
boolean readyForBlowerOff() {
return !on_ && (!roomValvesOpen() || roomValvesTimedOut()) &&
!oilOn() && blowerOn() && blowerDelayTimedOut();
}
boolean readyForOilOff() {
return !on_ && (!roomValvesOpen() || roomValvesTimedOut()) && oilOn();
}
boolean blowerTimeOutFault() {
return blowerOn() && !rpmOK() && blowerInitTimedOut();
}
boolean blowerFailureFault() {
return oilOn() && !rpmOK();
}
boolean roomValveTimeOutFault() {
return !on_ && oilOn() &&
roomValvesOpen() && roomValvesTimedOut();
}
boolean waterTimeOutFault() {
return oilOn() && !waterHotEnough() && waterTimedOut();
}
boolean abnormality() {
return combustionFault() ||
fuelFlowFault() ||
waterTooHot() ||
blowerTimeOutFault() ||
waterTimeOutFault() ||
roomValveTimeOutFault() ||
blowerFailureFault() ;
}
// Actions corresponding to above states
void setFurnaceOn() { on_ = true; furnaceIndicator_.set(true); }
void setFurnaceOff() {
if (on_) {
on_ = false;
furnaceIndicator_.set(false);
recycleTimeOut_.reset();
roomValveTimeOut_.reset();
}
}
void setOilOn() {
if (!oilOn()) {
oilValveSwitch_.set(true);
ignatorSwitch_.set(true);
waterTimeOut_.reset();
}
}
void setOilOff() {
if (oilOn()) {
oilValveSwitch_.set(false);
blowerDelayTimeOut_.reset();
}
}
void setBlowerOn() {
if (!blowerOn()) {
blowerSwitch_.set(true);
blowerInitTimeOut_.reset();
}
}
void setBlowerOff() { blowerSwitch_.set(false); }
void setFault() { faultLight_.set(true); }
void clearFault() {
faultLight_.set(false);
faultClearRequested_.set(false);
}
// Check state; perform an action
synchronized void update() {
if (faulted() && (!systemOn() || clearRequested())) clearFault();
if (abnormality()) setFault();
if (!faulted() && systemOn() && wantHeat()) {
if (readyToHeat()) setFurnaceOn();
else if (readyForOilOn()) setOilOn();
else if (readyForBlowerOn()) setBlowerOn();
}
else {
if (on_) setFurnaceOff();
else if (readyForOilOff()) setOilOff();
else if (readyForBlowerOff()) setBlowerOff();
}
}
public void run() {
if (Thread.currentThread() != me_) return; // only one thread!
for (;;) {
update();
try { Thread.sleep(updateInterval); }
catch (InterruptedException ex) { return; }
}
}
public synchronized boolean boolValue() { return on_; }
};
public class HHS extends Applet {
static final int numberOfRooms = 4;
public void init() {
SystemTimer timer =
new SystemTimer();
BoolButton mainSwitch =
new BoolButton("Main");
BoolButton faultResetButton =
new BoolButton("Reset");
LabelledBoolVar faultLight =
new LabelledBoolVar("Fault");
LabelledBoolVar furnaceLight =
new LabelledBoolVar("Furnace");
LabelledBoolVar blower =
new LabelledBoolVar("Blower");
LabelledBoolVar oilValve =
new LabelledBoolVar("Oil");
PulsedLabelledBoolVar ignator =
new PulsedLabelledBoolVar("Ignator", 500);
Orer requestGate =
new Orer();
Orer valveGate =
new Orer();
RandomBoolStage fuelFlowFaultSensor =
new RandomBoolStage(0.00001f);
RandomBoolStage combustionFaultSensor =
new RandomBoolStage(0.00001f);
SimPhysSensor rpmSensor =
new SimPhysSensor(blower,
timer,
1.0f,
10.0f,
0.0f,
400.0f,
0.0f);
SimPhysSensor boilerSensor =
new SimPhysSensor(oilValve,
timer,
0.02f,
1.0f,
30.0f,
200.0f,
60.0f);
Furnace furnace =
new Furnace(blower,
blower,
oilValve,
oilValve,
ignator,
fuelFlowFaultSensor,
combustionFaultSensor,
rpmSensor,
boilerSensor,
timer,
mainSwitch,
faultLight,
furnaceLight,
faultResetButton,
requestGate,
valveGate);
Panel ctlPanel = new Panel();
ctlPanel.add(mainSwitch);
ctlPanel.add(faultResetButton);
ctlPanel.add(faultLight);
ctlPanel.add(furnaceLight);
ctlPanel.add(blower);
ctlPanel.add(oilValve);
ctlPanel.add(ignator);
add(ctlPanel);
for (int i = 0; i < numberOfRooms; ++i) {
LabelledBoolVar valveSensor =
new LabelledBoolVar("valve");
BoolButton occSensor =
new BoolButton("Occupy");
OccupancyReporter occReporter =
new OccupancyReporter(occSensor, timer);
Real dt =
new Real(72.0f - (float)i);
RealAdjustorButton dtUp =
new RealAdjustorButton(dt, true);
RealAdjustorButton dtDn =
new RealAdjustorButton(dt, false);
LabelledRealStage desiredTemp =
new LabelledRealStage("Desired:",dt);
SimPhysSensor at =
new SimPhysSensor(valveSensor,
timer,
0.002f,
0.0004f,
0.0f,
100.0f,
(float)(60.0f + 3.0f * (float)i));
LabelledRealStage actualTemp =
new LabelledRealStage("Actual:", at);
RoomHeatNeedCalculator heatNeedCalculator =
new RoomHeatNeedCalculator(valveSensor,
occReporter,
actualTemp,
desiredTemp);
RoomValve roomValve =
new RoomValve(heatNeedCalculator,
furnaceLight,
valveSensor);
requestGate.attach(heatNeedCalculator);
valveGate.attach(valveSensor);
Panel room = new Panel();
room.add(occSensor);
room.add(dtUp);
room.add(dtDn);
room.add(desiredTemp);
room.add(actualTemp);
room.add(valveSensor);
add(room);
};
mainSwitch.set(true);
}
public boolean action(Event evt, Object arg) {
if (evt.target instanceof BoolButton) {
BoolButton b = (BoolButton)(evt.target);
b.toggle();
return true;
}
else if (evt.target instanceof RealAdjustorButton) {
RealAdjustorButton b = (RealAdjustorButton)(evt.target);
b.adjust();
return true;
}
return false;
}
}