Skip to content

Repository files navigation

airquality

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.

The air-quality dashboard: live tiles for temperature, humidity (with a mold-risk label), sea-level and station pressure, relative air quality out of 100, and dew point, above 1h/24h/7d/30d/1y range buttons and an Export CSV button, with 24-hour temperature and humidity charts below.

Features

  • 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; /healthz endpoint 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_seconds fires) 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.

Screenshots

Historical charts — selectable ranges with min/avg/max per metric:

Seven days of history: separate line charts for temperature, humidity, pressure, relative air quality, and dew point over a time axis, each with min/avg/max printed beneath.

Settings — sensor calibration, comfort bands, and security, all editable live:

The dashboard settings panel: a Sensor section with temp offset, gas read interval, and altitude fields; a Comfort bands section with editable thresholds for air quality, humidity, and temperature plus mold-risk points; and a Security section with max login attempts, lockout seconds, and session lifetime.

Alerts — per-metric thresholds with sustain windows and recovery notifications:

The alerts config panel: a Discord webhook and ntfy URL field, a notify-on-recovery toggle, and a per-metric grid for air quality, humidity, temperature, pressure, and dew point with columns for armed status, alert-below, alert-above, recovery margin, and sustain seconds.

Alerts fire to Discord (and optionally ntfy), with green recovery messages:

A Discord channel showing fired alerts from the monitor: red entries for air quality dropping below its threshold and humidity rising above 65%, each followed by a green recovery message once the value cleared, with the score, humidity, and temperature printed on every line.

Prerequisites

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 address 0x76 or 0x77.
  • microSD card and power supply.

Software & accounts

  • Raspberry Pi OS (or any Debian-based Linux). deploy/setup.sh enables I2C and installs git, Python 3, and i2c-tools for you.
  • Python 3.11+ recommended; older 3.x works (tomli is installed automatically on < 3.11).
  • A Cloudflare account with a named tunnelsetup.sh prompts for its token to expose the dashboard over the internet with no open router ports. LAN-only? Leave the cloudflared service disabled.
  • Optional: a Discord webhook and/or an ntfy topic for alert notifications.

Local development (no hardware)

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 -v

Deploy to the Pi

The 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 branch

project-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.

First-time setup on a new Pi

  1. Install git and clone the repo:

    sudo apt-get update && sudo apt-get install -y git
    git clone <your-repo-url> ~/project
    cd ~/project
  2. 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 -f

Verify the sensor is wired correctly: i2cdetect -y 1 should show the BME680 at 0x76 or 0x77.

Configuration

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

Collaborators & Contact

Maintained by Jamie Ede.

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.

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages