Files
mg4/AGENTS.md
T
nilsandClaude Opus 5.5 dfd8569cfa AGENTS.md, SPECS: work happens on main now
english-translation is merged and deleted, master renamed to main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
2026-10-18 21:25:27 +02:00

130 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENTS.md — Nils' MG4
Start here. What this repository is, how its parts fit, how to build, test and ship, and the
rules for changing it. Details live in the documents linked below; `SPECS.md` is the journal
(status, to-dos, a dated log of every change) and is updated with each change.
## What this is
An app for the **MG4 (SAIC eh32)** head unit plus the backend it talks to:
```
MG4 head unit (Android 9, AAOS, MT2712)
app/ "Nils' MG4" ──live data, consumption, charging──▶ server/ (Go) ──▶ InfluxDB 2 ◀── Grafana
│ │ relays app releases from the forge
└──live data, straight, with the driver's own key──▶ ABRP (A Better Route Planner)
```
- **app/** — Android app (Java, minSdk 27, runs on API 28). Reads the car through the
`EVHardware/` library, shows it (Overview, Consumption, Charging), sends live data to the
server and to ABRP, and updates itself.
- **server/** — Go 1.26 API at `https://car.k8s.stinnesbeck.com`: stores telemetry, consumption
and charging in InfluxDB, pairs cars by QR code, carries settings typed on a phone, relays
app releases from the private forge. It does **not** talk to ABRP. See `server/README.md`.
- **k8s/** — plain Kubernetes manifests (InfluxDB, server, Grafana with three provisioned
dashboards, Traefik routes). See `k8s/README.md`.
- **EVHardware/** — vendored shared vehicle layer (PolyForm Noncommercial, see `LICENSE.md`).
Repository: `ssh://git@git.stinnesbeck.com:2222/nils/mg4.git` (remote `origin`), branch `main`. Upstream fork: `fatih` → github.com/fatihdonmezdev/FatihsMG4.
## Rules
1. **Nothing touches the Kubernetes cluster.** Write and validate manifests only; the owner
applies them. No `kubectl` against the cluster, not even read-only, unless asked.
2. **Secrets.** Filled-in values only in `k8s/secrets/secrets.yaml` (gitignored);
`secrets.example.yaml` holds `CHANGE-ME` placeholders only. Stage files explicitly, check the
index for changes that are not yours (the owner works in parallel), and scan the staged diff
for secrets before every commit. Never print a token.
3. **Telemetry is read-only.** The app never writes a vehicle setting.
4. **A failed read is not a zero.** Unreadable values are `null`, omitted from payloads and
shown as "—". Sending 0 for SOC tells ABRP the battery is empty.
5. **Fail closed.** Updates (origin, SHA-256, package, signature), credentials, unknown state.
6. **No credential in a URL, a log line or a crash report.** Tokens go in headers or bodies.
7. **The head unit must stay stable.** Every car call is reflection with try/catch; no
blocking car reads on the main thread.
8. **Power.** No wake locks or alarms; GPS pauses while parked and idle.
9. **Honesty.** Most behaviour cannot be verified off the car. Say what was tested (unit tests,
emulator, podman) and what was not. "Not tried on the car" is an acceptable answer.
## The car (confirmed on the owner's car, firmware `SWI68-29958-1300R69`)
- Display 1920×720 px at **160 dpi**; the app window is **1778×720 px** (the car's own sidebar),
about **1778×640 dp** for layouts. Pages must fit that without scrolling where possible.
- Signed with the public **AOSP test platform key**; the car build runs as `android.uid.system`.
- Steering-wheel keys arrive as the broadcast `com.saic.keyevent.hardkey.report`
(`android.intent.extra.hardkey.keycode` / `.down` / `.longpress`); the voice key is **287**.
- Not published by the car: battery, cabin and outside temperature, pack energy and capacity,
paint colour (only `ro.build.mtk2712.vehicle_type = AS28`). See `SPECS.md`, `FIRMWARE.md`.
- No third-party apps in the MG launcher: the app opens by a double press of the voice key.
- Its internet comes from the car itself (no Wi-Fi needed).
## The app
| Tab | Contents |
|---|---|
| Overview | value tiles (battery, range, range/usage/energy since start, 5-min averages of range, usage, power, speed), vehicle info, tyre pressures; read every 0.5 s on a background thread |
| Consumption / Charging | period totals, daily log, charging sessions and curve |
| Live data | on/off, ABRP API key and user token, upload frequency |
| Server | URL, pairing by QR, ingest token, installation ID (read-only), config import |
| Settings | full screen, voice key, tyre unit, accent colour, battery, charging price, version, update check, install from USB, About |
| Logs | upload log |
Key classes (`app/src/main/java/com/evsuite/abrp/`): `AbrpUploadService` (foreground service,
15 s tick), `TelemetryUploader` (+ `TelemetryQueue`, persisted), `TelemetryPayload` (server
limits), `AbrpApi`/`AbrpConfig`, `ServerHttp`/`ServerConfig`/`PairingClient`/
`PhoneSettingsClient`, `ConsumptionTracker`/`ConsumptionCloudClient`, `ChargeSessionTracker`,
`RangeEstimator`, `SecurePrefs`, `ConfigImport`, `SteeringKeyListener`, `MainActivity` (UI).
Updater (car build only): `app/src/unstable/` `OtaUpdater`, `UpdateHook`, `UsbUpdate`.
**Put logic in pure, unit-tested classes** (no Android types); the service and activity are
hard to test without a car.
**UI conventions:** OLED black, glass cards, an accent the user picks from the MG4 paints —
always `?attr/evAccent…` (`res/values/attrs_accent.xml`, overlays in `accents.xml`), never a
hard-coded accent hex. All UI text in `strings.xml`. 72 dp touch targets. Values right-aligned
with tabular digits. Drop-downs are Material exposed dropdown menus and dialogs go through
`showKeepingFullScreen` — a Spinner or a plain dialog brings the car's bars back. Android 9
crashes on gradients with theme colours: use layers.
**Flavours:** `stable` (no updater at all) and `unstable` (the car build: system UID, OTA and USB
install). The car always gets `unstable`.
## Build, test, ship
```sh
nix develop # JDK 21, Android SDK, Go
./gradlew testStableDebugUnitTest testUnstableDebugUnitTest lintStableDebug
build-car # build/car/NilsMG4-unstable-<version>.apk + .sha256
publish-car # pre-release car-<version> on the forge (needs a token)
cd server && go vet ./... && go test ./... && govulncheck ./...
```
- **Versions:** three-part `versionName` in `app/build.gradle`; raise `versionCode` by one with
every build that goes to the car (a lower code is refused as a downgrade).
- **Getting it to the car:** in-app update (Settings → Check for updates, via the server), or
Install from USB stick (APK + `.sha256` in the stick's top folder), or the first time through
the Bluetooth-settings file manager (`README.md`). `OTA.md` has the whole flow.
- **Server image:** `podman build` in `server/`, push to `git.stinnesbeck.com/nils/mg4-server:<v>`,
pin the pushed digest in `k8s/server.yaml`. Validate manifests offline with
`kustomize build k8s | kubeconform` (see `k8s/README.md`).
- **Emulator:** `nix run .#car-emulator` (add `-no-window` for headless) starts an Android 9 AOSP
emulator at 1920×720, 160 dpi on port 5560 (state in `build/emulator/`). Its image shares the
car's platform key, so the car build installs as the system user. `adb shell wm size 1778x640`
gives the car's app window (taps then use those coordinates; screenshots stay 1920×720). It has
no car data (every value is "—"). While the app is open, `uiautomator dump` never sees an idle
screen; use screenshots.
- **After each change:** a `SPECS.md` done-log entry, docs that the change affects, explicit
staging, commit, push.
## Documents
| File | What |
|---|---|
| `SPECS.md` | journal: architecture, status, signals the car lacks, to-dos, dated done-log |
| `README.md` | user-facing overview, install, setup, config file |
| `OTA.md` | releases, the updater's checks, USB install |
| `FIRMWARE.md` | the head unit, firmware generations, display |
| `server/README.md`, `k8s/README.md` | API, configuration, deployment |
| `LICENSE.md` | MIT, EVHardware's PolyForm Noncommercial, the fork chain |
| `DESIGN.md`, `CHANGELOG.md` | upstream's design system and release history |