A thermostat controls a point, not a room
A room thermostat holds one number at one place: wherever its sensor happens to be mounted. Every other square metre gets its temperature as a consequence, and nobody measures whether that consequence is any good.
In a small, square, single-facade room that is fine — the difference across it is a fraction of a degree and the assumption holds. Add a glazed facade, underfloor heating and a door to a stairwell, and the assumption quietly stops being true.
Where the error comes from
Nothing exotic — just the ordinary physics of a modern house, all of it acting at once:
- Glazing and solar gain: the strip along the window runs colder at night and hotter in the afternoon than anywhere else in the room.
- Draught paths from entrance halls and stairwells, which move cold air along the floor.
- Heat from equipment and from people — a media cabinet or a full dining table is a standing load.
- Stratification by height: near the ceiling and at ankle level are not the same room, thermally.
- Underfloor heating inertia: the floor is still releasing heat long after the loop has closed.
- Proximity to a heating riser or a distribution manifold, which warms the wall the sensor is screwed to.
How big is the error, in practice
In a 40 m² room with a glazed facade the spread between points reaches several degrees. A thermostat mounted on the inner wall — the usual place, because that is where the cable was — sits in the warmest part of the room and reports the room as warmer than it is.
A zone is a set of sensors, not a sensor
The fix is to stop treating one reading as the room. A zone collects one to four sensors, aggregates them into a single representative value and publishes that to the control loop. The loop itself does not change at all — it still consumes one number; the number just describes the room now instead of a spot on a wall.
Keeping the aggregation in its own module matters more than it looks. The control loop stays simple enough to read in one sitting, and the measurement problem is solved once for every zone rather than re-invented per room.
A dead sensor is the easy case
A failed sensor usually reports null or an empty string. It is excluded from the sample and the zone runs on whatever is left — three sensors instead of four is still a far better description of the room than the single sensor most installations rely on.
If nothing valid is left, the value is not updated at all. Holding the last known number is safer than driving a heating loop from garbage.
cfg.sensors.forEach(function (control) {
var value = dev[control];
// A failed sensor typically reports null or an empty string.
if (typeof value === 'number' && isFinite(value)) {
readings.push(value);
}
});
if (readings.length === 0) {
log.warning('temperature_zone_{}: no valid sensors, value not updated', id);
return;
}A stuck sensor is the hard case
Averaging solves the spread across a room. It does not solve a sensor that reports a plausible but wrong number. A type check does not catch it — 25,4 °C is a perfectly valid float. It simply drags the average, and the loop then works to a setpoint that is quietly offset.
Comparing each reading against the median of the group catches it. The median is unmoved by a single outlier in a way the mean is not, so it makes a stable reference to measure deviation against. Readings further from it than the threshold are dropped and logged. Below three readings there is no majority to compare against, so the check stands down rather than guessing.
Choosing sensor count, placement and threshold
- One sensor is genuinely enough in a small, square room with one facade and no underfloor heating. Do not add hardware to prove a point.
- Two to four sensors once a room has a glazed facade, is over roughly 30 m², or spans two thermal situations — an open-plan kitchen and dining area is two rooms as far as heat is concerned.
- Place them at working height, spread across the thermal situations of the room, not clustered on one wall.
- Do not place them above a heat source, in direct sun, next to a heating riser, or right beside a door to an unheated space — those points describe the fault, not the room.
- Set the deviation threshold wider than the real spread you expect between points, or the check will throw away a true gradient. Start from the spread the sensors actually show over a week of normal use.
What this does not solve
The median check needs a majority to be right. If two of four sensors stick at similar wrong values, the median moves with them and the check confirms the fault instead of catching it.
It also cannot tell a fault from a real gradient. An open window in winter produces exactly the pattern a stuck sensor produces — one reading far from the others — and the rule will reject a reading that is perfectly correct.
Both limits come from the same root: the check looks at one instant. Distinguishing a broken sensor from a real event needs the history of the readings and the rate at which they move, not a single snapshot. That is a different module, and we have not written it yet.
The module, and where it runs
The whole thing is about a hundred lines and reads in five minutes. It is published under MIT — take it, adapt the control names to your own installation, and it will run on wb-rules 2.0 as it stands.
temperature-zone.js — MITThe 350 m² case this runs onHow the zone uses the value: dead band, season lockKNX programming as a service