Close
0%
0%

SensorStation3

Open-source ESP32 CYD environmental station: T/H/pressure/CO2eq on a touchscreen, MQTT (HA autodiscovery, ThingsBoard, Domoticz), OTA

Public Chat
Similar projects worth following
An open-source ESP32 environmental monitoring station built on the CYD (Cheap Yellow Display) board. It shows temperature, humidity, dew point, pressure, and CO2-equivalent on a built-in LVGL touchscreen dashboard, pushes telemetry over MQTT — with Home Assistant autodiscovery, ThingsBoard and Domoticz support — and updates itself over the air. Sensors are auto-detected over I2C: wire one up and it just works. Prebuilt binaries available, no toolchain needed.

Why another sensor station?

I've been into IoT and home/office automation for quite a while, but the hobby went on a fairly deep pause for the last few years — a job change, a temporary move to another country, the usual life reasons. Lately the desire to get back into it came back, even if the free time hasn't necessarily followed. I wanted to properly equip my current place with something similar to what I used to run.

So I dug up two old projects, SensorStation and SensorStation2. Both had their place, but each fell short in its own way — rough edges specific to how they were built that made them not quite worth resurrecting as-is. Rather than patch old code, I decided to build something new: something affordable, and — this time — public and open source from the start.

The result runs on hardware anyone can order for about $10. The ESP32-2432S028, better known in the community as the Cheap Yellow Display (CYD), is an ESP32 with an integrated 2.8" TFT touchscreen (ILI9341 or ST7789, depending on the batch — the firmware detects which) and an onboard light sensor. All you add is one I2C sensor.

What it does

The touchscreen dashboard shows:

  • Current temperature and humidity, each with a 24-hour graph, trend arrow, and rate of change
  • Calculated dew point
  • Atmospheric pressure
  • CO2-equivalent / IAQ when a BME680 with Bosch BSEC is connected
  • Date, time (NTP-synced, timezone configurable), and Wi-Fi signal strength

Beyond the screen:

  • MQTT telemetry to ThingsBoard and Domoticz, plus Home Assistant MQTT Autodiscovery — flash it, connect it to your broker, and the station announces its own sensors to Home Assistant with zero manual configuration
  • OTA updates — the device checks for new firmware, shows an on-screen notification, and can install automatically
  • Auto-dimming backlight driven by the CYD's onboard light sensor, so it doesn't glow like a beacon at night
  • On-device settings — Wi-Fi, MQTT broker, timezone, temperature offset — all configured from the touchscreen with an on-screen keyboard. No config files, no recompiling.

Hardware

The bill of materials is refreshingly short:

  • ESP32 CYD (ESP32-2432S028) — the whole computer, display, touch, and light sensor in one board
  • One I2C sensor, connected to the board's I2C header:
SensorMeasures
BMP280temperature, pressure
BME280temperature, humidity, pressure
BME680+ IAQ / CO2-equivalent (with Bosch BSEC)

Sensors are detected automatically on the I2C bus — no menuconfig flags, no code changes. Plug in a BME280, get temperature/humidity/pressure; swap in a BME680 and the CO2 panel appears.

One honest caveat: the BME680's CO2 figure is an IAQ-derived CO2-equivalent, not a direct measurement. Direct NDIR CO2 via SCD40/41 is on the roadmap.

The display controller is auto-detected too — CYD boards ship with either an ILI9341 or an ST7789 panel, and the same binary handles both.

Firmware

The firmware is written in C on ESP-IDF v6.0.2 with LVGL (v9.5) for the UI. A few design decisions worth calling out:

You don't need a toolchain to try it. Releases ship a merged binary (bootloader + partition table + app in one file), so flashing is a single esptool command:

# first flash, blank board

esptool.py --chip esp32 --port /dev/ttyUSB0 write_flash 0x0 SensorStation3-v1.5.10-merged.bin 

# updating an existing install

esptool.py --chip esp32 --port /dev/ttyUSB0 write_flash 0x20000 SensorStation3-v1.5.10.bin

Web-based flashing straight from the browser is on the roadmap, which will drop the barrier even further.

Everything is runtime-configured. All settings live in NVS and are managed from the on-device Settings screen (PIN-protected). You can hand a flashed device to someone who has never seen a compiler and they can get it on their Wi-Fi and connected to their broker entirely from the touchscreen.

BSEC is optional and cleanly separated. Bosch's BSEC...

