Recipes¶
Ready-made configurations for the tools this project is designed to plug into.
All of them assume the daemon is already collecting into aw2sqlite.db - see
the Quick Start.
Datasette¶
The daemon writes a <database_stem>_metadata.json file in the
Datasette metadata format,
containing human-readable sensor labels, units, and licensing. Point Datasette
at the database and that file to get a browsable, queryable UI for free:
The units in the metadata are understood by the datasette-pint plugin, which renders them alongside the values:
Backup first
Run Datasette against a backup copy rather than the live database if you want to browse while the daemon is writing.
Prometheus and Grafana¶
The /metrics endpoint serves the Prometheus text exposition format whenever the
Accept header asks for text/plain - which is exactly what a Prometheus
scraper sends - so no extra configuration is needed on this side. Add the station
to prometheus.yml:
scrape_configs:
- job_name: ambientweather
scrape_interval: 60s
static_configs:
- targets: ["192.168.0.5:8080"]
If you set an auth_token in the config, pass it to the scrape job:
scrape_configs:
- job_name: ambientweather
scrape_interval: 60s
authorization:
credentials: "s3cret"
static_configs:
- targets: ["192.168.0.5:8080"]
Every sensor is exported as a labeled gauge, so a Grafana panel for outdoor temperature is a one-liner:
Useful alerting expressions:
# Collection has stalled - no observation in 5 minutes
aw2sqlite_last_observation_age_seconds > 300
# Database growth has flatlined over the last hour
increase(aw2sqlite_observations_total[1h]) == 0
Home Assistant¶
Enable MQTT discovery in the [mqtt] table of your config:
On startup the daemon publishes one retained discovery config per sensor under
<discovery_prefix>/sensor/aw2sqlite_<client_id>/<column>/config. Home Assistant
creates the entities automatically, with names, units, and device classes derived
from the station's own labels - no YAML entity definitions to write, and the
retained configs survive a Home Assistant restart.
Sensor values are read from the regular observation topic via a value template, so enabling discovery does not add any extra publishing traffic.
Set discovery_prefix if your Home Assistant instance uses a non-default prefix.
Uptime monitoring¶
/health returns 503 once the most recent observation is older than 180
seconds, so any check that treats non-2xx as failure will catch stalled
collection without custom parsing.
Container healthcheck:
healthcheck:
test:
- "CMD"
- "python"
- "-c"
- >-
import urllib.request;
request = urllib.request.Request(
"http://localhost:8080/health",
headers={"Authorization": "Bearer s3cret"},
);
urllib.request.urlopen(request)
interval: 60s
timeout: 5s
retries: 3
Kubernetes liveness probe:
livenessProbe:
httpGet:
path: /health
port: 8080
httpHeaders:
- name: Authorization
value: Bearer s3cret
initialDelaySeconds: 30
periodSeconds: 60
Replace s3cret with the configured auth_token. If authentication is disabled,
omit the Authorization header from either probe.
An empty database reports "ok" with age_seconds: null, so a freshly deployed
daemon passes its readiness probe before the first observation lands.
Nightly backups¶
aw2sqlite backup uses SQLite's VACUUM INTO, which is safe to run while the
daemon is writing - a plain file copy of a WAL database is not. The destination
must not already exist, which makes a dated filename a natural fit. Each backup
includes observations, the raw-to-physical sensor mapping, and persisted
labels/units:
As a cron entry that keeps 30 days of history:
0 3 * * * aw2sqlite backup /backups/aw2sqlite-$(date +\%F).db && find /backups -name 'aw2sqlite-*.db' -mtime +30 -delete
Note the escaped \% - cron treats an unescaped % as a newline.
After restoring a backup beside a valid config, regenerate the adjacent Datasette sidecar without contacting the station:
Run aw2sqlite metadata without --offline when the station is reachable and
you want to refresh its current labels and units. Offline regeneration exits
with status 1 and preserves an existing sidecar if the database contains no
stored sensor metadata.
Ad-hoc analysis¶
Export a date range to CSV for a spreadsheet or notebook:
Or query the database directly - it is a plain SQLite file with one REAL column
per sensor:
sqlite3 aw2sqlite.db "
SELECT date(ts) AS day, round(max(outTemp), 1) AS high, round(min(outTemp), 1) AS low
FROM observations
WHERE ts >= date('now', '-7 days')
GROUP BY day
ORDER BY day;
"
Check for collection gaps, including one that is still ongoing: