ABRP keeps getting live data while the car server is down: with an ABRP API key and user token set in the app (Live data tab, or abrp_api_key / abrp_token in the config file), the car sends the newest sample to ABRP itself at every upload. It tells the server the newest sample ABRP took (abrpSentUtc), and the server forwards only newer ones, so ABRP gets each sample once. Without credentials in the app the server forwards everything, as before. The credentials sit in encrypted storage, go in the request body, and neither they nor ABRP's answer are logged. Settings are grouped by what they are for: - Live data (was Upload): on/off, ABRP credentials, upload frequency. - Settings (replaces the Wi-Fi tab): full screen and voice key, usable capacity and manual SoH (were on Consumption), charging price (was on Charging), Wi-Fi. Charging and Live data get icons of their own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
9.8 KiB
Nils' MG4
An Android Automotive app for the MG4 (SAIC eh32) head unit, plus a small server: the car sends its telemetry to my own server at car.k8s.stinnesbeck.com, which stores it in InfluxDB 2, forwards live data to A Better Route Planner and feeds the Grafana dashboards.
No OBD dongle and no phone in the loop: the app runs on the head unit and reads the car's own APIs.
- Repository: https://git.stinnesbeck.com/nils/mg4
- Fork of fatihdonmezdev/FatihsMG4, itself built on malys' EVABRPUploader and Leon Kernan's
ABRP_Uploader. - What changed and what is still to do:
SPECS.md.
⚠️ No warranty, no liability. This software is provided "as is" and runs on a vehicle. Installing it is your decision and your risk; see
DISCLAIMER.md. Not affiliated with SAIC, MG Motor or Iternio. MG and MG4 are third-party marks used only to identify compatibility.
Licence. The original
ABRP_Uploadercarries no licence, which limits what may legally be published. ReadLICENSE.mdbefore redistributing anything.
Contents
- Screenshot
- How it fits together
- The app
- Install on the car
- Setup
- Building
- Server and dashboards
- Project layout
- Project documents
- Credits
Screenshot
Version 2.8.2 on the head unit's 1920x720 screen, in full screen (Android 9 emulator; no vehicle there, so every value shows as a dash).
How it fits together
car ──live telemetry───▶ car.k8s.stinnesbeck.com (Go) ──▶ ABRP (what the car did not send)
│ ──daily chunks─────▶ │
│ ──charging sessions▶ ▼
│ InfluxDB 2 ◀── Grafana
└──live telemetry (optional, direct)──────────────────▶ ABRP
With ABRP credentials set in the app (Live data tab), the car sends live data to ABRP itself, so ABRP keeps getting it while the server is down; the server then forwards only samples the car could not deliver. Without them in the app, the server forwards everything with its own ABRP credentials. The car authenticates with the server using a token of its own, obtained by pairing with a phone (below).
The app
| Tab | What it shows |
|---|---|
| Overview | Battery, range, range since start and at the 5-minute average with their usage, power, speed; odometer, climate setpoint and tyre pressures |
| Consumption | Daily and period consumption, efficiency |
| Charging | Charging sessions and curves, cost in euro |
| Live data | Live data on/off, ABRP credentials for sending directly from the car, upload frequency |
| Server | Server URL, pairing by QR code, ingest token, installation ID, config import |
| Settings | Full screen, voice key, battery (usable capacity, manual SoH), charging price, Wi-Fi |
| Logs | Upload log and the last steering-wheel key reports |
- Opening the app: the MG4 launcher does not list third-party apps. Double-press the voice key on the steering wheel and the app comes to the front (switch on the Settings tab, on by default).
- Full screen: hides SAIC's top and bottom bars while the app is open; a swipe from the edge shows them briefly (Settings tab, on by default).
- Live telemetry: every 15 s while driving or charging, much less often while parked. A value the car cannot read is left out, never sent as zero, so ABRP is never told the battery is empty because a read failed.
- Read-only: the app never writes a vehicle setting.
Signals the MG4 does not publish (battery, cabin and outside temperature, pack energy and
capacity) are listed in SPECS.md.
Install on the car
The car build (NilsMG4-unstable-<version>.apk) runs with the system UID and is signed with the
AOSP test platform key that the MG4 head unit uses. It installs next to the old FatihsMG4 app
(package com.stinnesbeck.nilsmg4.unstable), not over it.
Without adb, through the head unit's hidden file manager:
- Copy the APK to a USB stick and plug it in.
- Open Bluetooth Settings and select the car name.
- Long-press the comma key until the keyboard settings appear.
- Android Keyboard Settings → Languages, then search for
filein the box at the top. - That opens the Files app: open the USB stick, tap the APK and install.
With adb:
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"
⚠️ Do this parked, and only with an APK you trust. See DISCLAIMER.md.
Self-updating (OTA) is switched off: the updater points at an unresolvable host until it is
pointed at this repository's releases (see SPECS.md).
Setup
- Open the app (voice key double press, or "Open" right after installing).
- On the Server tab the URL is already
https://car.k8s.stinnesbeck.com. - Tap Pair with phone. Scan the QR code with a phone (or open the address shown and enter the code), and approve with the server's approval password. The car receives its token.
- On the Live data tab, turn live data on. The service starts with the car from then on.
- Optional: enter your ABRP API key and user token on the same tab, so the car also sends live data straight to ABRP.
Config file
Instead of typing, put the settings in a text file and tap Import file on the Server tab. The head unit has no file picker, so copy the file to
Android/data/com.stinnesbeck.nilsmg4.unstable/files/
on the USB stick or the internal storage. The app scans that folder on every mounted volume and
takes the first file that parses. Format, one key = value per line, # for comments; keys left
out keep their current value:
server_url = https://car.k8s.stinnesbeck.com
token = the server's INGEST_TOKEN (not needed after pairing)
installation_id = only to carry over history from an install older than 2.9.0
interval_sec = 60 # 15 / 30 / 60 / 120 / 300
boost_low_soc = true
low_soc_percent = 20
abrp_api_key = optional: your ABRP API key, to send to ABRP directly
abrp_token = optional: your ABRP user token
After a successful import the app deletes the file, since it holds the token; credentials live only in the app's encrypted store.
The installation ID is derived from the head unit, so a reinstall finds its history again on its
own. installation_id is only needed to carry history over from an install made before 2.9.0.
A paired token belongs to one installation ID: after changing the ID, pair again.
Power
The app holds no wake lock and schedules no alarms, so it cannot wake a sleeping head unit and costs nothing while the car is off.
Building
With Nix:
nix develop # JDK 21, Android SDK, Go
./gradlew assembleStableDebug # debug APK for a phone or tablet
build-car # car APK, signed with the AOSP test platform key
build-car writes build/car/NilsMG4-unstable-<version>.apk and a .sha256 next to it, and
checks the signing certificate before it finishes.
Tests and lint:
./gradlew testStableDebugUnitTest testUnstableDebugUnitTest lintStableDebug
mise tasks from upstream (mise run check, mise run build) still
work for the plain Gradle builds.
Server and dashboards
- Server (
server/): Go, API only. Live telemetry, daily consumption chunks, charging sessions, pairing; forwards live data to ABRP. Image atgit.stinnesbeck.com/nils/mg4-server. - Kubernetes (
k8s/): plain manifests for the server, InfluxDB 2 and Grafana, applied by hand withkubectl apply -k k8s/. Secrets go in the gitignoredk8s/secrets/secrets.yaml, from the template next to it. - Grafana dashboards (
k8s/grafana/dashboards/): Live, Consumption and Charging, provisioned with the InfluxDB datasource.
Project layout
app/src/main app: upload service, server client, pairing, UI
app/src/stable no-op update hook (no updater in the APK)
app/src/unstable car build: system UID, updater (currently pointed nowhere)
app/src/debug VHAL probe tools, absent from release builds
app/src/test JVM unit tests
EVHardware shared vehicle reads per firmware generation
server/ Go server
k8s/ Kubernetes manifests and Grafana provisioning
flake.nix dev shell and build-car
Project documents
| Document | What it covers |
|---|---|
| SPECS.md | Journal of this fork: architecture, status, to-dos, done log |
| FIRMWARE.md | MG4 firmware generations and how the app reads each |
| AGENTS.md | Context for AI agents working in this repository |
| DESIGN.md | The EVSuite design system (the app overrides its palette) |
| SECURITY.md | Threat model |
| DISCLAIMER.md | Vehicle-safety disclaimer; read before installing |
| CHANGELOG.md | Upstream release history |
| LICENSE.md | Licence status |
