Backfills the working firmware that had stayed uncommitted across the build sessions, so the branch is self-contained. - audio: audio_player, radio_pipeline, sd_player, calm_tone, aac_player - net: podcast (RSS), udp_log - modes: balloons, plus calm/games/radio tweaks - ui: fonts header, icons; build files (CMakeLists, idf_component.yml, sdkconfig.defaults) - docs/superpowers: Histoires design + plan Note: sdkconfig stays gitignored (Wi-Fi creds + tuned LFN/FS/mbedtls settings); a fresh checkout must reconfigure those.
6.9 KiB
Lisael Box — Écran « Histoires » à vignettes (design)
Date: 2026-06-14
Statut: validé (brainstorming), prêt pour le plan d'implémentation.
Projet: firmware Lisael Box (ESP32-S3-BOX-3), ESP-IDF v5.4, /tmp/lisael-box,
repo Gitea electron-rare/lisael-box.
But
Remplacer les menus « Histoires » (download on-device) et « Sons » (liste) par un seul écran « Histoires » à vignettes, où Lisael (6 ans) choisit une histoire par sa pochette (image), façon Fabrique à histoires Merlin. Le contenu (audio
- pochettes + manifeste) vit sur la carte SD ; la box lit et affiche.
Périmètre
- DANS : l'écran à vignettes (3 niveaux), le chargement des pochettes depuis la SD, le manifeste enrichi v2, la fusion des tuiles d'accueil, le chargement initial du contenu sur la SD côté Mac (via le flux USB-MSC déjà rodé).
- HORS (specs séparées) :
- Spec A — webapp de gestion de contenu. Direction figée : Tower héberge l'app + télécharge les épisodes + convertit les pochettes, puis pousse les fichiers à la box en HTTP simple (ou la box tire depuis Tower sur le tailnet). La box ne fait jamais de HTTPS lourd (évite le reset de download connu). B produit exactement le format SD que A alimentera ensuite.
- Carrousel « grande pochette », bruitages/sons courts, écran réglages.
Navigation (3 niveaux)
- Accueil → la tuile « Histoires » ouvre la grille. Les anciennes tuiles « Histoires » + « Sons » fusionnent en cette unique tuile → 5 tuiles à l'accueil (6ᵉ emplacement laissé libre, futur « Réglages/Web »).
- Niveau 1 — grille 2×2 paginée : 4 pochettes par page (grosses cibles tactiles),
balayage horizontal + points indicateurs (
●○○) entre pages dès qu'il y a plus de 4 podcasts. Taper une pochette → niveau 2. - Niveau 2 — épisodes : liste verticale des épisodes du podcast choisi (petite vignette du show + titre). Bouton retour vers la grille. Taper un épisode → niveau 3.
- Niveau 3 — lecture plein écran : grande pochette + titre + transport
⏮ ⏸/▶ ⏭+ barre de progression + bouton retour. Le « Stop » global reste disponible (lisael_audio_stop_all).
Pochettes — pipeline image (approche retenue)
Approche 1 : pochettes pré-converties au format binaire LVGL (RGB565) sur la SD, lues via un driver de système de fichiers LVGL. Choisie pour la RAM déterministe (pas de décodeur JPEG à l'exécution — la RAM interne est tendue) et parce qu'elle correspond au « vrai Merlin » (pochettes sur la carte) et prépare la spec A.
- Format :
.binLVGL (LVGLImage.py --ofmt BIN,cf RGB565ouRGB565A8), produit côté Mac (pipeline déjà maîtrisé pour les icônes Twemoji). Une seule taille stockée par pochette : 160×160 ; LVGL la met à l'échelle à l'affichage (≈88 px en grille, ≈150 px en lecture, ≈48 px en liste) vialv_image_set_scale. - Driver FS LVGL : activer
CONFIG_LV_USE_FS_STDIO=yavec la lettre'S'(mappeS:→fopen/fread, donc la SD FATFS). Chargement :lv_image_set_src(img, "S:/sdcard/podcasts/<cover>.bin"). Le décodeur.binLVGL est toujours présent (aucun décodeur JPEG/PNG requis). - Cache image : les ~4 pochettes décodées tiennent en PSRAM (160×160×2 ≈ 51 Ko chacune, ~205 Ko pour 4). Acceptable.
- Source : artwork des flux Radio France (
<itunes:image>), redimensionné 160×160 côté Mac. Repli si pochette absente : une vignette générée (emoji + couleur du show).
Données — manifeste enrichi v2 (par show)
/sdcard/podcasts/manifest.json passe d'une liste plate d'épisodes à une liste de
shows, chacun portant sa pochette et ses épisodes :
[
{
"show": "Une histoire et Oli",
"cover": "oli.bin",
"episodes": [
{ "file": "oli_01.m4a", "title": "OLI en concert, p.1" },
{ "file": "oli_02.m4a", "title": "OLI en concert, p.2" }
]
},
{
"show": "Bestioles",
"cover": "bestioles.bin",
"episodes": [ { "file": "bestioles_01.mp3", "title": "La gerboise…" } ]
}
]
cover= nom de fichier.binsous/sdcard/podcasts/.file= nom audio ASCII sous/sdcard/podcasts/.show/title= UTF-8 (accents ; rendus par Fredoka).- Les noms de fichiers restent ASCII (FAT) ; le « joli » texte vit dans le JSON.
Composants firmware
main/modes/histoires.c(neuf) — l'écran à 3 niveaux (grille paginée + liste + player). Remplacemain/modes/podcasts.cetmain/modes/soundbox.c(supprimés).- Chargeur manifeste v2 — dans
main/audio/sd_player.c(ou un petitmain/net/podcast.créutilisé) : parse le tableau de shows en structureslisael_show_t { char show[48]; char cover[32]; lisael_ep_t eps[N]; int n; }. Remplace/complètelisael_sd_load_manifest_dir(v1 plate, devenue inutile). - Driver FS LVGL —
CONFIG_LV_USE_FS_STDIO=y(lettreS), pourlv_imagedepuis SD. - Lecture — inchangée : dispatch par extension
.m4a → lisael_aac_play_file, sinonlisael_sd_play_file;lisael_audio_stop_allau changement/Stop. (déjà en place) - Accueil —
main/modes/home.c: tableau de tuiles 6 → 5 (retirer « Sons », garder « Histoires » →LISAEL_MODE_HISTOIRES).main/ui/ui.h: retirerLISAEL_MODE_SOUNDBOX, garder/retitulerlisael_screen_histoires.main/ui/ui.c: routerLISAEL_MODE_HISTOIRES→lisael_screen_histoires. Mettre à jourmain/CMakeLists.txt(retrait podcasts.c/ soundbox.c, ajout histoires.c).
Contenu initial (pour B, maintenant)
Stand-in manuel de ce que Tower fera plus tard (spec A) :
- Côté Mac : pour chaque show, récupérer l'
<itunes:image>du flux RF → redimensionner 160×160 → convertir en.binLVGL (LVGLImage.py). - Écrire le
manifest.jsonv2 (shows + pochettes + épisodes déjà téléchargés). - Déposer pochettes
.bin+ manifeste sur/sdcard/podcasts/via le firmware USB-MSC (/tmp/lisael-msc) déjà construit, puis éjecter proprement. - Reflasher le firmware normal (BOOT+RESET).
Gestion d'erreurs
- Pas de SD / pas de manifeste → écran placeholder amical (« Branche la box en USB pour ajouter des histoires »), pas d'erreur.
- Pochette manquante /
.binillisible → vignette de repli (emoji + couleur du show). - Épisode introuvable à la lecture → toast/log, on reste sur la liste.
- 0 épisode pour un show → le show n'apparaît pas dans la grille.
Tests (acceptation)
Build + flash, puis sur la vraie carte :
- Accueil affiche 5 tuiles, « Histoires » ouvre la grille.
- Grille 2×2 affiche les vraies pochettes des 4 podcasts ; pagination/points si > 4.
- Taper une pochette → liste des épisodes du bon show.
- Taper un épisode → lecture plein écran avec pochette ; son OK en
.m4aET.mp3. - Retour à chaque niveau + Stop fonctionnels.
- Carte sans pochette → repli affiché, pas de crash.