-
SCD41 support, and a zoo of sensors that refuses to agree
08/03/2026 at 19:28 • 0 commentsI 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
Device Temperature Delta SCD41 26.4 °C −1.0 BME280 27.4 °C 0.0 (arbitrary zero) BME680 28.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_offsetto 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 for CO2 is not another sensor, it's fresh outdoor air at roughly 420 ppm, used via forced recalibration, plus letting automatic self-calibration do its job. For humidity, the cheap and genuinely useful reference is saturated salt solutions — NaCl gives 75.3 % RH, MgCl2 gives 33 % RH at 25 °C. A spirit hygrometer is not accurate enough to arbitrate this.
Plan
Go on holiday, let the stations pile up data, and come back and actually analyse it — specifically, correlate the inter-sensor delta against time of day and rate of temperature change, rather than staring at absolute values.
If you've done this comparison, or have experimental data, or just a hypothesis I've missed, I'd like to hear it.
P.S. SHT35/45 are already queued up to join the zoo.
P.P.S. OTA update endpoint, if you want it: http://iot.scorpionzzz.com/sensorstation3/firmware/sensorstation3.latest.bin
-
Same board, different screen: adding ST7789 support with auto-detection
07/28/2026 at 13:35 • 0 commentsThis 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_PANELinidf.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
07/19/2026 at 14:52 • 0 commentsThe 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.
Viacheslav

