docs(esp-now): structured CMD contract for annex cards (D5 roadmap)
Repo State / repo-state (pull_request) Failing after 31s
Repo State / repo-state (push) Failing after 35s
Validate Zacus refactor / validate (pull_request) Failing after 5m37s

This commit is contained in:
2026-06-14 10:10:00 +02:00
parent e3b868cbe1
commit 0d736459f4
+71
View File
@@ -0,0 +1,71 @@
# Contrat CMD ESP-NOW — cartes annexes Zacus (roadmap)
**Statut** : design / roadmap (D5 du redesign DSL). Aucun firmware annexe n'implémente encore ce contrat — ce document le *spécifie* pour que les cartes M5 / BOX-3 / PLIP / p7_coffre le décodent plus tard.
## 1. État actuel (constaté)
Le master Freenove relaie vers ses pairs ESP-NOW de deux façons :
- **IR de scénario** : `POST /game/scenario/relay {peers,ir}``scenario_mesh_send` (chunké, ≤64 KiB, ack 300 ms). Sert à *pousser un scénario* à une carte.
- **Canal CMD texte** : `scenario_mesh_send_text`**texte libre UTF-8 ≤200 octets**, déclenché par `POST /game/espnow/cmd {peer, command}`. Aujourd'hui le master émet la trame (`{ok:true, ESP_OK}`) mais **aucun executor annexe ne consomme les CMD entrants** (elles sont seulement loguées).
Le routage gateway D2 (`/v1/media/play`, `/v1/step/{board}`) utilise déjà ce canal en best-effort avec des commandes texte ad-hoc : `play:<path>`, `step:<id>`. **D5 formalise ce texte ad-hoc en schéma structuré.**
## 2. Schéma CMD structuré
Trame = **JSON compact, UTF-8, ≤ 200 octets** (limite `scenario_mesh_send_text`). Forme :
```json
{"op":"<verbe>","a":{<args>}}
```
- `op` : verbe court (≤12 car.).
- `a` : objet d'arguments (clés courtes pour tenir dans 200 o). Absent si pas d'argument.
- Pas d'espaces superflus (séparateurs `,`/`:` compacts). Encodage côté gateway ; décodage côté carte annexe.
### Verbes (v1)
| `op` | Args (`a`) | Sémantique | Carte cible typique |
|---|---|---|---|
| `play` | `{"p":"<path>","l":<0\|1>}` | Jouer un média par chemin SD/LittleFS (`l`=loop) | toutes (audio) |
| `stop` | — | Stopper l'audio | toutes |
| `vol` | `{"v":<0-100>}` | Volume | toutes |
| `step` | `{"s":"<step_id>"}` | Armer une étape | toutes |
| `screen` | `{"t":"<title>","u":"<subtitle>","y":"<symbol>","e":"<effect>"}` | Afficher une scène LCD | M5/BOX-3 (écran) |
| `led` | `{"p":"<pattern>"}` | Motif LED | M5/BOX-3 |
| `ring` | `{"d":<sec>}` | Faire sonner (PLIP) | PLIP |
| `evt` | `{"n":"<name>"}` | Remonter un événement vers le master (sens carte→master) | toutes |
### Exemples (≤200 o)
```json
{"op":"play","a":{"p":"zacus/box3/intro.wav","l":0}}
{"op":"step","a":{"s":"STEP_LEFOU"}}
{"op":"screen","a":{"t":"LABORATOIRE","y":"LA","e":"pulse"}}
{"op":"evt","a":{"n":"solved:1"}}
```
## 3. Sens master → carte (CMD) et carte → master (EVT)
- **CMD** (master→pair) : émise par le gateway via `POST /game/espnow/cmd {peer, command:<json>}`. Le master relaie la trame ; la carte annexe décode `{op,a}` et exécute.
- **EVT** (pair→master) : la carte annexe émet `{"op":"evt","a":{"n":"<name>"}}` ; le master la remonte au gateway (endpoint à définir, p.ex. réutiliser le log CMD/EVT entrant). Les `evt` (`solved:<id>`, `button:<x>`, `done`) alimentent les **transitions réelles** (D4) côté gateway : `evt n="solved:1"` ↔ transition `onPuzzleSolved(1)` (`event_type:unlock`, `event_name:puzzle:1`).
## 4. Implémentation gateway (présent / futur)
- **Présent (D2)** : `/v1/media/play` et `/v1/step/{board}` envoient déjà du texte (`play:<path>`, `step:<id>`) via `/game/espnow/cmd`. À migrer vers le JSON structuré ci-dessus (changement local, sans toucher le firmware master).
- **Futur** : un endpoint d'**EVT entrant** (le master expose les CMD/EVT reçues) pour fermer la boucle carte→gateway→transition.
## 5. Implémentation firmware annexe (futur, hors de ce repo-ci)
Chaque carte annexe (M5 / BOX-3 / PLIP / p7_coffre) doit :
1. recevoir la trame ESP-NOW texte (callback existant côté master à relayer/exposer) ;
2. parser le JSON `{op,a}` (parser compact, tolérant : trame inconnue → ignorée + log) ;
3. dispatcher sur `op` vers ses capacités locales (audio I2S, écran, LED, sonnerie…) ;
4. émettre des `evt` pour les événements joueur (énigme résolue, bouton…).
Tant que ce firmware n'existe pas, les blocs de la **voie 🔬 hardware futur** (M5/BOX-3/PLIP, etc.) restent validés mais non exécutés — le compilateur émet le warning « hardware non câblé » (D1), et le routage relais du gateway répond `ok:false` + warning D5 (cf. `/v1/media/play`, `/v1/step` sur une carte annexe).
## 6. Contraintes & notes
- **Budget 200 o** : privilégier des clés courtes (`p`,`s`,`t`,`v`…). Un `screen` avec title+subtitle longs peut dépasser → tronquer côté gateway (limites firmware : title≤47, subtitle≤63, symbol≤15) avant émission.
- **Idempotence / ordre** : ESP-NOW n'est pas fiable ordonné ; les `op` doivent être idempotents quand c'est possible (`step`, `screen`, `vol`). Pour les séquences critiques, préférer le push d'IR complet (`/game/scenario/relay`).
- **Versionnement** : si le schéma évolue, ajouter un champ `v` au niveau racine (`{"v":2,"op":...}`) ; absence = v1.