diff --git a/docs/esp-now-cmd-contract.md b/docs/esp-now-cmd-contract.md new file mode 100644 index 0000000..f1c4f8e --- /dev/null +++ b/docs/esp-now-cmd-contract.md @@ -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:`, `step:`. **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":"","a":{}} +``` + +- `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":"","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":""}` | Armer une étape | toutes | +| `screen` | `{"t":"","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.