My thermostat was lying to me
October 2023 — on why your heating system’s sensor is probably wrong, and what to do about it
The floor heating in my house had a problem I couldn’t immediately identify. The thermostat was set to 21°C. The rooms were never quite right. Sometimes too cold in the morning, sometimes stuffy by evening. I’d adjust the setting, wait, adjust again. Classic thermostat frustration.
Then I actually thought about where the temperature sensor was.
It was inside the Thermion floor heating controller module. The same module that contains a relay, a transformer, and the electronics to drive the heating circuit. That module generates heat. It sits in a wall unit that has limited airflow. The sensor reporting the room temperature was sitting inside a warm box, not in the room.
The thermostat wasn’t malfunctioning. It was doing exactly what it was told — reading the temperature where its sensor was located. The problem was that this location had nothing to do with the actual room temperature.
That’s the 5 WHYs in 30 seconds: the room was uncomfortable → the controller was cutting heat too early → it thought the room was already warm → its sensor was reading high → the sensor was inside a warm electronics enclosure, not in the room.
The fix isn’t a new thermostat. It’s a better sensor in the right place.
Dallas DS18B20: the right tool
The DS18B20 is a one-wire digital temperature sensor. It’s accurate to ±0.5°C, runs on 3.3V or 5V, communicates over a single data wire (plus ground), and most importantly: it comes on cables up to several meters long. You can put the sensor where the temperature actually matters, with the electronics somewhere else entirely.
Multiple DS18B20 sensors can share a single one-wire bus. Each has a unique 64-bit address burned in at the factory — you can identify which reading comes from which physical sensor.
I placed sensors at floor level in the relevant rooms. The ESP8266 with its microcontroller sits in the cupboard near the heating module. The sensors are at the other end of the wires. The gap between “where the electronics are” and “where the measurement matters” is resolved by 3 metres of cable and a €2 sensor.
The PID thermostat firmware
A simple on/off thermostat (hysteresis mode) works like a switch: heat on below threshold, heat off above threshold. It’s fine for slow systems. For floor heating — which has significant thermal mass and slow response — a PID controller does better.
PID (Proportional-Integral-Derivative) calculates not just “am I above or below target” but “how far am I off, how long have I been off, and how fast is the temperature changing.” It produces a continuous output — a power percentage — that drives a slow PWM signal on the relay. Instead of slamming the heating on and off, it modulates it.
The firmware supports three modes switchable via HTTP:
GET/POST /state → {"mode": "pid", "target": 21.0, "actual": 20.8, "power": 34.2}
GET/POST /settings → PID tuning (Kp, Ki, Kd), sensor index, calibration offset
The PID runs every 250ms. The relay PWM period is 5 seconds (PWM_PERIOD = 5000). If the PID output is 34%, the relay is on for 1.7 seconds out of every 5. The heating element sees proportional power rather than binary switching.
// Simulate low frequency PWM
if ((pid.output * PWM_PERIOD) / 100 > now - pid.windowStartTime) {
switchOn();
} else {
switchOff();
}
Settings persist to EEPROM with CRC verification, so they survive power cycles. Calibration offsets are stored per-sensor by their unique one-wire address, so even if sensors have small manufacturing variations, you can dial them in once.
The Prometheus exporter
The second ESP8266 I deployed — the temp_prom_exporter — does something simpler. It polls all connected DS18B20 sensors every 5 seconds and exposes their readings at /metrics in Prometheus format:
# HELP beertemp_device_temperature_celsius Current device temperature in celsius.
# TYPE beertemp_device_temperature_celsius gauge
beertemp_device_temperature_celsius{id="28041654914ff" resolution="12"} 21.44
beertemp_device_temperature_celsius{id="280316442b74ff" resolution="12"} 20.87
(The beertemp prefix is historical — this firmware started life monitoring fermentation temperatures for homebrewing and got repurposed. I never renamed the metrics. They work fine.)
Prometheus scrapes this endpoint every 30 seconds. Grafana shows me the actual temperature history at each sensor location. Now when the heating behaves unexpectedly, I’m not guessing — I have a graph.
What the graph showed
Once I had real data, the problem became visible immediately. The original Thermion controller’s sensor would plateau ~0.8°C above the actual room temperature during the afternoon, when the electronics in the wall unit had been running for hours and had warmed up. The controller would cut heating roughly 45 minutes early every day, which is exactly why the rooms were never quite right by evening.
With the DS18B20 in the room, the controller gets an honest reading. The floor heating runs the right amount. The rooms are comfortable. The thermostat is no longer lying.
The parts, if you want to replicate this
- ESP8266 (Wemos D1 Mini is a good form factor for wall mounting)
- DS18B20 sensors on 1–3m cables — widely available for <€3 each
- 4.7kΩ resistor (pull-up between data line and 3.3V, required for one-wire)
- A 5V relay module (active-low to match the
GPIO_RELAY 0default) - Arduino IDE with these libraries:
ESP8266WiFi,OneWire,DallasTemperature,PID_v1,ESP8266WebServer
Flash the PID firmware, set your WiFi credentials, connect the sensor and relay, configure the target temperature via the HTTP API. Done.
The Prometheus exporter is a separate firmware for a separate ESP8266 — it just reads and reports, no control logic. You could combine them if you want a single device that both controls and exports metrics, but I keep them separate: the controller is in the heating cupboard, the sensor array is wherever I need readings.
The broader point
Consumer thermostats are designed to be installed and forgotten. The sensor is wherever is convenient for the manufacturer. The control algorithm is whatever was cheap to implement. For most people in most situations, that’s fine.
But if you’ve ever felt like your heating system “doesn’t quite work right,” the first question to ask is: where is the sensor, and what is it actually measuring? Not what it’s supposed to measure — what it physically can measure given where it’s located.
Understanding the measurement before trusting the output. That’s the first step in solving almost any physical system problem.


