Skip to content

Releases: StuckAtPrototype/AirCube

AirCube Firmware v2.0.3

Choose a tag to compare

@StuckAtPrototype StuckAtPrototype released this 12 Aug 22:12

The first release since v2.0.1, carrying the 2.0.2 sensor-reliability work as
well. Two themes run through it: a sensor that has failed should read as failed,
and the USB link should be fast and impossible to wedge.

What you will notice

  • Temperature reads about 0.8 °C (1.5 °F) higher than 2.0.1 on the same
    cube. That is a calibration correction, not a regression. Humidity is
    unchanged.
  • Pulling history takes about 5 seconds instead of about 30.
  • A broken sensor now shows as broken rather than reporting plausible
    nonsense and burying it in the history record.
  • The cube keeps answering commands. Earlier firmware could stop responding
    until it was power-cycled.
  • A browser flasher and dashboard at
    https://stuckatprototype.github.io/AirCube/ — no install, no drivers.

Measurement changes (Pro)

The SCD41 now runs in low-power periodic mode at roughly a 30-second cadence
instead of being commanded into a forced one-shot every 30 seconds, and the
driver polls the sensor's data-ready register rather than trusting fixed delays.

Periodic mode has different thermal behaviour, so the software temperature trim
was recalibrated against a reference: +1.36 °C, up from +0.53 °C. The
on-chip offset stays at the 4.0 °C factory default deliberately — the SCD4x uses
that same offset to compensate humidity, so correcting temperature there would
have dragged humidity with it.

A failed sensor now reads as failed

Earlier firmware could decode a zeroed I2C buffer or a frozen sensor register as
a real measurement, average it into history, and — on a Pro whose SCD41 had died
— quietly re-detect the cube as a Base.

  • I2C errors propagate out of the ENS210 and ENS16X drivers instead of being
    swallowed and the zeroed buffer decoded as a reading.
  • Stuck readings are detected. Ten consecutive identical raw temperature and
    humidity words, about five minutes at this cadence, latch a fault. A genuinely
    new reading clears it.
  • Recovery is bounded. After repeated failed reads the driver tries
    stop_periodicreinit → re-apply the temperature offset → restart
    periodic mode, capped and paced so a dead sensor is not hammered forever.
  • History records gaps, not guesses. Windows average valid samples only, and
    a window with no valid sample writes an impossible sentinel (INT16_MIN /
    0xFFFF) so a reader can draw a gap instead of a flat line.
  • The model is latched in NVS. A Pro whose SCD41 stops answering stays a
    Pro. Use redetect_model to force a re-probe after a repair.

History sync is about six times faster

Replies used to be written with printf, which sent them to the primary console
(UART0) first and only copied them to USB afterwards. Every byte of a history
transfer was paced by a serial port that nothing on the cube is connected to —
10.6 KB/s, which is 115200 baud once framing is accounted for.

Protocol output now goes straight to the USB-Serial-JTAG driver the firmware was
already using for receive. Measured on hardware the same transfer runs at
62–67 KB/s, taking a full 2016-slot sync from about 30 seconds to about 5.

Raising the console baud rate was never the answer, because the old USB path was
also lossy: the secondary console sink pushes one byte at a time and silently
discards data once its buffer stays full for 50 ms. The slow UART write was the
only thing holding the producer below the rate where that happened. The driver
path blocks instead of dropping, so a slow host now slows the transfer rather
than corrupting it. A 1.16 MB stress transfer produced no malformed frames.

Log output goes through the same writer, so it stays visible over USB and can no
longer interleave into the middle of a response. One side effect: lines end with
a bare newline rather than CRLF, since the console is no longer converting them.
Both the desktop and web clients accept either.

The command path can no longer be wedged

Commands were read one per 10 ms poll, and an over-full receive buffer was reset
only inside the branch that runs when a read returns data. Those two facts
combined into a dead end: once the buffer filled, the next read asked the driver
for zero bytes, which returns zero without touching the buffer, so the reset was
unreachable. The cube then ignored every command until it was power-cycled —
while live readings kept streaming, which made it look like a client bug rather
than a stuck cube.

The loop now drains every complete command a read delivers, and clears an
unterminated full buffer before reading rather than after. A burst of 2000
commands leaves the cube responsive.

Serial protocol

Additions only. Nothing was renamed or removed, so existing clients keep working
untouched.

  • The periodic sensor frame carries a health object: ok, temp_valid,
    hum_valid, co2_valid, etvoc_valid, sensor_missing.
  • New in 2.0.3: the frame also carries fw, the running firmware version,
    so a client can display it without flashing the cube first or guessing.
  • New commands for field triage: get_sensor_health, get_model,
    redetect_model, scd41_raw, scd41_selftest, scd41_reinit.

