Feature #64
Von Julius Haman vor 9 Tagen aktualisiert
# BLE-Verbindung: Handshake-Robustheit & Feature-Erkennung ausbauen ## Kontext `BleFtmsErgometer` baut die GATT-Session aktuell rein best-effort auf: `RequestControl` + `StartOrResume` wurden Der Request-Control-Handshake wird genau einmal gesendet, ohne Retry, Timeout oder Auswertung der Antwort. des Ergebnisses. Ablehnungen (`OnControlPointResponse`) wurden (Result Code != Success am Control Point, `OnControlPointResponse`) werden nur geloggt, 0x2ACC (Fitness Verbindung und Training laufen unabhängig davon weiter. Es gibt keine Erkennung, ob das Gerät überhaupt steuerbar ist (`0x2ACC` Fitness Machine Feature) wurde Feature wird nie gelesen, gelesen), und bei verlorener Control wurde wird beim Setzen der Zielleistung nie neu `RequestControl` angefragt. ## Umsetzung Ist-Zustand ### 1. Verbinden (`EstablishConnectionAsync`) - `BleFtmsErgometer.cs:111-112`: `RequestControl` senden, pro Versuch ~2 s auf die Indication warten. + `StartOrResume` einmal, ohne Retry/Timeout; Ergebnis wird nicht geprüft. - **Success:** Control gewährt, danach `StartOrResume`. `BleFtmsErgometer.cs:266-271`: Control-Point-Ablehnung wird nur geloggt. - **Stille** (keine Antwort) oder **nicht auswertbarer Code** (0x02, 0x03, unbekannt): sofort weiter wie bisher (Zustand "Assumed"), einmaliger Hinweis `BleFtmsErgometer.cs:313-324`: `SendAndReceive` (SetTargetPower) fragt keine Control neu an, behandelt `ControlNotPermitted` (`0x05` im Log. Kein Verbindungsfehler, keine Verzögerung. Result) nicht. - **Nur `0x2ACC` wird nirgends gelesen; Steuerbarkeit wird immer angenommen. - Positiv: `0x2AD8` (Supported Power Range) wird bereits gelesen. ## Gewünschte Änderungen 1. **Request-Control-Gating beim Verbinden** (`EstablishConnectionAsync`) - RequestControl in Schleife mit kleiner Retry-Verzögerung (~1 s) bis Erfolg oder Timeout (~10 s, kündbar via `CancellationToken`). - Nur noch bei ausdrücklicher Ablehnung** (0x05 gewährter Control Not Permitted, 0x04 Operation Failed): Retry im 1-s-Takt bis ~10 s, danach schlägt die `StartOrResume` senden. - Timeout → Verbindung fehl (der bestehende Verbindungs-Loop versucht es erneut). bewusst als fehlgeschlagen melden (nicht „verbunden, aber ungesteuert"). ### 2. **Re-RequestControl bei jeder Zielleistung** - In `SendAndReceive` vor dem SetTargetPower-Write Control verloren Antwortet das Gerät auf Set Target Power mit 0x05, startet im Hintergrund eine Recovery (`RequestControl`, `StartOrResume`, letzter Soll-Wert). Begrenzt auf 3 aufeinanderfolgende Versuche, danach 30 s Pause. sicherstellen. - Bei "Assumed" (Stille) gibt es keine Recovery. Der UI-Thread wartet nie auf die Indication (der Poll-Tick läuft auf dem UI-Thread). `ControlNotPermitted`-Antwort Control-Flag zurücksetzen und beim nächsten Befehl neu anfragen. ### 3. Feature-Erkennung 0x2ACC (Minimalversion, im Ticket enthalten) **Feature-Erkennung über `0x2ACC`** (optional, eigenes Teil-Ticket möglich) - Fitness Machine Feature + Target Setting Features Bit 3 (Power Target Setting) auswerten. Gelesen Flags lesen und Bit fehlt: reines Messgerät, kein `RequestControl`/`StartOrResume`, keine Steuerbefehle. daraus ableiten, ob ERG-Modus (setPower) verfügbar ist. - Fehlt 0x2ACC oder ist sie nicht lesbar: Verhalten wie bisher (Abweichung von "degradieren sie: auf Reinlesen", um Regressionen bei schlecht implementierten Geräten zu vermeiden). Reinlesen degradieren und nur Meldung loggen (kein `RequestControl`/`StartOrResume` erzwingen). ### 4. Ergebnisauswertung Result Codes werden über eine einmalige Mapping-Tabelle klassifiziert (Spec: 0x02 Op Code Not Supported, 0x03 Invalid Parameter, 0x04 Operation Failed, 0x05 Control Not Permitted). Set Target Power mit 0x02 schaltet weitere Soll-Werte ab, 0x03/0x04 werden nur einmal geloggt. ### 5. Lifecycle Recovery und offene Wartende werden in `CloseConnection` abgebrochen. ## Tests **Ergebnisauswertung statt Nur-Loggen** - Neues Unit-Test-Projekt `ErControl.Core.Tests` (xUnit) für Parsing, Result-Code-Tabelle, Zustandsmodell `SendControlCommandAsync`: Result-Code der `0x80`-Antwort zurückgeben, damit Aufrufer (1) und Recovery-Begrenzung. - `BleFtmsReceiver` (Simulator) erweitert um die Befehle `reject N`, `silent`, `lose`, `nopower` für die manuelle Abnahme. (2) darauf reagieren können. ## Nicht Teil Nicht-Teil dieses Tickets - Kein 0x2AD8-Verhalten `0x2AD8`-Verhalten (bleibt wie ist). - Keine Sportart-Erkennung aus Advertising-Service-Data. Advertising-Service-Data (Rower/Treadmill). - Kein Parsing von Speed/Distanz/Energie. Speed/Distanz/Energie (bewusst nicht erwünscht, Speed kommt aus `ErgometerSpeedCalculator`). ## Akzeptanzkriterien - Lehnt ein Verbindung schlägt bei steuerbarem Gerät `RequestControl` dauerhaft ausdrücklich ab (0x05/0x04), schlägt die Verbindung fehl, wenn RequestControl nach ~10 s fehl. Ablauf des Timeouts / Abbruch nie bestätigt wird. - Nach einem `ControlNotPermitted` auf Set Target Power wird Control neu angefragt (max. 3 Versuche, dann 30 s Pause) und setzt der letzte nächste Soll-Wert erneut geschrieben. wieder einen RequestControl ab und erreicht das Gerät. - Geräte, die `RequestControl` nicht bestätigen oder mit 0x02/0x03 beantworten, verbinden wie bisher. - Nicht-steuerbare Geräte (0x2ACC ohne Power Target Setting) (kein/leeres `0x2ACC` bzw. keine PowerTarget-Flag) verbinden weiterhin als reine Messgeräte, ohne Fehler-Spam. - Kein UI-Einfrieren durch die Recovery. Keine Änderung am bestehenden Verhalten für reine Lese-Geräte ohne Control Point (`0x2AD9` optional). ## Offene Risiken (nur mit echtem Gerät prüfbar) Umfang/Schätzung - Ob reale Trainer nach Control-Verlust zusätzlich `StartOrResume` brauchen (wird vorsorglich mitgesendet). - Ob steuerbare Geräte `RequestControl` dauerhaft mit 0x04 beantworten (dann Verbindungsfehler nach ~10 s). Beta-Test mit echtem Trainer empfohlen. ~1–2 Tage Kern (1+2+4), Feature-Erkennung (3) +0,5–1 Tag.