Files
yaris-obd/poc/yaris-mediacenter-plan.md
T
Mathieu Moran e19511fc67 fix: downloads use WorkManager foreground service + inline progress
- DownloadWorker: foreground service with notification + setProgressAsync
- ActiveDownload state tracked via WorkManager LiveData observation
- Downloads tab shows progress bar + % for in-flight downloads
- Survives screen lock / background
- Fixed FGS type for Android 14+ (FOREGROUND_SERVICE_TYPE_DATA_SYNC)
2026-08-12 02:27:33 -04:00

352 lines
18 KiB
Markdown

# 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~~ — **CONFIRMED: owner has a USB
Bluetooth dongle on hand.** This preserves GPS UART routing, gives cleaner
separation (no WiFi/BT combo-chip contention), and likely provides better
range/audio quality.
- ~~**Option B:** Use the mini-UART~~ — ruled out. No need to touch 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.
---
## Hardware Status
| Component | Status |
|-----------|--------|
| USB Bluetooth dongle | ✅ Confirmed — owner has one on hand |
| Car radio Bluetooth capability | ❓ Unknown — Q1 (does the 2013 Yaris radio have Bluetooth/A2DP?) |
| Test Bluetooth speaker (for M1 validation) | ❓ Unknown — needed to simulate radio side if car radio has no BT |
| Pi 3 USB port availability | ⚠️ Check — already has PL2303 OBD adapter + TP-Link T3U WiFi + possibly E3372 LTE modem; may need a powered USB hub |
---
## 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?~~ | ✅ Resolved — USB dongle confirmed available, onboard BT stays disabled for GPS |
| 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 onboard Bluetooth | High (confirmed) | Low | ✅ USB dongle confirmed available — onboard BT stays disabled, dongle handles mediacenter |
| 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.