AirCube Firmware v2.0.4

Choose a tag to compare

@StuckAtPrototype StuckAtPrototype released this 13 Aug 16:03

AirCube firmware v2.0.4

This release keeps the LED display stable through brief Zigbee radio recovery.

What changed

  • Removed the green LED blink when Zigbee reconnects after a parent link failure.
  • Preserved the current air-quality color, brightness, hue, and auto-dim state
    across warm MCU restarts.
  • Resumed the Pro model's running SCD41 measurement cycle after a warm restart
    instead of restarting the sensor.
  • Kept the existing blue pairing-mode indication unchanged.

AirCube Firmware v2.0.1

Choose a tag to compare

@StuckAtPrototype StuckAtPrototype released this 31 Jul 16:38

AirCube Firmware v2.0.1

A major release. It brings Bluetooth support, the first public firmware for
AirCube Pro, and companion app compatibility. It supersedes 2.0.0.

Upgrading from 1.5.x is safe and preserves your settings.

New: Bluetooth (BLE)

AirCube now has a full Bluetooth radio mode, so a Zigbee dongle is no longer
required to get readings off the device.

  • Works with Home Assistant with no pairing. The cube broadcasts standard
    BTHome v2 data — temperature, humidity, CO2, and TVOC. Any HA instance with
    a Bluetooth adapter or an ESPHome Bluetooth proxy nearby can pick these up
    passively, with no pairing step and no Zigbee hardware.
  • Connectable GATT service for apps: live readings notified about once per
    second, brightness that can be read and written remotely, and full history
    download.
  • History sync over Bluetooth. Up to 7 days of 5-minute summaries stream to a
    connected app in a couple of seconds, with incremental sync so reconnecting
    only transfers what's new.
  • Brightness stays in sync everywhere. Changing brightness by button, over
    Bluetooth, or over Zigbee notifies connected clients of the confirmed value.

Building your own client? See the
BLE GATT Protocol reference.

How the radio mode is chosen. The ESP32-H2 shares a single radio between
Bluetooth and Zigbee, so AirCube runs exactly one at a time, decided at boot. An
un-paired cube boots into Bluetooth mode. Long-pressing to pair moves it to
Zigbee, and if your hub later removes it, it returns to Bluetooth on its own.
Cubes already paired to Zigbee keep working exactly as before and stay on Zigbee
after the update.

New: AirCube Pro support

Pro adds two sensors Base doesn't have, and this firmware drives both:

  • True CO2 from a Sensirion SCD41 (direct NDIR, 400–5,000 ppm) — a real
    measurement rather than the eCO2 figure estimated from VOCs. On Pro the SCD41
    also supplies temperature and humidity.
  • Ambient light sensing via a VCNL4040, reported in lux.
  • Automatic night dimming. The LED dims itself in a dark room and returns to
    your chosen brightness when the lights come back on.

LED reflects the worse of two readings on Pro. Color is driven by VOC Level
or CO2 Level — a 0–500 score derived from true CO2 on the same band scale —
whichever is worse. On Base, CO2 Level is always 0, so VOC Level drives the LED
exactly as it did in 1.5.x.

Two new Home Assistant entities on Pro: Carbon Dioxide Measurement (cluster
0x040D) and Illuminance Measurement (cluster 0x0400).

App compatibility

The USB serial feed now reports the hardware model alongside true CO2 and ambient
light readings. The additions are backward compatible — existing fields keep
their old names and positions, so older tools continue to work and simply won't
show the new values.

Use the current desktop app and AirCube Tray release to see CO2, ambient light,
and the Base/Pro model indicator.

Fixed

LED no longer cycles between bright and dim (Pro). Auto-dim decides day
versus night from the ambient light reading, which has the cube's own LED glow
subtracted out. That subtraction was overstated by roughly 4x — enough to push
the reading across a threshold every time brightness changed. Dimming made the
room look brighter, brightening made it look darker, and the cube oscillated
every few seconds under perfectly steady lighting.

Ambient light readings are more accurate (Pro). The same overstated
correction made reported lux read about 10 lux low whenever the LED was bright.
If you graph AirCube's light sensor in Home Assistant, expect higher values that
no longer swing with LED brightness.

