IoT/3 min read
Measuring cold drafts with a thermal camera and an ESP32
How we built ChillCheck for ACS Buildings, an ESP32 with an MLX90640 thermal camera and an SGP30 air-quality sensor that posts readings to a Flask and SQLite dashboard.
People rarely complain that a room is 19.2 °C. They complain that it feels cold, or that there's a draft by the window. ChillCheck was a school project for ACS Buildings in 2023 that tried to measure that feeling: radiant temperature and cold air falling from cold surfaces (koudeval), so building managers can fix comfort problems without simply turning up the heating.
The hardware
The sensor unit is an ESP32 with two sensors on the I²C bus:
- an MLX90640, a 32×24 pixel thermal camera. Instead of one temperature it gives 768, so it can see a cold window or a warm radiator across the room;
- an SGP30 air-quality sensor that measures eCO₂ and TVOC, because stuffy air also affects how comfortable a room feels.
The firmware is written in C++ with the Arduino framework, built with PlatformIO.
Reading the thermal camera
The MLX90640 isn't plug-and-play. At start-up the firmware reads the sensor's EEPROM and extracts its calibration parameters. Each loop it:
- reads two subframes (the sensor reads the pixels in a chessboard pattern, so one full image takes two reads),
- computes the sensor's own ambient temperature and uses it to estimate the reflected temperature,
- converts the raw values into object temperatures with an emissivity of 0.95, a typical value for walls, skin and most building surfaces.
The I²C clock is raised to 400 kHz so a frame can be read fast enough.
Getting the data to the server
The ESP32 joins Wi-Fi and sends two HTTP POSTs to a Flask server:
/mlxDatagets the 768 temperatures as one CSV line,/shtDatagets a small JSON object with the air-quality level, eCO₂ and TVOC.
The firmware also maps eCO₂ and TVOC to a simple level (Good, Moderate, Poor, Unhealthy) before sending, so the dashboard can show something readable.
On the server, the CSV is parsed, nan values from bad pixels are dropped, and only the minimum, maximum and average temperature are stored in SQLite. Storing 768 numbers per reading would have filled the database without adding much for this use case; the spread between the coldest and warmest pixel is what shows a cold surface.
The dashboard
The Flask app, with Bootstrap 5 templates, has three main parts:
- Dashboard: cards with the latest min, max and average temperature and air quality, plus charts of the last seven readings.
- Advice: a simple rule on the average temperature. Below 16.5 °C it suggests turning the heating up; above 19 °C it suggests turning it down to save energy; in between, it's fine.
- Feedback: people in the room rate how it feels on a slider, which is stored on a 7-point scale per room. Comparing what people feel with what the sensors measure is the whole point: it's the data you'd need to train a model of thermal comfort later.
Admins can register users and log in to see the dashboard.
Looking back
It was a student project, and some choices wouldn't survive a code review today:
- Passwords were hashed with plain SHA-256. Fast, unsalted hashes are easy to brute-force. I'd use bcrypt or Argon2 now.
- Secrets and the database were committed to the repository. They belong in environment variables and
.gitignore. - The server address was hardcoded in the firmware, and the dashboard queries used a fixed start date. Both should be configuration.
- HTTP without authentication between sensor and server is fine on a closed test network, not in a real building.
What did work well was the split: a small device that measures and sends, and a server that stores, summarises and advises. That made each part easy to change on its own, and it's the same shape I'd use for an IoT project today.
Blog / Keep reading
Related articles.
Robotics/7 min read
Teaching a robot gripper to check its own work
A ROS 2 state machine that confirms every gripper step with sensor data.
Data & AI/5 min read
Reading place codes out of 125 years of archive scans
OCR, OpenCV and fuzzy matching to sort Staatsbosbeheer's paper archive.