BME680 environmental monitor for a Raspberry Pi Zero W. Samples temperature, humidity, pressure, and a relative air-quality score every 10s into SQLite and serves a password-protected dashboard over a Cloudflare named tunnel.
Full build write-up: Building a BME680 air-quality monitor on a Raspberry Pi Zero W.
- Live dashboard: temperature, humidity, pressure, relative air quality.
- Comfort-band labels on live tiles (e.g. "comfortable", "damp", "warm").
- Dew point calculated and displayed with a mold-risk indicator.
- Temperature-offset calibration (editable live from the dashboard) to correct BME680 self-heating.
- Persistent rolling air-quality baseline — survives restarts, no re-burn-in.
- Historical charts with selectable ranges (1h / 24h / 7d / 30d / 1y), averaged server-side into buckets so plots stay light at any range.
- Per-range min / avg / max statistics and CSV export.
- Data-staleness indicator on the dashboard;
/healthzendpoint for uptime monitors. - Sensor-stall self-alert: fires a notification if no new reading arrives within a configurable window.
- Discord threshold alerting with optional sustained-duration trigger (only a
breach lasting
sustain_secondsfires) and recovery notifications. - Optional ntfy channel notified alongside Discord; "Send test" button in the Alerts panel.
- Pressure and dew point are alertable metrics alongside temperature, humidity, and air quality.
- Gas-heater read interval is configurable (UI): take VOC readings less often than the base sample to steady the baseline and reduce heater wear.
- Sea-level pressure: set site altitude (UI) to display altitude-corrected pressure alongside the raw station value.
- Editable comfort-band thresholds and mold-risk points (UI).
- Login backoff/lockout after repeated failures and a configurable session lifetime (UI).
- Single shared password, session-cookie auth.
- Internet-accessible over a Cloudflare named tunnel (no open router ports).
- Installable PWA (web-app manifest + service worker).
- Runs as two systemd services, auto-starting on boot. One year of retention, pruned nightly.
Historical charts — selectable ranges with min/avg/max per metric:
Settings — sensor calibration, comfort bands, and security, all editable live:
Alerts — per-metric thresholds with sustain windows and recovery notifications:
Alerts fire to Discord (and optionally ntfy), with green recovery messages:
Hardware
- A Raspberry Pi (developed on a Pi Zero W) running Raspberry Pi OS, on your network.
- A BME680 sensor wired to the Pi's I2C pins:
3V3→ VCC,GND→ GND,GPIO2 (SDA)→ SDA,GPIO3 (SCL)→ SCL. The sensor answers at I2C address0x76or0x77. - microSD card and power supply.
Software & accounts
- Raspberry Pi OS (or any Debian-based Linux).
deploy/setup.shenables I2C and installs git, Python 3, andi2c-toolsfor you. - Python 3.11+ recommended; older 3.x works (
tomliis installed automatically on < 3.11). - A Cloudflare account with a named tunnel —
setup.shprompts for its token to expose the dashboard over the internet with no open router ports. LAN-only? Leave thecloudflaredservice disabled. - Optional: a Discord webhook and/or an ntfy topic for alert notifications.
The hardware library (bme680) is only needed on the Pi — tests and local dev
use a FakeSensor, so everything runs on any machine.
python -m venv .venv
.venv/bin/pip install Flask waitress pytest # bme680 is Pi-only
.venv/bin/python -m pytest -vThe Pi tracks this git repo. Code ships with a single command from your machine:
ssh pi project-deploy # deploy origin/main
ssh pi 'project-deploy feat/x' # deploy a specific branchproject-deploy runs deploy/deploy.sh on the Pi: it git reset --hards to the
target branch, reinstalls Python deps only if requirements.txt changed, syncs
the systemd units if they changed, and restarts airquality.service. Your
config.toml and the SQLite database are git-ignored and never touched.
-
Install git and clone the repo:
sudo apt-get update && sudo apt-get install -y git git clone <your-repo-url> ~/project cd ~/project
-
Provision and start:
bash deploy/setup.sh # enables I2C, installs deps + cloudflared + systemd + project-deploy nano config.toml # set [auth].password and [server].secret_key sudo systemctl restart airquality
deploy/setup.sh prompts for the Cloudflare tunnel token and stores it in
/etc/cloudflared.env (mode 600).
Check status and logs:
systemctl status airquality cloudflared
journalctl -u airquality -fVerify the sensor is wired correctly: i2cdetect -y 1 should show the BME680 at
0x76 or 0x77.
Copy config.example.toml to config.toml and edit. config.toml and the
SQLite database are git-ignored.
| Section | Key | Meaning |
|---|---|---|
[server] |
host, port |
Bind address for the local app |
[server] |
secret_key |
Signs session cookies (set a random one) |
[auth] |
password |
Dashboard login password |
[sensor] |
interval_seconds |
Sampling interval (default 10) |
[sensor] |
burn_in_seconds |
Gas-sensor warm-up before scoring |
[sensor] |
stall_alert_seconds |
Fire a stall alert if no reading arrives in this many seconds (default 300) |
[calibration] |
temp_offset |
Degrees C subtracted per reading to correct self-heating (default 0); editable from the dashboard |
[db] |
path |
SQLite file path |
[db] |
retention_days |
History kept before pruning (default 366) |
[alerts] |
ntfy_url |
Optional ntfy topic URL notified alongside Discord |
[sensor] |
gas_interval_seconds |
How often to fire the gas heater / take a VOC reading (default = interval_seconds); editable in UI |
[location] |
altitude_m |
Site altitude for sea-level pressure display (default 0); editable in UI |
[security] |
login_max_attempts |
Failed logins from one IP before lockout (default 5); editable in UI |
[security] |
login_lockout_seconds |
Lockout duration after max attempts (default 900); editable in UI |
[security] |
session_lifetime_hours |
Login session lifetime (default 720); editable in UI |
Maintained by Jamie Ede.
- LinkedIn: linkedin.com/in/jamie-ede-5622a662
- Issues & PRs: open one on this repo — the fastest way to get a fix or feature in.
Want to collaborate? Bug reports, hardware build notes, sensor-calibration tips, and new alerting integrations are all welcome. Reach out on LinkedIn or by email if you'd rather chat before opening a PR.
MIT — see LICENSE.




