WeatherLink Bridge
A Python service that polls the Davis WeatherLink v2 API and forwards personal weather station observations to Weather Underground and Windy. It is a clean rewrite of the original Node.js app, adding robust configuration management, Prometheus metrics, and a production-ready container image.
Architecture
WeatherLink API
|
v
WeatherLinkCollector (httpx, X-Api-Secret header)
|
v
WeatherObservation (canonical imperial fields, Pydantic v2)
|
+----> FieldMapper(wunderground.yaml) --> WundergroundPublisher --> WU HTTPS API
|
+----> FieldMapper(windy.yaml) --> WindyPublisher --> Windy v2 API
(unit transforms: °F→°C, mph→m/s, inHg→Pa, in→mm)
Prometheus /metrics on METRICS_PORT (default 8080)
- wl_fetch_total, publish_total, collection_run_total
- observation_value (per-field gauge)
- last_successful_cycle_timestamp <-- liveness signal
Publishers are registered via PublisherFactory — adding a new destination (CWOP, PWSWeather, …) requires only a new YAML sensor map and a publisher class.
Quickstart
Prerequisites: Python 3.12+, uv.
# Clone and install
git clone https://github.com/rodenj1/weatherlink-bridge.git
cd weatherlink-bridge
# Copy the example env file and fill in your credentials
cp .env.example .env
$EDITOR .env
# Run directly (reads .env automatically)
uv run weatherlink-bridge
# Or install into a venv and run the console script
uv sync
weatherlink-bridge --version
weatherlink-bridge
Configuration Reference
All configuration is via environment variables (or a .env file in the working directory). Nested settings use the __ delimiter.
| Variable | Required | Default | Description |
|---|---|---|---|
WEATHERLINK__API_KEY |
Yes | — | WeatherLink v2 API key |
WEATHERLINK__API_SECRET |
Yes | — | WeatherLink v2 API secret |
WEATHERLINK__STATION_ID |
Yes | — | WeatherLink station ID |
WUNDERGROUND__ENABLED |
No | false |
Enable Weather Underground publishing |
WUNDERGROUND__STATION_ID |
No | "" |
WU PWS station ID (e.g. KCASANDI123) |
WUNDERGROUND__PASSWORD |
No | "" |
WU station key / password |
WINDY__ENABLED |
No | false |
Enable Windy publishing |
WINDY__STATION_ID |
No | "" |
Windy station ID (numeric) |
WINDY__PASSWORD |
No | "" |
Windy station password (see note below) |
UPDATE_INTERVAL_MINS |
No | 5 |
Poll interval in minutes (minimum 5) |
METRICS_PORT |
No | 8080 |
TCP port for Prometheus /metrics |
LOG_LEVEL |
No | INFO |
Log verbosity (DEBUG, INFO, WARNING, ERROR) |
Windy credentials note
Windy uses a per-station password for uploads, not the management API key.
Find it at stations.windy.com under
My Stations → Station settings → Key/Password. Set this value in
WINDY__PASSWORD.
Weather Underground credentials note
WU needs the station ID (WUNDERGROUND__STATION_ID, e.g. KCASANDI123)
and the station key / password (WUNDERGROUND__PASSWORD). Both are found
in the WU device management dashboard.
Running with Docker
Single container
# Pull the latest image
docker pull ghcr.io/rodenj1/weatherlink-bridge:latest
# Run with a .env file
docker run --rm --env-file .env -p 8080:8080 ghcr.io/rodenj1/weatherlink-bridge:latest
docker-compose
cp .env.example .env
$EDITOR .env # fill in real credentials
docker compose up -d
# metrics available at http://localhost:8080/metrics
The image is published to ghcr.io/rodenj1/weatherlink-bridge for both
linux/amd64 and linux/arm64 (suitable for Raspberry Pi / ARM homelab).
Running on Kubernetes
Manifests are under deploy/k8s/.
# 1. Create the namespace
kubectl create namespace weather
# 2. Copy the secret example, fill in real values, apply
cp deploy/k8s/secret.example.yaml deploy/k8s/secret.yaml
$EDITOR deploy/k8s/secret.yaml # add real credentials
kubectl apply -f deploy/k8s/secret.yaml
# 3. Apply the rest
kubectl apply -f deploy/k8s/deployment.yaml
kubectl apply -f deploy/k8s/service.yaml
# Verify
kubectl -n weather get pods
kubectl -n weather logs -f deployment/weatherlink-bridge
Do not commit
secret.yamlwith real values. Use Sealed Secrets or a secrets manager to encrypt before committing.
The Deployment is intentionally replicas: 1 — two replicas would
double-upload every observation to WU and Windy.
Liveness and readiness probes
Both probes hit GET /metrics on port 8080. The liveness probe also lets you
monitor last_successful_cycle_timestamp in Prometheus — if that gauge is
older than 2 * update_interval_seconds, the service may be stalled.
Metrics
The service exposes Prometheus metrics at http://<host>:8080/metrics.
| Metric | Type | Description |
|---|---|---|
wl_fetch_total |
Counter | WeatherLink fetch attempts (status=success|error) |
wl_fetch_duration_seconds |
Histogram | WeatherLink API latency |
publish_total |
Counter | Publisher attempts (publisher=wu|windy, status=success|failure|skipped) |
publish_duration_seconds |
Histogram | Publisher call latency |
collection_run_total |
Counter | Full cycle outcomes (status=success|partial|error) |
collection_run_duration_seconds |
Histogram | Full cycle duration |
observation_value |
Gauge | Latest numeric field value per field + station |
last_successful_cycle_timestamp |
Gauge | Unix timestamp of last successful fetch (liveness signal) |
update_interval_seconds |
Gauge | Configured poll interval |
weatherlink_bridge_info |
Info | App version |
Liveness vs freshness
- Liveness (
last_successful_cycle_timestamp): advances after every successful WeatherLink fetch, regardless of publisher outcomes. Alert iftime() - last_successful_cycle_timestamp > 2 * update_interval_seconds. - Publisher health: monitor
publish_total{status="failure"}separately. Publisher failures do not gate liveness — a rate-limited or temporarily unavailable WU/Windy should not restart the pod.
Migration from the Node.js version
| Old environment variable | New environment variable | Notes |
|---|---|---|
WEATHERLINK_API_KEY |
WEATHERLINK__API_KEY |
Delimiter changed (_ → __) |
WEATHERLINK_API_SECRECT |
WEATHERLINK__API_SECRET |
Typo fixed (SECRECT → SECRET) |
WEATHERLINK_STATION_ID |
WEATHERLINK__STATION_ID |
Delimiter changed |
WUNDERGROUND_ID |
WUNDERGROUND__STATION_ID |
Renamed for clarity |
WUNDERGROUND_KEY |
WUNDERGROUND__PASSWORD |
Renamed; this is the station password |
UPDATE_INTERVAL_MINS |
UPDATE_INTERVAL_MINS |
Unchanged |
PORT |
METRICS_PORT |
Renamed; default is now 8080 |
Sensor map: sensor_map.json is replaced by YAML files in
config/sensor_maps/ (wunderground.yaml, windy.yaml). The YAML format
supports field transforms (unit conversions) and is checked into version
control.
Development
# Install all deps including dev tools
uv sync
# Run tests with coverage
uv run pytest
# Lint + format check
uv run ruff check src tests
uv run ruff format --check src tests
# Type-check
uv run pyright src
uv run mypy src
# Pre-commit hooks (runs on every commit)
uv run pre-commit install
uv run pre-commit run --all-files
License
MIT — see LICENSE.
Metadata
Release files for weatherlink-bridge 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| weatherlink_bridge-0.1.2.tar.gz | 24.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| weatherlink_bridge-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 57.3 kB
Release files / weatherlink_bridge-0.1.2.tar.gz
| Download URL | weatherlink_bridge-0.1.2.tar.gz |
|---|---|
| Size | 24.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
adb55f2b6550a544ab613bfba1c7d7ca4a52f513f16f3922f02c7e2c0e4633f3
|
|
BLAKE2b-256 checksum How to use checksums |
6623cb6fa1eefc28b8c44b277ca7c8379edfcc38cec1eb94cc17aeac45dd6c1f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.12.9
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 19, 2026.
Transparency logRelease files / weatherlink_bridge-0.1.2-py3-none-any.whl
| Download URL | weatherlink_bridge-0.1.2-py3-none-any.whl |
|---|---|
| Size | 33.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bfb9caccb5139ca11de9dc8f317791f1a46bace02ddcc48cb8c28d9435c8f13b
|
|
BLAKE2b-256 checksum How to use checksums |
4a1256c434a97c42d10203e648b478d1fc5143df0e580d6f14cd71d5e40255bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.12.9
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 19, 2026.
Transparency log