Changed

  • Auto-dim thresholds are now night below 2 lux, day above 10 lux. Devices
    carrying older stored values are migrated automatically on first boot.
  • Day/night switching is limited to once every 30 seconds, and light readings are
    ignored for 3 seconds after a brightness change so the LED ramp and the sensor's
    integration window can settle.
  • Day/night thresholds set too close together are widened automatically to keep
    the cube stable, and this is now reported in the serial log instead of being
    applied silently.

Notes for Base owners

Base hardware has no CO2 sensor, no light sensor, and no automatic dimming, so
the Pro items above don't apply and your configured brightness passes through
unchanged. Bluetooth support and app compatibility apply to both models. LED
behavior, VOC Level, and the 0–500 scale are unchanged from 1.5.x.

Upgrade notes

  • Settings are preserved: brightness, Zigbee pairing, and stored history all
    survive the update.
  • AQI-S remains deprecated and reports 0 over USB serial, unchanged since
    1.5.0. Use VOC Level, eCO2/eTVOC, or true CO2 on Pro.
  • Bluetooth and Zigbee cannot run at the same time — this is a hardware limit of
    the radio, not a configuration choice.
  • Ambient light compensation is still tuned for a single LED color, so accuracy
    varies slightly as the cube moves along the green-to-red gradient. A
    color-aware correction is planned.

Release_V1.5.2

Choose a tag to compare

@StuckAtPrototype StuckAtPrototype released this 02 Jun 16:18

First release since v1.4.3. New absolute AQI, AQI-driven LED, and Zigbee brightness control.

Highlights

  • Canonical AQI (0–500): new absolute, TVOC-derived air-quality score (replaces reliance on the relative AQI-S).
  • AQI-driven LED: smooth green → yellow → red gradient tied to fixed indoor-air thresholds.
  • Zigbee brightness control: set/read LED brightness from Home Assistant; reports on startup and button press.
  • Updated HA integrations: refreshed ZHA quirk and Zigbee2MQTT converters (1.x .js + 2.x .mjs), backward compatible.

Improvements

  • Zigbee stability: rejoin flap watchdog, exponential backoff, relaxed keep-alive (3 s → 7 s), bounded locks.
  • Documentation: new AQI/TVOC/color reference tables and updated setup guides.

Breaking changes

  • Serial: aqi is now the TVOC-derived AQI (0–500); the old relative value moved to aqi_s.
  • Zigbee: custom cluster 0xFC01 attr 0x0002 now carries the TVOC-derived AQI (0–500). Use the updated ZHA/Z2M converter.

Bug fixes

  • Fixed Zigbee rejoin/lockup edge cases and merge artifacts (reboot handler, init-fail watchdog, pairing lock).

Flashing

Download AirCube_firmware_v1.5.2.bin, flash at 0x0 via ESP Launchpad (DIY mode). See FIRMWARE_UPDATE.md. Settings are preserved.

AirCube Firmware v1.4.3

Choose a tag to compare

@StuckAtPrototype StuckAtPrototype released this 11 May 16:23

Zigbee stability fixes.

What's new

  • Rejoin flap watchdog. After 10 rejoins in 5 minutes, AirCube reboots itself cleanly instead of letting the Zigbee MAC assert.
  • Smarter rejoin backoff. The 1 s → 5 min backoff only resets after 60 s of stable uptime, so a flapping parent no longer pins us to NETWORK_STEERING every second.
  • Keep-alive 3 s → 7 s. Two missed polls used to trip PARENT_LINK_FAILURE on marginal RF.
  • Default TX power 10 → 20 dBm (ESP32-H2 max). Configurable in menuconfig.
  • Overflow-safe history readout over USB serial; the reply reports slots actually emitted so the desktop app's cursor stays correct.

AirCube Firmware v1.4.2

Choose a tag to compare

@StuckAtPrototype StuckAtPrototype released this 16 Apr 15:19
3df3946

