Files
mg4/SPECS.md
T
nilsandClaude Opus 5.5 4a1f178b5c App 2.30.0: a chibi fox as the third Mascot
FoxFigure draws a sitting fox with a scarf in the accent colour whose
ears and tail act the mood with the face: wagging, stiff, drooping,
laid back. The chibi's shared parts (frame, eyes, mouths, limbs by
mood, effects) moved into ChibiBase, which both figures extend.
Settings → Mascot: Classic / Chibi / Fox.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
2026-10-24 23:28:16 +02:00

631 lines
52 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.
# 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.
## Architecture: car server at car.k8s.stinnesbeck.com
The car sends everything to our own server, which stores it in InfluxDB 2, and its live data
straight to ABRP with the ABRP credentials kept (encrypted) in the app. The server does not talk
to ABRP. Grafana reads InfluxDB directly for the dashboards.
```
car ──live telemetry───▶ car.k8s.stinnesbeck.com (Go)
│ ──daily chunks─────▶ │
│ ──charging sessions▶ ▼
│ InfluxDB 2 ◀── Grafana (car-grafana.k8s.stinnesbeck.com)
└──live telemetry─────▶ ABRP api.iternio.com/1/tlm/send
```
- **Server:** `server/`, Go 1.26, API only — see `server/README.md` for endpoints, configuration
and the storage layout. Replaced the Next.js app from upstream (`web/`, removed 2026-10-17).
- **Dashboards:** Grafana, provisioned from `k8s/grafana/`, reading with a read-only token: Live
(stats, SoC/range, power/speed, route map, temperatures, tyres), Consumption (period totals, lifetime, SoH, per-day energy,
distance and efficiency) and Charging (totals, session table, curves). Prices in euro.
- **Pairing:** the car shows a QR code, a phone approves it with the approval password, and the car
gets its own token, bound to its installation id (RFC 8628-style; `server/README.md`). The
shared `INGEST_TOKEN` still works. The same QR flow carries settings typed on the phone.
- **Car app:** tabs Overview, Consumption, Charging, Live data (on/off, ABRP credentials,
frequency), Server (URL, pairing, token, installation id, config import), Settings (display,
voice key, tyre unit, accent colour, battery, charging price, Wi-Fi), Logs. Live telemetry every
15 s while driving or charging, to the server and straight to ABRP; consumption chunks and
charging sessions to the server.
- **Deployment:** `k8s/`, plain manifests. **Nothing is ever applied to the cluster by us** —
Nils applies them (`k8s/README.md`). TLS comes from Traefik's default certificate.
### Status
Current: app 2.14.2, server 2.5.0 (pinned by digest in `k8s/server.yaml`).
- [x] On the car: QR pairing, values on the Overview, voice-key double press, full screen
without black bands.
- [x] Go server: tests and govulncheck clean; end to end in podman against InfluxDB 2.7.12
(telemetry, consumption, charging, pairing, settings from the phone) and Grafana 13.2.3 (all
dashboard queries return data).
- [x] Manifests validated offline (kubeconform, with Traefik CRDs). **Applied by Nils only.**
- [ ] Check Grafana's host in `k8s/ingress.yaml` (placeholder `car-grafana.k8s.stinnesbeck.com`).
- [x] Screen on the car: 1920×720 px at 160 dpi, app window 1778×720 px (Logs → SCREEN).
- [x] Car-colour probe on the car: only `ro.build.mtk2712.vehicle_type = AS28`; the paint colour
is not in the system properties. It may be in the VHAL (a debug-build VhalProbe listing
would tell); until then the accent is chosen by hand.
- [ ] Not yet tried on the car: live data straight to ABRP (2.11), Enter on phone (2.12), accent
colours (2.14), right-aligned values (2.14.1).
### Signals the car does not provide
Checked 2026-10-17 against what actually reached InfluxDB (`schema.measurementFieldKeys`). The car
sends soc, est_battery_range, odometer, power, voltage, current, speed, hvac_setpoint, the four
tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does **not** send:
- **Battery temperature:** read from `EV_BATTERY_AVERAGE_TEMPERATURE`, an Android 14 property; the
head unit runs Android 9, and the SAIC services offer none.
- **Cabin temperature:** `HVAC_TEMPERATURE_CURRENT` is not published; only the setpoint is.
- **Outside temperature:** not published by the vehicle (upstream noted the same). Upstream used
the head unit's weather service as a stand-in (`HeadUnitWeather`, removed in `ea8032a`);
restoring it was considered and declined.
- **Pack energy (`soe`) and capacity:** not published. The Live dashboard computes energy in pack
as SOC × usable capacity, from the hidden dashboard constant `usableKwh` (50.8 kWh for the
51 kWh battery) in `k8s/grafana/dashboards/live.json`.
## To do
- [ ] **Grafana high availability needs another database.** Grafana keeps its own state (users,
sessions, preferences, provisioned-dashboard records, alerting state) in SQLite at
`/var/lib/grafana/grafana.db` on the `grafana-data` PVC. That is fine for one replica, and is
why `k8s/grafana.yaml` uses `replicas: 1` and the `Recreate` strategy: only one Grafana may
write the file. To run more than one replica, first move Grafana to PostgreSQL or MySQL
(`GF_DATABASE_TYPE`, `GF_DATABASE_HOST`, …); the replicas then share that database instead
of a volume. The car data itself is in InfluxDB and unaffected.
- [ ] **EVHardware's comments are partly French.** About 120 comment lines in the vendored
`EVHardware/` library (mostly `EVHardware.kt`, some tests), half-translated upstream. The
app, the docs and the build files are English.
- [ ] **Idea: climate energy on the Overview (motor vs climate).** Nothing the app reads splits
energy by consumer: battery power (voltage × current) is the total of motor, climate, 12 V
and the rest. SAIC's charging service, which EVHardware already binds, has counters that
might: `TotalConsumption`, `TotalRegen` and `AccConsumption`, each since start and since the
last charge (`SaicCharging.consumedKwhSinceStart()`, `regeneratedKwhSinceStart()`,
`accCounterKwhSinceStart()` and the `…SinceCharge()` twins). Whether "Acc" means
*accessory* (a measured climate/auxiliary share; motor ≈ total − acc) or *accumulated* (a
duplicate of the total) is unknown — EVHardware deliberately does not call it climate — and
whether SWI68 answers at all is untested. Plan: (1) a read-only diagnostic showing the raw
counters since start; (2) two drives, climate off and on, with a photo of the counters
after each; (3) if Acc grows only with the climate, an Overview card "Climate &
accessories since start" with the motor's share beside it — otherwise drop the diagnostic
and, at most, show a labelled estimate (power drawn at a standstill with the climate on).
## 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 — Server rewritten in Go (API only); dashboards moved to Grafana with provisioned
InfluxDB datasource and three dashboards; the Next.js app (`web/`) removed. Per-host
cert-manager certificates dropped in favour of Traefik's default certificate.
- 2026-10-17 — Pairing by QR code: server 2.1.0 issues per-car tokens after approval on a phone;
app 2.4.0 shows the QR code and collects its token. Tested end to end against InfluxDB.
- 2026-10-17 — Incident: a commit swept the filled-in `secrets.example.yaml` (InfluxDB admin token,
INGEST_TOKEN) into git and pushed it. Commit amended and force-pushed; both values regenerated in
the now gitignored `k8s/secrets.yaml` (since moved to `k8s/secrets/secrets.yaml`, outside
`kubectl apply -f k8s/`). Filled-in secrets live only there from now on.
- 2026-10-18 — `english-translation` merged into the default branch (merge commit `f54e684`, the
summary as its message), `master` renamed to `main`, the work branch deleted. Work goes on `main`.
- 2026-10-18 — AGENTS.md rewritten as the entry point for agents: what the project is, rules,
confirmed car facts, app structure, UI conventions, build/test/ship, documents. It replaces
upstream's EVABRPUploader text. The test emulator is now in the flake: `nix run .#car-emulator`
(Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked).
- 2026-10-24 — App 2.30.0 (branch `styles-retro-future`): a third Mascot, the **chibi fox**
(`FoxFigure`): a sitting fox with a big head, pointed ears, cream cheeks, chest and tail tip,
dark paws and a scarf in the accent colour. Ears and tail act the mood with the face: the
tail wags while content and fast while cheering, stands stiff when determined and hangs low
when worried or asleep; ears droop when worried or asleep and lie back when fast, when the
scarf also flies. Its own lines (`fox_*`, "Kon!"). The chibi's shared parts (frame, eyes,
mouths, limbs by mood, effects) moved into `ChibiBase`, which both figures extend. Settings →
Mascot: Classic / Chibi / Fox. Emulator: all moods checked with sample values in a throwaway
build; the tail's swing is kept to about 18° so it stays clear of the values. Not tried on the
car.
- 2026-10-24 — App 2.29.0 (branch `styles-retro-future`): a second Mascot, the **chibi**
(`ChibiFigure`): small and full-figure, a big round head with a coral bob and a star clip,
a hoodie in the accent colour, dark outlines like a sticker; an adult character in chibi
proportions, clothed. Her whole body acts the mood: waves when content, both arms up and
hopping with star eyes when cheering, fist at her chest when determined, hands on her cheeks
with a tear when worried, a nose bubble asleep in P, glancing and pointing back in R, leaning
into the wind when fast, "?" while waiting. Her own lines (`chibi_*`). Settings → Mascot
(Classic / Chibi, `MascotCharacter`, pref `mascot_character`) sits beside Tyre pressure and
shows only while the Mascot style is chosen. Emulator: all moods checked with sample values in
a throwaway build, and the Settings drop-down. Not tried on the car.
- 2026-10-24 — App 2.28.0 (branch `styles-retro-future`): Overview style **Mascot**
(`MascotView` on `ClusterCanvasView`): an anime co-pilot, head and shoulders, drawn on a
100 × 100 grid — an adult woman, fully dressed, with a headset and a racing jacket in the
accent colour (so the style takes the accent). Her face follows `MascotMood`, chosen from the
reading in this order: waiting (nothing read), reverse (glances back), worried under 20 %
(sweat drop), asleep in P (drifting Zs), cheering while regenerating more than 5 kW
(sparkles), determined over 60 kW, enjoying 100 km/h and more (wind lines), else content. A
new mood shows after it lasts 1.5 s (`MascotMood.Holder`), so values hovering at a threshold
do not make her flicker; R and "waiting" show at once. Unread values are never a reason for a
mood. She says a line per mood in a speech bubble (`mascot_*` string arrays, one picked every
30 s, shrunk to fit). Beside her: speed, the gear in a ring, battery bar with percent and
range, power, 5-minute usage, trip, and the clock. She blinks and the Zs, sparkles and wind
move with the 0.2 s readings; no animation loop of its own. Emulator: all eight moods checked
with sample values in a throwaway build, and "waiting" with no data. Not tried on the car.
- 2026-10-24 — App 2.27.3 (branch `styles-retro-future`): first step to the cruise control (ACC)
set speed. The head unit's `CarAdvancedAssistedDrivingManager` names a vendor float
`AAD_EPTCCTRGTSPD` = `0x2160A15B`, by its name the powertrain's cruise control target speed;
nothing else in the decompiled SDK looks like a set speed (the other ACC properties are
objects ahead: distances, relative speeds). EVHardware reads it raw
(`getCruiseTargetSpeedRaw`, `EnergySnapshot.cruiseTargetSpeedRaw`; read-only), and the Logs
tab lists every change with time, speed and gear (`CruiseLog`, last 12). Unit and the value
while cruise control is off are unknown until read on the car; the dashboards show nothing of
it yet. Emulator: the property is absent there, one "unread" line, no crash. Not tried on the
car.
- 2026-10-24 — App 2.27.2 (branch `styles-retro-future`): Terminal can be drawn in the accent
colour too (text in its light shade, the command line in the accent, plain black; the low
battery stays amber). Instead of separate "(accent colour)" entries, Retro and Terminal are one
entry each and the accent colour drop-down beside the style offers "Original" first (their own
palette, the default) and then the accent colours; the choice is kept per style
(`original_colours_<STYLE>`). Picking "Original" leaves the app's accent as it is. The
"Retro (accent colour)" entry is gone: a stored `RETRO_ACCENT` becomes Retro in the accent
colour (`OverviewStyle.hasOriginal`, `wasAccentEntry`; unit-tested). Fourteen entries in the
style list. Checked on the emulator: Terminal original → Dynamic Red, Retro still original,
and a stored `RETRO_ACCENT` migrated to Retro in Dynamic Red
(`screenshots/nilsmg4-2.27.2-terminal-accent.png`); no crash. Not tried on the car.
- 2026-10-24 — App 2.27.1 (branch `styles-retro-future`): Settings → Display rearranged:
Overview style first, the accent colour beside it, tyre pressure on its own row below. The
accent colour is only offered for styles drawn in it (Tiles, Gauges, Cockpit, Retro (accent
colour), Futuristic, MG, Minimal, Chronograph); for the styles with their own palette (Retro,
Sci-fi, Cartoon, Blueprint, Terminal, Aviation, Nixie tubes) the field is greyed out and
ignores taps, keeping the colour chosen before, and a line says "… has its own colours. To
change the accent colour, pick a style that uses it." The styles are now an enum,
`OverviewStyle` (stored names, `usesAccent`, the old `DASHBOARD` value; unit-tested), in place
of the string array. The style list is wider than its field so "Retro (accent colour)" fits.
Checked on the emulator: Tiles (accent open), Cartoon (greyed, tap ignored, note), back to
Tiles; the page fits without scrolling
(`screenshots/nilsmg4-2.27.1-settings-own-colours.png`). Not tried on the car.
- 2026-10-24 — App 2.27.0 (branch `styles-retro-future`): the Overview can go full width. A
small round button in its top-right corner (40 dp disc in a 72 dp touch area, over the pages)
hides the app's navigation (sidebar, or the bottom bar on narrow screens); its icon turns to
"shrink" and a second tap brings the navigation back. Only on the Overview: other pages
always show the navigation and no button, swiping between pages still works, and coming back
to the Overview restores full width. Remembered (`overview_fullscreen`). The top-right corner
was chosen after looking at all fifteen styles (top left covered Tiles' "Battery"); Minimal's
clock moved left of it. Checked on the emulator: toggle, swipe away and back, restart, Tiles
and MG at full width (`screenshots/nilsmg4-2.27.0-overview-fullscreen.png`); no crash. Not
tried on the car.
- 2026-10-24 — App 2.26.0 (branch `styles-retro-future`): five more Overview styles, chosen to
differ in arrangement as much as in look, on a new base class `ClusterCanvasView` (values,
easing, null-safe formatting, animating only while something moves and the view is shown).
**Minimal** (`MinimalView`): the speed alone, very large and thin; power as a short line
growing left (green, regenerating) or right from the centre; the battery as a hairline along
the bottom edge. **Terminal** (`TerminalView`): text only, green monospace with scan lines —
rows with ASCII bars, the gear as [D], tyres, a prompt with a cursor that blinks as readings
redraw it; a low battery row turns amber. **Aviation** (`AviationView`): a primary flight
display — speed and battery on scrolling tapes with readout boxes (blank, not centred on 0,
when unread), a horizon that pitches with power (up drawing, down regenerating) with a kW
ladder and the aircraft symbol, annunciators on top, data boxes below. **Nixie tubes**
(`NixieView`): orange glowing digits in glass tubes with the unlit numerals behind and a mesh,
brass name plates on walnut; speed, gear and battery large, power, range, usage and clock
smaller (`SevenSegment` lays the text out in tubes). **Chronograph** (`ChronographView`): a
watch face — the speed on the main hand over a bezel scale, sub-dials for battery, power and
usage, the gear in a window, engraved columns either side (its radial gradient is a Canvas
shader made in `onSizeChanged`). Checked on the emulator with a throwaway build cycling the
five styles with sample values (drawing and regenerating;
`screenshots/nilsmg4-2.26.0-minimal.png`, `-terminal`, `-aviation`, `-nixie`, `-chronograph`)
and without data; no crash. Not tried on the car.
- 2026-10-24 — App 2.25.0 (branch `styles-retro-future`): Overview style "MG" (`OctagonView`),
after the classic MG dashboards whose dials sat in octagonal bezels — not a copy of the MG4's
own driver display, and without MG's octagon logo. Three octagonal dials with chrome bezels,
cream scales and needles in the accent colour: speed in the middle (digits and P R N D under
the hub), battery E…F on the left (red zone below 20 %, range), power −50…150 kW on the right
(green charge zone below zero). A cream line below: odometer, trip, 5-minute usage, climate,
clock, separated by small accent octagons. Needles ease and redraw only until settled, only
on screen; unread values draw no needle and "—". Palette `mg_*`. Checked on the emulator with
sample values (`screenshots/nilsmg4-2.25.0-mg.png`) and without data; no crash. Not tried on
the car.
- 2026-10-24 — App 2.24.1 (branch `styles-retro-future`): the line of text above every page
("Your drive at a glance.", "Settings. Display, battery…" and so on) is gone, at the owner's
request; the pages start at the top, level with the sidebar, and gain about 45 dp. Its 14
strings and the title arrays are removed. Checked on the emulator: no crash, and every tab
still fits 1778×640 without scrolling. Not tried on the car.
- 2026-10-24 — App 2.24.0 (branch `styles-retro-future`): three more Overview styles, each one
Canvas view fed the shared `ClusterValues` (now with the tyre pressures, formatted in the
chosen unit). **Sci-fi** (`SciFiView`): a starship console — rounded elbow frames in orange
and lilac, pill labels, condensed capitals; velocity large, power as blocks (regeneration
blue), rows for reserves, range, efficiency, since start, trip and climate, the reserves as a
stack of blocks, gears as blocks in the frame; below 20 % the frames turn red ("red alert").
**Cartoon** (`CartoonView`): comic panels with ink outlines and hard shadows on night blue; a
battery with a face (happy, unsure, worried and sweating), a speedometer whose needle springs
into place (animated only until it settles, only on screen), the speed in a speech bubble,
the gear on a sticker, a "zap-o-meter" with a sound word (PSST, PURR, VROOM, ZOOM!,
RECHARGE!), stats on pills. **Blueprint** (`BlueprintView`): a top-view drawing of the car on a
gridded navy sheet — the battery hatched as far as it is charged, leader lines to each tyre's
pressure, power as a force arrow (dashed and reversed while regenerating), range and trip as
dimension lines, the cabin setpoint as a note, and a title block with speed, power, gear,
usage, odometer and time. Palettes `scifi_*`, `cartoon_*`, `blueprint_*` in colors.xml.
`MainActivity` now maps each style to its pane in a table (`stylePanes`); the new panes are
built in code (`drawnPane`). Checked on the emulator with sample values in a throwaway build
(`screenshots/nilsmg4-2.24.0-scifi.png`, `-cartoon.png`, `-blueprint.png`) and without data
through the real setting; no crash. Not tried on the car.
- 2026-10-24 — App 2.23.1 (branch `styles-retro-future`): "Retro (accent colour)", the Retro
style in the chosen paint colour — display in `evAccent`, labels in `evAccentLight`;
regeneration stays green, a low battery turns amber (not red, so it never looks like Dynamic
Red). Same `RetroView`, `setAccentPalette`; listed under Retro, stored as `RETRO_ACCENT`.
Checked on the emulator in Dynamic Red with sample values
(`screenshots/nilsmg4-2.23.1-retro-accent.png`). Not tried on the car.
- 2026-10-24 — App 2.23.0 (branch `styles-retro-future`): two more Overview styles, each one
Canvas view fed a `ClusterValues` per reading. **Retro** (`RetroView`): an 80s digital cluster
as a vacuum fluorescent display — slanted seven-segment digits (`SevenSegment`, unit-tested)
with the unlit segments faintly visible, amber labels, a lit P R N D selector, the speed large,
power as a −50…150 kW bar (regeneration green), charge as a vertical bar (red below 20 %),
range, 5-minute and since-start usage, trip, climate, clock and odometer; its fixed palette is
`retro_*` in colors.xml (a style's own colours, not the accent). **Futuristic** (`FlowView`): a
hairline HUD in the accent colour — a battery capsule of scan lines, an energy channel whose
particles flow to the wheel while drawing and back in green while regenerating (faster with
more power), the speed inside a ring of dashes that turns with the speed and a power arc,
P R N D in a column, and a readout line. It animates at about 30 frames a second only while
power or speed is non-zero and the view is shown. Unread values show dashes and light or move
nothing in both. The Overview now reads the car every 0.2 s instead of 0.5 s (a read still
running skips the tick, as before). Checked on the emulator with sample values in a throwaway
build (`screenshots/nilsmg4-2.23.0-retro.png`, `…-futuristic.png`) and without data through
the real setting; no crash. Not tried on the car, including the 0.2 s reading rate.
- 2026-10-24 — App 2.22.3: the Overview style "Dashboard" is now called "Gauges" (stored as
`GAUGES`; the old value `DASHBOARD` still selects it, so the choice survives the update;
`pane_dashboard.xml` is `pane_gauges.xml`). The branches `dashboard-gauges` and
`dashboard-cockpit`, merged into main, are deleted. Tests and lint.
- 2026-10-24 — App 2.22.2 (branch `dashboard-cockpit`): the gear badge names all four gears.
The owner shifted through the lever with 2.22.1 and read the codes off the Logs tab: 1 P,
2 R, 3 N, 4 D (firmware SWI68-29958-1300R69). `Gear.label` maps them (unit-tested); any
other code shows no letter, unread "—". The gear list on the Logs tab, which existed only for
this, is gone again. Codes recorded in AGENTS.md and EVHardware's comments. Unit tests only;
the mapping itself comes from the car.
- 2026-10-24 — App 2.22.1 (branch `dashboard-cockpit`): the gear badge on the Dashboard and
Cockpit no longer guesses. It showed "D" for anything not in P, so N and R read as D. The app
only knew "in P or not"; EVHardware now passes on the car's raw gear code
(`EVHardware.getGearRaw`, `EnergySnapshot.gearRaw`; `isVehicleInPark` is built on it).
`Gear.label`: 1 → "P" (the only established code), any other code → no letter, unread → "—".
To map R, N and D, the Logs tab lists every change of the code with time and speed
(`GearLog`, last 12, kept in preferences). Checked on the emulator with a throwaway build
cycling codes 1–4 (pill shows P on 1, nothing on 2–4; the Logs tab lists each change). Not
tried on the car; the codes for R, N and D are still to be read there.
- 2026-10-24 — App 2.22.0 (branch `dashboard-cockpit`, from `dashboard-gauges`): a third Overview
style, "Cockpit", designed without a reference: three segmented rings (`RingView`, Canvas) —
battery (0…100 %, range inside, below 20 % in the warning colour), power with the speed and the
gear inside (zero at the top: drawing power clockwise to 150 kW in the accent colour,
regeneration anticlockwise to −50 kW in green) and 5-minute usage (0…30 kWh/100 km, a white tick
for the average since start). Segments brighten towards the leading one. Below, the power of the
last two minutes as a trace (`TraceView`; `PowerTrace`, one slot per second, unit-tested; gaps
for unread or missed readings, never zeros), the clock, climate setpoint and odometer. Settings
→ Overview style: Tiles / Dashboard / Cockpit. Checked on the emulator with sample values in a
throwaway build (`screenshots/nilsmg4-2.22.0-cockpit.png`) and without data through the real
setting (nothing lit, "—", the page fits 1778×640). Not tried on the car.
- 2026-10-24 — App 2.21.0 (branch `dashboard-gauges`): Overview style "Dashboard", an instrument
cluster after a reference picture of the owner's: power (−50…150 kW), speed (0…180 km/h, with
TOTAL odometer and TRIP km since start) and battery (E…F, below 20 % in the warning colour,
range underneath) as gauges (`GaugeView`, Canvas, accent-coloured arcs, white needles that ease
to a new value), and a strip of gear (P/D), climate setpoint, clock, 5-minute usage and a
battery bar (`BatteryBarView`). Unread values draw no needle and "—". Settings → Display →
"Overview style: Tiles / Dashboard"; Tiles stays the default; both are fed from the same
reading. Checked on the emulator with sample values in a throwaway build
(`screenshots/nilsmg4-2.21.0-dashboard.png`) and without data through the real setting; the
Overview and Settings fit 1778×640. Not tried on the car.
- 2026-10-23 — App 2.20.0: the Overview's Vehicle info card shows the lease mileage: "Lease
allowance today" (40,000 km over four years from 2023-05-01, accruing evenly per day, in
`LeaseAllowance`, unit-tested) and "Versus allowance", the odometer against it ("… km over" /
"… km under", "—" when the odometer is unread). The odometer now has a thousands separator,
like the new rows. Checked on the emulator with a sample odometer of 25,000 km in a throwaway
build (34,798 km allowed on 2026-10-23, 9,798 km under); the Overview still fits.
- 2026-10-23 — App 2.19.9: the Foreground apps card is gone from the Logs tab (it found CarPlay's
package; nothing left to find), with ForegroundProbe and ForegroundHistory. The Logs tab is one
card again — update log, then uploads — with the other tabs' page padding.
- 2026-10-23 — App 2.19.8: the charging curve's labels no longer overlap: "kW" has a row of its
own above the plot, the power values are right-aligned against the axis, and the SOC ticks read
"0 %" … "100 %" (the separate "SOC %" sat on top of "100"). The wide Charging tab splits 35/65,
values | curve. Checked on the emulator with sample AC values and a curve in a throwaway build;
the page still fits 1778×640.
- 2026-10-23 — App 2.19.7: AC charging is metered too, while the app runs (owner's choice; Fatih
had metered DC only, `e9bfa12`, because an overnight AC charge is slept through). A session runs
whenever power flows into the pack (`ChargingState.isMeteredCharging`: inbound > 0.3 kW and a
charge by `isCharging`), and its curve takes every inbound sample. Gaps where the head unit
slept are dropped, not billed, so an overnight AC session shows the awake part only. The label
says "AC charging" / "DC fast charging"; `isDcCharging` stays for it and ABRP's `is_dcfc`. Server
comment, server README and the Grafana charging dashboard's texts no longer say DC only (the
dashboard ConfigMap changes with them: re-apply `k8s/`). Unit tests; not tried on the car.
- 2026-10-23 — App 2.19.6: the CarPlay full-screen switch and the "Export CarPlay data" button are
removed (CarPlay's size is fixed, see below). On start, the car build takes the CarPlay entry out
of `policy_control` if the switch was left on, and leaves anything else there alone — checked on
the emulator. The Foreground apps card stays as a diagnostic. AGENTS.md lists the CarPlay and
install findings among the confirmed car facts.
- 2026-10-23 — CarPlay full screen: not possible on this firmware (SWI68). The export from the
car showed CarPlay's window at 1920×720 with both SAIC bars hidden (policy_control on), and its
video SurfaceView at (0,80), 1920×640, with a 1920×640 buffer: the grey strip is the window's own
background above the video. Allgo's `/system/app/AllgoCarplay_EH32` hard-codes it twice:
`CarPlayService` announces `SCREEN_HEIGHT = 640`, `SCREEN_WIDTH = 1920` to the iPhone (it reads
`sys.screen.heightpixel/widthpixel`, then overwrites them with the constants), and
`carplay_screen_layout.xml` places the SurfaceView at `640px` high with `80px` top margin. Both
are in the read-only, signed system image. The export (`dump/`) is gitignored: it holds Allgo's
software.
- 2026-10-23 — App 2.19.5: installing from USB (and over the air) works on the car again, with a
confirmation. The update log showed the car refusing the silent session: `Not allowed to install
non-system apps on internal storage`. The fallback, Android's installer screen, then said "There
was a problem parsing the package": as the system user, our FileProvider's read grant to the
installer is refused (`not exported from UID 1000`). The car build now hands the installer a
file URI to the verified copy in Downloads (StrictMode's file-URI check lifted for that one
call). On the emulator, with the session forced to fail as on the car, the installer screen
asked to update and installed it. Not tried on the car yet. OTA.md updated.
- 2026-10-23 — App 2.19.4: "Export CarPlay data" on the Logs tab (Foreground apps card), for the
question whether CarPlay's resolution can change. CarPlay with policy_control still leaves a grey
strip, also after reconnecting: Allgo announces a smaller picture to the iPhone. 20 s after the
press (time to open CarPlay) it writes to `<stick>/Android/data/<package>/files/
nilsmg4-carplay-<time>/` — the only place on a removable volume Android 9 lets an app write:
`dumpsys window windows`, `SurfaceFlinger`, `display`, `activity activities`, `wm size/density`,
the display-related system properties (identifiers filtered out), Allgo's APK and native
libraries, and CarPlay/Allgo/iAP2-named files under /system, /vendor, /odm, /product. It reads
the car and writes only to the stick. Owner's choice to export Allgo's software for analysis
(2026-10-23). On the emulator (no Allgo there) every dump was written, without permission
errors. XDA (leonkernan, page 30) found SAIC's SystemUI hides its top bar itself for a whitelist
that includes com.allgo.carplay.service — compiled into SystemUI, not changeable from the app.
- 2026-10-23 — App 2.19.3: an update log on the Logs tab, above the uploads. A USB install on the
car ended in an installer message about a broken package, while the same APK installed through
the Bluetooth-settings route; the message was only a toast and logcat, so its exact words are
lost. Now every step is kept (last 10, survives the restart an install causes): file found with
its size, SHA-256 match, the package manager refusing to parse, a failed installer session with
its exception, bytes handed to the installer, the platform's result in its own words, and "Now
running <version>" on the first start of a new version. On the emulator, with a FAT image as a
removable volume (like the car's stick), the USB install worked silently and the log showed the
whole sequence; a truncated APK with a matching hash was refused at the package check, logged.
The car's failure is not reproduced or explained yet.
- 2026-10-23 — App 2.19.2: Settings → "Full screen for CarPlay (experiment)". The Logs tab showed
CarPlay on the car as `com.allgo.carplay.service/.CarPlayActivity`, an ordinary activity. The
switch adds that package to `immersive.full` in Android 9's `policy_control` global setting
(system user only; elsewhere it is disabled) and, off, removes just that entry; it shows what the
setting holds. On the emulator: policy_control hid the status bar of another app and its content
took the space; the switch wrote and removed its entry and left an existing one alone. Not tried
on the car: whether SAIC's top bar is the status bar, and whether CarPlay's picture grows or
leaves a black strip until the phone reconnects. Tyre pressure and accent colour now sit side by
side in the wide Settings layout, so the page still fits.
- 2026-10-23 — App 2.19.1: "Foreground apps" card on the Logs tab, step 1 of the CarPlay
full-screen experiment. It reads which apps came to the front in the last 30 minutes (usage
events, open to the system user without a declared permission), the open tasks, the
`policy_control` global setting and the status bar height — on opening the tab, on returning to
it and on Refresh, never in the background. It changes nothing. On the emulator (car build,
system user) it named Settings, the clock, the calculator and the launcher with times, and the
page still fits 1778×640 with every line filled. The stable build says it cannot read them. Not
tried on the car; the CarPlay package name is what this is for. Next: a switch that sets
`policy_control` to `immersive.full=<that package>`, on approval.
- 2026-10-19 — `k8s/agent-access/`: a service account `mg4-agent` for a coding agent, limited to
the `mg4` namespace. Read-only role by default; an optional deploy role. Neither may touch
Secrets, exec, port-forward or RBAC; the deploy role cannot delete PVCs. `make-kubeconfig.sh`
writes a kubeconfig with a `kubectl create token` token that expires (default 24 h). Validated
with kubeconform and shellcheck, the script against a stub kubectl; not applied.
- 2026-10-19 — App 2.19.0: EVHardware updated from malys/EVHardware `b595ccb` (2026-09-17, the
base Fatih copied) to `e4e1158` (2026-10-18, after 1.18.0), 42 commits, merged three ways so Fatih's
changes stay (listed in `LICENSE.md`). Upstream brings among others: trip, charge and eco
telemetry fixes, a daily battery digest, the snapshot's widest window and raw drive mode and
regen, probes for the vendor charge status, plug and energy ids, read-only vendor-surface probes,
exterior-light conditions and actions, and a diagnostic inbox. The app uses only
`EnergyTelemetryReader` and its snapshot, so the new reads (windows, drive mode, regen) are the
only new calls it makes; they are reads. The new actions and the inbox are not called. Conflicts
were only where both sides added fields; the charge-status id is now upstream's
`PROP_BMS_CHRG_STS`. EVHardware's 491 tests and the app's pass; the app ran on the emulator
without errors. Not tried on the car.
- 2026-10-19 — App 2.18.3: the Battery tile's value is 40 sp too. The first row of tiles now
shares one height, since the Battery tile, with its charging line, grew taller than the rest.
Checked on the emulator with sample values: the Overview still fits 1778×640. Not tried on the car.
- 2026-10-19 — App 2.18.2: Overview's Range, Power and Speed tiles (the ones without a line under
the value) show it at 40 sp instead of 28; auto-size still shrinks a long value. Checked on the
emulator with sample values ("-142.5 kW", "160 km/h") in a throwaway build: they fit at full
size and the row keeps its height. Not tried on the car.
- 2026-10-19 — Server 2.6.1: the code field on the phone pages (`/pair`, `/settings`, when opened
without the code in the link) upper-cases what is typed and puts the dash in after the third
character. That takes the pages' first script: one inline script, allowed by its SHA-256 hash in
the CSP (`script-src 'sha256-…'`, still `default-src 'none'`); a test checks the served script
against the header. Without it the server still accepts the code in any case, with or without
the dash. Image pinned by digest in `k8s/server.yaml` (not applied).
- 2026-10-18 — README brought up to date: screenshots of all seven tabs from 2.18.1 at the car's
1778×640 window (the upstream and 2.8.2 screenshots are gone), tab descriptions, updating over
the air and from a USB stick, the emulator, the release relay. OTA.md: updates are checked from
Settings, no longer from a top-bar button.
- 2026-10-18 — App 2.18.1: every tab fits the car's window (1778×640 dp) without scrolling — checked
on the emulator by swiping each page and comparing screenshots. Charging and Consumption get wide
layouts with two columns (values | curve; period totals | daily log and storage note); the curve
table scrolls inside its card, so a long session no longer stretches the page. Live data: ABRP key
and token side by side, Save / Test / Enter on phone in one row. Settings: save buttons beside
their fields, charging price under Battery, "Enter on phone" in a card of its own above App. Wide
pages use 12 dp top padding. With real data the value texts are longer; not tried on the car.
- 2026-10-18 — App 2.18.0: Settings → App → "Install from USB stick" (car build only): the newest
`NilsMG4-unstable-<version>.apk` newer than the installed one in a stick's top folder, checked
against its `.sha256` and the package, installed with the OTA installer session. Tested on the
emulator: 2.18.0 installed a 2.18.1 from the storage's top folder silently, as the system user —
so the OTA install step should not need a confirmation either.
- 2026-10-18 — App 2.17.5: Server tab fits the car's window. The "Your private connection" card is
gone (its text was out of date); Import from file sits with Save and Test in one row; the
installation ID is shown as text, not edited, since it is derived from the head unit (a config
file can still carry another one over). The subtitle no longer says the server forwards to ABRP.
- 2026-10-18 — App 2.17.3: the Overview panel "Energy and temperature" is "Vehicle info" (2.17.4; "Odometer and climate" in 2.17.3):
it only holds those two, since the car publishes no pack energy or temperatures.
- 2026-10-18 — App 2.17.2: Wi-Fi auto-connect removed with its two permissions (ACCESS_ and
CHANGE_WIFI_STATE). Upstream used it to keep the head unit on a phone hotspot; this car's head
unit has its own connection.
- 2026-10-18 — App 2.17.1: Settings without the Wi-Fi and saved-networks cards, in three columns
(display and steering wheel | battery | charging price and app). Logs keeps only the upload log: the steering-key
reports, the car-colour probe and the screen line are gone, their questions answered.
- 2026-10-18 — App 2.17.0: "Speed, 5-min average" card under Speed. The top bar is one compact line
(title and subtitle); version, update check and About moved to an App card on the Settings tab.
The Overview fits the car's window without scrolling: checked on the emulator at 1778×640, the
layout height the car reports (less page padding, a lower tyre card).
- 2026-10-18 — App 2.16.4: drop-downs again, as Material exposed dropdown menus: their list leaves
the focus with the activity, so full screen stays (a Spinner's list is a focusable window of its
own without the full-screen flags). 2.16.3 had replaced them with buttons.
- 2026-10-18 — App 2.16.3: full screen stays while choosing or reading a dialog. Drop-downs (upload
frequency, tyre unit, accent colour) became buttons on the page: a drop-down is a window of its
own without the full-screen flags, so the car showed its bars while it was open. Dialogs are
shown unfocusable, given the activity's full-screen flags, then allowed focus
(`showKeepingFullScreen`); none of them takes typing.
- 2026-10-18 — From the car (2.16.2): screen 1920×720 px at 160 dpi with a 1778×720 px app
window, so EVProfile's 1280×480 does not apply; colour probe found only `vehicle_type = AS28`.
FIRMWARE.md, mise.toml and SPECS.md say so.
- 2026-10-18 — App 2.16.2: Logs → SCREEN shows the panel's pixels and density and the app
window in px and dp. The MG4's display figures disagree (1920×720 px used here; EVProfile lists
1280×480, which is 1920×720 at 240 dpi in dp; upstream reported a 1778×720 window); FIRMWARE.md
lists all three until a photo of that line settles it.
- 2026-10-18 — EVProfile's licence checked: MIT, forked from SliDeeN/MG4Control and last merged
from it on 2026-08-02, inside MG4Control's MIT period (MIT up to 2.6.5 of 2026-08-05, GPL v3
from 2.6.6 of 2026-09-02). Recorded in `LICENSE.md`. History scanned for secrets before the
repository went public: none.
- 2026-10-18 — Licence: Leon Kernan's `ABRP_Uploader` has been MIT since 2026-07-29 (its commit
`75d8f2bc`), so the "unresolved" notes are gone. `LICENSE` names every copyright holder in the
fork chain; `LICENSE.md` explains that `EVHardware/` is PolyForm Noncommercial, which makes the
built APK non-commercial. README, AGENTS.md and CONTRIBUTING.md updated.
- 2026-10-18 — App 2.16.1: the Overview reads the car every 0.5 s (was 2 s), on a background
thread; the consumption and charging trackers stay on the 2-second tick (they integrate over
time, and the charging tracker writes its session to storage on every sample). Note for
emulator tests: uiautomator can no longer dump the screen while the Overview is open, since
the UI is never idle.
- 2026-10-18 — Grafana's read-only InfluxDB token without a manual step: an init container in
the Grafana pod creates it with the admin token at every start, deletes the previous one and
leaves only the read-only token in a memory volume; the datasource reads it with
`$__file{/run/influx/token}`. InfluxDB ignores chosen token values (tested), so it cannot come
from the Secret. The `grafana-influx` Secret is gone. Tested in podman: InfluxDB 2.7.12, the
script as the Grafana user (twice: rotates, no pile-up), Grafana 13.2.3 datasource healthy.
- 2026-10-18 — App 2.16.0, server 2.6.0: over-the-air updates from this repository. `publish-car`
creates the pre-release `car-<version>` on the private forge with the APK and its `.sha256`;
the server relays releases and attachments to the car (`/v1/app/…`, read with a read-only
`FORGE_TOKEN`); the app checks them through its server with its own token, allows exactly the
server's host, downloads itself (no DownloadManager, so the token stays in the app), verifies
SHA-256 and package, and tries a PackageInstaller session before falling back to Downloads.
`OTA.md` rewritten for this flow. Not yet tried on the car.
- 2026-10-18 — App 2.15.0: unsent live samples survive a restart. The queue is saved to
`files/telemetry_queue.json` (AtomicFile) at most every 5 minutes and only after a change, and
at once when the service stops; it is restored, in order and within its 2000-sample cap, when
the service starts. A killed process loses at most 5 minutes. Tested on the emulator across a
reboot (6 queued samples back).
- 2026-10-18 — App 2.14.4: every UI text is in `strings.xml` (Charging, Consumption and Settings
labels, toasts, the trip-reset dialog, charging and Wi-Fi states, the daily summary, the "—"
placeholder); lint reports no hard-coded text. Upload-log entries stay in code as log text.
- 2026-10-18 — Translated the rest: `mise.toml`, the comments in `app/build.gradle`, `OTA.md`'s
message table (now the updater's current English messages), the Turkish messages quoted in
`CHANGELOG.md` and `OTA.md` (glossed), two test messages, the ignition-state comment in
`EVHardware.kt`.
- 2026-10-18 — About dialog: link and QR code point to https://git.stinnesbeck.com/nils/mg4
(were Fatih's GitHub). Status section brought up to date.
- 2026-10-18 — App 2.14.2: "Range, 5-min average", like the usage and power cards (was "Range at 5-min average").
- 2026-10-18 — App 2.14.1: Overview values and their notes right-aligned, with tabular
(equal-width) digits, so 9 → 10 → 100 no longer shifts the text; labels stay left.
- 2026-10-18 — App 2.14.0: accent colour selectable after the MG4 paints (Settings → Display):
Dynamic Red (default), Volcano Orange, Holborn Blue, Racing Green, Camden Grey, Monument Silver,
Arctic White, Black Pearl; white, silver and black become light greys and a pearl blue-grey,
since they cannot be an accent on black. The accent palette is theme attributes
(`attrs_accent.xml`, overlays in `accents.xml`); a change recreates the activity on the same
page. Android 9 crashes on gradients with theme colours, so the battery card is layered.
Logs tab: "Look for the car colour" lists system properties that mention colour, paint or car
model, to find out whether the paint colour can be read (nothing on the emulator).
- 2026-10-18 — App 2.13.0: tyre pressures in bar by default, kPa or psi selectable (Settings →
Display; display only, the car and the server keep kPa). "Power, 5-min average" card under
Power: time-weighted mean battery power, standing included.
- 2026-10-18 — App 2.12.1: no scroll bars (the 2-second refresh kept them from ever fading);
"Energy since start" tile (net kWh of the trip, recuperation counted back) left of "Usage since
start".
- 2026-10-18 — App 2.12.0, server 2.5.0: settings entered on a phone. "Enter on phone" shows a QR
code; the phone form on the server (approval password) takes the ABRP credentials, charging
price and usable capacity, and the car collects them once (RFC 8628-style, like pairing; values
in server memory only, at most 10 minutes). The config file also takes `charging_price` and
`usable_kwh`. The QR dialog is shared with pairing (`PhoneQrDialog`). Tested end to end with
curl against the server image and InfluxDB; the app side on the emulator only up to the dialog.
- 2026-10-18 — App 2.11.1, server 2.4.0: ABRP forwarding removed from the server. The car and the
server use the same mobile connection, so a server fallback for ABRP bought nothing. The car no
longer sends `abrpSentUtc`; the server ignores unknown fields, so older apps keep working.
- 2026-10-18 — App 2.11.0, server 2.3.0:
- Live data goes to ABRP straight from the car when ABRP credentials are set in the app
(Live data tab or config file: `abrp_api_key`, `abrp_token`), so ABRP keeps getting it
while the server is down. The car tells the server the newest sample ABRP took
(`abrpSentUtc`); the server forwards only newer ones. Without credentials in the app the
server forwards as before. This deliberately puts ABRP credentials in the car again
(encrypted, never logged).
- Settings regrouped: Live data (on/off, ABRP, frequency), Server, Settings (full screen, voice
key, usable capacity and SoH from Consumption, charging price from Charging, Wi-Fi, which
lost its own tab). Charging and Live data have their own icons.
- 2026-10-18 — App 2.10.2: the range shows "∞" instead of "> 999 km" while recuperating.
- 2026-10-18 — App 2.10.1: net regeneration (recuperating downhill) shows as negative usage, and
the range as "> 999 km" with "while recuperating", instead of "needs a few minutes of driving".
- 2026-10-18 — App 2.10.0: "Range since start" replaces the range at current power. It uses
the consumption since the car was switched on, including standing with the climate on, and a
new trip starts after 10 minutes without samples or when charging. Usage (kWh/100 km) for both
estimates moved into cards of their own under the range tiles, which fit on the full-screen
1920x720 overview without scrolling.
- 2026-10-18 — Code review fixes. App 2.9.0, server 2.2.0, Grafana token:
- The server drops a bad value (or a sample without a plausible time) instead of rejecting
the batch, and answers 200 with what it dropped; the app sends only values within the
server's ranges (speed as a magnitude) and no samples before its clock is set. Before, one
reversing sample cost the whole queued batch.
- Pairing: the token is handed out and accepted only after the device is saved; wrong-password
lockouts and open-request limits are per client address; a paired token only serves its
own installation id (403 otherwise).
- ABRP forwarding skips samples stamped ahead of the server's clock, which used to block it.
- App: settings written in plaintext during a keystore failure move back into encrypted
storage; the installation id is derived from ANDROID_ID, so a reinstall keeps its history;
the imported config file is deleted; GPS stops when live telemetry is off and pauses while
parked and idle; range estimates use the monotonic clock; one HTTP helper (`ServerHttp`)
for all server calls; the last Turkish strings translated.
- Grafana reads InfluxDB with its own read-only token (`grafana-influx` Secret, created by
hand: `k8s/README.md`).
- 2026-10-18 — README rewritten for Nils' MG4. `OTA.md`, `mise.toml` and
`docs/MG4_PLATFORM_INSTALL.md` use the new package ID and `NilsMG4-*.apk` names; `OTA.md` notes
that updates are switched off. `CHANGELOG.md` keeps the old ID as upstream history.
- 2026-10-18 — App 2.8.2: full screen uses the whole display. The MG4 hid its bars but left a black
band where they were: LAYOUT_STABLE kept reporting the bars' insets and the fitsSystemWindows root
padded for them. Both are off in full screen now.
- 2026-10-17 — App 2.8.1: three-part version names (`2.8.1-unstable`, `NilsMG4-unstable-2.8.1.apk`).
Upstream's fourth part was its CI build number; the version code keeps the x100000 factor so it
never falls below codes already installed.
- 2026-10-17 — App 2.8.0: immersive full screen (switch in the Upload tab, default on). Hides the
system bars on the emulator; whether SAIC's own bars on the MG4 can be hidden is only known once
tried on the car.
- 2026-10-17 — App 2.7.1: the voice key works. The car's key report, read off the on-screen log:
`android.intent.extra.hardkey.keycode` (Integer), `.down` (Boolean, a report per press and per
release), `.longpress` (Boolean). 2.7.0 looked only for short names and never found the code.
Floating button removed (it did not show over the MG4 launcher).
- 2026-10-17 — App 2.7.0: a double press of the steering wheel's voice key (code 287) opens the
app, from the head unit's `com.saic.keyevent.hardkey.report` broadcast. Its extras are not
documented; the usual names are tried and every report is listed raw on the Logs tab. The
floating button (2.6.0) did not show over the MG4 launcher. Pages no longer jump back to the
top on every refresh (`StableScrollView`), and the Vehicle tab shows all six value tiles,
including both range estimates, in one row at the top.
- 2026-10-17 — App 2.6.0: floating button to open the app, because the MG4 launcher does not list
it. Drawn over other apps (needs the overlay permission, which the car build has through the
system UID), draggable, hidden while the app is open, switch in the Upload tab. Tested on an
Android 9 AOSP emulator with the car APK (installs as UID 1000 there too), at 1920x1080 and at the
head unit's real 1920x720 / 160 dpi, where all tabs and the button were checked as well.
- 2026-10-17 — App 2.5.0: range estimates at current and 5-minute average power (Vehicle tab).
- 2026-10-17 — Live dashboard reworked around the signals the car sends: Climate setpoint, Last
update and a computed Energy in pack replace the empty temperature tiles; Battery voltage and
current replaces the Temperatures chart.
- 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`.