Read more »

  • 1 × ESP32-2432S028 (CYD) Cheap Yellow Display (ESP32 + ILI9341 2,8" touchscreen)
  • 1 × BME280 BOSCH temperature + humidity + pressure sensor
  • 1 × BME680 BOSCH temperature + humidity + pressure + VOC sensor

  • SCD41 support, and a zoo of sensors that refuses to agree

    Viacheslav08/03/2026 at 19:28 0 comments

    I finally got my hands on an SCD41, added driver support for it, and landed a few other improvements along the way. The sensor arrived together with a couple more CYD boards, so the fleet is now five stations: one BME680 (with BSEC), two BME280, and two SCD41.

    That's when the party started.

    The setup

    Units running the same sensor agree with each other closely, so there was no point comparing all five. I narrowed it down to three devices, one per sensor type, all sitting in the same spot with the sensing elements inside a 3 cm radius of each other. Same air, same room, same moment.

    The SCD41 runs in periodic measurement mode (one reading every 5 s).

    The numbers

    DeviceTemperatureDelta
    SCD4126.4 °C−1.0
    BME28027.4 °C0.0 (arbitrary zero)
    BME68028.2 °C+0.8

    Almost 2 °C spread. The zero point is arbitrary — I picked the BME280 purely so the numbers are easy to read in both directions, not because I think it's correct.

    Humidity is worse, and CO2 from the BME680 and the SCD41 might as well be measuring different rooms.

    What is not the problem

    Two BME280s and two SCD41s both stay inside their datasheet specs relative to their twins: ±0.3 °C, ±5 % RH, and ±50 ppm for the SCD41. Pressure across all the BME parts differs by no more than 1 hPa. If everything were that well-behaved, I would not be writing this.

    So the disagreement is between sensor types, not between boards.

    Then it got more interesting

    I hooked all of them up to a server running Domoticz, Home Assistant and ThingsBoard to collect proper telemetry, and left them alone for a day.

    The next evening I sat down to work out how to reconcile them and pick a reference. And the floor fell out again: the difference between sensors is not constant. It drifts, over a fairly wide range, and the largest divergences line up with the moments when the room temperature is changing fastest.

    A constant offset is easy — you calibrate it out once and forget about it. A drifting one means something in the system is modulating the readings, and I don't yet know what.

    Working hypotheses

    No conclusions yet, only suspects:

    • Thermal time constants. The sensors sit in different packages, at different heights, with different amounts of copper and plastic around them. During a fast transient they will lag by different amounts even if all three are perfectly accurate at steady state. The graphs support this — the lines diverge most during the sharp drops.
    • SCD41 self-heating. The SCD4x has a configurable temperature_offset to compensate for its own self-heating, shipping with a 4 °C default, and it affects both the reported temperature and, through it, the reported humidity. Mine is running in periodic mode, which self-heats considerably more than low-power or single-shot.
    • Filtering and resolution. IIR filter and oversampling settings on the BME parts are not equivalent to whatever the SCD41 does internally, which shows up as differences in apparent noise as much as in absolute value.

    None of these obviously explains a drifting offset in a room that is otherwise thermally boring, which is the part that bothers me most.

    About humidity

    Relative humidity is a function of temperature, so with the units reporting temperatures up to 2 °C apart, some of the %RH spread has to be an artefact rather than a real disagreement. I checked dew point for exactly that reason — and it differs substantially too. So the humidity readings genuinely disagree; the temperature offset is not the whole story.

    About CO2 and references

    Worth stating plainly, because it comes up every time: the BME680 does not measure CO2. BSEC outputs a CO2 equivalent, estimated from the VOC signal. The lack of correlation with the SCD41 is not an anomaly — it is the expected result. Subjectively the SCD41 behaves far more sensibly; the BME680's estimate feels like it's reading tarot cards.

    The reference...

    Read more »

  • Same board, different screen: adding ST7789 support with auto-detection

    Viacheslav07/28/2026 at 13:35 0 comments

    This one started as a purely logistical task. The station works, friends asked for one, so I ordered a batch of CYD boards from AliExpress — same ESP32-2432S028 listing I'd used before — to assemble a few more units.

    They arrived. I flashed the firmware. And I saw rotated and overlayed image

    The board was almost the same in every visible respect: same silkscreen, same layout, same product page, only one visible difference was additional USB-C connector. The display controller was not. Instead of the ILI9341 the firmware expected, these units shipped with an ST7789 (luckily it was labeled on the box, so I didn't spend time to identify it).

    This is a known hazard with the CYD family. "ESP32-2432S028" is less a specific board than a loose family of boards that share a form factor, and manufacturers quietly swap components between production runs. Buy two at different times and you may well get two different pieces of hardware under one part number.

    The fix had to be automatic. I could have added a menuconfig option — pick your display, rebuild. But that breaks the thing I care most about in this project: you flash a prebuilt binary and it works, no toolchain, no decisions to make. A firmware that asks the user which chip is inside their board is a firmware that friends can't install themselves.

    The good news is that both panel families use identical pinout on the CYD, so the wiring didn't change at all — only the driver. At boot the firmware now reads the panel's display ID register (RDDID) over the existing SPI bus and picks the matching driver. Same binary, either panel, nothing to configure. There's also a manual override (CONFIG_SS3_LCD_PANELin idf.py menuconfig) for anyone who hits a module where auto-detection guesses wrong.

    Also in this release. Two smaller things worth mentioning. The station used to reboot in a loop if it booted with no sensor attached — the history buffer was never initialised in that path while the UI kept querying it every 500 ms. It now shows a "Connect sensor and reboot the device" message on the affected panels instead. And the trend arrows became much more responsive: they compare the current reading against its own rolling 30-second mean and update every 500 ms, rather than comparing two consecutive 30-second means and sitting frozen in between.

    A caveat, and a request. All of this is tested on the units I have in hand, and on those it works correctly. But given how varied CYD boards are in the wild, I'd very much like to hear from anyone who flashes it onto a different batch — especially if the screen comes up blank, mirrored, or with wrong colours. Which variant do you have, and did detection get it right? Reports are welcome in the comments or as an issue on GitHub.

    Available in v1.5.10. There you can find two bin files: SensorStation3-v1.5.10.bin to update your device (offset 0x20000) and SensorStation3-v1.5.10-merged.bin for initial flash (offset 0x0)

  • The BME680 that ran 3 °C hot, and the twin experiment that proved it

    Viacheslav07/19/2026 at 14:52 0 comments

    The project didn't start with air quality at all. The first working version used a BME280 — just temperature, humidity, dew point, pushed to a server. That was enough to make me want something more universal, something that would also make sense in a living room or an office. Which meant air quality, and I remembered a BME680 sitting in an old box labeled "climate sensors."

    I wired it up and extended my BMP/BME driver component to read it, including the sensor's gas resistance (VOC). The station samples everything every 500 ms, and in the first pass I read gas resistance at that same rate. The dashboard immediately started showing about 3 °C more than the same room used to read with the BME280.

    Trust, but verify with a twin. A constant +3 °C smelled like self-heating rather than a bad sensor, but I didn't want to guess. So I built an identical second station — same firmware, BME280 instead — and ran both side by side on the desk. The twin confirmed it: the BME680 unit read almost exactly 3 °C higher, consistently. Not drift, not calibration. Heat.

    Why: the BME680's gas sensor works by firing an internal hotplate to a few hundred °C on every gas reading, millimeters from the temperature element on the same die. Reading gas resistance every 500 ms meant firing that heater constantly.

    Fix, round one. I cut the VOC sampling rate by 10x, keeping temperature/humidity/pressure at 500 ms. That helped a lot — but the BME680 was still running 0.5–0.7 °C above the reference. Here's the detail that explains that residual: the station writes every reading into a 60-entry ring buffer and displays the average of the last 30 seconds. That averaging smooths the heat spike from every tenth gas reading into what looks like a steady bias instead of a visible spike — great for a readable display, bad for spotting the real cause.

    At this point I added a ±5 °C temperature offset setting for the BME680, adjustable right on the touchscreen. Useful to have regardless — but papering over self-heating with an offset felt like the wrong fix, and the raw VOC numbers still didn't look particularly realistic.

    Fix, round two: BSEC. I'd used a BME680 with Bosch's BSEC library years ago on an Orange Pi Zero — it worked, but the integration experience left mixed impressions. Still, BSEC is where Bosch put the actual sensor know-how: heater scheduling, compensation, IAQ processing. I downloaded BSEC 2.6.1.0 and wired it into the project. (It's proprietary, so it's not bundled in the repo — the README explains how to drop it in, and the firmware still builds fine without it.) After the switch, the twin stations finally agreed with each other, and I stopped needing to compare two displays every time I walked past.

    What's still bugging me: the CO2-equivalent numbers from BSEC still don't fully convince me. I've ordered SCD41 sensors and dug an old MH-Z19 out of a parts bin to compare against real NDIR CO2 — that comparison will be the next log.

View all 3 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates