Deployed on integr8 VM (pve01/700) at environment.mineracks.com. Polls Netatmo every 5 min, stores in SQLite, exposes via FastAPI REST + MCP. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
162 lines
6.1 KiB
Markdown
162 lines
6.1 KiB
Markdown
# netatmo-mcp
|
|
|
|
Self-hosted bridge that exposes a Netatmo Weather Station to Claude (via MCP)
|
|
**and** to anything else (via REST), with a local SQLite store for history and
|
|
threshold-based alerts. Designed to run as a Docker container on Proxmox.
|
|
|
|
## What you get
|
|
|
|
One process serving three things on port `8088`:
|
|
|
|
| Path | What it is |
|
|
|-------------|-----------------------------------------------------------------|
|
|
| `/mcp/` | MCP server (Streamable HTTP transport). Add to Claude as a connector. |
|
|
| `/api/...` | REST API — same data, for n8n / Grafana / cron / curl. |
|
|
| `/health` | Unauthenticated liveness + poller status. |
|
|
|
|
A background poller hits Netatmo every 5 minutes (configurable), stores every
|
|
new reading in SQLite, and evaluates any alerts you've configured.
|
|
|
|
## Capabilities
|
|
|
|
- **Current readings** for every station and module (`current_readings` tool / `GET /api/current`)
|
|
- **Local historical queries** at native ~5min resolution (`history` tool / `GET /api/history`)
|
|
- **Direct Netatmo historical pulls** for longer windows or pre-aggregated scales — 1hour, 1day, 1week, 1month (`history_from_netatmo` tool)
|
|
- **Threshold alerts** on any metric — e.g. CO₂ > 1000 ppm, temp > 28 °C (`create_alert` tool / `POST /api/alerts`)
|
|
- **Multi-station / multi-module** — handled natively; everything is keyed by `module_id`
|
|
|
|
## One-time Netatmo setup
|
|
|
|
1. Go to **https://dev.netatmo.com/apps** and create an app. Note the
|
|
**client_id** and **client_secret**.
|
|
2. On the app page, scroll to **Token generator**. Tick the `read_station`
|
|
scope and click "Generate token". Copy the **refresh token** (you don't
|
|
need the access token — it only lasts 3 hours).
|
|
3. Make sure your Netatmo account has the data centre station added.
|
|
|
|
> **Important:** Netatmo rotates the refresh token on every refresh call. This
|
|
> service persists the rotated token to `/data/tokens.json` so it survives
|
|
> restarts. Don't share that file or the refresh chain breaks.
|
|
|
|
## Deploy on Proxmox
|
|
|
|
On your Ubuntu VM with Docker:
|
|
|
|
```bash
|
|
git clone <this repo> netatmo-mcp && cd netatmo-mcp
|
|
cp .env.example .env
|
|
# fill in NETATMO_CLIENT_ID, NETATMO_CLIENT_SECRET, NETATMO_REFRESH_TOKEN,
|
|
# and NETATMO_MCP_API_KEY=$(openssl rand -hex 32)
|
|
docker compose up -d --build
|
|
docker compose logs -f
|
|
```
|
|
|
|
You should see `Poller starting` and, within a minute or so, `Refreshing
|
|
Netatmo access token` followed by the first poll. Hit `http://<vm>:8088/health`
|
|
to confirm.
|
|
|
|
After ~10 minutes there'll be a few rows in the DB and you can query history.
|
|
|
|
### Putting it behind your reverse proxy
|
|
|
|
For Claude (web/app) connectors you need a **public HTTPS URL**. If you're on
|
|
Caddy:
|
|
|
|
```
|
|
netatmo.mineracks.ai {
|
|
reverse_proxy <vm-ip>:8088
|
|
}
|
|
```
|
|
|
|
Or stick it on Cloudflare Tunnel — same deal. Bearer auth is enforced at the
|
|
app, so exposing it is fine as long as your `NETATMO_MCP_API_KEY` is strong.
|
|
|
|
## Connecting Claude
|
|
|
|
In **claude.ai → Settings → Connectors → Add custom connector**:
|
|
|
|
- **Name:** Netatmo Datacentre
|
|
- **URL:** `https://netatmo.mineracks.ai/mcp/`
|
|
- **Auth:** Bearer token → paste your `NETATMO_MCP_API_KEY`
|
|
|
|
For **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
on macOS) or Claude Code (`~/.claude/settings.json`):
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"netatmo": {
|
|
"transport": {
|
|
"type": "streamable-http",
|
|
"url": "https://netatmo.mineracks.ai/mcp/",
|
|
"headers": {
|
|
"Authorization": "Bearer YOUR_NETATMO_MCP_API_KEY"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Then ask Claude: "What's the current CO2 in the data centre?" or "Plot the
|
|
last 48 hours of temperature for the rack module."
|
|
|
|
## Using the REST API
|
|
|
|
```bash
|
|
KEY=your_api_key
|
|
BASE=https://netatmo.mineracks.ai
|
|
|
|
# All modules (find your module_id here)
|
|
curl -H "Authorization: Bearer $KEY" $BASE/api/modules
|
|
|
|
# Current snapshot
|
|
curl -H "Authorization: Bearer $KEY" $BASE/api/current
|
|
|
|
# Last 24h of CO2 for a specific module
|
|
curl -H "Authorization: Bearer $KEY" \
|
|
"$BASE/api/history?module_id=02:00:00:aa:bb:cc&metric=CO2&hours=24"
|
|
|
|
# Create an alert: CO2 > 1000 ppm on the rack module
|
|
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
|
|
-d '{"name":"dc_co2_high","module_id":"02:00:00:aa:bb:cc","metric":"CO2","op":">","threshold":1000}' \
|
|
$BASE/api/alerts
|
|
```
|
|
|
|
## Metrics reference
|
|
|
|
The fields exposed depend on module type:
|
|
|
|
| Module | Metrics |
|
|
|------------------|----------------------------------------------------------------|
|
|
| Indoor (NAMain / NAModule4) | `Temperature`, `Humidity`, `CO2`, `Noise`, `Pressure` |
|
|
| Outdoor (NAModule1) | `Temperature`, `Humidity` |
|
|
| Rain (NAModule3) | `Rain`, `sum_rain_1`, `sum_rain_24` |
|
|
| Wind (NAModule2) | `WindStrength`, `WindAngle`, `GustStrength`, `GustAngle` |
|
|
|
|
For a data centre you almost certainly care about indoor `Temperature`,
|
|
`Humidity`, and `CO2` — the noise sensor is also a surprisingly useful
|
|
"is the AC running differently?" canary.
|
|
|
|
## Files / state
|
|
|
|
- `./data/netatmo.db` — SQLite history + alerts. Back it up.
|
|
- `./data/tokens.json` — rotating refresh token. **Do not delete** without
|
|
generating a new bootstrap refresh token.
|
|
|
|
## Notes & gotchas
|
|
|
|
- **Netatmo rate limits:** 50 requests / 10 seconds and 500 / hour per user.
|
|
At 5-minute poll interval you use ~12/hour, so you're fine — but don't
|
|
drop the interval below 60s.
|
|
- **First poll is slow** because it does a token refresh. Subsequent polls
|
|
reuse the access token until it nears expiry.
|
|
- **History granularity:** the local store records whatever Netatmo returns,
|
|
which is roughly one point per metric per ~5 min. For longer windows
|
|
(weeks/months) use `history_from_netatmo` with `scale="1day"` to get
|
|
Netatmo's pre-aggregated values.
|
|
- **Alerts are evaluated each poll**, so worst-case detection latency =
|
|
`POLL_INTERVAL_SEC`. To wire alerts to email/Slack/ntfy, point an n8n
|
|
workflow at `GET /api/alert-events`, or extend `Poller._tick` to POST
|
|
to a webhook.
|