01 / The Idea
A polished, ignition-controlled wireless CarPlay head unit for the DB9, built on a Raspberry Pi 5 and a custom dark theme over react-carplay. The design goal was a seamless single-logo experience: from the moment the ignition goes live to the live CarPlay session, the screen shows one continuous Aston Martin logo — no black frames, no white flashes, no console text, no "No Signal" OSD. The same discipline applies at shutdown: the AM splash returns and holds until power is cut.
Live in car ✓ Installed and running in the car since 29 April 2026 — ignition boot/shutdown, breathing splash chain, per-stream volume mixer, knob control, wireless CarPlay and DAC audio all confirmed working in-car.
02 / Hardware
| Item | Role / notes |
| Raspberry Pi 5 + NVMe SSD | NVMe boot via CarPiHat slot; RP1 GPIO needs lgpio/rpi-lgpio |
| CarPiHat Pro v1.1 (Pi 5 variant) | Ignition-controlled power latch, Burr-Brown DAC, NVMe slot, switched 12 V outputs |
| Carlinkit CPC200-Autokit | USB dongle — wireless CarPlay bridge to the iPhone |
| 1024×600 HDMI display | Vehicle-installed panel; 5 V power switched via CarPiHat OUT_0 + buck converter |
| Programmable rotary knob | Cheap "MINI_KEYBOARD" rotary, USB-C wired, mapped to react-carplay's default key bindings |
| USB microphone | In-car Siri input; phone calls stay on the car's factory Bluetooth kit |
| Wiring | Permanent 12 V → BAT · switched 12 V (ignition) → IGN · GND → chassis |
Key CarPiHat GPIO assignments (BCM)
| BCM | Direction | Function |
| 25 | OUT | Power latch — held HIGH from firmware stage (gpio=25=op,dh) |
| 12 | IN | Ignition sense (opto-isolated 12 V) |
| 27 | OUT | OUT_0 — delayed monitor power (OUT_1/BCM22 didn't switch in testing; BCM27 did) |
03 / The Boot Story
What the car experience looks like, power-on to CarPlay:
- 0 – 3 sKernel + userspace boot on NVMe (~3 s per systemd-analyze). No display output yet — the panel is deliberately held off (see below).
- ~3 sPlymouth paints the centred AM logo on black (custom aston.plymouth theme, letterboxed 1024×600 splash).
- ~11 sMonitor power enabled via CarPiHat OUT_0 — the panel wakes to an already-valid HDMI signal, so no "No Signal" OSD ever appears.
- ~12 ssway takes the framebuffer with the same AM image as wallpaper; Electron opens with a root-level <BootCover/> painting the identical splash from the very first React frame.
- ~12 – 38 sThe logo "breathes" (2 s opacity animation, 1.0 → 0.60 → 1.0) while Electron starts and the phone handshake completes over the Carlinkit dongle.
- ~38 sThe render worker signals firstFrame on its first successful draw — the splash crossfades out (500 ms) exactly when CarPlay renders its first real pixel. No guess-timers.
Shutdown mirrors it: ignition off starts a 10 s grace timer, then the shutdown daemon signals the app over socket.io — the AM splash returns, holds for 5 s (during which turning the ignition back on aborts the shutdown and CarPlay resumes), then a clean systemctl poweroff runs with the splash held until the CarPiHat cuts 5 V. Boot is fully automatic on ignition — no power button anywhere.
04 / Audio — Per-Stream Volume Mixer
The CarPiHat's Burr-Brown DAC runs as ALSA card 0 (HDMI audio disabled via vc4-kms-v3d,noaudio), streaming 48 kHz / S32_LE during CarPlay. On top of that sits a custom per-stream volume mixer matching commercial head units: adjusting volume during navigation speech changes nav volume, not music.
- — Independent persisted gains per logical stream: media, navigation, call, other (0.0–1.5, nav defaults boosted to 1.2)
- — Streams tagged by CarPlay audio command (AudioNaviStart → navigation, etc.); active stream chosen by priority call > nav > media with a 500 ms stale window
- — Context-aware on-screen pill OSD labels the active stream; amber "boost" overlay above gain 1.0
- — Rotary knob layer 2: rotate = volume up/down, press = mute; layer 1 keeps the stock nav/media key bindings
⚠ Bug worth remembering: PcmPlayer.volume(v, duration) takes duration in seconds (it forwards to WebAudio's setTargetAtTime). Passing 200 (intending ms) schedules the volume ramp 200 seconds into the future — which presents as "volume control does nothing".
05 / Hard-Won Lessons
On the Pi 5 the VideoCore boot firmware lives in write-protected SPI EEPROM as a closed Broadcom blob — the rainbow splash is generated algorithmically, not stored as an image. No tool or fork can swap it. The real fix for the "No Signal" OSD was the opposite of intuition: re-enable the rainbow (disable_splash=0) so the panel gets a valid HDMI signal from ~1 s, then hide the early boot entirely by delaying the monitor's own power rail.
X11's root-window stipple caused a visible white flash on every boot; Wayland has no equivalent. cage was then swapped for sway because sway's seat * hide_cursor 1 kills the 1–2 s default mouse pointer cage showed at startup. Electron runs with --in-process-gpu, which dodges a dma-buf crash the separate GPU sandbox process hits on Pi 5 + Bookworm Wayland.
NVMe-with-restart-loop skips USB/SD probing and saves ~1 s pre-kernel. 0x1 looks like "first boot device" but actually means SD card — an easy footgun. Combined with initial_turbo=60, a pre-extracted AppImage, direct systemd kiosk service and a tmpfs Electron profile, measured kernel+userspace is ~3 s.
The rotary knob advertises a fake Apple vendor ID (05AC:022C) over Bluetooth, which trips the hid-apple driver and needs experimental bluez flags, udev seat tags and hotplug handling — too brittle for a car. USB-C wired is rock solid, with the cable routed inside the dash.
A previous theming attempt destabilised the upstream app because edits spread through the codebase. The rebuild keeps all customisation to three new files plus three small wiring edits, applied by a single overlay script — so rebasing onto new upstream react-carplay releases stays painless.
Originally the boot splash faded on a hard-coded timer after the phone connected — safe but imprecise. The proper fix was instrumenting the render worker to post a firstFrame message on its first successful draw, so the splash disappears exactly when CarPlay's first real pixel lands.
06 / In-Car Status & Known Issues
Everything bench-tested was confirmed working in the car: ignition boot and shutdown, the continuous splash chain, per-stream mixer, knob control, wireless CarPlay and DAC audio, delayed monitor power.
⚠ Known issue: an occasional BAT-rail power glitch (mechanical connection, not software) can brownout-latch the Pi 5 SoC — solid red LED, needing a BAT power cycle to recover. Planned fix: a hidden normally-closed pushbutton inline with the BAT feed as a recovery mechanism.
Parked / future ideas
- — Chromium kiosk port: plan written; realistic 6–10 s boot saving for ~2 weeks of work
- — iDrive-style CAN-bus rotary controller: CarPiHat has a CAN header + MCP2515; a small daemon could translate BMW iDrive messages to input events
- — Higher DAC sample rates for non-CarPlay audio paths
AstonPi · Raspberry Pi 5 CarPlay head unit · live in car 29-04-2026
Owner's log