docs(routines): design spec for routine mode
Matin/Soir illustrated step routine: SD manifest pushed by Merlin Studio/Tower (Twemoji .bin + fr TTS .mp3), firmware mode with voice announce + done button + final cheer.
This commit is contained in:
@@ -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 <gateway>/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/<f>`.
|
||||
- **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).
|
||||
Reference in New Issue
Block a user