diff --git a/docs/superpowers/specs/2026-06-21-lisael-box-routines-design.md b/docs/superpowers/specs/2026-06-21-lisael-box-routines-design.md new file mode 100644 index 0000000..6af0092 --- /dev/null +++ b/docs/superpowers/specs/2026-06-21-lisael-box-routines-design.md @@ -0,0 +1,173 @@ +# Lisael Box — Mode « Routines » illustrées (design) + +- **Date** : 2026-06-21 +- **Repo** : `electron-rare/lisael-box` (firmware) + Tower `lisael-content` + Merlin Studio (`merlin.saillant.cc`) +- **Statut** : design validé, prêt pour le plan d'implémentation + +## 1. But + +Donner à Lisael (6 ans) un mode **Routine illustrée à étapes** : une séquence de +pictogrammes (s'habiller → petit-déj → dents → cartable…) qu'il avance une par une, +avec une **voix qui annonce chaque étape** et un **« Bravo ! » final**. Deux routines +au lancement : **Matin** et **Soir**. Le contenu (étapes, emojis, textes, ordre) est +édité par un parent depuis **Merlin Studio** (web) et poussé sur la box en WiFi — +aucun reflash pour changer une routine. + +## 2. Non-objectifs (YAGNI) + +- Pas de **minuteur** par étape ni d'avance automatique (décision : avance manuelle). +- Pas de **routines libres** au-delà de Matin/Soir (deux routines fixes). +- Pas de **logique horaire** (la box ne devine pas matin/soir : l'enfant choisit). +- Pas de **photos perso** (emojis Twemoji uniquement). +- Pas de **persistance de progression** entre sessions (chaque ouverture repart au début). +- Pas de **généralisation** du pipeline de contenu Merlin (on étend, on ne refactore pas + `lisael_content.py` en système de types génériques). + +## 3. Décisions de cadrage (verrouillées) + +| Sujet | Décision | +|---|---| +| Concept | Routine illustrée à étapes (gros picto + nom plein écran) | +| Routines | Matin + Soir (2, fixes) | +| Source du contenu | Pilotée par Merlin Studio (web) → manifest SD + assets poussés en WiFi | +| Interaction | Gros bouton « C'est fait ✓ » + voix TTS par étape + son de validation + Bravo final | +| Déclenchement | Tuile « Routine » à l'accueil → choix Matin/Soir | +| Pictogrammes | Twemoji choisis dans Merlin → `.bin` RGB565A8 généré par Tower | +| Édition web | Éditer les étapes de Matin/Soir : texte + emoji, ajouter/supprimer/réordonner | +| Voix | `fr` (= « siwis » de la gateway, voix validée pour le jeu Lire) | +| Reset | Repart de l'étape 1 à chaque ouverture, pas de persistance | +| Cap | ~12 étapes / routine, structures en PSRAM | +| Livraison | **Tout d'un bloc** (firmware + Tower + web), test final global | + +## 4. Architecture d'ensemble + +``` +Merlin Studio (web) Tower (lisael-content) Box (firmware) +merlin.saillant.cc clems@192.168.0.120 ESP32-S3-BOX-3 +───────────────── ────────────────────── ───────────────── +Onglet "Routines" ──POST──▶ routines.json (vérité) + édite Matin/Soir + Twemoji→.bin (RGB565A8) + (texte+emoji, + TTS→.mp3 (voix "fr") + ordre, +/−) + manifest SD + ───push WiFi (:8080/put)──▶ /sdcard/routines/* + (delta, comme podcasts) routines.json + .bin + .mp3 + │ + mode Routine lit le manifest +``` + +Réutilise trois briques déjà en prod : le jeu **Lire** (pictos Twemoji `.bin` + +TTS `.mp3` + cache PSRAM), l'**autopush Tower** (`box_register` + `compute_delta` ++ `push_batch` vers `:8080/put`), et **Merlin Studio** (UI + API + Basic Auth). + +## 5. Contenu sur la SD — `/sdcard/routines/` + +Manifest unique + assets, tout poussé par Tower : + +```json +{ "routines": [ + { "id":"matin", "title":"Le matin", "icon":"matin.bin", "steps":[ + {"text":"Habille-toi", "icon":"matin_01.bin", "audio":"matin_01.mp3"}, + {"text":"Petit-déjeuner", "icon":"matin_02.bin", "audio":"matin_02.mp3"}, + {"text":"Brosse tes dents", "icon":"matin_03.bin", "audio":"matin_03.mp3"}, + {"text":"Prépare ton cartable", "icon":"matin_04.bin", "audio":"matin_04.mp3"} ]}, + { "id":"soir", "title":"Le soir", "icon":"soir.bin", "steps":[ ... ] } +]} +``` + +Règles : +- pictos `.bin` **RGB565A8** (même format/pipeline que Lire), audio `.mp3` **mono**. +- noms de fichiers **ASCII** (`matin_01`) ; les libellés accentués vivent dans le JSON + (UTF-8, rendu par Fredoka) — leçon LFN/codepage FAT du jeu Lire. +- `icon` de routine = vignette du choix Matin/Soir (Twemoji ☀️ / 🌙). + +## 6. Firmware — mode `LISAEL_MODE_ROUTINE` + +### Câblage +- `main/ui/ui.h` : ajouter `LISAEL_MODE_ROUTINE` à l'enum + `lv_obj_t *lisael_screen_routine(void);`. +- `main/modes/routine.c` : nouvel écran. +- `main/ui/assets/lisael_ic_routine.c` : icône de tuile (Twemoji bakée, ✅/🗓️, RGB565A8). +- `main/modes/home.c` : ajouter la tuile au carrousel d'accueil + route dans `ui.c`. + +### Écrans +1. **Choix** : deux grosses cartes *Matin* (☀️) / *Soir* (🌙), tap → ouvre la routine. +2. **Étape plein écran** : gros picto (cache PSRAM + FS `S:`, comme `lire.c`) + nom + (Fredoka) + indicateur de progression **3/6** (pastilles) + bouton géant + **« C'est fait ✓ »** + bouton retour Accueil. + - à l'arrivée sur une étape : **annonce vocale** (`aac_player` lit le `.mp3`, type MP3). + - tap « C'est fait » : **son de validation** (court) → étape suivante. + - dernière étape validée : écran **Bravo** (animation étoiles `lisael_ic_star` + son) → + retour Accueil. + +### Données & RAM +- `main/audio/routine_manifest.{c,h}` : parse `/sdcard/routines/routines.json` → + structures `lisael_routine_t` / `lisael_step_t`, allouées en **PSRAM** + (`heap_caps_*(MALLOC_CAP_SPIRAM)`), caps **2 routines × 12 étapes**. +- Cache picto **PSRAM** : lire chaque `.bin` une fois en PSRAM et le présenter comme + `lv_image_dsc_t` RAM (data = buf+12), pour éviter la relecture SD à chaque frame + (piège perf corrigé dans `lire.c`). +- Reset : `lisael_screen_routine()` repart toujours de l'étape 0. + +## 7. Tower — génération + push + +Dans `tower/lisael_content.py` (module partagé), ajouter le support « routines » : +- **vérité** : `routines.json` côté Tower (`/home/clems/lisael-content/`), édité par l'API web. +- **pictos** : pour chaque étape, télécharger le Twemoji de l'emoji → resize → + `.bin` RGB565A8 via `LVGLImage` (pipeline du jeu Lire). +- **voix** : annonce TTS via la gateway (`POST /v1/audio/speech`, + `model=tts-1`, `voice=fr`) → `.mp3` mono. Gateway = origine tailnet Tower `:9300` + (⚠️ Cloudflare bloque l'UA `Python-urllib` sur les domaines publics → appel direct + tailnet, ou `User-Agent: Mozilla/5.0`). +- **manifest SD** : composer `routines.json` (chemins `.bin`/`.mp3` relatifs). +- **push** : étendre `box_register` pour annoncer aussi `routines/*` dans `have`, et + réutiliser `compute_delta` + `push_batch` vers `:8080/put?name=routines/`. +- **cache** : ne régénérer un `.bin`/`.mp3` que si l'emoji/texte a changé (hash du + couple emoji+texte dans le nom ou un index), comme le suffixe `_a48` des podcasts. + +## 8. Merlin Studio — onglet « Routines » + +- `tower/merlin_ui.html` : nouvel onglet, 2 sections **Matin** / **Soir**. Par étape : + champ **texte** + **sélecteur d'emoji** + bouton supprimer ; **+ ajouter une étape** ; + réordonner (flèches ↑↓) ; bouton **Enregistrer & pousser**. +- `tower/lisael_server.py` : `GET /api/routines` (lit `routines.json`), + `POST /api/routines` (sauve + régénère pictos/TTS + met à jour le manifest staging), + `POST /api/routines/push` (push delta à la box). Sous la **Basic Auth** existante. +- Sélecteur d'emoji : palette d'emojis enfant courants (dents/habits/repas/bain/dodo/ + cartable/jouets…) + saisie libre d'un emoji. + +## 9. Gestion d'erreurs (dégradés gracieux) + +- pas de `routines.json` sur SD → écran « Demande à un parent de préparer tes routines ». +- picto manquant → case colorée avec 1ʳᵉ lettre (fallback du jeu Lire). +- audio manquant pour une étape → étape affichée sans voix (non bloquant). +- TTS gateway indisponible → Tower garde texte+picto, audio absent (non bloquant). +- push échoué (WiFi faible) → réessai au prochain `register` (pattern existant) ; + fichier partiel supprimé côté box (`unlink`, durcissement file-server existant). + +## 10. Tests + +- **Hôte** : `test/host/test_routine_manifest.c` (JSON valide / malformé / vide / + caps dépassés) ; test Tower de génération + delta (calqué sur `test_delta.py`). +- **Sur carte** (box branchée en USB) : boot-burst USB-JTAG + écoute UDP — + manifest chargé, pictos affichés, voix jouée par étape, son de validation, Bravo final. + +## 11. Pré-requis build (dette à régler avant de compiler) + +Le `sdkconfig` (gitigné) a été perdu avec `/tmp`. `sdkconfig.defaults*` ne contient +PAS tous les réglages critiques. Avant le premier build neuf, reconfigurer (ou +remonter dans `sdkconfig.defaults`, recommandé) : +`FATFS_LFN_HEAP` + `FATFS_MAX_LFN=255`, `LV_USE_FS_STDIO` + lettre `S` (83), +`MBEDTLS_EXTERNAL_MEM_ALLOC` + `MBEDTLS_DYNAMIC_BUFFER`, `ESP_WIFI_TX_BUFFER_TYPE=1`, +`SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY`, + les creds WiFi (ou override NVS). Sans ces +réglages : manifest illisible, pictos invisibles, reset TLS. À traiter dans le plan +(ce sera la première tâche, sinon rien ne tourne). + +## 12. Critères d'acceptation + +1. Une tuile « Routine » apparaît au carrousel d'accueil ; tap → choix Matin/Soir. +2. Choisir Matin enchaîne les étapes : gros picto + nom, voix qui annonce, bouton + « C'est fait ✓ », son de validation, jusqu'au Bravo final. +3. Le contenu vient de `/sdcard/routines/routines.json` (aucun contenu en dur figé). +4. Depuis Merlin, ajouter/supprimer/réordonner une étape + changer texte/emoji, puis + « Enregistrer & pousser » → la box reflète le changement après push (au boot/register). +5. Dégradés gracieux vérifiés : pas de manifest, picto/audio manquant, push partiel. +6. Tests hôte verts (parser + delta Tower).