Fixed

  • Zigbee silent disconnect after 48–72 h runtime (#27). After a parent link failure triggered a rejoin, the internal s_rejoining flag was never cleared on the subsequent DEVICE_REBOOT success path. The device would stay "rejoining" forever, skip further rejoin scheduling, and appear offline in Home Assistant / ZHA with the red LED on. The flag and backoff timer are now properly reset whenever the stack reports a successful reboot into an already-commissioned network.
  • Sensor task could block forever on a stuck Zigbee stack. zigbee_update_sensors() and zigbee_start_pairing() now acquire the Zigbee lock with a bounded timeout (2 s / 5 s). On timeout the cycle is skipped or pairing is aborted instead of deadlocking the sensor pipeline.

Added

  • Task watchdog on the sensor task. sensor_task is now registered with the ESP-IDF Task Watchdog Timer and feeds it each loop, so any future hang is caught and recovered automatically via reset.
  • Auto-recovery from stale radio state. If Zigbee stack initialization fails 5 times in a row (e.g. after a soft reset leaves the 802.15.4 radio in a bad state), the device now calls esp_restart() to force a clean hardware reset instead of retrying forever.

AirCube Firmware v1.4.0

Choose a tag to compare

@StuckAtPrototype StuckAtPrototype released this 14 Apr 01:27

Added

  • Remote brightness control — LED brightness (0–100%) now controllable from Home Assistant, Zigbee2MQTT, and SmartThings via Zigbee Analog Output cluster (0x000D)
  • SmartThings Edge driver — Full Lua driver with setup guide (SMARTTHINGS.md)
  • Configurable Zigbee TX power — New Kconfig option, default 10 dBm
  • Firmware update guide — Browser-based flashing via ESP Launchpad (FIRMWARE_UPDATE.md)
  • Partition table tracked in git — Deterministic 2 MB flash builds

Changed

  • Zigbee attribute updates throttled to every 10 seconds (was every sensor cycle)
  • Button task stack doubled to 4096 words to prevent overflow during pairing
  • ZHA quirk updated to v2 imports for Home Assistant 2026 compatibility
  • Explicit attribute reports now sent for temperature and humidity clusters, not just custom cluster

Fixed

  • HA 2026 configuration path trailing slash
  • AQI-to-LED color mapping correction

Docs

  • README expanded with sensor measurement details and official vs. community extension distinction
  • HOME_ASSISTANT.md rewritten for 2025/2026 differences and brightness control

Release_V1.3_HA Home Assistant Integration

Choose a tag to compare

@StuckAtPrototype StuckAtPrototype released this 04 Mar 17:41
4be391e

This is a huge one! I have added a lot of functionality into the AirCubes, the biggest one being Home Assistant integration via Zigbee!

AirCube Firmware v1.3.0

Sensors: ENS210 (temperature, humidity) + ENS16X (eCO2, eTVOC, AQI) over I2C.
3-minute warm-up for stable air quality readings.

LED: Smooth green-to-red gradient based on AQI. Blue flash during pairing.
5 brightness levels (button cycles through them), saved across reboots.

Zigbee: Home Assistant integration over 802.15.4 (ESP32-H2 native radio).

  • Standard temp/humidity clusters + custom cluster (0xFC01) for eCO2, eTVOC, AQI
  • Works with ZHA and Zigbee2MQTT (custom quirk/converter included)
  • Pairing is on-demand only -- hold button 3s to start, times out after 60s
  • Previously paired devices auto-reconnect on reboot

Serial: JSON sensor output over USB at 1 Hz (configurable).
Commands for config, history retrieval, and CSV export.

History: 7-day ring buffer on flash, 5-minute intervals, min/avg/max.

Platform: ESP-IDF v5.5.1, 4 MB flash, custom partition table.

AirCube Tray v1.0.2

Windows system tray app -- puts your AQI right in the taskbar.

  • Tray icon changes color with AQI (green → red, gray when disconnected)
  • Click to open dashboard: live readings, charts, and device history
  • Charts for AQI, temp, humidity, eCO2, eTVOC with selectable time ranges
  • Device history viewer -- browse 7 days of data stored on the device, export to CSV
  • Alerts when AQI exceeds your threshold (configurable, default 100)
  • Settings: serial port, °F/°C, alert threshold, autostart with Windows
  • No install required -- single .exe, runs from anywhere

Built with Python, PyQt6, Matplotlib, pyserial.

Windows_App_v1.0

Choose a tag to compare

@StuckAtPrototype StuckAtPrototype released this 02 Feb 04:52
a83e27b

AirCube v1.0

Desktop app for real-time air quality monitoring from your AirCube sensor.

Features

  • Live display of Temperature, Humidity, AQI, eCO2, and eTVOC
  • Color-coded AQI (green → yellow → orange → red)
  • Historical charts with adjustable history length
  • Optional CSV logging for data export

Usage

  1. Connect AirCube via USB
  2. Run AirCube.exe
  3. Select COM port → Click Connect

Debug

If the device is not listed, flip the physical USB C cable and plug it in again.

Notes

  • Windows 10+ required
  • Close other serial monitors before connecting
  • First run may trigger SmartScreen warning - click "Run anyway"