@@ -1,101 +1,112 @@
# Lisael Box
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).
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).
> ⚠️ **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êt e
> et exhaustive.
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/` ](#merlin--studio-de-contenu-tower )), exposé sur `merlin.saillant.cc` .
> **Statut : compile vert (ESP-IDF 5.4.4 + BSP `esp-box-3` 3.2.x), pas encor e
> validé sur la carte.** `idf.py set-target esp32s3 && idf.py build` ré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](#à-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, 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) .
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** (nouvelles API s : `driver/i2c_master.h` ,
- **ESP-IDF 5.4 / 5.5** (APIs récente s : `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 ` .
port LVGL , codec ES8311 (`esp_codec_dev` ), microSD (FATFS), boutons, IMU.
Tire transitivement `esp_lvgl_port ^2` , **LVGL 9.x ** , `esp_codec_dev` .
- **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 .
- **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 via `esp_codec_dev` . Cela évite
le conflit de versions ESP-ADF `audio_board` ↔ `esp_lcd_ili9341` avec le BSP .
- **cJSON** : composant **built-in `json` ** d'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 ; 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
├── 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/
│ ├── 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
│ ├── app_main.c # séquence de boot + tâches
│ ├── lisael_config.h # constantes partagées (TZ, NVS keys, volume )
│ ├── 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
│ │ ├── 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} # ESP-ADF http_stream→decoder→i2s
│ │ └ ── sd_player.{c,h} # ESP-ADF fatfs→mp3→i2s + parse manifest.json
│ │ ├── 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} # 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
│ │ ├── 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
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")`).
**ESP-IDF 5.4+ ** installé et exporté :
> ⚠️ **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).
``` bash
. $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
@@ -103,7 +114,7 @@ lisael-box/
# 1. cibler l'ESP32-S3
idf.py set-target esp32s3
# 2. configurer le WiFi et la météo (lat/lon, ville)
# 2. configurer le Wi- Fi et la météo (lat/lon, ville)
idf.py menuconfig
# -> "Lisael Box configuration" -> Wi-Fi / Weather / Audio
@@ -117,102 +128,98 @@ idf.py -p /dev/cu.usbmodem* flash monitor
### Provisioning Wi-Fi
Deux voies :
Trois voies, par ordre de priorité au boot :
- **menuconfig ** : ` L isael 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 names pac e ` 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 ).
- **NVS à l'exécution ** : `l isael_wifi_save_credentials()` écrit `wifi_ssid` /
`wifi_pass` dans le namespace `lisael` — **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 pag e 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). 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.
`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 | 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/veilleus e | ` 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) |
| 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) |
| 📖 Lir e | `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` ](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.json` en 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` ).
``` bash
# 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 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 ! ⭐" .
- **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 :
Le code **compile ** ; ce qui suit n'a pas encore été 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** appel er ` 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) ` — el les
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).
1. **Audio réel ** : sortie ES8311 (webradio MP3, podcasts AAC, MP3 SD, tonalité
calme, pops), niveaux et plafond de volume.
2. **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` ).
3. **IMU (labyrinthe) ** : lecture ICM-42670 via `bsp_i2c_get_handle()` , sens des
axes, fallback « Capteur indisponible ».
4. **Backlight / veilleuse ** : `bsp_display_brightness_set()` doit tamiser en
mode horloge.
5. **microSD ** : montage FATFS, lecture des manifestes et des assets poussés.
6. **Réseau bout-en-bout ** : provisioning (NVS / SoftAP / menuconfig), SNTP,
open-meteo HTTPS, `regist er`/push delta depuis Merlin.
7. **URLs des webradios ** (`modes/radio.c` ) : confirmer chaque flux (`curl -I` →
`audio/mpeg` ).
8. **Profondeur de pile ** des tâches `net` /`weather` (TLS+HTTP+JSON) sous charge
audio + UI (`uxTaskGetStackHighWaterMark` ).
9. **Politique supply-chain ** : `idf_component.yml` ne 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