An end-to-end air quality monitor for my apartment, running 24/7 on a Raspberry Pi 4. It samples particulate matter, CO2, temperature, and humidity every 5 seconds, stores the readings in SQLite behind a FastAPI service, and serves a live React dashboard. The same data drives the fans of a DIY air purifier.
I built it to know what I'm breathing, and to practice building a system that keeps working when nobody is watching it: unattended for months, tolerant of network and power outages, and close to free to run.
- Websiteairmon.utoker.com
- Sourcehttps://github.com/utoker/airmon
- Built withPython / FastAPI / SQLite / React / Recharts / Raspberry Pi / ESP32 (ESPHome) / Home Assistant / Caddy / Cloudflare

Engineering highlights
- Hand-rolled Python drivers for all three sensors over UART and I2C, including the SPS30's byte-stuffed, checksummed SHDLC protocol, so OS upgrades can't break a dependency tree.
- Outage-proof ingestion: every reading lands in a local SQLite queue first and replays with a UUID idempotency key when the network returns, so nothing is lost and nothing is inserted twice.
- Tiered downsampling (5-second, per-minute, per-hour) keeps full resolution for recent data while the database settles near 100 MB for good. Before pruning, a daily job recomputes sampled aggregates from raw rows and refuses to delete anything if they disagree.
- Closed-loop purifier control: an ESP32 polls the API for a fan duty computed from the last 5 minutes of PM2.5. The curve lives on the server, so the policy changes without a firmware update, and Home Assistant boosts the fans when the cats use the litter box.
- Self-hosted on the Pi behind Cloudflare and Caddy, with DDNS, nightly backups to Cloudflare R2, a UPS, and a dead-man's-switch health check.