Firmware for a CRBox air purifier built around the Seeed Studio XIAO ESP32C6. Controllable via Matter over Thread. Integrates well and can be commissioned in Home Assistant. Since the device is not certified, commissioning with other platforms such as Apple Home and Google Home likely does not work directly.
For an experimental version without Matter that hosts a local web server with a web UI, see the standalone-mode branch.
The CRBox is designed for 2 IKEA STARKVIND filters. It has 3 140mm BeQuiet Pure Wings 3 High Speed fans which offer extremely quiet operation at low speeds. The case is made from laser-cut MDF and smoothed 7mm pine strips, glued with epoxy. A small 3D printed part is used as a front panel. It's powered though USB C. It requires a 12V capable USB-PD power supply (IKEA 20W SJÖSS works great).
Limited testing shows the Air Purifier can bring an extremely polluted room from ~400 ug/m3 PM2.5 to <10 ug/m3
- in 4 hours at 12% speed.
- in 53 minutes at 50% speed.
- in 17 minutes at 100% speed.
This is a hobby project. I'm sharing this to serve as inspiration for anyone that wants to build something similar. Assembly is quite involved and I'm not providing step by step instructions but I'm sharing all the relevant files. See hardware
- Matter Air Purifier device type — Fan Control cluster with Off/Low/Medium/High modes and 0–100% speed control
- 3-fan control — PWM speed control (25 kHz) with per-fan RPM tachometer feedback
- OLED display — 128×32 screen showing fan speed, RPM, signal bars, QR code for commissioning, and info/reset screens
- Physical buttons — Short press toggles Off/Low/Medium/High; 2 second long press shows info screen. 8 second long press triggers factory reset.
- Thread-only — Runs on the ESP32-C6's 802.15.4 radio; WiFi disabled (see Enabling WiFi)
- OTA updates — Firmware updates via Home Assistant Matter Server
- Factory NVS partition — Stores per-device commissioning codes, QR codes
Use the scripts in mfg-tool-scripts/ to generate and flash per-device commissioning data:
./0_clean.sh # remove previous outputs
./1_generate.sh # generate new commissioning data
./2_add_qrcode.sh # embed QR and manual pairing codes
./3_flash.sh # flash to device (default port: /dev/ttyACM0)Override the port: PORT=/dev/ttyUSB0 ./3_flash.sh.
Commissioning QR code and manual code will be shown on the tiny screen when the commissioning window is open, but it will be hard to scan. A png of the QR code can be found in mfg-tool-scripts/out/fff1_8002/<hash>/<hash>-qrcode.png
- Bump
CONFIG_DEVICE_SOFTWARE_VERSION/CONFIG_DEVICE_SOFTWARE_VERSION_NUMBERinsdkconfig.defaults.esp32c6 - Bump
PROJECT_VER/PROJECT_VER_NUMBERinCMakeLists.txt - Build and compute the OTA checksum:
openssl dgst -sha256 -binary matter-air-purifier-ota.bin | base64 - Copy
matter-air-purifier-ota.binto:/addon_configs/core_matter_server/updates/matter-air-purifier-ota.ota - Create
matter-air-purifier-ota.json:{ "modelVersion": { "vid": 65521, "pid": 32769, "softwareVersion": <VERSION>, "softwareVersionString": "<VERSION STRING>", "cdVersionNumber": 1, "softwareVersionValid": true, "otaUrl": "file:///matter-air-purifier-ota.ota", "otaChecksum": "<CHECKSUM>", "otaChecksumType": 1, "minApplicableSoftwareVersion": 0, "maxApplicableSoftwareVersion": <PREVIOUS VERSION>, "releaseNotesUrl": "" } } - Trigger the update from the Matter Server web UI by selecting the node in the sidebar.
The ESP32-C6 has separate radios for 802.15.4 (Thread) and WiFi, so both can run simultaneously. WiFi is disabled by default because it increases RAM/flash usage and the WiFi radio stays powered even when idle (no credentials commissioned).
To enable WiFi as a secondary network interface:
-
In
sdkconfig.defaults.esp32c6, change:CONFIG_ENABLE_WIFI_STATION=nto:
CONFIG_ENABLE_WIFI_STATION=y -
Switch mDNS to the platform implementation (required for Thread DNS-SD):
CONFIG_USE_MINIMAL_MDNS=nNote: The platform mDNS backend (
ESP32DnssdImpl.cpp) currently has build errors (ignoredCHIP_ERRORreturns treated as fatal by-Werror). If the build fails, setCONFIG_USE_MINIMAL_MDNS=yas a workaround until the upstream SDK is fixed. -
Rebuild and reflash.
This project was coded with heavy use of Claude Code





