diff --git a/docs/polar-ble-runbook-mac.md b/docs/polar-ble-runbook-mac.md index 6c5ac26..a1c7ad4 100644 --- a/docs/polar-ble-runbook-mac.md +++ b/docs/polar-ble-runbook-mac.md @@ -152,6 +152,14 @@ jamais `manager`. Les seuls accès du chemin de recherche `centralManagerDidUpdateState` jamais appelé, le sujet jamais alimenté : le filtre bloquait pour toujours. +**Deuxième cause, découverte juste après** : sans `scanPreFilter`, le listener +remonte **tous** les appareils BLE alentour — 43 mesurés. Le code ouvrait alors +une session sur chacun à tour de rôle, dont l'Apple Watch +(`API MISUSE: … name = Apple Watch de Sylvain … can only accept commands while +in the connected state`), à 12 s d'échéance chacun : l'envoi paraissait ne +jamais finir. Le SDK officiel pose ce filtre (`deviceFilter` dans +`PolarBleApiImpl`) ; ne pas le poser était l'omission. + **Correctif** : appeler `listener.blePowered()` — `return manager.state == .poweredOn`, le seul accès public exécuté immédiatement — **après** s'être abonné à `search()`. L'ordre est critique : si `bleStateSubject` est un diff --git a/ios/App/App/CoachPolarBLE.swift b/ios/App/App/CoachPolarBLE.swift index 645df34..c824db0 100644 --- a/ios/App/App/CoachPolarBLE.swift +++ b/ios/App/App/CoachPolarBLE.swift @@ -206,6 +206,28 @@ final class PolarPsFtpWriter { clients: [{ transport in BlePsFtpClient(gattServiceTransmitter: transport) }], identifier: 1) + // ⚠️ FILTRER, SINON ON OUVRE UNE SESSION SUR TOUT CE QUI PASSE. + // + // Sans `scanPreFilter`, le listener remonte TOUS les appareils BLE + // alentour — 43 mesurés le 2026-08-31. Le code tentait alors une + // session sur chacun à tour de rôle, dont l'Apple Watch : + // + // API MISUSE: can only accept commands while in the + // connected state + // + // À 12 s d'échéance par appareil, l'envoi paraissait ne jamais finir. + // Le SDK officiel pose exactement ce filtre (`deviceFilter` dans + // `PolarBleApiImpl`) : ne pas le poser était l'omission. + // + // `polarDeviceId` est vide pour un appareil qui n'est pas un Polar ; le + // repli sur le nom couvre le cas où l'identifiant n'est pas encore + // décodé au moment du filtrage. + listener.scanPreFilter = { contenu in + !contenu.polarDeviceId.isEmpty + || contenu.name.lowercased().contains("polar") + } + let (client, session) = try await trouverClient(listener, timeout: timeout, sonde: sonde) defer { listener.closeSessionDirect(session) }