Files
coach-ios/docs/polar-ble-runbook-mac.md
Sylvain Bettinelli 179528d563 Trois causes opposées disaient la même chose : le premier essai BLE n'a rien appris
Sylvain a lancé l'envoi Bluetooth et obtenu « aucune montre Polar exposant
PsFTP ». Ce message ne permet pas de conclure : il couvre à la fois « aucun
appareil vu » (Bluetooth éteint, autorisation refusée à l'app, montre hors de
portée) et « la montre est là mais ne répond pas » (canal déjà pris par Polar
Flow). Ce sont des causes opposées et des gestes différents. Un essai qui
n'apprend rien est un essai perdu, et celui-là demandait de brancher une montre.

L'erreur porte désormais ce qui a été observé — appareils vus, combien sans le
service FEEE, combien avec mais muets — et rend trois messages distincts, chacun
nommant le geste correspondant. Quatre tests verrouillent la distinction, dont
celui qui interdit d'accuser Polar Flow quand rien n'a été vu : ce serait envoyer
sur une fausse piste.

Le scan devient aussi répétitif, par fenêtres de 4 s jusqu'au timeout, au lieu
d'une passe unique de 10 s. CoreBluetooth peut n'être pas encore poweredOn au
premier appel : le scan ne démarre alors jamais et une fenêtre unique conclut à
tort qu'aucun appareil n'existe. C'est une cause plausible du premier échec, et
elle n'était pas couverte.

73 tests au vert sur tests-linux.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 12:47:28 +00:00

6.3 KiB

Écrire la séance sur la Polar en Bluetooth — ce qui reste à faire sur le Mac

Le code est écrit et poussé. Ce qui suit ne peut se faire que sur le Mac mini, Xcode ouvert, montre à portée. À lire en entier avant le premier envoi : la première tentative est celle qui peut bloquer la montre.

Pourquoi ce chemin, et pas un autre

Contrainte posée le 2026-08-31 : coach → la montre, sans intermédiaire. Pas de TrainingPeaks, pas de Polar Flow, pas d'Intervals.icu. Ça élimine toutes les autres voies, qui passent toutes par un tiers. Reste l'écriture directe du fichier d'objectif sur la montre par PFTP — prouvée sur la Vantage V3 le 2026-08-17, en USB. Le Bluetooth est ce qui la rend utilisable.

Le détail de ce qui a été mesuré sur la montre est dans le dépôt coach_sportif, tools/polar/README.md. Il fait foi.

1. Ajouter le SDK Polar au projet

⚠️ Pas dans ios/App/CapApp-SPM/Package.swift : ce fichier porte « DO NOT MODIFY — managed by Capacitor CLI » et sera réécrit. Ajouter le paquet au .xcodeproj :

Xcode → File → Add Package Dependencies…
https://github.com/polarofficial/polar-ble-sdk
→ produit « PolarBleSdk », cible « App »

Tant que le paquet n'est pas là, CoachPolarBLE.isAvailable() répond {available: false, reason: "PolarBleSdk absent de la cible"} — le fichier compile quand même, tout le code radio est derrière #if canImport(PolarBleSdk).

2. Target Membership

Deux fichiers, cible App :

  • ios/App/App/CoachPolarBLE.swift — le plugin
  • ios/App/App/PolarPftpStep.swift — les garde-fous et l'en-tête PFTP

Le second n'importe que Foundation : il est aussi lié dans tests-linux/Sources/CoachModel/ et couvert par 12 tests qui tournent sur Linux (./tests-linux/run.sh).

3. Permission Bluetooth

NSBluetoothAlwaysUsageDescription est déjà dans Info.plist, en français. iOS demandera l'autorisation au premier scan.

4. Le premier envoi — en dryRun, toujours

Depuis la console Safari attachée à la WebView :

const r = await fetch('/api/plan/polar-target?date=2026-08-31&hour=18')
const plan = await r.json()
await window.Capacitor.Plugins.CoachPolarBLE.send({ ...plan, dryRun: true })

Attendu :

