Files
coach-ios/docs/polar-ble-runbook-mac.md
Sylvain Bettinelli 0110368e17 L'heure de l'objectif est une question ouverte, pas un réglage à deviner
Sylvain ne sait pas encore à quelle heure il court, et j'allais lui faire
choisir une valeur comme si elle était contrainte. Elle ne l'est peut-être pas.

Ce qui est établi : le dossier <HHMMSS> et le start_time du fichier doivent
concorder, et les outils les produisent ensemble — rien à accorder à la main.

Ce qui ne l'est pas : la montre conditionne-t-elle l'affichage de l'objectif à
cette heure, ou le propose-t-elle toute la journée ? Le test du 17/08 ne l'a pas
mesuré. Écrit comme observation à faire au premier envoi plutôt que tranché au
jugé, avec les deux conséquences selon la réponse — dont aucune n'est bloquante.

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

126 lines
5.5 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 les cas — montre introuvable, PsFTP
muet, requête refusée. Ce ne sont pas les mêmes causes.
## 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.