docs(esp-now): structured CMD contract for annex cards (D5 roadmap)
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user