{ sent: false, dryRun: true, log: [
    "mkdir /U/0/20260831/TST/ (0 o)",
    "mkdir /U/0/20260831/TST/180000/ (0 o)",
    "put   /U/0/20260831/TST/180000/TST.BPB (240 o)",
    "put   /U/0/20260831/TST/180000/ID.BPB (51 o)" ] }

Lire ce journal avant d'ôter dryRun. Un mkdir avec des octets, ou un put vers un chemin terminé par « / », est la requête exacte qui a bloqué la montre le 2026-08-17 — le plugin la refuse, mais la voir refusée vaut mieux que de la découvrir en radio.

5. L'envoi réel

Retirer dryRun. Fermer l'app Polar Flow d'abord : le canal BLE ne se partage pas, et une synchro Flow en cours empêchera la connexion.

En cas d'échec, le message distingue trois causes opposées — la distinction a été ajoutée le 31/08 après un premier essai où un message unique ne permettait pas de conclure :

Message Cause probable Geste
« aucun appareil Bluetooth détecté » Bluetooth éteint, autorisation refusée à coach, montre hors de portée Réglages → coach → Bluetooth
« N appareil(s) vu(s), M portant PsFTP mais sans réponse » canal déjà occupé fermer complètement Polar Flow
« N appareil(s) vu(s), aucun n'expose PsFTP » la V3 n'annonce pas le service tant qu'elle est appairée à Flow c'est l'inconnue de fond

⚠️ Le scan se fait par fenêtres successives de 4 s jusqu'au timeout, et non en une passe : CoreBluetooth peut n'être pas encore poweredOn au premier appel, et une fenêtre unique conclurait à tort qu'aucun appareil n'existe.

L'heure de l'objectif — ce qu'on sait, et ce qu'on ne sait pas

Un objectif est rangé sous /U/0/<AAAAMMJJ>/TST/<HHMMSS>/, et le start_time écrit dans le fichier doit concorder avec ce dossier : c'est le couple qui situe la séance dans la journée. build_session.py --hour et le paramètre hour des routes produisent les deux ensemble, il n'y a donc rien à accorder à la main.

⚠️ En revanche, on ignore si la montre conditionne l'affichage à cette heure. Propose-t-elle l'objectif toute la journée, ou seulement à son approche ? Le test du 2026-08-17 ne l'a pas mesuré. À observer au premier envoi réussi : écrire un objectif daté 18 h et regarder s'il apparaît immédiatement.

  • S'il apparaît tout de suite → l'heure n'est qu'une étiquette, en fixer une par défaut suffit.
  • S'il n'apparaît qu'à l'approche → il faut écrire à l'heure du départ. Sans conséquence pratique : l'envoi se fait de toute façon juste avant de sortir.

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 à l'app Flow ? Jamais testé. C'est le risque principal.
  2. Le filtrage de la montre se fait sur « expose le service PsFTP (FEEE) », pas sur le nom : ce que la V3 met dans son advertisement n'a pas été observé, et s'appuyer dessus aurait été une supposition. À resserrer une fois vu. Un capteur H10 s'écarte de lui-même, il ne porte pas PsFTP.
  3. Le firmware plante sur requête malformée. Les garde-fous couvrent les formes connues ; ils ne prouvent pas qu'il n'en existe pas d'autres.

Si la montre se bloque

Logo Polar puis écran noir. Appui long sur OK, 10-15 s. À défaut, BACK + DOWN pendant 10 s. En dernier recours, réinitialisation d'usine par FlowSync — les données déjà synchronisées dans Flow sont préservées.

Après l'envoi

L'objectif est éphémère : une synchro Flow supprime ce qui est déposé à une date qu'elle ignore (mesuré). C'est pour ça que l'envoi se fait juste avant de sortir, et c'est pour ça que le Bluetooth était la condition — brancher la montre au Mac cinq minutes avant de courir n'était pas un usage.

⚠️ Les cibles écrites sont des numéros de zone, pas des battements : la montre les exécute selon les zones réglées dans Flow. Vérifiées alignées le 2026-08-17, à 1 bpm près sur la frontière Z2/Z3. La réponse du serveur porte les bornes de current_zones() pour ce contrôle — les afficher avant l'envoi.