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>
137 lines
6.3 KiB
Markdown
137 lines
6.3 KiB
Markdown
# É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 :
|
|
|
|
```js
|
|
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.
|