Le firmware n'utilise plus ESP-ADF (audio via esp_codec_dev + esp-audio-player/esp_audio_codec). Supprime le bloc ADF_PATH du CMake racine et main, le commentaire idf_component.yml, et l'en-tete ADF de main/CMakeLists. Retire modes/radios.h (orphelin, URLs dans radio.c). Corrige le commentaire 'STUB' de games.c (Coloriage est implemente: canvas + flood-fill). Reecrit le README (statut compile vert, archi box+Merlin, modes a jour).
11 KiB
Lisael Box
Firmware ESP32-S3-BOX-3 pour une « boîte » à destination d'un jeune enfant : webradios FR enfants, podcasts (Radio France), boîte à sons (microSD), coin calme / respiration, horloge / veilleuse, apprendre à lire, routines du matin / soir et mini-jeux. UI tactile LVGL 9, sans lecture obligatoire (gros boutons
- icônes/emoji), volume bridé (plafond enfant).
Le contenu (podcasts, vignettes, voix des routines, mots de lecture) est produit
et poussé vers la box par Merlin, un petit studio web côté serveur (voir
tower/), exposé sur merlin.saillant.cc.
Statut : compile vert (ESP-IDF 5.4.4 + BSP
esp-box-33.2.x), pas encore validé sur la carte.idf.py set-target esp32s3 && idf.py buildréussit (binaire ≈ 2,5 Mo, 39 % libre dans la partition app de 4 Mo). Les comportements matériels (audio réel, tactile, IMU, backlight, SD) restent à dérouler sur une vraie BOX-3 — voir À vérifier sur le matériel.
Matériel cible
- ESP32-S3-BOX-3 : ESP32-S3 (8 Mo PSRAM octal, 16 Mo flash), LCD 320×240 tactile capacitif (GT911), codec audio ES8311 + HP, micro ES7210, microSD, IMU ICM-42670 (utilisée par le jeu de labyrinthe). L'add-on AHT30 (température/humidité) n'est pas supposé présent.
Stack logicielle
- ESP-IDF 5.4 / 5.5 (APIs récentes :
driver/i2c_master.h,driver/temperature_sensor.h,esp_netif_sntp.h, …). - BSP
espressif/esp-box-3^3.2.0(managed component) — écran, tactile, port LVGL, codec ES8311 (esp_codec_dev), microSD (FATFS), boutons, IMU. Tire transitivementesp_lvgl_port ^2, LVGL 9.x,esp_codec_dev. - LVGL 9 pour l'UI tactile.
- Audio SANS ESP-ADF. Le BSP possède l'I2S/le codec ; le décodage passe par
chmorgan/esp-audio-player(MP3) +espressif/esp_audio_codec(AAC/M4A des podcasts), et le PCM est remis au codec ES8311 viaesp_codec_dev. Cela évite le conflit de versions ESP-ADFaudio_board↔esp_lcd_ili9341avec le BSP. - cJSON : composant built-in
jsond'ESP-IDF (manifestes SD, réponses open-meteo).
Architecture
Radio France RSS / open-meteo / TTS FR
│
┌────────▼─────────┐ HTTP POST /put
merlin.saillant.cc│ Merlin (tower/) │ ────────────────────▶ Lisael Box
(studio web) │ serveur :9555 │ (push delta) (ESP32-S3)
└────────▲─────────┘ ◀──────────────────── :8080/put
│ POST /register {have} file_server
merlin_ui.html
- Lisael Box (
main/) — le firmware embarqué (UI + modes + audio + réseau). - Merlin (
tower/) — le studio de contenu côté serveur : récupère podcasts et génère vignettes/voix, puis pousse le delta vers la box.
Arborescence
lisael-box/
├── CMakeLists.txt # projet (pas d'ESP-ADF)
├── partitions.csv # 16 MB : factory 4 MB + storage FAT
├── sdkconfig.defaults[.esp32s3]# PSRAM octal, 16 MB flash, LVGL/PSRAM
├── main/
│ ├── app_main.c # séquence de boot + tâches
│ ├── lisael_config.h # constantes partagées (TZ, NVS keys, volume)
│ ├── net/
│ │ ├── wifi.{c,h} · wifi_portal.{c,h} # STA + SoftAP/portail captif
│ │ ├── sntp_time.{c,h} # SNTP + fuseau Europe/Paris
│ │ ├── weather.{c,h} # open-meteo.com (HTTPS, sans clé)
│ │ ├── file_server.{c,h} # endpoint /put (réception du push Merlin)
│ │ ├── box_register.{c,h} # POST /register {have} vers Merlin :9555
│ │ ├── podcast.{c,h} # téléchargement des épisodes
│ │ └── udp_log.{c,h} # sink de log UDP (debug)
│ ├── audio/
│ │ ├── audio_player.{c,h} # façade codec ES8311 + volume bridé (NVS)
│ │ ├── radio_pipeline.{c,h} # webradio HTTP MP3 (sans ESP-ADF)
│ │ ├── sd_player.{c,h} # lecture MP3 SD (esp-audio-player)
│ │ ├── aac_player.{c,h} # AAC/M4A (podcasts) via esp_audio_codec
│ │ ├── calm_tone.{c,h} # synthèse 432 Hz du coin calme
│ │ ├── podcast_manifest.{c,h} · routine_manifest.{c,h} # parseurs JSON
│ ├── ui/
│ │ ├── ui.{c,h} # shell LVGL + routeur de modes + styles
│ │ ├── fr_date.{c,h} # date FR en lettres (sans setlocale)
│ │ ├── icons.{c,h}
│ │ ├── fonts/ # Fredoka custom (accents FR), 18/24/30 px
│ │ └── assets/ # icônes/emoji Twemoji -> descripteurs LVGL
│ └── modes/ # home, radio, calm, clock, games, balloons,
│ # maze, histoires, wifi_setup, lire, routine
├── tower/ # Merlin : studio de contenu (Python, voir + bas)
├── tools/ # gen_covers.py, gen_icons.py, gen_lire_content.py,
│ # push_to_box.sh
├── test/host/ # tests hôte des parseurs de manifeste
└── sdcard/ # disposition attendue de la carte + manifeste ex.
Prérequis
ESP-IDF 5.4+ installé et exporté :
. $IDF_PATH/export.sh
C'est tout — plus aucune dépendance ESP-ADF à installer. Les managed
components (esp-box-3, esp-audio-player, esp_audio_codec, …) sont
téléchargés automatiquement dans managed_components/ au premier build.
Build & flash
# 1. cibler l'ESP32-S3
idf.py set-target esp32s3
# 2. configurer le Wi-Fi et la météo (lat/lon, ville)
idf.py menuconfig
# -> "Lisael Box configuration" -> Wi-Fi / Weather / Audio
# 3. build (télécharge les managed components dans managed_components/)
idf.py build
# 4. flash + monitor (macOS : le port apparaît en /dev/cu.usbmodem*)
idf.py -p /dev/cu.usbmodem* flash monitor
# (Linux : -p /dev/ttyUSB0 ou /dev/ttyACM0 ; Ctrl+] pour quitter le monitor)
Provisioning Wi-Fi
Trois voies, par ordre de priorité au boot :
- NVS à l'exécution :
lisael_wifi_save_credentials()écritwifi_ssid/wifi_passdans le namespacelisael— prime sur le reste. - Portail captif SoftAP : le mode
wifi_setup(modes/wifi_setup.c+net/wifi_portal.c) ouvre un point d'accès et une page de saisie quand aucune crédential valide n'est connue. - menuconfig :
Lisael Box configuration → Wi-Fi → SSID / password(pratique en dev ; les valeurs partent dans le binaire).
Météo
api.open-meteo.com (gratuit, sans clé, HTTPS via le CA bundle mbedTLS).
Lat/lon depuis NVS (lat/lon) sinon défauts menuconfig (Paris).
Rafraîchissement toutes les CONFIG_LISAEL_WEATHER_REFRESH_MIN min ; en cas
d'échec réseau, l'accueil affiche —.
Modes
| Mode | Fichier(s) | Notes |
|---|---|---|
| 🏠 Accueil | modes/home.c |
date FR en lettres, heure (SNTP), météo (open-meteo), temp interne de la puce, tuiles en carrousel |
| 📻 Webradios | modes/radio.c + audio/radio_pipeline.c |
flux HTTP MP3, stations RadioKing réelles |
| 🔊 Boîte à sons | audio/sd_player.c |
grille depuis manifest.json, MP3 SD |
| 🎧 Histoires | modes/histoires.c + audio/aac_player.c |
podcasts Radio France (covers SD + AAC/M4A) |
| 📖 Lire | modes/lire.c |
apprendre à lire (366 mots, words.json, icône + voix TTS) |
| 🫧 Coin calme | modes/calm.c + audio/calm_tone.c |
bulle de respiration + tonalité 432 Hz synthétisée |
| 🕐 Horloge/veilleuse | modes/clock.c |
grande heure + date FR, fond sombre, backlight tamisé, « histoire du soir » |
| 🗓️ Routine | modes/routine.c + audio/routine_manifest.c |
matin / soir : étapes picto + voix, « Bravo ! », contenu poussé par Merlin |
| 🎮 Jeux | modes/games.c |
Memory · Compter · Coloriage (canvas + flood-fill) · Labyrinthe (inclinaison via IMU) |
| 🎈 Ballons | modes/balloons.c |
pop sounds synthétisés |
| ⚙️ Réglages Wi-Fi | modes/wifi_setup.c |
portail captif SoftAP |
Carte microSD
Voir sdcard/README.md. En bref : FAT32, dossiers /sounds/
(MP3 + manifest.json), /podcasts/, /routines/, /lire/ peuplés par le push
Merlin. Les manifestes suivent [{file, title, emoji}] (et variantes par mode).
Merlin — studio de contenu (tower/)
Serveur HTTP stdlib (http.server, port 9555), déployé sur l'hôte
tower. Deux faces :
- Machine :
POST /register {id, have}(réservé LAN/tailnet). Le serveur calcule le delta (staging \ have,manifest.jsonen dernier) et le pousse vers la box :POST http://<box_ip>:8080/put?name=<subdir>/<file>. - Humain : le studio
merlin_ui.html(SPA, 5 onglets : Podcasts, Fichiers, Box, Routines, Transferts), derrière Basic Auth optionnelle (MERLIN_AUTH="user:pass").
Génération de contenu (lisael_content.py) : RSS Radio France → transcodage
ffmpeg (mp3 mono 48 kbps), covers redimensionnées 160×160 RGB565 .bin via
LVGLImage.py, voix des routines via gateway TTS FR. Rafraîchissement hebdo
(lisael_refresh.py + systemd/lisael-refresh.timer, dimanche 04:00). Le
serveur tourne en service systemd/lisael-content.service (Restart=always).
# tests (offline) — nécessite Pillow + ffmpeg
cd tower && python3 -m pytest -q # 23 tests
Sécurité enfant
- Volume bridé : plafond
CONFIG_LISAEL_VOLUME_MAX_PERCENT(défaut 60 %), appliqué partout viaaudio_player.c(clamp_volume), persisté en NVS. - Pas de lecture requise : icônes/emoji + gros boutons (≥ 56 px).
- Jeux sans échec : aucun score punitif, retour « Bravo ! ⭐ ».
À vérifier sur le matériel
Le code compile ; ce qui suit n'a pas encore été validé sur une vraie carte :
- Audio réel : sortie ES8311 (webradio MP3, podcasts AAC, MP3 SD, tonalité calme, pops), niveaux et plafond de volume.
- Tactile / UI : réactivité des tuiles et carrousels (le wrap infini est
par flèches seulement, pas au swipe — voir
.superpowers/sdd/carousel-wrap-report.md). - IMU (labyrinthe) : lecture ICM-42670 via
bsp_i2c_get_handle(), sens des axes, fallback « Capteur indisponible ». - Backlight / veilleuse :
bsp_display_brightness_set()doit tamiser en mode horloge. - microSD : montage FATFS, lecture des manifestes et des assets poussés.
- Réseau bout-en-bout : provisioning (NVS / SoftAP / menuconfig), SNTP,
open-meteo HTTPS,
register/push delta depuis Merlin. - URLs des webradios (
modes/radio.c) : confirmer chaque flux (curl -I→audio/mpeg). - Profondeur de pile des tâches
net/weather(TLS+HTTP+JSON) sous charge audio + UI (uxTaskGetStackHighWaterMark). - Politique supply-chain :
idf_component.ymlne fige aujourd'hui que les versions sémantiques ; à miroiter/épingler par hash contre la baseline auditée avant toute prod (conformément au dépôt parent).
Licence
À définir (aligner sur la politique de l'org electron-rare).