Sans filtre, on ouvrait une session sur les 43 appareils BLE du voisinage

Les traces le disent en clair : le SDK tentait de discuter avec l'Apple Watch.

    API MISUSE: <CBPeripheral … name = Apple Watch de Sylvain,
    state = disconnected> 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) <noreply@anthropic.com>
This commit is contained in:
Sylvain Bettinelli
2026-08-31 13:20:05 +00:00
parent 81582ce969
commit 94b1f6243d
2 changed files with 30 additions and 0 deletions

View File

@@ -152,6 +152,14 @@ jamais `manager`. Les seuls accès du chemin de recherche
`centralManagerDidUpdateState` jamais appelé, le sujet jamais alimenté : le `centralManagerDidUpdateState` jamais appelé, le sujet jamais alimenté : le
filtre bloquait pour toujours. 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 == **Correctif** : appeler `listener.blePowered()``return manager.state ==
.poweredOn`, le seul accès public exécuté immédiatement — **après** s'être .poweredOn`, le seul accès public exécuté immédiatement — **après** s'être
abonné à `search()`. L'ordre est critique : si `bleStateSubject` est un abonné à `search()`. L'ordre est critique : si `bleStateSubject` est un

View File

@@ -206,6 +206,28 @@ final class PolarPsFtpWriter {
clients: [{ transport in BlePsFtpClient(gattServiceTransmitter: transport) }], clients: [{ transport in BlePsFtpClient(gattServiceTransmitter: transport) }],
identifier: 1) 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: <CBPeripheral name = Apple Watch de Sylvain,
// state = disconnected> 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, let (client, session) = try await trouverClient(listener, timeout: timeout,
sonde: sonde) sonde: sonde)
defer { listener.closeSessionDirect(session) } defer { listener.closeSessionDirect(session) }