From 3cc7d935adfa1489332f5ae6f788e8bdea88092b Mon Sep 17 00:00:00 2001 From: Sylvain Bettinelli Date: Mon, 31 Aug 2026 13:40:12 +0000 Subject: [PATCH] =?UTF-8?q?La=20montre=20ne=20diffuse=20pas=20parce=20qu'e?= =?UTF-8?q?lle=20est=20d=C3=A9j=C3=A0=20connect=C3=A9e=20:=20la=20chercher?= =?UTF-8?q?=20ailleurs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 4020 advertisements vus — HomePod, Furbo, des sans-nom — et aucune Polar. Le scan marchait donc parfaitement depuis le correctif précédent. C'est la montre qui ne s'annonce pas. Et c'est normal : un appareil BLE connecté cesse d'émettre. La Vantage est liée à l'iPhone par l'app Flow, donc invisible au scan par conception. Tous les correctifs de scan qui ont précédé ne pouvaient rien y changer — on cherchait dans le seul endroit où elle ne pouvait pas être. `search()` prévoit exactement ce cas, mais seulement si on lui passe des UUID : `manager.retrieveConnectedPeripherals(withServices: uuids!)` rend les périphériques DÉJÀ connectés exposant le service, et leur fabrique une session. Je passais `nil`, donc cette branche n'était jamais empruntée. Au passage, déduplication des sessions : AllowDuplicates est armé côté SDK, d'où les 4020 pour une poignée d'appareils réels — sans quoi on ouvrirait quarante fois la même session. Ce que la journée aura montré : chaque message d'erreur qui affirmait une cause unique a fait perdre du temps, et chaque message qui RAPPORTAIT ce qu'il avait vu a fait avancer. La liste des appareils vus valait tous les raisonnements. 84 tests au vert. Co-Authored-By: Claude Opus 5 (1M context) --- docs/polar-ble-runbook-mac.md | 24 ++++++++++++++++++++++++ ios/App/App/CoachPolarBLE.swift | 22 +++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/polar-ble-runbook-mac.md b/docs/polar-ble-runbook-mac.md index 72b97a9..b8c98b9 100644 --- a/docs/polar-ble-runbook-mac.md +++ b/docs/polar-ble-runbook-mac.md @@ -227,6 +227,30 @@ et garder le câble. **Le câble, lui, fonctionne intégralement** : `tools/polar/README.md` du dépôt `coach_sportif`. Rien de ce qui a été livré côté serveur n'est perdu. +## 🔑 La montre ne s'annonce pas : elle est déjà connectée + +Mesuré le 31/08 : **4020 advertisements vus** (HomePod, Furbo…), **aucune +Polar**. Le scan fonctionne parfaitement — la Vantage ne diffuse simplement pas. + +C'est le comportement normal du BLE : **un appareil connecté cesse d'émettre**. +La montre est liée à l'iPhone par l'app Flow, donc elle est invisible au scan, +par conception. L'attendre était sans espoir, et tous les correctifs de scan qui +ont précédé ne pouvaient rien y changer. + +`search()` prévoit ce cas, mais seulement quand on lui passe des UUID : + +```swift +foundPeripherals = self.manager.retrieveConnectedPeripherals(withServices: uuids!) +``` + +⇒ **Appeler `search([BlePsFtpClient.PSFTP_SERVICE], identifiers: nil, +fetchKnownDevices: true)`**, et non `search(nil, …)` : c'est cette branche qui +rend les périphériques déjà connectés exposant PsFTP et leur fabrique une +session. + +⚠️ Dédupliquer aussi les sessions vues : `AllowDuplicates` est armé côté SDK, le +même appareil revient à chaque advertisement. + ## Ce qui reste inconnu, et ne se lèvera qu'ici 1. **La V3 accepte-t-elle une connexion BLE tierce** alors qu'elle est appairée diff --git a/ios/App/App/CoachPolarBLE.swift b/ios/App/App/CoachPolarBLE.swift index 3f47323..59d63b6 100644 --- a/ios/App/App/CoachPolarBLE.swift +++ b/ios/App/App/CoachPolarBLE.swift @@ -304,7 +304,12 @@ final class PolarPsFtpWriter { // L'état, lui, est acquis avant d'arriver ici : `blePowered()` a été // sondé jusqu'à devenir vrai. do { + // `AllowDuplicates` est armé côté SDK : le même appareil revient à + // chaque advertisement (4020 émissions pour une poignée d'appareils + // réels). On déduplique avant de compter et d'ouvrir quoi que ce soit. + var dejaVues = Set() let toutes = try await sessionsVues(listener, fenetre: timeout) + .filter { dejaVues.insert(Self.etiquette($0)).inserted } vues = toutes.count noms = toutes.map { Self.etiquette($0) } @@ -372,7 +377,22 @@ final class PolarPsFtpWriter { rendu = true suite.resume(returning: vues) } - listener.search(nil, identifiers: nil, fetchKnownDevices: true) + // ⚠️ PASSER L'UUID DU SERVICE — c'est ce qui trouve une montre DÉJÀ + // CONNECTÉE. + // + // Mesuré le 2026-08-31 : 4020 advertisements vus (HomePod, Furbo…), + // aucune Polar. Normal — **un appareil BLE connecté cesse + // d'émettre**. La Vantage est liée à l'iPhone par l'app Flow : elle + // est donc invisible au scan, par conception, et l'attendre était + // sans espoir. + // + // Avec un `uuids` non nil, `search()` emprunte une autre branche : + // `manager.retrieveConnectedPeripherals(withServices: uuids!)`, qui + // rend les périphériques déjà connectés au système exposant ce + // service, et leur fabrique une session. Passer `nil` — ce que + // faisait ce code — n'exécutait jamais cette branche. + listener.search([BlePsFtpClient.PSFTP_SERVICE], + identifiers: nil, fetchKnownDevices: true) .sink(receiveCompletion: { [weak self] fin in // Distinguer « le publisher a terminé » de « il n'a // jamais rien dit » : dans le second cas, `flatMap`