docs: FEAT-05 DTC sync shipped — multi-device selector, portal DTC page, Pi snapshot upload
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
# yaris-mediacenter — Bluetooth Audio Bridge for the Car
|
||||
|
||||
**Status:** `PLANNING` — do not start building without owner approval.
|
||||
|
||||
A new Pi-side service that acts as a transparent Bluetooth middleman between a phone
|
||||
(Spotify, any audio app) and the car radio. The Pi broadcasts as a Bluetooth A2DP sink
|
||||
that the phone connects to (phone thinks it's the car stereo), receives the audio stream,
|
||||
then re-broadcasts it as an A2DP source that the car radio connects to. Media controls
|
||||
(play, pause, skip, volume) relay bidirectionally through the Pi via AVRCP.
|
||||
|
||||
The car radio sees a normal Bluetooth audio device. The phone sees a normal Bluetooth
|
||||
speaker. The Pi sits invisibly in the middle, bridging them.
|
||||
|
||||
---
|
||||
|
||||
## The Problem (why this exists)
|
||||
|
||||
- The 2013 Yaris radio likely has either no Bluetooth at all, or an older Bluetooth
|
||||
module that only supports hands-free calling (HFP), not music streaming (A2DP).
|
||||
- Even if the radio does support Bluetooth A2DP, there's no way to have the phone
|
||||
simultaneously connected to the radio for audio AND the Pi for the local web UI
|
||||
over WiFi without manual reconnection each time.
|
||||
- This service makes the Pi the one permanent Bluetooth connection for the radio,
|
||||
and the phone connects to the Pi instead — the Pi becomes the car's infotainment hub.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌──────────┐ Bluetooth A2DP/AVRCP ┌──────────┐ Bluetooth A2DP/AVRCP ┌───────────┐
|
||||
│ Phone │ ──────────────────────────▶ │ Pi │ ──────────────────────────▶ │ Car Radio │
|
||||
│ (Spotify)│ ◀────────────────────────── │(mediacntr)│ ◀────────────────────────── │ │
|
||||
│ │ audio stream (SBC/AAC) │ │ re-encoded audio (SBC) │ │
|
||||
│ │ media controls (AVRCP) │ │ media controls (AVRCP) │ │
|
||||
└──────────┘ └──────────┘ └───────────┘
|
||||
│
|
||||
WiFi hotspot (existing)
|
||||
"yaris 911" @ 10.42.0.1
|
||||
│
|
||||
┌──────────────────────┐
|
||||
│ Existing yaris-obd UI │
|
||||
│ (dashboard, dashcam, │
|
||||
│ status — unchanged) │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
### Bluetooth profiles involved
|
||||
|
||||
| Profile | Role (Phone side) | Role (Radio side) | Purpose |
|
||||
|---------|-------------------|-------------------|---------|
|
||||
| **A2DP** | Pi is **Sink** (receives audio from phone) | Pi is **Source** (sends audio to radio) | Stereo audio streaming |
|
||||
| **AVRCP** | Pi is **Target** (receives commands from phone) | Pi is **Controller** (sends commands to radio) | Play/pause/skip/track metadata relay |
|
||||
|
||||
### Audio pipeline
|
||||
|
||||
```
|
||||
Phone → [Bluetooth A2DP Sink] → PulseAudio source → PulseAudio loopback → PulseAudio sink → [Bluetooth A2DP Source] → Radio
|
||||
```
|
||||
|
||||
- BlueZ registers the Pi as a connectable A2DP sink (for the phone).
|
||||
- BlueZ also connects the Pi to the car radio as an A2DP source.
|
||||
- PulseAudio bridges the two: `module-loopback` routes audio from the phone's A2DP
|
||||
source (in PulseAudio) to the radio's A2DP sink (in PulseAudio).
|
||||
- No audio file is ever written to disk — it's a live stream pipeline, minimal latency.
|
||||
|
||||
---
|
||||
|
||||
## Hardware Considerations
|
||||
|
||||
### Pi 3's built-in Bluetooth
|
||||
|
||||
The Pi 3's BCM43438 combo chip provides both WiFi and Bluetooth on the same physical
|
||||
radio. This has two implications:
|
||||
|
||||
1. **Coexistence:** 2.4 GHz WiFi (especially the AP/hotspot on wlan0) shares spectrum
|
||||
with Bluetooth. This can cause audio dropouts. Mitigations:
|
||||
- Bluetooth uses adaptive frequency hopping (AFH), which avoids WiFi channels — this
|
||||
is built into the BCM43438 firmware and normally works well.
|
||||
- If dropouts are a problem in practice, a **USB Bluetooth 5.0 dongle** (~$10) can
|
||||
offload the phone or radio connection to a separate radio entirely, eliminating
|
||||
WiFi/BT contention.
|
||||
- The Phase 23.5 wlan1 (TP-Link Archer T3U, 5 GHz-capable) is already installed —
|
||||
moving the hotspot to 5 GHz would completely eliminate 2.4 GHz WiFi/Bluetooth
|
||||
interference. This is a configuration change, not new hardware.
|
||||
|
||||
2. **Disable-bt overlay:** The Pi currently uses `dtoverlay=disable-bt` in
|
||||
`/boot/firmware/config.txt` to free the PL011 UART for the SIM868 GPS module (see
|
||||
Phase 1 bring-up notes). **This completely disables the onboard Bluetooth
|
||||
controller.** To use Bluetooth for mediacenter:
|
||||
- **Option A:** Use a USB Bluetooth dongle instead (preserves GPS UART routing,
|
||||
cleaner separation, likely better range/audio quality with BT 5.0).
|
||||
- **Option B:** Use the mini-UART (`/dev/ttyS0`) for the SIM868 at a lower,
|
||||
core-frequency-dependent baud rate, re-enable the onboard Bluetooth for the
|
||||
PL011 UART. Riskier — mini-UART is less stable and baud rate depends on the
|
||||
core clock, which can drift.
|
||||
|
||||
**Recommendation: Option A (USB dongle).** The onboard Bluetooth is disabled for
|
||||
GPS, and a USB dongle is cheap, isolated, and doesn't require touching the
|
||||
already-working GPS UART config.
|
||||
|
||||
---
|
||||
|
||||
## Phases
|
||||
|
||||
### Phase M1: Bluetooth stack validation (research spike)
|
||||
**Goal:** Prove the Pi can simultaneously be an A2DP sink + A2DP source with
|
||||
audio passing through, before writing any production code.
|
||||
|
||||
- [ ] Install BlueZ + PulseAudio + `pulseaudio-module-bluetooth` on the Pi.
|
||||
- [ ] Pair and connect the Pi as an A2DP sink from a real phone.
|
||||
- Verify `pactl list sources` shows the phone's A2DP stream as an audio source.
|
||||
- [ ] Pair and connect the Pi as an A2DP source to a real Bluetooth speaker/radio.
|
||||
- Verify `pactl list sinks` shows the radio/speaker as an audio sink.
|
||||
- [ ] Set up a PulseAudio loopback: `pactl load-module module-loopback source=<phone_source> sink=<radio_sink>`.
|
||||
- [ ] Play audio on the phone, confirm it comes out of the radio/speaker.
|
||||
- Measure rough latency (video-sync test: watch a video on the phone, observe lip sync).
|
||||
- [ ] Test AVRCP relay: play/pause/skip from the radio's controls should affect the phone.
|
||||
- This may require a custom DBus script (BlueZ exposes `org.bluez.MediaPlayer1` on
|
||||
both the sink and source sides — commands received on the source side need to be
|
||||
forwarded to the target/sink side).
|
||||
- [ ] Document: what exact BlueZ/PulseAudio commands, what worked, what didn't, latency
|
||||
numbers, codec info, audio quality observations.
|
||||
|
||||
**Estimated time:** 2-3 hours of hands-on Pi tinkering.
|
||||
**Deliverable:** A markdown file in `poc/` with raw terminal logs, commands, and findings.
|
||||
|
||||
**Go/No-go decision after M1:** If audio passes through with acceptable latency (< ~300ms
|
||||
additional) and AVRCP relay is feasible via DBus scripts, proceed to M2. If the onboard
|
||||
Bluetooth is too flaky or latency is unusable, re-evaluate USB dongle or shelve.
|
||||
|
||||
---
|
||||
|
||||
### Phase M2: `yaris-mediacenter` service scaffold
|
||||
**Goal:** Package the M1 proof-of-concept into a proper Python service that auto-starts
|
||||
alongside `yaris-obd`, with persistent configuration.
|
||||
|
||||
- [ ] Create `modules/app_mediacenter.py`:
|
||||
- `BluetoothBridge` class managing the dual-role Bluetooth session.
|
||||
- Config loaded from `config/mediacenter.json` (device names, PIN codes, auto-connect
|
||||
MAC addresses, codec preferences).
|
||||
- Subprocess management of `bluetoothctl`, `pactl`, `parec`/`pacat` or direct DBus
|
||||
calls via `pydbus`/`dasbus` to avoid shelling out.
|
||||
- Health check: are both connections alive? Reconnect logic on disconnect.
|
||||
- [ ] PulseAudio loopback management:
|
||||
- Create/destroy the loopback module programmatically.
|
||||
- Handle dynamically appearing/disappearing sources and sinks (phone connects, radio
|
||||
connects — the loopback needs to adapt).
|
||||
- [ ] AVRCP relay via DBus:
|
||||
- Listen for `org.bluez.MediaPlayer1.PropertiesChanged` on the source side (radio).
|
||||
- Forward `Play`, `Pause`, `Next`, `Previous`, `FastForward`, `Rewind` to the target
|
||||
side (phone).
|
||||
- Relay track metadata (Title, Artist, Album, Duration) from the phone to the radio
|
||||
side — the radio's display can show "now playing" info.
|
||||
- [ ] systemd unit: `deploy/yaris-mediacenter.service`
|
||||
- Requires `bluetooth.service` and `pulseaudio.service` (user-mode pulseaudio for
|
||||
the `yaris` user).
|
||||
- `Wants=yaris-obd.service` (co-located, not strictly dependent).
|
||||
- [ ] Install script: `deploy/install_mediacenter.sh`
|
||||
- Installs required apt packages (BlueZ, PulseAudio, pulseaudio-module-bluetooth,
|
||||
Python DBus bindings).
|
||||
- Configures PulseAudio for the `yaris` user (systemd user service).
|
||||
- Enables Bluetooth discoverable/connectable mode permanently.
|
||||
- [ ] UI: minimal status indicator on the existing `/status` page:
|
||||
- Phone connected? (MAC, device name, codec)
|
||||
- Radio connected? (MAC, device name, codec)
|
||||
- Audio flowing? (PulseAudio loopback state)
|
||||
- Latency estimate.
|
||||
|
||||
**Deliverable:** Running service on the Pi, phone → Pi → radio audio chain verified.
|
||||
**Dependencies:** M1 must be complete (Bluetooth stack proven).
|
||||
|
||||
---
|
||||
|
||||
### Phase M3: Pairing & auto-connect UX
|
||||
**Goal:** Make pairing practical — not a manual `bluetoothctl` sequence every time
|
||||
the car starts.
|
||||
|
||||
- [ ] **Phone pairing (persistent):**
|
||||
- Pi always discoverable as "yaris-mediacenter" (or configurable name).
|
||||
- First-time phone pairing is manual (phone scans, finds Pi, pairs — standard).
|
||||
- Pi remembers paired phones via BlueZ's trusted-device store.
|
||||
- Auto-connect: when a trusted phone comes in range, Pi automatically accepts
|
||||
the A2DP connection. No user action needed after the first pairing.
|
||||
|
||||
- [ ] **Radio pairing (persistent):**
|
||||
- If the car radio supports Bluetooth scanning, the radio finds "yaris-media"
|
||||
(separate name from phone side — the two A2DP roles need distinct names).
|
||||
- Pair once, Pi remembers the radio's MAC.
|
||||
- Auto-connect: on service start, Pi actively connects to the radio's MAC as
|
||||
an A2DP source (the radio is the "speaker" from the Pi's perspective).
|
||||
|
||||
- [ ] **Reconnection ordering:**
|
||||
- On ignition-on (Pi boots): mediacenter starts → connects to radio first
|
||||
(A2DP source) → then enables discoverability for the phone (A2DP sink).
|
||||
- If radio isn't in range yet (radio boots slower than the Pi), retry with
|
||||
exponential backoff.
|
||||
- If phone disconnects (user walks away), audio pipeline gracefully pauses,
|
||||
reconnects when phone returns.
|
||||
|
||||
- [ ] **Settings page** (new section on existing `/settings`):
|
||||
- Radio MAC address (manual entry or scan-and-select).
|
||||
- Phone-side Bluetooth device name.
|
||||
- Radio-side Bluetooth device name.
|
||||
- Auto-connect toggle (on/off).
|
||||
- Preferred codec (auto / SBC / AAC if supported).
|
||||
|
||||
- [ ] **Quick-access control widget:**
|
||||
- A small "Now Playing" card on the dashboard showing track metadata
|
||||
(Title, Artist) when audio is flowing.
|
||||
- Play/pause/skip buttons (passenger-safe, not for driver use while moving).
|
||||
|
||||
**Deliverable:** Phone and radio pair once, reconnect automatically every drive.
|
||||
**Dependencies:** M2 service scaffold complete.
|
||||
|
||||
---
|
||||
|
||||
### Phase M4: Audio quality & latency tuning
|
||||
**Goal:** Make it sound good and feel responsive.
|
||||
|
||||
- [ ] **Codec selection:**
|
||||
- Phone → Pi: negotiate the best available codec (AAC preferred on iPhone,
|
||||
aptX if the USB dongle supports it, SBC as fallback).
|
||||
- Pi → Radio: most car radios only support SBC. SBC bitpool tuning (higher
|
||||
bitpool = better quality, more bandwidth).
|
||||
- Document negotiated codecs per connection in the status endpoint.
|
||||
|
||||
- [ ] **Latency measurement & reduction:**
|
||||
- Measure end-to-end latency (phone speaker → Pi → radio speaker) with a
|
||||
clap test or audio sync video.
|
||||
- Tune PulseAudio buffer sizes (`latency_msec` in `module-loopback`).
|
||||
- If latency > 500ms, consider:
|
||||
- Smaller Bluetooth packet sizes.
|
||||
- Lower PulseAudio fragment sizes.
|
||||
- Direct ALSA loopback (bypass PulseAudio entirely) as a fallback path.
|
||||
|
||||
- [ ] **Audio glitch handling:**
|
||||
- Detect buffer underruns (audio dropouts), log them.
|
||||
- Automatic recovery: if the loopback module dies, recreate it.
|
||||
- Graceful handling of temporary Bluetooth interference.
|
||||
|
||||
- [ ] **Volume passthrough:**
|
||||
- Phone volume → Pi's A2DP sink volume → Pi's A2DP source volume → Radio volume.
|
||||
- Keep the chain linear: phone volume controls the full path, radio volume
|
||||
is a secondary hardware trim.
|
||||
- AVRCP absolute volume if the radio supports it.
|
||||
|
||||
**Deliverable:** Measured latency numbers, acceptable audio quality.
|
||||
**Dependencies:** M3 connection stability.
|
||||
|
||||
---
|
||||
|
||||
### Phase M5: Hands-free calling (HFP) relay (stretch goal)
|
||||
**Goal:** If the car radio has Bluetooth but only supports HFP (hands-free calling),
|
||||
not A2DP (music), the Pi can bridge phone calls too. If the radio has NO Bluetooth
|
||||
at all, this does nothing — the Pi would need a mic/speaker connected directly,
|
||||
which is a different scope.
|
||||
|
||||
- [ ] Pi acts as HFP Audio Gateway (AG) for the radio.
|
||||
- [ ] Pi acts as HFP Hands-Free (HF) for the phone.
|
||||
- [ ] Call audio relay: same PulseAudio loopback pattern.
|
||||
- [ ] Call control relay: answer, hang up, dial.
|
||||
- [ ] Phonebook access relay (PBAP) — the radio can show incoming caller ID.
|
||||
|
||||
**This phase is explicitly optional** — most users will use A2DP music streaming,
|
||||
and HFP relay adds significant complexity (SCO audio routing, AT command handling,
|
||||
call state machine). Only build if M1-M4 are all solid and the user explicitly
|
||||
requests it.
|
||||
|
||||
---
|
||||
|
||||
### Phase M6: Spotify Connect / media source integration (stretch goal)
|
||||
**Goal:** Instead of the phone streaming Bluetooth audio, run a lightweight Spotify
|
||||
client directly on the Pi so the phone is only a remote control, not the audio
|
||||
source. This eliminates the phone→Pi Bluetooth hop entirely.
|
||||
|
||||
- [ ] Install `spotifyd` or `librespot` on the Pi (headless Spotify Connect receiver).
|
||||
- [ ] Route Spotify audio directly to the A2DP source (Pi → Radio), no loopback needed.
|
||||
- [ ] Phone uses the Spotify app as a remote (Spotify Connect protocol — built into
|
||||
the Spotify app, no custom code needed).
|
||||
- [ ] Fallback: if the user isn't playing Spotify (e.g., YouTube, podcasts, navigation
|
||||
voice), fall back to the Bluetooth A2DP relay from M1-M3.
|
||||
|
||||
**Trade-off vs. M1-M3 approach:**
|
||||
- **Pro:** One fewer Bluetooth hop = less latency, better audio quality, lower battery
|
||||
drain on the phone (phone just sends control commands, not an audio stream).
|
||||
- **Con:** Only works for Spotify. Any other audio app (Apple Music, YouTube, podcast
|
||||
apps, navigation) still needs the Bluetooth relay.
|
||||
- **Con:** `spotifyd`/`librespot` requires a Spotify Premium account and the Pi needs
|
||||
internet access (phone hotspot uplink via wlan1 — already built in Phase 22).
|
||||
|
||||
**Recommendation:** Build M1-M3 (Bluetooth relay) first, since it works for ALL audio.
|
||||
Add M6 later as an optimization for Spotify specifically.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions (to resolve during M1)
|
||||
|
||||
| # | Question | Impact |
|
||||
|---|----------|--------|
|
||||
| Q1 | Does the 2013 Yaris radio have Bluetooth at all? If so, which profiles? | Defines what the Pi needs to broadcast (A2DP vs HFP only) |
|
||||
| Q2 | Can the Pi's BCM43438 (or a USB dongle) maintain two simultaneous Bluetooth connections (phone + radio) without audio dropouts? | Determines if USB dongle is required |
|
||||
| Q3 | What is the real end-to-end latency (phone → Pi → radio)? | Determines if this is usable for video/navigation or music-only |
|
||||
| Q4 | Does `dtoverlay=disable-bt` permanently block the onboard Bluetooth, or can it be re-enabled while keeping the SIM868 on mini-UART? | Determines if USB dongle is mandatory |
|
||||
| Q5 | Does the car radio support AVRCP metadata display (track title/artist on the radio screen)? | Determines if Phase M2 metadata relay is worth building |
|
||||
|
||||
---
|
||||
|
||||
## Deployment Impact
|
||||
|
||||
- **New apt packages:** `pulseaudio`, `pulseaudio-module-bluetooth`, `bluez-tools`,
|
||||
Python `pydbus` or `dasbus`.
|
||||
- **New systemd units:** `yaris-mediacenter.service` (possibly a user unit for
|
||||
PulseAudio).
|
||||
- **New config files:** `config/mediacenter.json`.
|
||||
- **Existing services:** no changes to `yaris-obd.service`, GPS, OBD, WiFi, or
|
||||
uplink paths. Mediacenter is an independent service that happens to run on the
|
||||
same Pi.
|
||||
- **WiFi/Bluetooth coexistence:** if using a USB dongle on a USB 3.0 port, be aware
|
||||
of USB 3.0 2.4 GHz interference (this is a known hardware issue with Pi 4; Pi 3
|
||||
only has USB 2.0, so not a concern here).
|
||||
|
||||
---
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
| Risk | Likelihood | Impact | Mitigation |
|
||||
|------|-----------|--------|------------|
|
||||
| Audio latency too high for video/nav | Medium | High | Phase M1 validates early; M6 (Spotify Connect) bypasses it for music |
|
||||
| Bluetooth coexistence with WiFi hotspot causes dropouts | Medium | Medium | USB dongle + 5 GHz WiFi on wlan1 (already available) |
|
||||
| `disable-bt` overlay blocks all Bluetooth | High (confirmed) | Low | USB dongle is the planned path anyway |
|
||||
| AVRCP relay via DBus is unreliable or incomplete | Medium | Medium | Fall back to phone-side controls only; M1 validates |
|
||||
| Car radio has no Bluetooth at all | ? | High | M1 clarifies; if no BT radio, this project needs a different approach (FM transmitter? aux cable? replace the head unit?) |
|
||||
|
||||
---
|
||||
|
||||
## Go/No-Go Checklist
|
||||
|
||||
Before starting Phase M2 (committed code):
|
||||
- [ ] M1 research spike complete with real hardware.
|
||||
- [ ] Latency measured and deemed acceptable by owner.
|
||||
- [ ] Audio quality confirmed acceptable.
|
||||
- [ ] Both A2DP sink + source roles confirmed working simultaneously.
|
||||
- [ ] Owner explicit approval to proceed past M1.
|
||||
Reference in New Issue
Block a user