Files
mg4/SPECS.md
T
nilsandClaude Opus 5.5 538d03361d Translate the dashboard to English; euro instead of lira
Dashboard: every label, empty state, chart legend and the login page
are English; numbers and dates use en-GB (24-hour clock, day-month
dates), money is EUR, percentages read 80% instead of %80, durations
"2 h 05 min". html lang is en. Server version 1.1.2; manifests pin it.

Car app: the charging tab's price suffix, validation message and total
cost use € instead of ₺.

Checked by rendering all three pages from the 1.1.2 image against
InfluxDB 2.7.12 in podman.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
2026-10-17 14:39:59 +02:00

132 lines
7.5 KiB
Markdown

# SPECS — Nils' MG4
Journal for this fork of [fatihdonmezdev/FatihsMG4](https://github.com/fatihdonmezdev/FatihsMG4):
what has been changed, and what is still to do.
- Our repository: https://git.stinnesbeck.com/nils/mg4
- Upstream: https://github.com/fatihdonmezdev/FatihsMG4 (itself a fork of malys / EVABRPUploader
and Leon Kernan / ABRP_Uploader). Credited in the app's About dialog.
## Plan: car server at car.stinnesbeck.com
The car sends everything to our own server. The server stores it and forwards live telemetry to
ABRP, so the ABRP token lives only on the server, never in the APK.
```
car ──live telemetry───▶ car.stinnesbeck.com ──▶ ABRP api.iternio.com/1/tlm/send
──daily totals─────▶ │
──charging sessions▶ ▼
InfluxDB 2
```
### Server — extend `web/` (Next.js)
- **Storage: MongoDB → InfluxDB 2.** Rewrite `web/src/lib/mongo.ts` / `data.ts` against one bucket
(tag `installationId` on every point):
| 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_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 |
Same measurement + tags + timestamp overwrites, so the car's retries stay idempotent
(MongoDB used unique indexes for that).
- **New endpoint `POST /v1/telemetry`:** a batch of live samples, ingest-token auth, validated
and bounded like the existing endpoints.
- **ABRP forwarder:** after storing, forward the newest sample to `tlm/send` with `ABRP_API_KEY` and
`ABRP_USER_TOKEN` from the environment. Rate-limited; a failed forward is logged but not
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:** `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, 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
- Image: `git.stinnesbeck.com/nils/mg4-server:<version>`, pushed to the forge's registry
(needs a registry login on the build machine).
- `k8s/` with plain manifests: Namespace, InfluxDB StatefulSet + PVC + Service (cluster-internal),
server Deployment + Service + Ingress for `car.stinnesbeck.com` (TLS), and Secret *templates*
with placeholder values only.
- **Nothing is ever applied to the cluster by us.** Nils applies the manifests.
### Decisions (2026-10-17)
- 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 (`mg4-server:1.1.2`) 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
- [ ] **About dialog link and QR code → https://git.stinnesbeck.com/nils/mg4.**
Both come from `REPOSITORY_URL` in `app/src/main/java/com/evsuite/abrp/MainActivity.java`
and `about_repository` in `app/src/main/res/values/strings.xml`. They still point to Fatih's
GitHub repository.
- [ ] **Re-enable OTA updates against our own releases.** `RELEASES_API` in
`app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java` points at `updates.invalid` on purpose,
so checks fail. The updater speaks the GitHub releases API and only allows GitHub hosts
(`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).
- [ ] **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`.
- [ ] **Move hardcoded UI strings into `strings.xml`.** Many are inline in Java and layouts.
## Done
- 2026-10-17 — Translated the app UI from Turkish to English.
- 2026-10-17 — Nix flake: `nix develop` provides JDK 21 and the Android SDK. `build-car` builds the
unstable APK and signs it with the AOSP test platform key, which the MG4 head unit uses
(cert SHA-256 `c8a2e9bc…192ab8`).
- 2026-10-17 — OTA update checks disabled by pointing at an unresolvable host (see To do).
- 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 — Accent colour: MG red instead of muted gold. Web app renamed to Nils' MG4.
- 2026-10-17 — Dashboard translated to English (en-GB formatting); euro instead of lira in the
dashboard and the car app's charging tab.
- 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.
## Installing on the car
```
nix develop
build-car
adb push build/car/NilsMG4-unstable-<version>.apk /data/local/tmp/update.apk
adb shell "pm install -r -f -d -t /data/local/tmp/update.apk"
```
To remove the old upstream app: `adb uninstall com.evsuite.abrp.unstable`.