Server: rewrite in Go as an API-only service
Same endpoints, request formats, validation rules and InfluxDB layout as
the Next.js server, so the car needs no change: POST /v1/telemetry,
PUT /v1/consumption/daily, PUT /v1/charging-sessions/{id}, GET
/v1/consumption/history/{id}, GET /v1/health. The dashboard, login and
the /v1/history reads are gone; Grafana takes over the dashboards.
- Go 1.26 standard library (net/http routing, log/slog) plus the
official InfluxDB 2 client. Fails closed at startup on a missing
InfluxDB setting or an ingest token under 24 characters.
- ABRP forwarder ported: newest fresh sample, rate limited, never
queued, credentials only in the form body.
- 22 tests: validation, aggregation, forwarding, config, and the
handlers through a fake store. govulncheck clean (x/net 0.60.0,
toolchain go1.26.9).
- Image: static binary on distroless, about 11 MB, UID 65532, port
8080; the build runs vet and the tests.
- Dev shell: Go, gopls and govulncheck instead of Node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
# Nils' MG4 — car server
|
||||
|
||||
A small Go service at car.stinnesbeck.com: the API the car writes to, and the ABRP
|
||||
forwarder. Data lives in InfluxDB 2; Grafana reads it from there for the dashboards
|
||||
(`../k8s/grafana/`).
|
||||
|
||||
```
|
||||
car ──live telemetry───▶ server ──▶ ABRP api.iternio.com/1/tlm/send
|
||||
──daily chunks─────▶ │
|
||||
──charging sessions▶ ▼
|
||||
InfluxDB 2 ◀── Grafana
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Environment only; the server refuses to start when a required value is missing.
|
||||
|
||||
| Name | Notes |
|
||||
|---|---|
|
||||
| `INFLUX_URL`, `INFLUX_TOKEN`, `INFLUX_ORG` | required; the token needs read/write on the bucket |
|
||||
| `INFLUX_BUCKET` | default `mg4` |
|
||||
| `INGEST_TOKEN` | required, at least 24 characters: `openssl rand -hex 32` |
|
||||
| `ABRP_API_KEY`, `ABRP_USER_TOKEN` | optional; both needed to forward |
|
||||
| `ABRP_MIN_INTERVAL_SECONDS` | default 5 |
|
||||
| `LISTEN_ADDR` | default `:8080` |
|
||||
|
||||
The ABRP credentials never reach the car.
|
||||
|
||||
## Endpoints
|
||||
|
||||
All but `/v1/health` need `Authorization: Bearer <INGEST_TOKEN>`.
|
||||
|
||||
- `POST /v1/telemetry` — `{ installationId, samples: [...] }`, up to 500 samples in ABRP's `tlm`
|
||||
field names and units (`utc` in epoch seconds; flags as 0/1 or booleans). Unknown fields are
|
||||
dropped, known ones bounded. Stored, then the newest sample is forwarded to ABRP if it is under
|
||||
5 minutes old.
|
||||
- `PUT /v1/consumption/daily` — one delta chunk of a day's consumption.
|
||||
- `PUT /v1/charging-sessions/{sessionId}` — a finished DC session with its curve.
|
||||
- `GET /v1/consumption/history/{installationId}` — the car reads its own totals back.
|
||||
- `GET /v1/health` — `{"status":"ok"}`.
|
||||
|
||||
A bad request is a 400 naming the field; a database failure is a 500 without detail.
|
||||
|
||||
## Storage
|
||||
|
||||
One bucket, `installationId` tag on every point:
|
||||
|
||||
| Measurement | Timestamp | Tags | Fields |
|
||||
|---|---|---|---|
|
||||
| `telemetry` | sample `utc` | — | the ABRP fields that were sent |
|
||||
| `consumption_chunk` | `recordedAt` | `date` | km, kwh, hours, socDrop, sohPercent |
|
||||
| `charging_session` | `startedAt` | `sessionId` | endedAtMs, duration, energy, cost, start/end SOC |
|
||||
| `charging_curve` | point time | `sessionId` | powerKw, socPercent |
|
||||
|
||||
A point with the same measurement, tags and timestamp replaces the earlier one, so the car's
|
||||
retries are idempotent: a cached chunk is re-sent with its original `recordedAt`. Days and the
|
||||
lifetime total are summed from the chunks when read. Numbers are always written as floats,
|
||||
because InfluxDB fixes a field's type on first write.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
nix develop # from the repository root: Go, govulncheck, InfluxDB 2
|
||||
cd server
|
||||
go test ./...
|
||||
govulncheck ./...
|
||||
```
|
||||
|
||||
## Container image
|
||||
|
||||
```sh
|
||||
podman build -t git.stinnesbeck.com/nils/mg4-server:<version> .
|
||||
podman push git.stinnesbeck.com/nils/mg4-server:<version>
|
||||
```
|
||||
|
||||
The build runs `go vet` and the tests. The image is a static binary on distroless (about
|
||||
11 MB), running as UID 65532 on port 8080.
|
||||
Reference in New Issue
Block a user