Files
Aime/docs/06-iphone-bt.md
schakal00783 c09827bf86 fix(iphone-bt): v3 — pw-link -l filtert nicht, v2 machte den Uplink stumm
Testanruf 13:51 loggte nur "-callrec_mix", kein +micgate und kein +far-end: v2 hat
WirePlumbers Link getrennt und dann nichts verlinkt, der Uplink hatte keine Quelle
und die Gegenseite hörte Stille.

Ursache: `pw-link -l <node>` filtert nicht. Mit einem Node-Namen, der nicht als
Port auflösbar ist, kommt der ganze Graph zurück — und der enthält immer
micgate.out und callmix. Also war jedes "ist das schon verlinkt?"-grep wahr und nur
das Trennen lief. Derselbe Ausdruck stand schon in v1, dort aber hinter dem
Einmal-Guard, der nie erreicht wurde.

v3:
- links_of PORT parst die Peers eines exakten Ports aus einem Graph-Dump (awk),
  kein grep mehr über die gesamte Ausgabe.
- Erst verlinken, dann trennen: callrec_mix->Uplink fliegt nur raus, wenn
  micgate.out am Uplink-Port bestätigt anliegt. Sonst bleibt der WirePlumber-Link
  und es wird !no-micgate-uplink-keeping-callrec_mix geloggt. Ein Echo ist
  schlecht, unhörbar sein ist schlimmer.
- Log-Zustand als Array statt String: " " hätte -n passiert und bei fertig
  geroutetem Anruf jede Sekunde eine Journal-Zeile geschrieben.

Getestet: links_of gegen den echten Graph (verlinkt/unverlinkt/nicht existent);
Entscheidungslogik gegen einen synthetischen Mono-HFP-Graph — Default-Zustand,
fertig geroutet (No-op), micgate nicht bestätigt (behält callrec_mix).

Gegenprobe am echten Anruf steht noch aus.
2026-07-30 15:58:07 +02:00

4.9 KiB

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

flowchart LR
    subgraph IPHONE["iPhone (im Anruf)"]
        CALL["Telefonat"]
    end
    subgraph BOARD["Board (BT 'Aime')"]
        FAR["bluez_input.MAC.0<br/>= Gegenseite"]
        UP["bluez_output.MAC.1<br/>= 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

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 <node> 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.