App 2.22.3 → 2.34.1. Server, Grafana and the k8s manifests are unchanged.
Overview
- Read every 0.2 s. Nineteen styles in Settings → Overview style: Tiles,
Gauges, Cockpit, Retro (VFD), Futuristic (energy flow), Sci-fi,
Cartoon, Blueprint (top view with tyre pressures), MG (octagonal
dials), Minimal, Terminal, Aviation, Nixie tubes, Chronograph and
Mascot. The drawn styles share ClusterValues and take the accent
colour; Retro and Terminal also offer their original palette.
- Mascot: an anime co-pilot whose mood follows the drive (MascotMood:
reverse, low battery, asleep in P, regenerating, accelerating hard,
fast, content, waiting), with lines in a speech bubble. Three
characters: the classic one, a chibi and a chibi fox (ChibiBase).
- Full width: a button beside the MG4 logo hides the navigation; a
swipe in from the left edge brings it back, or, beside the car's own
sidebar, a tab at the left edge. Pages change only by the navigation.
Drive mode (the first vehicle setting the app writes; AGENTS.md rule 3)
- Settings: Eco, Normal, Sport, Snow, Custom. The lit one is what the
car reports. Writes only in P at 0 km/h, both read fresh, through
EVHardware's standstill gate; the result is the mode read back.
- "Set at start": the last confirmed choice is set again once when the
head unit starts, only before the car first leaves P.
Logs
- Signal recorder: every property and vendor value the head unit offers,
read for 3 minutes with the steering-wheel keys, changes listed rarest
first, saved to a USB stick.
- Adaptive cruise control was looked for and is not there: the head unit
gets neither the set speed nor the distance (SPECS.md, "Signals the
car does not provide"). The cruise reads are removed again.
Tested: unit tests, lint, the emulator. Not tried on the car: the drive
mode writes, the tab beside the car's sidebar, the Mascot moods with
real data.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
Without the app's own full screen, the car's sidebar takes the left edge
and a swipe from there never reaches the app. A small tab at the app's
left edge now brings the navigation back, by a tap or a swipe from it.
In the app's full screen the handle and edge swipe stay as they were.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
The car falls back to its own default mode at every start. With "Set at
start" (on by default), the service sets the mode last chosen on
Settings and confirmed by the car again, once, while the car is in P at
0 km/h, and gives up as soon as the car leaves P or after two minutes
(DriveModeWrite.atStart, unit-tested). The buttons and the start share
the write and read-back (DriveModeSetter). Not tried on the car.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
The owner lifted the read-only rule for the drive mode alone. Settings
has Eco, Normal, Sport, Snow and Custom; the lit button is what the car
reports. A tap reads gear and speed fresh and writes only in P at 0 km/h
(DriveModeWrite, unit-tested; unreadable is a refusal), through a CPM-only
write behind EVHardware's standstill gate, then reads the mode back and
says what the car reports. The wide layout's charging price joined the
battery card so the page still fits. Not tried on the car.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
The 2.32.0 recording (ACC set to 101, 90 and 80 km/h behind another car)
found none of it among 1,194 values: the head unit does not get the ACC
data. Removed the cruise log and the set-speed read (EVHardware is as
before 2.27.3); the finding is under "Signals the car does not provide".
The recorder stays as the Signal recorder, for finding other signals.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
The 2.31.1 log on the car showed the named assist properties constant
while ACC was engaged behind another car: they are not fed. The recorder
reads every property and vendor getter (read only, at most half the time
busy) for 3 minutes, plus steering-wheel key broadcasts, and lists what
changed, the rarest first. The recording is saved in the app and on a
USB stick if one is plugged in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
The 2.27.3 log wrote a line only when the target speed changed, so the
car showed one line at the start. Now a line is written on any change of
a mode or the target and every 2 s while driving, with the raw
driver-assistance properties (target speed, assist and speed-assist
modes, the followed object) and the reason for any unread value. The
last 600 lines are kept in an appended file; the Logs tab shows 150.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
The Overview's full-screen button moved from the page's corner into the
navigation, beside the MG4 logo. A swipe in from the left edge (EdgeSwipe,
unit-tested) brings the navigation back; a faint handle marks the edge.
Sideways swipes no longer change the page: only the navigation does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
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
ChibiFigure draws a small, full-figure co-pilot in a hoodie whose whole
body acts the mood (waving, arms up and hopping, fist, hands on cheeks,
nose bubble, pointing back, leaning into the wind). Settings → Mascot
(Classic / Chibi, MascotCharacter) shows only for the Mascot style; the
chibi has lines of her own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
MascotView draws her head and shoulders beside speed, gear, battery,
power, usage and trip. MascotMood picks her expression from the reading
(waiting, reverse, worried, asleep, cheering, determined, fast, content)
and holds a new mood for 1.5 s so values at a threshold do not make her
flicker. A speech bubble carries a line per mood.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
The head unit names a vendor float AAD_EPTCCTRGTSPD (0x2160A15B),
the cruise control target speed by its name. EVHardware reads it raw;
the Logs tab lists each change with time, speed and gear, to establish
the unit and the off value on the car. Emulator only (absent there).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
Retro and Terminal are one entry each; the accent colour drop-down
offers "Original" first for them, remembered per style. The old
"Retro (accent colour)" entry is folded in and its stored value
migrated. Emulator only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
Settings → Display now has the Overview style first with the accent
colour beside it; for styles with their own palette the accent field is
greyed out with a note. Styles are an enum, OverviewStyle, with
usesAccent (unit-tested). Emulator only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
A small button in the Overview's top-right corner hides the app's
sidebar (or bottom bar) and brings it back; only on the Overview,
remembered, swiping still works. Emulator only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
Five more styles that differ in arrangement as well as look: the speed
alone with hairlines; a green text terminal with ASCII bars; a flight
display with tapes and a horizon that pitches with power; nixie tubes on
walnut and brass; a watch face with sub-dials. They share a new base,
ClusterCanvasView. Emulator only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
After the classic MG dashboards: three octagonal dials with chrome
bezels, cream scales and accent-coloured needles for speed, battery and
power, and a cream line of odometer, trip, usage, climate and clock.
No MG logo. Emulator only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
Sci-fi: a starship console with elbow frames, pill labels and block
graphs, red alert below 20 %. Cartoon: comic panels, a battery with a
face, a springy speedometer and sound words for power. Blueprint: a
top-view drawing with the battery hatched to the charge, tyre pressures
on leader lines, power as a force arrow and a title block. Styles are
now mapped to panes in a table. Emulator only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
"Retro (accent colour)" draws the display in the chosen paint colour
and the labels in its light shade; regeneration stays green, a low
battery turns amber. Listed under Retro. Emulator only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
Retro: an 80s vacuum fluorescent cluster with seven-segment digits,
bar graphs and a lit P R N D selector. Futuristic: a hairline HUD with
an animated energy flow between battery and wheel, a turning speed ring
and a power arc. Both show unread values as dashes. The Overview now
reads the car five times a second. Emulator only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
Codes read on the owner's car with 2.22.1: 1 P, 2 R, 3 N, 4 D. Gear
maps them (unit-tested); other codes show no letter, unread "—". The
temporary gear list on the Logs tab is removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
The badge said "D" for every gear that was not P, so N and R read as D.
EVHardware now passes on the raw gear code; the badge shows P for 1,
no letter for codes not yet mapped, "—" when unread. The Logs tab lists
each change of the code with time and speed, to match R, N and D on
the car. Emulator only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
A third Overview style beside Tiles and Dashboard: rings for battery,
power (zero at the top, regeneration anticlockwise in green) with the
speed and gear inside, and 5-minute usage with a tick for the average
since start; below, the power of the last two minutes, the clock,
climate and odometer. Unread values light nothing and show "—"; the
trace leaves gaps (PowerTrace, unit-tested). Emulator only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
The car refuses the silent session for a non-system app; the fallback
installer screen could not read our FileProvider URI because grants
from the system user are refused. The car build now hands it a file
URI to the verified copy in Downloads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
Read-only diagnostic for CarPlay's resolution: window, SurfaceFlinger
and display state captured while CarPlay is in front, display-related
properties without identifiers, Allgo's APK and libraries and
CarPlay-named system files, written to the app's folder on the stick.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
Read-only: recent foreground apps (usage events), open tasks,
policy_control and the status bar height, for finding the CarPlay
package before a full-screen experiment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three-way merge from b595ccb, the commit Fatih copied, to upstream's
current state; Fatih's vendor reads (tyres, mileage, charge status,
SWI69) stay. LICENSE.md records the base for the next update.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
The phone pages' code field gets one inline script, allowed by its
SHA-256 hash in the CSP; a test checks the served script against the
header. Image pinned by digest in k8s/server.yaml (not applied).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
Charging and Consumption get two-column wide layouts; Live data and
Settings are tightened (fields beside their buttons, button rows,
16 dp vertical card padding). Checked on the emulator at 1778x640.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
Turns the FatihsMG4 fork into Nils' MG4: an English Android Automotive
app for the MG4 head unit, a Go car server with InfluxDB 2 and Grafana,
Kubernetes manifests, and updates over the air or from a USB stick.
AGENTS.md is the overview, SPECS.md the journal of every change.
App (com.stinnesbeck.nilsmg4, car build .unstable)
- English throughout; all UI text in strings.xml. Renamed with a new
package ID, NilsMG4-*.apk names, three-part versions; About links to
git.stinnesbeck.com/nils/mg4 and keeps the upstream credits.
- build-car (Nix flake) signs the car build with the AOSP platform key
the head unit trusts; nix run .#car-emulator gives a matching Android
9 emulator.
- Overview, fitting the car's 1778x720 window without scrolling:
battery, range, range/usage/energy since start, 5-minute averages of
range, usage, power and speed, vehicle info, tyre pressures in
bar/kPa/psi. Read every 0.5 s on a background thread; values
right-aligned with equal-width digits; recuperation as negative usage
and an infinite range.
- Head unit: full screen (kept with drop-downs and dialogs), voice-key
double press opens the app, selectable accent colour after the MG4
paints (Dynamic Red by default), no scroll bars.
- Tabs: Overview, Consumption, Charging, Live data (on/off, ABRP
credentials, frequency), Server (URL, QR pairing, token, read-only
installation ID, config import), Settings (display, battery,
charging price, update check, install from USB, About), Logs.
- Live data to the car server and straight to ABRP; only values within
the server's ranges; the queue survives restarts; GPS pauses while
parked and idle.
- Settings can be typed on a phone (QR code, form on the server) or
imported from a config file, which is deleted afterwards.
- Security: credentials in encrypted storage (moved back after a
keystore failure), installation ID derived from the head unit.
- Updates: from this repository's pre-releases through the car server,
or from a USB stick; SHA-256, package and signature checked; the
installer session ran silently on the emulator.
- Removed: Wi-Fi auto-connect (and its permissions), the floating
button, upstream's diagnostics.
Server (server/, Go 1.26, replaces upstream's Next.js app)
- Telemetry, consumption chunks and charging sessions in InfluxDB 2;
idempotent writes; a bad value or time drops only itself.
- Pairing by QR code approved on a phone; paired tokens bound to their
installation; lockouts and limits per client address.
- Settings from a phone (in memory only, at most 10 minutes, handed out
once); app releases relayed from the private forge with a read-only
FORGE_TOKEN. No ABRP forwarding: the car sends to ABRP itself.
Infrastructure (k8s/, plain manifests, applied by hand)
- InfluxDB 2.7.12, the server pinned by digest, Grafana 13.2.3 with
provisioned Live, Consumption and Charging dashboards (euro),
Traefik traefik-internal routes.
- Grafana's read-only InfluxDB token is made by an init container at
each start; the Grafana process never sees the admin token.
- Filled-in secrets only in the gitignored k8s/secrets/secrets.yaml.
Licence
- MIT throughout, like Leon Kernan's ABRP_Uploader (MIT since
2026-07-29) and the forks in between; LICENSE names every copyright
holder. EVHardware/ is PolyForm Noncommercial, so the built APK is
non-commercial. EVProfile's MG4Control lineage is from MG4Control's
MIT period (checked).
Deploying
1. Fill in k8s/secrets/secrets.yaml from the template (InfluxDB admin,
ingest token, approval password, read-only FORGE_TOKEN, Grafana
admin password).
2. kubectl apply -f k8s/namespace.yaml, then
kubectl apply -f k8s/secrets/secrets.yaml, then kubectl apply -k k8s/
3. Car: install the car build once by hand; after that build-car, then
publish-car (or copy the APK and .sha256 to a USB stick), then the
buttons on the Settings tab.
Testing
- App unit tests (stable and car build) and lint pass; server tests
pass and govulncheck is clean; manifests validate offline.
- End to end in podman: server with InfluxDB (telemetry, pairing, phone
settings), Grafana with the init-container token; release relay
against a fake Gitea.
- Emulator (Android 9, 1920x720 and 1778x640): layouts, tabs, accent
colours, config import, queue restore, USB install.
- On the car: pairing, live values, voice key, full screen, screen
size. Not yet on the car: direct ABRP sending, Enter on phone, accent
colours, 0.5 s refresh, over-the-air and USB updates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
AGENTS.md still described upstream's EVABRPUploader (GitHub updates,
the old architecture and tabs). It is now the entry point for agents:
what the project is (app, Go server, k8s, EVHardware), the rules
(nothing touches the cluster, secrets, read-only telemetry, null is not
zero, fail closed, honesty), the facts confirmed on the car, the app's
structure and UI conventions, how to build, test, publish and install,
and which document holds what. SPECS.md stays the journal.
The Android 9 test emulator, so far a file under the ignored build/,
is now reproducible: nix run .#car-emulator creates an AVD at
1920x720/160 dpi with the AOSP image that shares the car's platform key
and starts it on port 5560. Checked from scratch: the car build
installs as the system user.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki