# 06 — iPhone-Telefonate über Bluetooth (HFP) Das Board gibt sich gegenüber dem iPhone als **Bluetooth-Headset** aus („Aime“, AX210/`hci0`). iPhone-Anrufe laufen dadurch über das Board: Gegenseite → Aufnahme-Mix, Nutzer-Mikrofon (gegated) → iPhone-Uplink. Läuft **parallel** zum PC/USB-Gadget-Pfad — kein Umschalten; bei eingehendem Anruf pausiert der Nutzer sein PC-Video manuell. ## Konfiguration (die funktionierende Kombination) - WirePlumber-Drop-in: `bluez5.roles = [a2dp_sink hfp_hf hsp_hs]` + `hfphsp-backend = native` (oFono war ein Irrweg, deaktiviert). - **Frischer Pair nötig** (auf dem iPhone „Gerät ignorieren“, dann neu koppeln) — alte Bonds machen Ärger. - iPhone „Skynet16“ (B4:96…), bt-agent NoInputNoOutput, Gerät muss **Trusted=yes** sein (nach Re-Pair stand es auf NO → WirePlumber baut für untrusted Geräte keine HFP-Nodes → Aufnahme #58 hatte nur die Nahseite). - Einziger Verzicht: BT-Ohrhörer als Aufnahmegerät (AG-Rolle) ist aus — Board ist HF-only. - `profile="off"` zwischen Anrufen ist **normal**: HFP-Nodes existieren nur während eines aktiven Anrufs (eSCO/mSBC, 16 kHz). ## Audio-Routing während eines Anrufs ```mermaid flowchart LR subgraph IPHONE["iPhone (im Anruf)"] CALL["Telefonat"] end subgraph BOARD["Board (BT 'Aime')"] FAR["bluez_input.MAC.0
= Gegenseite"] UP["bluez_output.MAC.1
= Uplink zum iPhone"] MIX[("callmix")] GATE["micgate.out (gegatetes Mikro)"] HS["Headset-Hörer"] end CALL --> FAR FAR --> MIX FAR --> HS GATE --> UP --> CALL ``` **Echo-Fix:** WirePlumber verlinkte den Uplink zunächst mit `callrec_mix` (ungegatetes Mikro + Gegenseite selbst!) → Gegenseite hörte sich selbst. Korrekt: Uplink ← **micgate.out**. Vom Nutzer bestätigt echofrei. Gegenseite → **Headset-Hörer** macht WirePlumber selbst (Default-Sink); der Watcher baut diesen Link **nicht**. `callmix` ist eine reine Aufnahme-Senke ohne Ausgang — Gegenseite → `callmix` macht die Gegenseite also aufnehmbar, nicht hörbar. ## Automatik ```mermaid sequenceDiagram participant IP as iPhone participant BZ as BlueZ/WirePlumber participant W as aime-iphone-route.sh (Watcher, 1 s Takt) participant PW as PipeWire IP->>BZ: Anruf beginnt (eSCO aufgebaut) BZ->>PW: HFP-Nodes erscheinen — NICHT atomar, Uplink oft vor Gegenseite BZ->>PW: Gegenseite → Headset-Hörer (WirePlumber-Default) loop jede Sekunde, solange Uplink-Node existiert W->>PW: Portnamen ermitteln (HFP = mono, playback_MONO/capture_MONO) W->>PW: Uplink ← micgate.out verlinken, falls fehlt W->>PW: nur wenn micgate BESTÄTIGT anliegt: callrec_mix → Uplink trennen W->>PW: Gegenseite → callmix verlinken, falls fehlt (Node kommt später!) end IP->>BZ: Anruf endet BZ->>PW: HFP-Nodes verschwinden (profile=off — normal) ``` - Watcher persistent: `/opt/callrec/aime-iphone-route.sh` + `aime-iphone-route.service`; überlebt Reboots (Config/Service/Bond+Trusted verifiziert). - Der Watcher-Neustart lässt callmix/Loopbacks intakt. - **Jeder Link wird in JEDEM Durchlauf geprüft, nicht einmal pro Anruf** — siehe Changelog 2026-07-30. - **Portnamen werden ermittelt, nicht hartcodiert.** HFP/mSBC ist mono, die Ports heißen `playback_MONO`/`capture_MONO`, nicht `input_FL`/`input_FR`. - **Erst verlinken, dann trennen.** `callrec_mix → Uplink` fliegt nur raus, wenn `micgate.out` am Uplink-Port bestätigt anliegt — sonst bleibt der Uplink ohne Quelle und die Gegenseite hört Stille. - ⚠️ **`pw-link -l ` filtert NICHT.** Bei einem Namen, der nicht als *Port* auflösbar ist, kommt der **gesamte Graph** zurück. Jedes `grep` darauf ist immer wahr. Link-Zustand deshalb nur per `links_of` (exakter Port-Header aus einem Graph-Dump) prüfen, nie per `grep` über die ganze Ausgabe. ## Kontrolle nach einem Anruf `journalctl -t aime-iphone-route` — die eine Zeile sagt alles: | Ausgabe | Bedeutung | |---|---| | `+micgate -callrec_mix +far-end` | korrekt geroutet | | keine Zeile | Watcher hat die HFP-Nodes nicht gesehen → Aufnahme hat nur die Nahseite | | nur `-callrec_mix` | Uplink ohne Quelle → **Gegenseite hört nichts** (v2-Bug, behoben) | | `!no-micgate-uplink-keeping-callrec_mix` | Uplink hörbar, aber mit Echo — micgate-Link ging nicht | | `(no-far-end-node-yet)` | unkritisch, wenn eine spätere Zeile `+far-end` zeigt | ## Offen - App-seitiger automatischer Aufnahme-Start bei iPhone-Anrufen. - Voller E2E-Test „Anruf → Aufnahme → Analyse“ über den BT-Pfad. ## Referenz: BT-Bond-Quirks (Ohrhörer, historisch) Buds mit No-Bonding-Quirk: LinkKey musste manuell in `/var/lib/bluetooth` persistiert werden (btmon-Capture-Prozedur). RTL8821CU-Wedge-Recovery (USB-Reset, nur über LAN .50) — betrifft den alten Dongle, der entfernt wurde; nur noch relevant, falls die Hardware zurückkommt.