diff --git a/docs/polar-ble-runbook-mac.md b/docs/polar-ble-runbook-mac.md index 9a76fe1..af4705d 100644 --- a/docs/polar-ble-runbook-mac.md +++ b/docs/polar-ble-runbook-mac.md @@ -383,3 +383,33 @@ documentée et devra être mesurée. **câble**. [ancs]: https://developer.apple.com/library/archive/documentation/CoreBluetooth/Reference/AppleNotificationCenterServiceSpecification/Specification/Specification.html + +### La séquence du 01/09, et ce qu'elle apprend sur la fenêtre + +Trois envois consécutifs, trois résultats différents — c'est la chronologie qui +explique tout : + +| # | résultat | lecture | +|---|---|---| +| 1 | **réussi** | montre fraîchement réinitialisée, **pas encore liée à Flow** : canal libre | +| 2 | **errorcode 104** `DIRECTORY_EXISTS` | canal **toujours** libre, la session s'ouvre — seul le `mkdir` bute sur un dossier déjà créé au 1ᵉʳ envoi | +| 3 | **« canal probablement occupé »** | Flow a repris la main entre-temps | + +⇒ Le canal n'est pas ouvert ou fermé « en général » : il y a une **fenêtre**, +qui commence quand la montre n'est liée à personne et se referme dès que Flow +se reconnecte. C'est cohérent avec le 31/08 et avec l'USB, où il faut fermer +Flow. + +⚠️ **Le message d'erreur du plugin conseillait « fermer complètement l'app Polar +Flow ». C'est faux, et ça l'était déjà le 31/08** : la montre affichait +« connexion impossible » pendant que les réglages iOS la disaient **toujours +connectée** à Flow. iOS maintient le lien d'un accessoire appairé, app fermée ou +non. Message corrigé le 01/09 — il nomme désormais le geste qui libère +réellement le canal (redémarrer la montre) et rappelle que le câble est le +chemin fiable. + +**Hypothèse ouverte, non vérifiée** : redémarrer la montre puis écrire *dans la +foulée*, avant que Flow ne se reconnecte, pourrait suffire. Ça expliquerait les +envois 1 et 2. Ce n'est pas une méthode tant que ça n'a pas été reproduit +plusieurs fois — et ⛔ chaque essai a un coût matériel avéré (l'USB désactivé le +31/08). **Écrire la séance par câble AVANT tout essai.** diff --git a/ios/App/App/PolarPftpStep.swift b/ios/App/App/PolarPftpStep.swift index 717ef1a..be70d57 100644 --- a/ios/App/App/PolarPftpStep.swift +++ b/ios/App/App/PolarPftpStep.swift @@ -210,17 +210,37 @@ public enum PolarPftpError: Error, CustomStringConvertible, Equatable { + "la montre allumée et à portée." case .watchNotFound(let seconds, let vues, _, let muettes) where muettes > 0: - // Le service est là mais ne répond pas : signature d'un canal déjà pris. + // Le service est là mais ne répond pas : canal déjà pris. + // + // ⚠️ Ce message conseillait « fermer l'app Polar Flow ». C'était + // faux, et mesuré comme tel le 31/08/2026 : la montre affichait + // « connexion impossible » pendant que les réglages iOS la + // disaient TOUJOURS connectée à Flow. iOS maintient le lien d'un + // accessoire appairé, app fermée ou non — fermer l'app ne rend pas + // le canal. + // + // Ce qui l'a rendu, le 01/09 : une montre fraîchement + // réinitialisée, donc pas encore liée à Flow. Redémarrer la montre + // libère aussi son canal, mais Flow le reprend. return "\(vues) appareil(s) vu(s), \(muettes) portant PsFTP mais sans " - + "réponse en \(Int(seconds)) s. Le canal est probablement déjà " - + "occupé : fermer complètement l'app Polar Flow (elle synchronise " - + "en arrière-plan), puis réessayer." + + "réponse en \(Int(seconds)) s. Le canal PsFTP de la Vantage " + + "n'accepte qu'une session, et Polar Flow la détient tant que " + + "la montre lui est appairée — fermer l'app n'y change rien, " + + "iOS maintient le lien. Redémarrer la montre (appui long sur " + + "OK) libère le canal ; il faut écrire dans la foulée, avant " + + "que Flow ne le reprenne. Chemin fiable : le câble." case .watchNotFound(let seconds, let vues, _, _): // Des appareils, mais aucun ne porte le service. + // ⚠️ Ce message parlait d'« inconnue restante ». Elle a été levée le + // 31/08/2026 : la montre EST atteignable et expose bien PsFTP + // (« 12 appareils vus, 1 portant PsFTP ») — quand elle est + // connectée. N'en voir aucun est donc autre chose : montre éteinte, + // hors de portée, ou pas encore connectée à l'iPhone. return "\(vues) appareil(s) Bluetooth vu(s) en \(Int(seconds)) s, aucun " - + "n'expose PsFTP. La Vantage n'annonce peut-être pas ce service " - + "tant qu'elle est appairée à Flow — c'est l'inconnue restante." + + "n'expose PsFTP. La Vantage expose bien ce service quand elle " + + "est connectée (vérifié le 31/08) : la chercher plutôt du côté " + + "de la montre — allumée, à portée, et reliée à l'iPhone." } } } diff --git a/tests-linux/Tests/CoachModelTests/PolarPftpStepTests.swift b/tests-linux/Tests/CoachModelTests/PolarPftpStepTests.swift index 8ed4ce8..924e5af 100644 --- a/tests-linux/Tests/CoachModelTests/PolarPftpStepTests.swift +++ b/tests-linux/Tests/CoachModelTests/PolarPftpStepTests.swift @@ -138,13 +138,31 @@ final class PolarPftpErrorTests: XCTestCase { func testUneMontreMuettePointeVersFlow() { let e = PolarPftpError.watchNotFound(30, vues: 3, sansPsFtp: 2, muettes: 1) XCTAssertTrue(e.description.contains("Polar Flow")) - XCTAssertTrue(e.description.contains("occupé")) } - func testDesAppareilsSansPsFtpNommentLInconnueRestante() { + /// ⚠️ Le message conseillait « fermer complètement l'app Polar Flow ». + /// Mesuré faux le 31/08/2026 : la montre affichait « connexion impossible » + /// pendant que les réglages iOS la disaient toujours connectée à Flow. iOS + /// maintient le lien d'un accessoire appairé, app fermée ou non. + func testLeMessageNeConseillePlusDeFermerLApp() { + let e = PolarPftpError.watchNotFound(30, vues: 3, sansPsFtp: 2, muettes: 1) + XCTAssertTrue(e.description.contains("fermer l'app n'y change rien"), + "le geste inutile doit être explicitement écarté") + XCTAssertTrue(e.description.contains("Redémarrer la montre"), + "le geste qui libère réellement le canal doit être donné") + XCTAssertTrue(e.description.contains("câble"), + "le chemin fiable doit être rappelé") + } + + /// L'« inconnue restante » a été levée le 31/08 : la montre expose bien + /// PsFTP quand elle est connectée. Le message ne doit plus la présenter + /// comme une question ouverte. + func testDesAppareilsSansPsFtpNePresententPlusUneInconnue() { let e = PolarPftpError.watchNotFound(30, vues: 4, sansPsFtp: 4, muettes: 0) XCTAssertTrue(e.description.contains("n'expose PsFTP")) XCTAssertTrue(e.description.contains("4 appareil")) + XCTAssertFalse(e.description.contains("inconnue"), + "la question a été tranchée, ne pas la rouvrir dans l'UI") } func testLesTroisMessagesSontDistincts() {