Air quality logger for the Tempest weather dashboard. Runs on a Raspberry Pi with a Pimoroni Enviro+ hat and PMS5003 particulate matter sensor, polling every 60 seconds and sending readings to the dashboard for display.
The PMS5003 sensor provides:
- PM1.0, PM2.5, PM10 — particulate matter concentrations in µg/m³
- Particle counts — number of particles per 0.1L air across six size bands (0.3µm to 10µm)
The dashboard derives US EPA AQI and UK DAQI (1–10) from these readings.
- Raspberry Pi 3B+ (or similar) with GPIO header
- Pimoroni Enviro+ HAT
- PMS5003 particulate matter sensor (connected via ribbon cable to the Enviro+ board)
The Enviro+ also has temperature, light, and gas sensors. This project only uses the PMS5003 — the other sensors are ignored.
logger.py runs as a systemd service and:
- Polls the PMS5003 every 60 seconds
- Stores each reading to a local SQLite database (
air.db) - Every 5 minutes, flushes unsent readings to the dashboard ingest endpoint via HTTP POST
- Readings that fail to send are buffered locally and retried on the next flush cycle
- Successfully sent readings older than 7 days are purged from the local buffer
This means the Pi can lose network connectivity for extended periods without losing data.
pip3 install pms5003 requests --break-system-packagesThe Pi also requires UART enabled for the PMS5003. On Pi 3, Bluetooth must be moved to the mini UART to free up the hardware UART for the sensor. Add the following to /boot/firmware/config.txt:
enable_uart=1
dtoverlay=pi3-miniuart-bt
cd ~
git clone https://github.com/stetho/tempest-air.git Projects/tempest-airEdit tempest-air.service and set INGEST_SECRET to the shared secret configured on the dashboard.
sudo cp ~/Projects/tempest-air/tempest-air.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable tempest-air
sudo systemctl start tempest-aircd ~/Projects/tempest-air
git pull
sudo systemctl restart tempest-airAll configuration is via environment variables set in tempest-air.service:
| Variable | Default | Description |
|---|---|---|
INGEST_URL |
http://192.168.1.5:5001/api/ingest/air |
Dashboard ingest endpoint |
INGEST_SECRET |
(empty) | Shared secret — must match AIR_INGEST_SECRET on the dashboard |
POLL_INTERVAL |
60 |
Seconds between sensor readings |
FLUSH_INTERVAL |
300 |
Seconds between flush attempts |
FLUSH_BATCH |
100 |
Maximum readings per flush |
PURGE_DAYS |
7 |
Days to retain sent readings in the local buffer |
# Live logs
sudo journalctl -u tempest-air -f
# Service status
sudo systemctl status tempest-airNormal log output looks like:
2026-07-10T15:53:51 INFO Reading 122 stored — PM2.5: 9.0 µg/m³ PM10: 9.0 µg/m³
2026-07-10T15:55:00 INFO Flushing 2 reading(s) to proliant1
2026-07-10T15:55:01 INFO Flushed 2 reading(s) successfully
The dashboard side is handled by air_db.py in tempest-dashboard. The Pi POSTs to /api/ingest/air and the dashboard exposes /api/air/current and /api/air/history/24h for the frontend.