From 94b1f6243da05ca3416bca3201d6c87eb9795c32 Mon Sep 17 00:00:00 2001 From: Sylvain Bettinelli Date: Mon, 31 Aug 2026 13:20:05 +0000 Subject: [PATCH] Sans filtre, on ouvrait une session sur les 43 appareils BLE du voisinage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les traces le disent en clair : le SDK tentait de discuter avec l'Apple Watch. API MISUSE: can only accept commands while in the connected state Sans `scanPreFilter`, le listener remonte TOUS les appareils BLE alentour — 43 mesurés ce matin. Le code ouvrait une session sur chacun à tour de rôle pour lui demander s'il portait PsFTP, avec 12 s d'échéance par appareil. D'où l'envoi qui tournait sans fin, et ces erreurs sur un appareil qui n'a rien à voir. Le SDK officiel pose exactement ce filtre — `deviceFilter` dans `PolarBleApiImpl` — et je ne l'avais pas repris. Le voilà : `polarDeviceId` non vide, avec un repli sur le nom quand l'identifiant n'est pas encore décodé au moment du filtrage. C'est le pendant de la découverte précédente : le manager lazy empêchait de voir quoi que ce soit, et une fois réveillé, l'absence de filtre faisait tout voir. Les deux se cachaient l'un l'autre. Co-Authored-By: Claude Opus 5 (1M context) --- docs/polar-ble-runbook-mac.md | 8 ++++++++ ios/App/App/CoachPolarBLE.swift | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) 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) }