Sends live telemetry from an MG4 (SAIC eh32) to A Better Route Planner — battery level, speed, range, temperature, charging state and position — straight from the car's own APIs.
No OBD dongle. No Home Assistant. No phone in the loop. The app runs on the car's head unit and talks to ABRP directly.
⚠️ No warranty, no liability. This software is provided "as is" and runs on a vehicle. Installing it is your decision and your risk — seeDISCLAIMER.md. Not affiliated with SAIC, MG Motor or Iternio.
Fork notice. This is a fork of Leon Kernan's
ABRP_Uploader, substantially reworked. ReadLICENSE.mdbefore redistributing anything — the upstream project carries no licence, which limits what may legally be published.
| Signal | Source | Firmwares |
|---|---|---|
| State of charge, range | SWI68 vendor EV properties (CarPropertyAdapter) |
SWI68 only |
| Speed, parked state | MG4Hardware (per-generation) |
all supported |
| Outside temperature | MG4Hardware (standard AAOS) |
all supported |
| Charging, DC fast charging | Charge port state + charge rate heuristic | where the VHAL implements them |
| Cabin temperature | HVAC property | where the VHAL implements it |
| Position, elevation, heading | GPS | — |
Firmware-agnostic signals go through MG4Hardware, which detects the generation
(SWI68/69/131/132/133/165) and picks the right underlying API. SOC and range are the
exception: MG4Hardware exposes no EV-battery abstraction, so they still use vendor IDs
reverse-engineered from SWI68 and are read-confirmed on that generation only — see
FIRMWARE.md.
A property the car will not give up is omitted from the payload, never sent as zero — so ABRP is never told the battery is empty because a read failed.
The app never writes to the car. It only reads. Any write path would be a bug; see
SECURITY.md.
The MG4 head unit hides Settings and APK install. The known route in: The MG4 head unit has no visible way to open Settings or install an APK. The known route in (via the on-screen keyboard) is:
- Open any app with a text field and tap it to raise the on-screen keyboard — e.g. the Amazon Music app's email/login field.
- Long-press the comma
,(or the@) key on the keyboard. - Tap "Language settings".
- Tap the search icon in the top bar and type
backup. It opens an empty page — now press the back arrow, and you land in Android's Settings panel. - Enable Developer options, and turn on "Install unknown apps" (unknown sources).
- In Settings, search
storage— you now have access to internal storage and the USB key. Navigate to the APK and tap it to install.
⚠️ You are enabling developer options and sideloading on a car. Do this parked, and only with an APK you trust. See DISCLAIMER.md.
Two channels. Pick one — they install side by side.
| Channel | Auto-update | Use it if |
|---|---|---|
| Stable | No. Contains no updater at all. | You want the car to run what you put on it |
| Unstable | Yes, from GitHub pre-releases | You are testing and want fixes as they land |
Grab the APK from Releases. Stable builds are the tagged ones; unstable builds are marked pre-release.
The MG4 has no visible file manager. To reach it:
- Open Bluetooth Settings and select the car name.
- Long-press the comma key until the keyboard settings appear.
- Android Keyboard Settings → Languages.
- Search for
filein the search box at the top. - That gives you the Files app.
From there, open your USB stick and tap the APK.
- Open the app.
- Get an API key from ABRP's telemetry page and a token from your ABRP account.
- Paste both, press Test (read-only — it does not send telemetry), then Save.
- Turn the switch on. The service restarts with the car from then on.
Typing a long API key and token on the car's on-screen keyboard is painful. Instead you can put them in a text file and tap Import file — see Config file below.
| Setting | Default | Notes |
|---|---|---|
| Upload frequency | 60 s | 15 / 30 / 60 / 120 / 300 s. Applies while driving |
| More often at low battery | On | Tightens to 15 s below the threshold |
| Low battery threshold | 20 % | Editable, 1–99 |
| Service on/off | Off | Also the "stop uploading" control |
Parked and unplugged, the app drops to one upload every 15 minutes on its own, whatever the setting. A state change — parked, plugged in, unplugged — always uploads immediately.
To avoid typing on the head unit, put your settings in a plain text file and tap Import file on the ABRP tab. The app fills in whatever the file contains. Nothing is sent — press Test, then Save as usual.
The head unit has no file picker, so the file is not chosen by hand: copy it from a PC into
Android/data/com.mg4.abrptelemetry/files/
on the USB stick (or on the head unit's internal storage), then tap Import file. The app scans that folder on every mounted volume and takes the first file that parses — the name does not matter. If nothing is found it shows the exact path to copy to. On a phone or tablet, where a document picker exists, the button falls back to letting you pick any file.
The file is a key = value list, one per line. Blank lines and lines starting with # are
ignored, keys are case-insensitive, and any key you leave out keeps its current value:
# ABRP Uploader config
api_key = your-abrp-api-key
token = your-abrp-user-token
# optional — the cadence controls, same values as the UI
interval_sec = 60 # snapped to 15 / 30 / 60 / 120 / 300
boost_low_soc = true
low_soc_percent = 20 # clamped to 1–99
Only api_key and token are usually needed. The file is read once on import and not kept —
your credentials live only in the app's encrypted store afterwards.
The app holds no wake lock and schedules no alarms, so it cannot wake a sleeping head unit; it costs nothing while the car is off. While the car is on, both the upload tick and the GPS subscription cost power, and the GPS interval follows your configured frequency.
At defaults that is roughly 59 uploads/hour driving and 3/hour parked.
Requires JDK 17 and the Android SDK. With mise:
mise install # JDK
mise run bootstrap # Android SDK + local.properties
mise run check # permission gate + lint + tests
mise run build # debug APKWithout mise: set JAVA_HOME to a JDK 17, write sdk.dir into local.properties, then
./gradlew assembleStableDebug.
Release builds are signed from environment variables. These sign the APK and have nothing to do with your ABRP API key, which you type into the app itself:
| Variable | What it is |
|---|---|
SIGNING_KEYSTORE |
Path to the keystore file (CI decodes SIGNING_KEYSTORE_BASE64 into one) |
SIGNING_STORE_PASSWORD |
Opens the keystore container |
SIGNING_KEY_ALIAS |
Which key inside it to sign with (default platform) |
SIGNING_KEY_PASSWORD |
Opens that particular key — a keystore protects each key separately |
Never commit a keystore or a password. Put them in mise.local.toml, which is
gitignored, or in GitHub Actions secrets.
app/src/main shared code: service, car adapter, telemetry, UI
app/src/stable no-op update hook — the stable channel cannot self-update
app/src/unstable OTA updater (origin allowlist + signature check)
app/src/debug VHAL probe tools, absent from every release build
app/src/test JVM unit tests
See CONTRIBUTING.md. Short version: this code runs on a moving
vehicle, so changes need tests and a clear account of what you verified on a car and what
you did not.
DISCLAIMER.md— no warranty, no liability, not affiliated with the carmaker or with ABRP.LICENSE.md— unresolved licence status inherited from the fork source. Read it before redistributing.SECURITY.md— how to report a vulnerability privately.
- Original app: Leon Kernan — the car-API approach and the first working uploader.
- ABRP telemetry API: Iternio.
- Sibling project: MG4Control, whose security and CI patterns this repo reuses.

