From 72fee10538b801210df2247a0dacc43aad206162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Saillant?= Date: Sun, 14 Jun 2026 00:40:46 +0200 Subject: [PATCH] docs: add README and SD card layout Context: The README is the primary usable deliverable: how to install ESP-IDF + ESP-ADF, build, flash, and prepare the microSD card, plus an honest list of what is unverified. Changes: - README.md: prerequisites (ADF_PATH), build/flash, provisioning, module table, child-safety notes, and a thorough "A verifier sur le materiel" section (build, BSP<->ADF I2S, radio URLs, emoji fonts...) - sdcard/README.md + sdcard/sounds/manifest.json example Impact: A reader can stand the project up and knows exactly what still needs validation on real hardware. --- README.md | 220 +++++++++++++++++++++++++++++++++++- sdcard/README.md | 44 ++++++++ sdcard/sounds/manifest.json | 8 ++ 3 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 sdcard/README.md create mode 100644 sdcard/sounds/manifest.json diff --git a/README.md b/README.md index 2d98ce4..c1de41b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,219 @@ -# lisael-box +# Lisael Box -Lisael Box — firmware ESP32-S3-BOX-3 : webradios FR enfants + boite a sons SD + coin calme + horloge. ESP-IDF/ESP-ADF/LVGL. \ No newline at end of file +Firmware **ESP32-S3-BOX-3** pour une "boîte" à destination d'un jeune enfant : +webradios FR enfants, boîte à sons (microSD), coin calme / respiration, +horloge / veilleuse, et jeux adaptés. UI tactile **LVGL**, sans lecture +obligatoire (gros boutons + icônes), volume **bridé** (plafond enfant). + +> ⚠️ **Statut : point de départ soigné, NON compilé / NON flashé.** Ce dépôt a +> été écrit sans la carte ni ESP-IDF/ESP-ADF installés. Le code suit les APIs +> ESP-IDF 5.x, le BSP `esp-box-3` (v3.2.x, LVGL 9) et ESP-ADF de façon +> idiomatique, mais **rien n'a pu être bâti**. Voir la section +> [À vérifier sur le matériel](#à-vérifier-sur-le-matériel) — elle est honnête +> et exhaustive. + +## Matériel cible + +- **ESP32-S3-BOX-3** : ESP32-S3 (8 Mo PSRAM octal, 16 Mo flash), LCD 320×240 + tactile capacitif, codec audio **ES8311** + HP, micro **ES7210**, **microSD**, + IMU (ICM-42670). Pas de capteur température/humidité externe sur la carte de + base (un add-on AHT30 existe ; non supposé présent ici). + +## Stack logicielle + +- **ESP-IDF 5.4 / 5.5** (nouvelles APIs : `driver/i2c_master.h`, + `driver/temperature_sensor.h`, `esp_netif_sntp.h`, …). +- **BSP `espressif/esp-box-3` `^3.2.0`** (managed component) — écran, tactile, + LVGL port, codec ES8311 (`esp_codec_dev`), microSD (FATFS), boutons, IMU. + Tire transitivement `esp_lvgl_port ^2`, **LVGL 9.x**, `esp_codec_dev ~1.5`. +- **LVGL 9** pour l'UI tactile. +- **ESP-ADF** pour l'audio en streaming/décodage (webradio + lecture SD). + ESP-ADF **n'est pas** un managed component : il s'installe à part (voir + ci-dessous). +- **`espressif/cjson`** pour le `manifest.json` SD et les réponses open-meteo. + +## Arborescence + +``` +lisael-box/ +├── CMakeLists.txt # projet ; injecte ESP-ADF via ADF_PATH +├── partitions.csv # 16 MB : factory 4 MB + storage FAT +├── sdkconfig.defaults # défauts communs +├── sdkconfig.defaults.esp32s3# PSRAM octal, 16 MB flash, 240 MHz, LVGL/PSRAM +├── main/ +│ ├── CMakeLists.txt # sources + REQUIRES (BSP, ADF guardé) +│ ├── idf_component.yml # managed deps (esp-box-3, cjson) +│ ├── Kconfig.projbuild # menuconfig : WiFi, lat/lon météo, volume +│ ├── lisael_config.h # constantes partagées (TZ, NVS keys, volume) +│ ├── app_main.c # séquence de boot + tâches +│ ├── net/ +│ │ ├── wifi.{c,h} # STA + reconnexion + creds NVS/menuconfig +│ │ ├── sntp_time.{c,h} # SNTP + fuseau Europe/Paris +│ │ └── weather.{c,h} # open-meteo.com (HTTPS, sans clé) périodique +│ ├── audio/ +│ │ ├── audio_player.{c,h}# façade codec ES8311 + volume bridé (NVS) +│ │ ├── radio_pipeline.{c,h} # ESP-ADF http_stream→decoder→i2s +│ │ └── sd_player.{c,h} # ESP-ADF fatfs→mp3→i2s + parse manifest.json +│ ├── ui/ +│ │ ├── ui.{c,h} # shell LVGL + routeur de modes + styles +│ │ ├── fr_date.{c,h} # date FR en lettres (sans setlocale) +│ │ └── icons.{c,h} # glyphes/emoji des tuiles et boutons +│ └── modes/ +│ ├── home.c # accueil : date FR, heure, météo, capteur, tuiles +│ ├── radio.c # écran webradios + volume/stop +│ ├── radios.h # liste de stations (URLs à vérifier) +│ ├── soundbox.c # grille SD depuis manifest.json +│ ├── calm.c # bulle de respiration (anim LVGL) +│ ├── clock.c # horloge/veilleuse + histoire du soir +│ └── games.c # Memory + Compter + Coloriage (stub) +└── sdcard/ + ├── README.md # disposition attendue de la carte + └── sounds/manifest.json # exemple de manifeste +``` + +## Prérequis + +1. **ESP-IDF 5.4+** installé et exporté : + ```bash + . $IDF_PATH/export.sh + ``` +2. **ESP-ADF** installé séparément (audio streaming/décodage). ESP-ADF n'est + pas distribué via le registre de composants ; on le clone et on exporte + `ADF_PATH` : + ```bash + git clone --recursive https://github.com/espressif/esp-adf.git ~/esp/esp-adf + export ADF_PATH=~/esp/esp-adf + # (esp-adf fournit aussi $ADF_PATH/export.sh qui définit ADF_PATH) + ``` + Le `CMakeLists.txt` racine ajoute `$ADF_PATH/components` à + `EXTRA_COMPONENT_DIRS`. **Si `ADF_PATH` est absent**, le projet *configure* + quand même (UI/horloge/respiration), mais les modules audio (webradio, + lecture SD) ne sont pas liés et renvoient `ESP_ERR_NOT_SUPPORTED` à + l'exécution (gardes `__has_include("audio_pipeline.h")`). + + > ⚠️ **Compatibilité de versions à vérifier.** ESP-ADF s'épingle + > historiquement à des versions précises d'ESP-IDF, et son abstraction + > `audio_board` peut entrer en conflit avec le BSP `esp-box-3` (les deux + > veulent gérer l'I2S/le codec). Voir + > [À vérifier sur le matériel](#à-vérifier-sur-le-matériel). + +## Build & flash + +```bash +# 1. cibler l'ESP32-S3 +idf.py set-target esp32s3 + +# 2. configurer le WiFi 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 + +Deux voies : + +- **menuconfig** : `Lisael Box configuration → Wi-Fi → SSID / password`. Les + valeurs partent dans le binaire (pratique en dev). +- **NVS à l'exécution** : `lisael_wifi_save_credentials()` écrit les clés + `wifi_ssid` / `wifi_pass` dans le namespace `lisael` ; elles **priment** sur + les valeurs menuconfig au boot suivant. Brancher un petit portail captif ou + une commande console reste **à faire** (les valeurs menuconfig suffisent pour + démarrer). + +### Météo + +`api.open-meteo.com` (gratuit, sans clé, HTTPS via le CA bundle mbedTLS). La +latitude/longitude viennent de NVS (`lat`/`lon`) sinon des défauts menuconfig +(Paris). Rafraîchissement toutes les `CONFIG_LISAEL_WEATHER_REFRESH_MIN` min. +En cas d'échec réseau, l'accueil affiche `—`. + +## Carte microSD + +Voir [`sdcard/README.md`](sdcard/README.md). En bref : FAT32, dossier +`/sounds/` contenant des `.mp3` + un `manifest.json` +(`[{file, title, emoji}]`). Les clips seront générés plus tard par TTS FR. + +## Modes + +| Mode | Fichier | Implémenté | Notes | +|------|---------|-----------|-------| +| Accueil | `modes/home.c` | ✅ | date FR en lettres, heure (SNTP), météo (open-meteo), temp interne de la puce (fallback), 5 tuiles | +| 📻 Webradios | `modes/radio.c` + `audio/radio_pipeline.c` | ✅ code / ⚠️ URLs | pipeline ESP-ADF `http_stream→decoder→i2s` ; **URLs de stations à confirmer** (`modes/radios.h`) | +| 🔊 Boîte à sons | `modes/soundbox.c` + `audio/sd_player.c` | ✅ | grille depuis `manifest.json` ; pipeline `fatfs→mp3→i2s` | +| 🫧 Coin calme | `modes/calm.c` | ✅ | bulle qui gonfle (~4 s) / dégonfle (~6 s) en boucle, "Inspire…/Souffle…", ambiance SD optionnelle | +| 🕐 Horloge/veilleuse | `modes/clock.c` | ✅ | grande heure + date FR, fond sombre doux, backlight tamisé, bouton "Histoire du soir" | +| 🎮 Jeux | `modes/games.c` | ✅ Memory + Compter / 🚧 Coloriage stub | feedback positif uniquement, pas de défaite ; Coloriage est un stub documenté (canvas tactile = plus lourd sur 320×240) | + +## Sécurité enfant + +- **Volume bridé** : plafond `CONFIG_LISAEL_VOLUME_MAX_PERCENT` (défaut 60 %), + appliqué partout via `audio_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 + +Liste honnête de ce qui **n'a pas pu être testé** et doit être validé sur une +vraie carte : + +1. **Compilation.** Le code n'a jamais été bâti (ni ESP-IDF ni ESP-ADF + installés ici). Attendez-vous à des ajustements : noms exacts de composants + managés ESP-ADF dans `REQUIRES` (`audio_pipeline`, `audio_stream`, + `esp-adf-libs`, `audio_sal`) et includes/macros décodeur exacts + (`DEFAULT_ESP_MP3_DECODER_CONFIG` vs `…_AUDIO_DECODER_CONFIG` varie selon la + révision ADF). cJSON = composant **built-in `json`** d'ESP-IDF. +2. **Coexistence BSP ↔ ESP-ADF sur l'I2S/codec.** C'est **le** point chaud. Le + BSP `esp-box-3` initialise l'I2S et le codec ES8311 (`bsp_audio_init` / + `bsp_audio_codec_speaker_init`), et ESP-ADF `i2s_stream` veut lui aussi + posséder le port I2S. Deux approches connues, à arbitrer sur le matériel : + - **(A)** ADF possède l'I2S : configurer `i2s_stream` avec le port/pins du + BOX-3 et **ne pas** appeler `bsp_audio_init` ; n'utiliser le handle codec + du BSP que pour le volume/gain. *(approche codée ici)* + - **(B)** Le BSP possède l'I2S : écrire le PCM décodé vers le codec via + `esp_codec_dev_write()` depuis un `raw_stream` au lieu de `i2s_stream`. + Les configs `i2s_std` (port, broches, horloge via `i2s_stream_set_clk`) + dans `radio_pipeline.c` / `sd_player.c` portent des `TODO(hardware)` — elles + doivent matcher les valeurs du BSP de votre version. +3. **Version ESP-ADF compatible** avec votre ESP-IDF 5.4/5.5 (ADF s'épingle + souvent à une révision IDF précise). L'abstraction `audio_board` d'ADF peut + doublonner le BSP — c'est pourquoi `board.h` n'est *pas* inclus. +4. **URLs des webradios** (`modes/radios.h`) : ce sont des **placeholders + plausibles** (Radio Pomme d'Api, Radio Doudou, etc.). Confirmez chaque flux + (`curl -I` → `audio/mpeg` ou `audio/aac`) et préférez du MP3 HTTP simple. +5. **Polices / emoji LVGL.** Les fonts Montserrat par défaut **ne contiennent + pas** les emoji ; les glyphes 📻🔊🫧… risquent d'apparaître en "tofu". Pour + les rendre, embarquez une police emoji (subset Noto via `lv_font_conv`) ou + remplacez par le set monochrome `LV_SYMBOL_*` (déjà utilisé pour les flèches + et contrôles). Les accents FR (é, à, û, î) demandent une police Latin-1 — à + vérifier aussi. +6. **Tailles de police montserrat** (`lv_font_montserrat_28/48`) : activez-les + dans `menuconfig → Component config → LVGL → Font usage` si elles ne sont + pas dans le set par défaut. +7. **Backlight / luminosité.** `bsp_display_brightness_set()` / + `bsp_display_backlight_on()` supposés présents dans le BSP — à confirmer sur + votre version (sinon le mode veilleuse ne tamise pas). +8. **Capteurs.** Seule la température **interne de la puce** + (`driver/temperature_sensor.h`) est lue, en fallback : ce n'est PAS la + température de la pièce. L'IMU ICM-42670 et l'éventuel AHT30 ne sont pas + exploités (le BSP expose `bsp_sensor_init` — à brancher si voulu). +9. **Profondeur de pile des tâches.** `net`/`weather` à 8192 o (TLS+HTTP+JSON), + `tick` à 4096 o, tâche LVGL gérée par le BSP — à surveiller au monitor + (`uxTaskGetStackHighWaterMark`) sous charge audio + UI. +10. **Mémoire de jeu (`games.c`).** Les états Memory/Compter sont alloués via + `lv_malloc` et vivent le temps de l'écran (non libérés à la destruction) — + OK pour une app mono-écran longue durée, à durcir si les écrans sont + recréés en boucle. +11. **Politique supply-chain.** Conformément au dépôt parent, les deps doivent + à terme être miroitées/épinglées par hash contre la baseline auditée + (`idf_component.yml` ne fige aujourd'hui que les versions sémantiques). + +## Licence + +À définir (aligner sur la politique de l'org `electron-rare`). diff --git a/sdcard/README.md b/sdcard/README.md new file mode 100644 index 0000000..8119acf --- /dev/null +++ b/sdcard/README.md @@ -0,0 +1,44 @@ +# Carte microSD — disposition attendue + +Formatez la carte en **FAT32** et copiez l'arborescence suivante à la racine : + +``` +/ (racine de la carte microSD, montée sur /sdcard) +└── sounds/ + ├── manifest.json # décrit la grille de la "Boîte à sons" + ├── miaou.mp3 + ├── ouaf.mp3 + ├── coucou.mp3 + ├── bravo.mp3 + ├── histoire.mp3 # joué par le bouton "Histoire du soir" (Horloge) + └── calme.mp3 # ambiance douce optionnelle (Coin calme) +``` + +## `manifest.json` + +Tableau JSON d'objets `{ file, title, emoji }` : + +```json +[ + { "file": "miaou.mp3", "title": "Le chat", "emoji": "🐱" } +] +``` + +- `file` : nom du fichier **sous `/sounds/`** (pas de chemin absolu). +- `title` : libellé affiché sous le bouton. +- `emoji` : icône du bouton (rendu si une police emoji est embarquée — voir + README principal, section "À vérifier sur le matériel"). + +Le firmware lit jusqu'à `LISAEL_SD_MAX_SOUNDS` (32) entrées. Sans carte ou sans +`manifest.json`, l'écran "Boîte à sons" affiche un message d'invite au lieu +d'une erreur. + +## Format audio + +Fichiers **MP3** (le pipeline SD utilise le décodeur MP3 d'ESP-ADF). Les clips +seront générés plus tard par TTS FR. Préférez un débit modéré (ex. 128 kbit/s, +44.1 kHz) pour un décodage léger sur l'ESP32-S3. + +Les fichiers `histoire.mp3` et `calme.mp3` sont chargés par nom en dur +(respectivement par l'écran Horloge et le Coin calme) ; ils peuvent aussi +figurer dans le `manifest.json` pour apparaître dans la grille. diff --git a/sdcard/sounds/manifest.json b/sdcard/sounds/manifest.json new file mode 100644 index 0000000..2c5f01d --- /dev/null +++ b/sdcard/sounds/manifest.json @@ -0,0 +1,8 @@ +[ + { "file": "miaou.mp3", "title": "Le chat", "emoji": "🐱" }, + { "file": "ouaf.mp3", "title": "Le chien", "emoji": "🐶" }, + { "file": "coucou.mp3", "title": "Coucou", "emoji": "👋" }, + { "file": "bravo.mp3", "title": "Bravo", "emoji": "⭐" }, + { "file": "histoire.mp3", "title": "Histoire", "emoji": "📖" }, + { "file": "calme.mp3", "title": "Calme", "emoji": "🫧" } +]