App 2.3.0: Server tab, live telemetry to the car server

- New Server tab: server URL, ingest token and installation id, with
  Save, Test connection (health, then a read-only token check) and the
  config-file import that upstream removed (app folder / USB stick first,
  document picker as fallback). Settings are kept encrypted and read on
  every use, so the service picks up a change without a restart. Each
  installation generates its own id; the inherited upstream UUID and
  the baked-in CONSUMPTION_API_* build fields are gone.
- Live telemetry restored from before ea8032a (TelemetryPayload,
  UploadCadence, UploadSettings, GPS) and sent in batches to
  POST /v1/telemetry instead of ABRP; the server forwards to ABRP. Adds
  ext_temp, voltage and current. Unsent samples queue in memory (about
  8 h) while offline.
- Upload tab: the live-telemetry switch and cadence controls work again;
  the dead service and autostart switches are removed.
- Manifest: location permissions and foreground service types.
- Unit tests compile again: 114 (stable) / 136 (unstable) pass. Tests
  for the deleted AbrpApi and HeadUnitWeather removed; new tests for
  ServerConfig and TelemetryQueue. Lint passes.

Not yet tried on the car.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 14:23:04 +02:00
co-authored by Claude Opus 5.5
parent c253876ed2
commit 6145736c97
27 changed files with 1590 additions and 287 deletions
+32 -24
View File
@@ -27,7 +27,7 @@ car ──live telemetry───▶ car.stinnesbeck.com ──▶ ABRP api.ite
| Measurement | Timestamp | Tags | Fields |
|---|---|---|---|
| `telemetry` | sample time | — | soc, power, speed, lat, lon, heading, elevation, is_charging, is_dcfc, is_parked, capacity, soe, odometer, est_battery_range, batt_temp, cabin_temp, ext_temp, hvac_setpoint, voltage, current, tire_pressure_fl/fr/rl/rr |
| `consumption_daily` | the day, 00:00 UTC | — | km, kwh, hours, socDrop, sohPercent |
| `consumption_chunk` | recordedAt | date | km, kwh, hours, socDrop, sohPercent (days summed on read) |
| `charging_session` | startedAt | sessionId | endedAt, durationSeconds, energyKwh, gridEnergyKwh, pricePerKwh, totalCost, start/endSocPercent |
| `charging_curve` | point time | sessionId | powerKw, socPercent |
@@ -40,15 +40,16 @@ car ──live telemetry───▶ car.stinnesbeck.com ──▶ ABRP api.ite
retried, since stale live data is useless to ABRP. Off when the token is unset.
- **Existing endpoints stay:** `PUT /v1/consumption/daily`, `PUT /v1/charging-sessions/:id`,
`GET /v1/consumption/history/:id`, the dashboard and `/v1/health`.
- **Self-hosting:** drop the Vercel assumptions, `output: "standalone"`, a `Dockerfile`, and
`ensureIndexes()` replaced by bucket checks at startup.
- **Self-hosting:** `output: "standalone"`, a `Dockerfile`, and InfluxDB's own overwrite semantics
instead of `ensureIndexes()`.
### Car app
- Restore the live telemetry payload and upload cadence from before `ea8032a` (`TelemetryPayload`,
`UploadCadence`, location access), sending to the server instead of ABRP.
- Server URL and ingest token: open question, see below.
- Generate our own `CONSUMPTION_INSTALLATION_ID`.
- Server URL, ingest token and installation id are entered in a new **Server** tab or imported from
a config file; nothing about the server is compiled into the APK. Each installation generates
its own id.
### Image and deployment
@@ -59,15 +60,23 @@ car ──live telemetry───▶ car.stinnesbeck.com ──▶ ABRP api.ite
with placeholder values only.
- **Nothing is ever applied to the cluster by us.** Nils applies the manifests.
### Open questions
### Decisions (2026-10-17)
- [ ] Run InfluxDB inside the cluster from our manifests (assumed), or use an existing instance?
- [ ] Ingress class and TLS setup (e.g. cert-manager `ClusterIssuer` name), or an existing wildcard
certificate Secret?
- [ ] Car config: bake the server URL and ingest token into the APK at build time (as upstream
does), or enter them in the app, which restores the settings tab with the config-file import?
In the app keeps the token out of the APK.
- [ ] ABRP API key: the open-source default key from upstream, or our own?
- InfluxDB 2 runs in the cluster from our manifests.
- TLS: cert-manager `ClusterIssuer` `letsencrypt-prod`; Traefik `IngressRoute` on `websecure`.
- Car config is entered in the app (Server tab / config file), not baked into the APK.
- The ABRP API key and user token are set by Nils in the `mg4-server` Secret.
### Status
- [x] Server on InfluxDB 2 with `/v1/telemetry` and the ABRP forwarder (`web/`), tested locally
against InfluxDB 2.7.12. Image builds.
- [x] Manifests in `k8s/`, validated offline. **Not applied** — that is Nils' step (`k8s/README.md`).
- [x] Car app 2.3.0: Server tab with config import, live telemetry upload, cadence controls.
Unit tests and lint pass; **not yet tried on the car**.
- [ ] Push the image to git.stinnesbeck.com (needs a registry login on the build machine).
- [ ] Check the Traefik ingress class in `k8s/ingress.yaml` (placeholder `traefik-external`).
- [ ] First drive with 2.3.0: telemetry arrives in InfluxDB and at ABRP.
## To do
@@ -81,19 +90,12 @@ car ──live telemetry───▶ car.stinnesbeck.com ──▶ ABRP api.ite
(`ALLOWED_HOSTS`). For git.stinnesbeck.com it needs the host on the allowlist, and the
response parsing checked against that server's releases API. Release assets must be named
`NilsMG4-unstable-<version>.apk` with a `.sha256` sidecar (`build-car` writes both).
- [ ] **ABRP upload is gone upstream.** Upstream commit `ea8032a` ("chore", 2026-10-09) removed the
ABRP tab, the config-file import (`ConfigImport`), the upload (`AbrpApi`, `TelemetryPayload`,
`UploadCadence`), `SecurePrefs` and `HeadUnitWeather`. The app now only tracks consumption
and syncs it to the MongoDB API. Leftover `strings.xml` entries (`btn_import`, `import_*`,
`hint_api_key`, …) belong to it. Decide whether to restore it.
- [ ] **Fix the unit tests.** They don't compile upstream: `AbrpApiTest`, `TelemetryPayloadTest`
and others still use `AbrpApi`/`TelemetryPayload`, which upstream commit `ea8032a` deleted.
- [ ] **Charging currency.** Prices and costs are shown in Turkish lira (`₺/kWh`, `₺`) in
`pane_charging.xml` and `MainActivity.java`.
- [ ] **Cloud consumption sync.** `CONSUMPTION_INSTALLATION_ID` in `app/build.gradle` is a fixed
UUID inherited from upstream. Set our own before configuring `CONSUMPTION_API_URL` /
`CONSUMPTION_API_TOKEN`, so our data cannot land under upstream's id.
- [ ] **Translate the rest.** The `web/` dashboard (Next.js) and docs (`CHANGELOG.md`, `OTA.md`'s
- [ ] **Dashboard language and branding.** The `web/` pages are still Turkish, say "FatihsMG4" and
show prices in lira.
- [ ] **Telemetry queue survives restarts.** Unsent live samples are kept in memory only.
- [ ] **Translate the rest.** The docs (`CHANGELOG.md`, `OTA.md`'s
message table, comments in `mise.toml`/`app/build.gradle`) are still Turkish or French.
- [ ] **Docs still use the old package ID.** `README.md`, `OTA.md`, `CHANGELOG.md`, `mise.toml` and
`docs/MG4_PLATFORM_INSTALL.md` refer to `com.evsuite.abrp(.unstable)` and `FatihsMG4-*.apk`.
@@ -109,6 +111,12 @@ car ──live telemetry───▶ car.stinnesbeck.com ──▶ ABRP api.ite
- 2026-10-17 — Renamed to "Nils' MG4": package ID `com.stinnesbeck.nilsmg4` (`.unstable` for the car
build), APK names `NilsMG4-*.apk`. It installs next to the old FatihsMG4 app instead of replacing
it, and starts with empty settings and history.
- 2026-10-17 — Server rewritten for InfluxDB 2 with live telemetry and ABRP forwarding; Dockerfile;
Kubernetes manifests (not applied).
- 2026-10-17 — App 2.3.0: Server tab (URL, ingest token, installation id, config import), live
telemetry restored from before `ea8032a` and sent to the server, Upload-tab cadence controls
reconnected, dead switches removed. The removed ABRP classes' unit tests compile and pass again
(AbrpApi and HeadUnitWeather tests deleted with their classes).
- 2026-10-17 — Temporary on-screen diagnostics (b2a5b32) to debug blank values without adb;
removed again (1002962) once values showed on the car. Revert 1002962 to bring them back.