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.
103 lines
4.9 KiB
Markdown
103 lines
4.9 KiB
Markdown
# 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<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
|
|
|
|
```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 <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.
|