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
130 lines
8.0 KiB
Markdown
130 lines
8.0 KiB
Markdown
# 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 |
|