← All articles

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.

Mohamad.B Alhakim

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:

  1. reads two subframes (the sensor reads the pixels in a chessboard pattern, so one full image takes two reads),
  2. computes the sensor's own ambient temperature and uses it to estimate the reflected temperature,
  3. 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:

  • /mlxData gets the 768 temperatures as one CSV line,
  • /shtData gets 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.

Measuring cold drafts with a thermal camera and an ESP32 - Mohamad Bashar Alhakim