Architecture¶
Vue d'ensemble¶
La configuration ESPHome est découpée en packages YAML importés par un fichier d'entrée unique (la liste : bloc packages: de tab5-ha-hmi.yaml). Cela évite un fichier monolithique qui devient impossible à naviguer au-delà de 1000 lignes. Chaque package a une responsabilité clairement définie et peut être édité, testé, ou remplacé de façon isolée.
Flux de données : HA pousse, la tablette émet des événements¶
Le Tab5 n'interroge jamais l'état de Home Assistant (ADR-0001). Le blueprint « emplacements » et les packages tab5_* détectent les changements et appellent les actions ESPHome du firmware (tab5_maj_*, déclarées dans Tab5/paquets/tab5-api-logic.yaml) ; le firmware parse le payload et met à jour LVGL en une passe. Dans l'autre sens, la tablette n'appelle aucune action de Home Assistant : une tuile touchée, une demande (mois du calendrier, annonce, bouton de la console) ou sa (re)connexion devient un événement esphome.tab5_*, que le blueprint ou packages/tab5_evenements.yaml traduit en une action d'une liste blanche fixe (ADR-0025).
Choix de conception¶
- Push uniquement, zéro polling. L'appareil ne demande jamais son état à Home Assistant. Les automations côté HA détectent les changements et poussent les données vers l'écran via des appels de service ESPHome natifs. Dans l'autre sens, la tablette émet des événements, jamais des actions Home Assistant (ADR-0025). Le CPU reste proche de zéro quand rien ne change.
- YAML modulaire. La configuration ESPHome est découpée en trente-sept fichiers par domaine (tokens, carte (hardware, révision d'écran, taille de l'écran), canal de publication, capteurs diagnostics, capteurs domotique, logique API, styles, globales, scripts, UI, arcade, calendrier, assistant vocal, IMU, navigation, entités HA, réveil, pièces et tuiles, roue d'actions rapides, rangée sous l'horloge, popup Énergie, zones, popup Réglages, popup Température, popup Alertes, popup Maison, popup Météo, popup Musique, musique de toute la maison, capteurs suivis, frigos, popup Serveur IA, popup télécommande, économie d'énergie, thèmes), chacun lisible indépendamment. La plupart tiennent sous 500 lignes ; seuls les plus gros (
tab5-alarm.yaml,tab5-styles.yaml,tab5-lvgl.yaml,tab5-api-logic.yaml,tab5-sensors-diagnostics.yaml,carte-waveshare-7b.yaml) dépassent, et l'UI est encore découpée en 108ui_components/*.yamlréutilisables. - LVGL natif, pas de stack web. LVGL rafraîchit jusqu'à 60 fois par seconde, depuis un framebuffer en PSRAM, et ne redessine que ce qui a changé. Mesuré sur la tablette (firmware 3.2.0, 28/09/2026) : une valeur ou la minute de l'horloge se redessine en moins de 10 ms, le panneau tournant du centre en 15 à 21 ms par image, l'écran entier en 133 ms, et un popup s'ouvre en 126 à 197 ms. Les polices vectorielles (Material Design Icons) remplacent complètement les fichiers image.
- Compression de données. Les payloads complexes (prévisions 15 jours, prévisions horaires, alertes météo) sont sérialisés en chaînes délimitées côté HA et parsés en C++ sur l'appareil — un seul appel réseau, zéro requête suivante.
- Résilience hors-ligne. Toutes les lambdas C++ vérifient
api.connected()ethas_state()avant de toucher l'UI. Si HA redémarre, le dernier état connu reste affiché — et l'appareil reste utilisable seul (horloge, arcade, console diag). Il ne se redémarre de lui-même qu'après une heure entière sans aucun client API (api: reboot_timeout: 60min), un filet anti-« zombie » assumé, pas une réaction à une coupure HA passagère.
1. Point d'entrée : tab5-ha-hmi.yaml¶
Le fichier racine fait trois choses :
-
Charge les substitutions d'entités —
substitutions: !include Tab5/user_entities.yaml(fichier local gitignoré). CopierTab5/user_entities.example.yamlversuser_entities.yamlet éditer vos entity IDs HA. Depuis la 3.0, aucun secret n'est compilé : le Wi-Fi vient d'Improv ou de l'AP de secours, la clé API de Home Assistant, et les compilations sont signées partab5_signature.pem(ADR-0020). -
Définit la séquence de boot — le bloc
on_bootgère l'ordre de démarrage soigneusement : rétroéclairage → volume media player → activation ampli (dans cet ordre, pour éviter le pop ES8388) → envoi d'un événementtab5_connectedsur le bus HA, à Home Assistant lui-même dès qu'il écoute les actions (ha_evenement_envoyer(),Tab5/ecran/tab5_journal.cpp, depuis le 17/10/2026 : avant, n'importe quel client abonné aux états en premier, un outil aioesphomeapi par exemple, laissait partir l'événement sans personne pour l'entendre) → démarrage de la détection wake-word si activée. -
Importe tous les packages via
!include.
2. Rôles des packages¶
tab5-ui-tokens.yaml¶
Tokens dimensionnels partagés, chargés en premier pour que tous les autres packages puissent y faire référence : taille de la carte modale (modal_card_w/modal_card_h), décalage de la barre de titre de 52 px (modal_body_y), marge basse (modal_bottom_y), origine de la grille calendrier (cal_grid_y) et, sur l'accueil, la hauteur d'une ligne de la rangée sous l'horloge (ligne_zone_h) et des cadres Ok Nabu et − / + qui en entourent une (cadre_bas_h). Changer la géométrie d'un popup = changer un token ici, pas 8 nombres en dur dispersés dans ui_components/.
carte-*.yaml¶
La carte, choisie à la compilation par tab5_carte: dans Tab5/user_entities.yaml (ADR-0061) : tab5 (par défaut, le M5Stack Tab5) ou waveshare-7b (Waveshare ESP32-P4 7B, 1024×600, expérimentale : elle tourne sur la carte de l'auteur depuis le 14/10/2026, l'accueil et les popups suivent la taille de son écran, pas encore les jeux, voir Waveshare ESP32-P4 7B). carte-tab5.yaml charge geometrie-tab5.yaml, tab5-hardware.yaml et la révision d'écran ecran-*.yaml ci-dessous ; carte-waveshare-7b.yaml porte tout le matériel de cette carte et remplace par des bouchons les id du matériel du Tab5 qu'elle n'a pas (alimentations, batterie, RTC), pour que les autres packages compilent sans changer. Le geometrie-*.yaml de chaque carte donne la taille de l'écran (carte_ecran_l/carte_ecran_h, dont dérivent les jetons et les kEcranL/kEcranH du C++), la taille de la dalle, la rotation de LVGL et l'échelle des grosses polices. Le fichier racine choisit aussi tab5-imu.yaml (BMI270) sur le Tab5 et tab5-imu-absente.yaml ailleurs. Voir docs/hardware.md.
tab5-hardware.yaml¶
Configuration matérielle bas niveau du Tab5 (chargée par carte-tab5.yaml) :
- Réglages écran et tactile communs aux trois révisions du Tab5 (MIPI-DSI, broches, calibration) ; le modèle d'écran et la plateforme tactile viennent de Tab5/paquets/ecran-<révision>.yaml (par défaut : M5STACK-TAB5-ST7123 et la plateforme I2C officielle st7123, depuis ESPHome 2026.7.0 ; l'ancien shim external_components a disparu) — voir docs/hardware.md
- Bus I2C, expanders GPIO PI4IOE5V6408 (lignes de reset écran/tactile)
- DAC ES8388 (plateforme audio_dac:) et ADC micro ES7210 (audio_adc:)
- esp32_hosted — co-processeur Wi-Fi ESP32-C6 via SDIO
- PWM rétroéclairage (LEDC), bus I2S micro/haut-parleur, media player, ota: — la pile vocale (micro_wake_word/voice_assistant) est dans tab5-assist.yaml depuis le 25/09/2026
→ Détails : docs/hardware.md
ecran-*.yaml¶
Écran et tactile d'une révision du Tab5, choisie à la compilation par tab5_ecran: dans Tab5/user_entities.yaml : st7123 (par défaut, la tablette de l'auteur), st7121 (tourne chez un utilisateur depuis octobre 2026) ou ili9881c (compilée par la CI, jamais testée sur une tablette). Chaque fichier ne contient que ce qui change d'une révision à l'autre — le modèle mipi_dsi et la plateforme tactile (st7123, ou gt911 sur l'ILI9881C d'origine) — et étend les entrées tab5_display et touch de tab5-hardware.yaml par !extend. Voir docs/hardware.md.
publication-*.yaml¶
Canal de publication (ADR-0022), choisi par tab5_publication (par défaut locale ; la CI de publication, .github/workflows/publication.yml, lui donne stable ou beta en substitution). publication-locale.yaml est vide : un firmware compilé sur son PC n'a pas d'entité de mise à jour, car les binaires publiés sont signés par la clé du projet, qu'une tablette flashée avec une autre clé refuse. publication-stable.yaml et publication-beta.yaml incluent publication-commune.yaml avec le canal en variable : une plateforme ota: http_request et une entité update: « Firmware » qui lit le manifeste de la page de flashage (<canal>/<révision d'écran>/manifest.json). Une bêta passe à la stable qui la suit.
tab5-sensors-diagnostics.yaml¶
Entités système et réseau :
- Bloc wifi:, select antenne, switchs d'alimentation GPIO (Wi-Fi/USB/5V externe)
- Température interne ESP32-P4
- Uptime, signal Wi-Fi, IP/SSID, statut API HA
- RAM libre / loop time (debug), horloge SNTP, intervals de la console
tab5-sensors-domotique.yaml¶
Entités domotique poussées par Home Assistant :
- Capteurs humidité plantes (jusqu'à 5 moniteurs BLE)
- Température & humidité des pièces et de la serre
- Miroirs d'état lumières/PC, batterie téléphone
- Audio : ampli, détection jack, switch wake word
- Détails des pots (EC / éclairement / température / batterie) : pot_sensors.yaml, un package paramétré inclus cinq fois (packages: imbriqué + vars: {n})
Ces fichiers ne déclarent que des entités. Aucune logique UI ici.
tab5-api-logic.yaml¶
Le package le plus important. Deux choses y vivent :
Gestionnaires de services API ESPHome — ce sont les endpoints que Home Assistant appelle pour pousser des données vers l'écran. Chaque gestionnaire reçoit un payload, le valide, puis appelle une fonction C++ de la couche C++ (tab5_services.cpp, déclarée dans tab5_custom.h) pour le parser et l'appliquer.
Chaque gestionnaire porte ses métadonnées description / example (ESPHome 2026.9.0 et suivantes) : Home Assistant les affiche dans Outils de développement → Actions, seul endroit où le format d'un payload sérialisé à la main se lit sans ouvrir le fichier. Un gestionnaire sans métadonnées fait échouer pytest (règle 5 de tools/check_tab5_code_rules.py).
Lambdas C++ — pour la logique qui ne rentre pas proprement en YAML (transitions de machine d'états, parsing de chaînes, mises à jour LVGL conditionnelles).
La règle appliquée partout : avant tout appel lv_*, vérifier boot_complete et api.connected(). Cela empêche les valeurs NaN de crasher l'UI pendant les redémarrages HA.
tab5-styles.yaml¶
Définitions globales de styles LVGL, déclarées une fois et référencées par ID partout dans tab5-lvgl.yaml.
Le rationnel : LVGL alloue un objet style par widget si on définit les styles inline. Avec 80+ widgets à l'écran, ça fait 80+ allocations en PSRAM. Déclarer les styles globalement et les attacher par référence n'utilise qu'une poignée d'allocations pour tout l'écran.
tab5-globals.yaml¶
Variables globales partagées accessibles depuis tous les packages :
- boot_complete (bool) — verrou pour toutes les mises à jour UI
- conversation_mode (bool) — bascule entre le pipeline Home Assistant et le pipeline chat
- Snapshots d'état courant (dernières valeurs de température connues, mode clim, etc.)
Les variables ici sont typées et initialisées. Les globales non initialisées sur ESP32 sont un comportement indéfini.
tab5-lvgl.yaml¶
La mise en page UI. Déclare les pages, panneaux, labels, boutons, arcs et icônes, ainsi que la gestion des gestes swipe. Il !include directement 39 fichiers ui_components/*.yaml (carte/popup clim, popup lumière, popup du volet, popup d'un appareil, popup télécommande TV, popup Réglages — dont la page Système est l'ancienne console système —, popup Température, popup Alertes, popup Maison, popup Météo, popup et carte des capteurs suivis, popup Serveur IA, popups assistant/calendrier/plantes, fenêtre du réveil et calque de sonnerie, cartes prévisions, rangée sous l'horloge (jauges humidité et lignes de capteurs), liste de la tuile − / +, carte switches, le bandeau d'alerte HA de la carte centrale — un fichier inclus quatre fois avec vars —, le sélecteur arcade et les 8 jeux) ; ceux-ci incluent à leur tour les sous-templates paramétrés (pot_detail_card.yaml, modal_header.yaml…), soit 108 fichiers de composants au total.
Le dashboard tient sur une seule page LVGL, pas une navigation multi-pages par onglets (ADR-0002) — toute la domotique vit sur un page_main unique en 1280×720, accessible au tap, à l'appui long ou au swipe. Les seules autres pages sont les 9 pages gaming (page_arcade + une par console), toutes en skip: true pour que le swipe ne puisse jamais y atterrir ; elles ne font pas partie du parcours dashboard.
page_main (1280×720) :
page_main (1280×720, tout le dashboard)
├── contenu accueil toujours visible (horloge, temp/humidité intérieure,
│ actions rapides, carte clim, carte
│ humidité)
├── carte centrale rotative (planning / pluie / alertes / info —
│ cycle auto toutes les 8s, `interval:`
│ de tab5-globals.yaml ; en pause hors
│ de la fenêtre prévisions par défaut)
├── zone carte du bas — l'une des deux, basculée par `btn_control_ha` (icône maison, en haut à droite) :
│ ├── cartes du mode HA (`layer_switches` — jusqu'à 20 éléments de la pièce courante sur cinq places, ADR-0023 / ADR-0066)
│ └── carte prévisions (`layer_forecast_daily` / `layer_forecast_hourly` — météo, 5 onglets)
├── climate_popup (modale quasi plein écran, ouverte au tap sur la carte clim)
├── light_popup (modale quasi plein écran, ouverte au tap sur une carte switch lumière)
├── tv_remote_popup (télécommande Samsung, ouverte par un appui long sur le bouton
│ manette ou sur la carte PC — services `remote.*` via HA)
├── assistant_popup (transcription STT + réponse LLM en Markdown, appui long micro)
├── calendar_popup (grille mensuelle 7×6, appui long sur la date)
├── pots_popup (5 cartes détail plantes, appui long sur les slots humidité)
└── reglages_popup (réglages en 4 pages : écran, apparence, batterie, système — la
page système est `console_sys` : diagnostics + volume + gestion HA
avec overlays de confirmation ; tap sur `btn_control_console` =
page écran, appui long = page système)
pages séparées, hors parcours dashboard (toutes en `skip: true` — voir §6) :
├── page_arcade (sélecteur 4×2, ouvert par le bouton manette ou la roue de navigation)
└── page_marble / page_arkanoid / page_pinball / page_lode / page_go /
page_trivia / page_chess / page_draughts (une par console)
La navigation se fait au tactile (ouverture/fermeture des popups clim/lumière et du bouton console, et bascule de la zone du bas entre le mode météo et le mode HA) et par geste swipe, géré en C++ (handle_swipe_gesture() dans tab5_central.cpp) :
- swipe gauche/droite sur la bande basse de l'écran (y ≥ 333) → cycle les 5 pages de prévisions (2 fenêtres horaires + 3 fenêtres journalières, avec le bouclage volontaire documenté dans forecast_page_suivante()) en mode météo, puis, vers la gauche, les pages des pièces 5 … N − 1 quand Home Assistant envoie plus de cinq pièces (pn|N, ADR-0066, avec « Tab5 Appareils sur la météo » allumé) ; en mode HA, le même geste va à la pièce suivante (gauche) / précédente (droite) en rouleau (dernière pièce → pièce 1, pièces vides comprises, tuiles_swipe_ha(), 18/10/2026), les pastilles dans l'ordre des pièces, sans jamais réafficher les calques météo sous les cartes
- depuis la refonte du 14/07/2026 il n'y a plus de swipe haut/bas — la console s'ouvre uniquement par un appui long sur le bouton engrenage
Depuis la 3.2 (ADR-0023), chaque page de prévisions est aussi une pièce de cinq appareils au plus, décrite par Home Assistant (tab5_tuiles.cpp). Le drapeau du mode HA est g_central_ctx.ha_mode (l'ancien global show_switches a disparu) ; tuiles_mode_ha() fait le fondu entre le calque météo et layer_switches (cachés via LV_OBJ_FLAG_HIDDEN, jamais retirés), peint les cinq cartes de la pièce courante, met le titre de la pièce dans la carte centrale et met en valeur le bouton « HA » (tab5-lvgl.yaml, btn_control_ha) — voir les blocs [AI-CONTEXT] de Tab5/ecran/tab5_tuiles.cpp et de ui_components/switches_card.yaml.
Toutes les références de style pointent vers des IDs définis dans tab5-styles.yaml. Aucune propriété de style inline.
→ Inventaire fichier par fichier et graphe de dépendances complet : ../CARTOGRAPHIE_TAB5.md
tab5-scripts.yaml¶
Depuis le 25/09/2026 (audit, lot 8c), les scripts des jeux, du calendrier et de la voix/assistant vivent dans leurs packages : tab5-arcade.yaml, tab5-calendar.yaml, tab5-assist.yaml. Blocs script: ESPHome réutilisables pour les actions multi-étapes appelées depuis les lambdas ou depuis HA. Évite que tab5-api-logic.yaml se remplisse de motifs répétés. Regroupés par famille : init du registre des modales, debounces (volume 150 ms, luminosité 200 ms, clim 250 ms — un appel HA par geste au lieu d'un par tick), volume (point d'entrée unique tab5_volume_apply), widgets de la clim (tab5_clim_ui ; la recoloration est en C++, clim_recolorer()), rotateur central + dismiss, volet, popup lumière, touches de la télécommande TV, et un interval: de 1 s qui ramène à l'accueil.
tab5-arcade*.yaml¶
Les jeux sont un module (ADR-0065), choisi par tab5_jeux (oui / non ; oui par défaut sur le Tab5, non sur une carte sans IMU comme la Waveshare 7B). tab5-arcade.yaml est le module : les fichiers C++ des jeux (esphome: includes:, dont la table des consoles Tab5/jeux/jeux_registre.cpp, GameRegistry::kGames, ADR-0013), leurs couleurs et la police d'échecs, leurs 9 pages LVGL (page_arcade et une par console), un tab5_<jeu>_open par console (il passe au jeu ses pointeurs LVGL et ses polices, que seule une lambda atteint par id()), tab5_arcade_open pour la page du sélecteur et tab5_jeux_traduire (la traduction de ses pages au démarrage). tab5-arcade-absente.yaml le remplace sans jeux : les deux mêmes scripts, vides, et une table de consoles vide (Tab5/jeux/jeux_absents.cpp) ; l'Arcade n'est alors plus un écran de la tablette. tab5_games_close_all vit dans tab5-navigation.yaml (le socle l'appelle). Chargé après tab5-lvgl.yaml : ses pages suivent page_main. Voir §6 et docs/arcade.md.
tab5-calendar.yaml¶
Scripts du popup calendrier (calendar_popup.yaml) : ouverture (appui long sur la date, « auto »), rendu du mois, mois précédent / suivant / aujourd'hui, préchargement au démarrage et tap sur un jour. La grille est calculée par la tablette (tab5_calendar.cpp) ; Home Assistant ne la remplit qu'à la demande : la tablette émet esphome.tab5_calendrier_mois (ou _jour pour un jour), packages/tab5_evenements.yaml lance le script HA, qui répond par les actions tab5_maj_calendrier_mois / _jour. Une demande de mois passe toujours par tab5_cal_request. Chargé après tab5-lvgl.yaml.
tab5-assist.yaml¶
Tout l'assistant vocal : micro_wake_word (okay_nabu, plus Stop), voice_assistant et ses callbacks (états visuels par assist_set_pipeline_state()), l'image de la réponse (http_request + online_image), les scripts vocaux (armement du mot « Stop », interruption, tab5_wake_word_dispatch — la décision elle-même est WakeWord::decide() dans tab5_assist.cpp —, réponse dans la carte centrale) et ceux du popup Assistant. Le matériel audio (bus I2S partagé, ES7210, ES8388, media player) reste dans tab5-hardware.yaml. Chargé après tab5-lvgl.yaml. Voir docs/voice_assistant.md.
tab5-imu*.yaml¶
tab5-imu.yaml sur le Tab5 ; sur une carte sans IMU (ADR-0061), tab5-imu-absente.yaml ne garde que l'interrupteur « Tab5 Tap-to-Wake » que lit le popup Réglages, sans nom et sans effet. Accéléromètre BMI270 : les trois axes sont internal: true (à 10–30 Hz ils satureraient le recorder HA pour rien). Deux consommateurs — le tap-to-wake du dashboard, et l'entrée à l'inclinaison des jeux. Un interval: réajuste la cadence à chaud via set_update_interval() : 100 ms (10 Hz) au repos, 33 ms (30 Hz) quand une console pilotée à l'inclinaison est ouverte. Voir §6.
tab5-navigation.yaml¶
Navigation entre les écrans (08/10/2026, sortie de tab5-ha-controls.yaml et de tab5-scripts.yaml) : le registre des fenêtres modales (tab5_modal_registry_init, ADR-0013 — chaque fenêtre, son libellé et, pour un écran ouvert par un script, ce script), tab5_ecran_ouvrir, la seule routine qui ouvre un écran (le select « Aller à l'écran » et les gestes de l'accueil, par le script tab5_geste, ADR-0039 ; refusée pendant que le réveil sonne), le select lui-même et le text_sensor qui nomme l'écran courant (page de jeu ou popup visible). La liste des écrans vit en trois endroits qu'ESPHome ne sait pas réunir — l'enum Ecran, les options du select lues par HA, le registre —, tenus d'accord par tests/test_appuis.py et tools/check_tab5_registry.py. Chargé après tab5-lvgl.yaml : ses lambdas référencent des ids de widgets LVGL.
tab5-ha-controls.yaml¶
Entités exposées à Home Assistant pour régler l'écran depuis un dashboard sans être devant la dalle : un number pour le volume (point d'entrée unique script.tab5_volume_apply, partagé avec les deux sliders de l'écran), la langue de l'écran, les interrupteurs et l'extinction automatique du popup Réglages, un button de rechargement du calendrier, et un rattrapage toutes les 5 s des changements de volume faits côté media_player.
tab5-alarm.yaml¶
Réveil + annonce des rendez-vous : mélodie rtttl: sur tab5_speaker (hors media player), ~20 entités de réglage exposées à HA (switches, datetime type: time, numbers, selects, text), la machine d'état de sonnerie (un passage de mélodie, puis 3 s de fenêtre d'écoute pour le mot de réveil local « Stop » ; répétition, durée max, crescendo) et un interval: de 1 s qui ne compare que deux entiers — toute l'arithmétique de dates vit dans le moteur pur alarm_clock.cpp (aucun id(), aucun réseau). Le réveil sonne sans Home Assistant ; HA n'ajoute que le briefing parlé, la liste des rendez-vous et une URL de sonnerie optionnelle. Point d'entrée unique script.tab5_alarm_refresh (entités → g_alarm_cfg, jamais l'inverse). Le relais micro/haut-parleur qu'il doit faire lui-même est l'ADR-0010.
tab5-tuiles.yaml¶
Pièces et tuiles (ADR-0023) : le script tab5_tuiles_ui pose dans g_tuiles_ui les widgets que dessine tab5_tuiles.cpp (épaules et boutons des tuiles météo de toutes les pages, cartes du mode HA, lignes et noms des pièces des popups Lumières et Volets, calques, pastilles, titre de la carte centrale, bouton « HA ») et ses deux commandes (événement esphome.tab5_action, tap du volet). Le modèle, la NVS et le dessin vivent dans tab5_tuiles.cpp ; les définitions arrivent par l'action tab5_maj_tuiles. Aucun lv_* ici. Lancé en premier par tab5_zones_apply, avant la première image ; chargé après tab5-lvgl.yaml. Il lance ensuite tab5_roue_ui (tab5-roue.yaml, sorti le 08/10/2026), qui pose dans g_roue_ui les widgets de la roue d'actions rapides (ADR-0036, ui_components/roue_actions.yaml), dessinée par tab5_roue.cpp : l'appui long d'une tuile lum, vol ou cli l'ouvre autour de la tuile — un moyeu, un premier anneau (« Maison », commandes, familles, « Détails ») et un second pour les choix de la famille touchée.
tab5-roue.yaml¶
Roue d'actions rapides d'une tuile (ADR-0036) : le script tab5_roue_ui pose dans g_roue_ui les widgets de ui_components/roue_actions.yaml, dessinés par tab5_roue.cpp, la légende de chaque bouton et la carte centrale comme ancre de la roue de navigation (ADR-0042, tab5_roue_navigation.cpp : son appui long ouvre la roue sur les écrans de la tablette), puis appelle roue_brancher(). tab5_tuiles_ui le lance juste après les widgets des tuiles, comme quand ils étaient dans tab5-tuiles.yaml (sorti le 08/10/2026). Aucun lv_* ici.
tab5-rangee.yaml¶
Rangée sous l'horloge (ADR-0031) : le script tab5_rangee_ui pose dans g_rangee_ui les widgets que dessine tab5_rangee.cpp (ui_components/rangee.yaml : la ligne des plantes, deux panneaux de quatre éléments pour les lignes de capteurs, les pastilles de position). Jusqu'à trois lignes de capteurs de quatre éléments, plus la ligne des plantes, tournent avec la carte centrale : tab5_central_rotator_auto appelle rangee_tour() 0,2 s avant que la carte centrale change, et une ligne reste N tours de 8 s (« Durée d'une ligne » dans le blueprint). Les définitions (clés hLI, hp, hd) arrivent par tab5_maj_tuiles, les états (hLI) par tab5_maj_emplacements ; le modèle et sa NVS vivent dans tab5_tuiles.cpp. Affichage seul : un appui passe à la ligne suivante, un appui long sur la ligne des plantes ouvre Mes Plantes. Aucun lv_* ici. Lancé par tab5_zones_apply, avant la première image ; chargé après tab5-lvgl.yaml.
tab5-reglables.yaml¶
Tuile − / + au choix (ADR-0033) : le script tab5_reglables_ui pose dans g_reglables_ui les widgets que dessine tab5_reglables.cpp — sur la carte clim (ui_components/climate_card.yaml : la valeur entre − et +) et dans la liste (ui_components/reglables_liste.yaml, dix lignes de reglables_ligne.yaml) — et les rappels vers les scripts (tab5_volume_apply, tab5_action, tab5_debounce_reglable). Toucher la température du salon déroule la liste : la clim du blueprint, les appareils de la section « Tuile − / + » du blueprint (clés rN de tab5_maj_tuiles et tab5_maj_emplacements), puis le volume de la tablette. Le choix reste en NVS. Clim choisie : − / + et la valeur gardent leur chemin d'avant. Autre appareil : la valeur change tout de suite, un seul esphome.tab5_action (rN / regler, ou consigne pour une clim) part quand le doigt s'arrête. Aucun lv_* ici. Lancé par tab5_zones_apply, avant la première image ; chargé après tab5-lvgl.yaml.
tab5-energie.yaml¶
Popup Énergie (ADR-0028) : le script tab5_energie_ouvrir pose dans g_energie_ui les widgets de ui_components/energie_popup.yaml à la première ouverture, puis appelle energie_ouvrir() (tab5_energie.cpp) ; tab5_energie_demande émet l'événement esphome.tab5_energie (vue = heures, jours ou mois) à l'ouverture et à chaque bouton de vue. Home Assistant répond par les actions tab5_maj_energie (valeurs en direct) et tab5_maj_energie_historique (barres). Ouvert par une tuile cap à l'option e ou par « Aller à l'écran → Énergie ». Aucun lv_* ici ; chargé après tab5-lvgl.yaml.
tab5-historique.yaml¶
Popup Température (ADR-0032) : le script tab5_historique_ouvrir (cle = salon ou serre) pose dans g_historique_ui les widgets de ui_components/historique_popup.yaml à la première ouverture, puis appelle historique_ouvrir() (tab5_historique.cpp) ; tab5_historique_demande émet l'événement esphome.tab5_historique (cle, vue = jour, semaine ou mois) à l'ouverture et à chaque bouton de vue. Home Assistant répond par l'action tab5_maj_historique (la courbe, et la prévision pour la seconde température). Ouvert par un appui long sur l'une des deux températures de l'accueil (climate_card.yaml). Aucun lv_* ici ; chargé après tab5-lvgl.yaml.
tab5-alertes.yaml¶
Popup Alertes (ADR-0034, lot 4 du plan des alertes, 06/10/2026) : les 20 dernières alertes, une ligne chacune (pastille de la gravité, libellé, « apparue 14 h 02 · lue 14 h 10 · terminée 15 h 30 »), et « Tout marquer comme lu ». Le script tab5_alertes_ouvrir pose dans g_alertes_ui les widgets de ui_components/alertes_popup.yaml à la première ouverture, puis appelle alertes_ouvrir() (tab5_alertes.cpp) ; tab5_alertes_demande émet l'événement esphome.tab5_alertes_historique à l'ouverture, et Home Assistant répond par l'action tab5_maj_alertes_historique (de nouveau tant que le popup reste ouvert et que l'historique change). tab5_alertes_tout_lu lit tout de suite les bandeaux de la carte centrale et émet esphome.tab5_alerte_lue avec alert_id « * ». Ouvert par un appui long sur la carte centrale de l'accueil ou par « Aller à l'écran → Alertes ». Aucun lv_* ici ; chargé après tab5-lvgl.yaml.
tab5-maison.yaml¶
Popup Maison (ADR-0037, 07/10/2026) : toute la maison, une colonne par pièce du blueprint qui a des appareils (pièce 1 → 10 ; cinq colonnes au plus par page, les pièces partagées également sur des pages ADR-0046, pages_repartir(), ADR-0066), une ligne par appareil avec la même icône, le même nom et le même état que la carte du mode HA. Le script tab5_maison_ouvrir pose dans g_maison_ui les widgets de ui_components/maison_popup.yaml (cinq en-têtes de colonne de maison_entete.yaml, 25 lignes de maison_ligne.yaml, deux noms de page de pages_onglet.yaml) à la première ouverture, puis appelle maison_ouvrir() (tab5_maison.cpp), qui dispose les colonnes et peint les lignes. Une ligne réagit comme sa tuile : le toucher, l'appui long et le bouton « ⋯ » passent par tuile_appui_piece() (tab5_tuiles.cpp) — l'appui long et « ⋯ » ouvrent d'abord la roue d'actions rapides de la tuile autour de la pastille de la ligne, sans son lien « Maison » (tuile_roue_ouvrir() ; roue_ouvrir() la met devant ce popup) —, les commandes sont donc celles des tuiles et rien de nouveau n'arrive à Home Assistant ; « Éteindre les lumières » envoie le « tout éteindre » de chaque pièce. peindre_tuile() et tuiles_appliquer_ui() le préviennent quand un état ou les définitions changent, seulement s'il est affiché. Ouvert par « Aller à l'écran → Maison » ou, en mode HA, par un tap sur le titre de la carte centrale. Aucun lv_* ici ; chargé après tab5-lvgl.yaml.
tab5-meteo.yaml¶
Popup Météo (ADR-0043, 09/10/2026) : la météo en graphiques, sur trois pages changées d'un glissé ou par leur nom à côté du titre, comme les Réglages — « Aujourd'hui » (le moment : icône, température, condition, minimum et maximum du jour, pluie sur les heures montrées ; puis jusqu'à 15 heures : icônes, courbe lissée des températures, barres de pluie, l'heure en cours marquée), « 10 jours » (une ligne par jour, barre du minimum au maximum sur une échelle commune) et « Détails » (pluie dans l'heure à l'échelle du temps, humidité, indice UV, probabilités de gel et de neige). Le script tab5_meteo_ouvrir (page) pose dans g_meteo_ui les widgets de ui_components/meteo_popup.yaml (quatre cartes de meteo_carte.yaml) à la première ouverture, puis appelle meteo_ouvrir() (tab5_meteo.cpp), qui construit les tracés une fois et peint la page. Rien n'est demandé à Home Assistant : le popup lit ce qu'il pousse déjà (prévisions horaires et journalières, barres de pluie, météo actuelle, probabilités). Ouvert par « Aller à l'écran → Météo », un geste de l'accueil (code meteo) ou un appui long sur une tuile de prévision sans appareil. Aucun lv_* ici ; chargé après tab5-lvgl.yaml.
tab5-lecteur.yaml¶
Popup Musique (ADR-0050, 10/10/2026) : une carte centrée pour n'importe quel media_player — pochette, titre, artiste, album, barre de position, aléatoire / précédent / lecture-pause / suivant / répétition, volume et sourdine, une pastille par lecteur choisi — la barre « en lecture » de l'accueil, sur le cadre Ok Nabu, et le lecteur compact que peut montrer la zone à gauche de l'horloge (ADR-0051 ; la barre se masque pendant ce temps) (ui_components/lecteur_popup.yaml, lecteur_mini.yaml, lecteur_zone.yaml). Home Assistant pousse les lecteurs et celui affiché par l'action tab5_maj_lecteur (lue par tab5_parse.cpp, section 9) ; tab5_lecteur_lier pose les widgets dans g_lecteur_ui, tab5_lecteur_evenement envoie l'événement esphome.tab5_lecteur (action, lecteur, valeur) pour chaque commande, et un interval de 1 s avance la position (lecteur_tic()). La pochette est une online_image (lecteur_pochette, RGB565 360 × 360) : le chemin entity_picture complété par l'adresse de HA vue à la connexion de l'API. Ouvert par l'appui long d'une tuile med sans option t, la barre « en lecture », le geste musique ou « Aller à l'écran → Musique ». Aucun lv_* ici ; chargé après tab5-lvgl.yaml et tab5-assist.yaml.
tab5-musique.yaml¶
Musique de toute la maison (ADR-0067, 18/10/2026) : les pages du popup Musique après « Lecture », sur sa brique de pages (tab5_pages.cpp, un glissé ou leur nom sur la ligne du titre, chaque page seulement quand elle a quelque chose à montrer). « Pièces » montre une carte par lecteur (huit au plus) : son genre, son nom, son état ou son morceau, le volume (envoyé au relâcher), la sourdine, le groupe avec le lecteur montré, « la musique ici », la marche ; un toucher montre ce lecteur sur « Lecture », un appui long ouvre sa roue (source, sourdine, séparer). « Bibliothèque » parcourt Music Assistant : favoris, playlists, albums, artistes, radios, podcasts, récents, une grille de dix éléments par page avec leurs pochettes ; un toucher joue un titre ou ouvre un contenant, un appui long propose maintenant, à la suite, en fin de file ou en radio. « Lecture » gagne « À suivre » et la puce « + n » d'un groupe. Home Assistant pousse les lecteurs par l'action tab5_maj_musique_pieces et répond à chaque parcours par tab5_maj_musique_liste (lues par tab5_parse.cpp, section 9 bis ; packages/tab5_musique.yaml) ; tab5_musique_lier confie à g_musique_ui les widgets de ui_components/musique_calques.yaml, tab5_musique_evenement envoie l'événement esphome.tab5_musique (action, piece, valeur), les dix pochettes sont des online_image du package paramétré musique_pochette.yaml (160 × 160, téléchargées une à une), et un interval d'1 s abandonne un parcours resté sans réponse 8 s (musique_tic()). Sans Music Assistant, seulement « Pièces » avec les lecteurs de « Tab5 · lecteurs de musique ». Aucun lv_* ici ; chargé après tab5-lvgl.yaml et tab5-assist.yaml.
tab5-cameras.yaml¶
Popup Caméras (ADR-0049, 09/10/2026, discussion #278) : l'image fixe de chaque caméra choisie dans la section « Caméras » du blueprint, en mosaïque de quatre cases par page ou une caméra en grand (ADR-0057 ; un glissé, les pastilles sous le cadre, un tap pour passer de l'une à l'autre), rafraîchie une image à la fois tant que le popup est ouvert — pas de vidéo. Le script tab5_cameras_ouvrir pose dans g_cameras_ui les widgets de ui_components/cameras_popup.yaml et quatre commandes (lambdas) à la première ouverture, puis appelle cameras_ouvrir() (tab5_cameras.cpp). tab5_cameras_demande envoie esphome.tab5_cameras (ouverture, toutes les 4 minutes tant qu'il reste ouvert, après un échec), auquel le blueprint répond par tab5_maj_cameras (l'entity_picture, la pièce et l'heure d'indisponibilité de chaque caméra). À partir de deux pièces, une colonne (ui_components/cameras_puce.yaml) filtre les caméras que parcourt le glissé (ADR-0057). La caméra montrée est téléchargée et décodée hors de la boucle principale (tab5_cameras_charge.cpp : une tâche FreeRTOS, esp_http_client, le décodeur JPEG matériel de l'ESP32-P4, RGB565 en PSRAM, chaque tampon décodé donné à l'écran, qui garde la dernière image de chaque caméra, 8 Mio au plus), toutes les 5 s, ses voisines une fois entre deux ; tab5_cameras_attente (mode: restart) le sonde, et la mémoire est rendue quand le popup se ferme. Ouvert par « Aller à l'écran → Caméras » ou un geste de l'accueil (code cameras). Aucun lv_* ici ; chargé après tab5-lvgl.yaml et tab5-assist.yaml.
tab5-suivi.yaml¶
Capteurs suivis (ADR-0054, 10/10/2026) : le popup « Suivi » — une carte par capteur choisi dans « Tab5 · capteurs suivis » (six au plus), avec son nom, sa valeur et son unité, sa variation en couleur avec une flèche de tendance et la courbe des 24 dernières heures — et la carte du premier capteur que peut montrer la zone à gauche de l'horloge (contenu « capteur », ADR-0051) (ui_components/suivi_popup.yaml, suivi_carte.yaml, suivi_zone.yaml). Home Assistant les pousse par l'action tab5_maj_suivi (lue par tab5_parse.cpp, section 11 ; packages/tab5_suivi.yaml) ; tab5_suivi_lier pose les widgets dans g_suivi_ui (lancé aussi au démarrage par tab5_zones_apply), tab5_suivi_ouvrir peint et ouvre le popup. Ouvert par un tap sur la carte de la zone, le geste suivi ou « Aller à l'écran → Suivi ». Rien n'est demandé à Home Assistant ; aucun lv_* ici ; chargé après tab5-lvgl.yaml.
tab5-froid.yaml¶
Réfrigérateurs et congélateurs (ADR-0055, 10/10/2026) : le popup « Froid », titré « Frigos et congélateurs » — une carte par appareil déclaré dans « Tab5 · réfrigérateurs » et « Tab5 · congélateurs » (quatre au plus en tout), avec sa température colorée par niveau, son statut (conforme, trop chaud, porte ouverte ?, porte mal fermée, capteur muet…), les extrêmes des 24 h et la norme, la courbe des 24 dernières heures entre les pointillés de la norme et le dernier incident (ui_components/froid_popup.yaml, froid_carte.yaml) — et l'icône fridge-alert qui clignote dans le coin haut droit de l'horloge tant qu'un appareil est au niveau grave (btn_froid_alerte dans tab5-lvgl.yaml, un lv_timer de 500 ms en pause quand elle est cachée). Home Assistant décide des niveaux (packages/tab5_froid.yaml) et les pousse par l'action tab5_maj_froid (lue par tab5_parse.cpp, section 12) ; tab5_froid_lier pose les widgets dans g_froid_ui, tab5_froid_ouvrir peint et ouvre le popup. Ouvert par un tap sur l'icône qui clignote, le geste froid, « Aller à l'écran → Froid » ou la roue de navigation (Agenda ▸ Frigos). Rien n'est demandé à Home Assistant ; aucun lv_* ici ; chargé après tab5-lvgl.yaml.
tab5-serveur-ia.yaml¶
Serveur IA local (ADR-0059, 10/10/2026) : le popup « Serveur IA » — un bandeau avec la pastille en ligne / hors ligne, l'état, le nom du serveur et le modèle chargé ; la carte des tokens par seconde (grand chiffre, courbe des 24 dernières poussées gardée par la tablette, requêtes en cours et en file) ; quatre cartes pour la VRAM (avec une barre), la température du GPU (colorée selon le niveau que décide Home Assistant), la RAM (avec une barre) et la puissance (ui_components/serveur_ia_popup.yaml, serveur_ia_carte.yaml). Une valeur inconnue s'écrit « — ». Home Assistant pousse les capteurs choisis dans les listes « Tab5 · serveur IA, … » (packages/tab5_serveur_ia.yaml, deux secondes au moins entre deux poussées) par l'action tab5_maj_serveur_ia (lue par tab5_parse.cpp, section 14) ; rien n'est peint tant que le popup est fermé. tab5_serveur_ia_lier pose les widgets dans g_serveur_ia_ui, tab5_serveur_ia_ouvrir peint et ouvre le popup. Ouvert par le geste serveur_ia, « Aller à l'écran → Serveur IA » ou la roue de navigation (Agenda ▸ Serveur IA, une fois un serveur poussé). Aucun lv_* ici ; chargé après tab5-lvgl.yaml.
tab5-telecommande.yaml¶
Popup télécommande à plusieurs télécommandes (ADR-0056, 10/10/2026) : tab5_telecommande_ui, lancé par tab5_tuiles_ui, pose dans g_telecommande_ui les widgets de ui_components/tv_remote_popup.yaml que tab5_telecommande.cpp montre ou cache selon la télécommande de la page — les noms en haut (pages_onglet.yaml), Source ou Stop, Muet ou l'icône du volume, la rangée d'applications (TV) ou la rangée de lecture (boîtier : Apple TV, Freebox Player) — et branche le glissement gauche / droite (tab5_pages.cpp, ADR-0046). La liste arrive par la clé « telecommandes » de tab5_maj_emplacements (tab5_parse.cpp, section 13) ; chaque touche émet un événement esphome.tab5_action dont l'emplacement (« tv », « tv1 »…) est la page montrée, et le blueprint « Tab5 — emplacements » le traduit pour cette télécommande. Rien n'est demandé à Home Assistant ; aucun lv_* ici ; chargé après tab5-lvgl.yaml.
tab5-zones.yaml¶
Zones optionnelles (ADR-0018) : une zone dont l'emplacement n'est pas choisi dans le blueprint « Tab5 — emplacements », ou dont l'entité n'existe pas, disparaît de l'écran avec ses boutons. tab5_zones_demande interroge Home Assistant une fois par connexion (événement esphome.tab5_zones), le blueprint répond par l'action tab5_maj_zones, et tab5_zones_apply passe les widgets à zones_apply_ui() (tab5_zones.cpp, où vit la décision) et publie le capteur de diagnostic « Zones masquées ». Il est lancé à la fin du setup, après chaque réponse de HA, et quand une donnée fait revenir une zone. Chargé après tab5-lvgl.yaml.
tab5-reglages.yaml¶
Popup Réglages (06/10/2026, quatre pages depuis le 08/10/2026) : les réglages qu'on veut changer sans Home Assistant — page Écran : luminosité, extinction auto, rallumage à « Okay Nabu » et d'une tape ; page Apparence : thème, clair ou sombre, interrupteur de nuit du mode Auto, langue ; page Batterie : limite de charge, économie d'énergie et batterie montée, avec l'état, le niveau, la tension et la consommation de la batterie (peints par reglages_batterie_peindre() seulement pendant que la page est montrée) — et la page Système, l'ancienne console système (console_sys.yaml, rafraîchie seulement pendant que la page est montrée : reglages_page_visible(REGLAGES_PAGE_SYSTEME)). On change de page d'une tape sur son nom en haut ou en glissant à gauche ou à droite dans le popup (LV_EVENT_GESTURE pris par le popup, qui ne le laisse pas remonter à page_main ; un glissement parti d'un curseur ne change pas de page), sans transition. Ouvert d'un tap sur le bouton engrenage (btn_control_console) sur la page Écran, par son appui long sur la page Système, par « Aller à l'écran → Réglages » (page Écran) ou « Aller à l'écran → Console système » (page Système). tab5_reglages_ouvrir(page) pose dans g_reglages_ui les widgets de ui_components/reglages_popup.yaml à la première ouverture ; tab5_reglages_sync_ui lit les entités et appelle reglages_peindre() (tab5_reglages.cpp), et chaque entité réglée là le lance depuis son propre on_value / on_state : le popup montre ce que voit Home Assistant, qui que ce soit qui ait changé le réglage ; tab5_reglages_choisir écrit une entité, et la langue demande d'abord une confirmation (tab5_reglages_langue_confirmer), puisqu'elle redémarre la tablette. Rien n'est gardé ici : les réglages restent les entités (restore_value / restore_mode). Aucun lv_* ici ; chargé après tab5-lvgl.yaml et avant tab5-themes.yaml.
tab5-economie.yaml¶
Économie d'énergie (06/10/2026) : le select « Tab5 Économie d'énergie » (Jamais, Sur batterie — le défaut —, Toujours) et l'état de diagnostic « Tab5 Sur batterie ». Son script tab5_economie_appliquer tourne chaque seconde et tout de suite au toucher, à l'allumage de l'écran et sur le courant, le niveau et la charge de la batterie ; il nourrit economie_decider() (tab5_economie.h, pur, testé sur PC) et applique la décision : le plafond du rétroéclairage (la lumière écrit dans l'output template backlight_plafonne de tab5-hardware.yaml, Home Assistant garde donc la luminosité choisie), le niveau des animations animations_niveau() (tab5_anim.cpp ; le select « Tab5 Animations », « Aucune » quand l'économie est active, ADR-0045) et la période de LVGL (33 ms hors des jeux). « Sur batterie » se décide au courant de l'INA226 dans tab5-sensors-diagnostics.yaml ; ce qui retient l'écran (réveil, voix, jeu, mise à jour) est la liste de l'extinction auto, rangée dans ecran_veille_permise. Chargé aussi par le rendu hors tablette, où rien ne l'enclenche.
tab5-themes.yaml¶
Thèmes (ADR-0029) : le select « Thème » (une option par fichier de Tab5/themes/), le select « Clair ou sombre » (Sombre, Clair, Auto) et l'interrupteur « Nuit (thème auto) » que Home Assistant allume au coucher du soleil (tab5_push.yaml, automatisation « Tab5 — thème jour/nuit »). tab5_theme_choisir choisit la palette (theme_selectionner(), tab5_theme.cpp) ; tab5_theme_repeindre repeint sans redémarrer : d'abord les styles partagés (bloc généré par tools/gen_themes.py), puis, le setup fini, chaque couleur posée à l'exécution (theme_rejouer_ui(), une fonction de rejeu par unité C++, plus les icônes des capteurs). Un thème peut aussi changer les formes de neuf styles partagés, garder le bandeau et l'horloge sombres en mode clair, et choisir les polices de l'heure, de la date et des titres (lot 3). Les jeux gardent la palette sombre (ADR-0014). Chargé EN DERNIER : sa repeinture lit les widgets, capteurs et scripts de tous les packages ci-dessus.
3. Couche C++ : tab5_custom.h + les unités tab5_*.cpp¶
Depuis le 08/09/2026, l'ancien tab5_custom.cpp unique (3 169 lignes) est scindé en neuf unités, une par responsabilité — tab5_text.cpp, tab5_forecast.cpp, tab5_central.cpp, tab5_services.cpp, tab5_assist.cpp, tab5_cards.cpp, tab5_console.cpp, tab5_anim.cpp, tab5_calendar.cpp — tab5_custom.h restant l'unique en-tête public et tab5_internal.h déclarant les rares helpers partagés entre unités. tab5_custom.cpp ne garde que les globals partagés. D'autres unités en sont sorties depuis (la clim a quitté tab5_cards.cpp pour tab5_clim.cpp le 08/10/2026, lot L7) ; CARTOGRAPHIE_TAB5.md les liste toutes. Depuis le 08/10/2026, ces fichiers sont dans Tab5/ecran/, le C++ pur qu'ils appellent (tab5_core, tab5_champs, tab5_tokens.h…) dans Tab5/socle/ ; chaque unité a aussi son en-tête (tab5_forecast.h…), et tab5_custom.h est l'en-tête parapluie qui les inclut tous : les lambdas n'incluent toujours que lui.
Le .h déclare toutes les fonctions utilisées depuis les lambdas YAML. Le .cpp les implémente.
Responsabilités principales :
- Tokenizer de chaînes — découpe in-place (strtok_r) des payloads délimités par des points-virgules. Utilisé pour les push bulk météo (parse_and_update_heures_bulk() / parse_and_update_jours_bulk()).
- Helpers LVGL — fonctions de mise à jour gardées contre les pointeurs nuls (update_*_ui(), refresh_*_forecast()) pour qu'un push arrivant avant l'init LVGL ne crashe pas l'UI.
- Logique couleur — mappe températures et humidité des plantes sur des gradients continus (get_temperature_color() / get_humidity_color()).
- Gestes & carte centrale — handle_swipe_gesture() (pagination prévisions), transition_widgets() (animations de panneaux), show_temporary_planning() (affichage 6 s puis restauration), update_info_text_ui() (panneau info), normalize_text_utf8() (correction d'accents des textes HA dynamiques).
(L'icône du micro, sa couleur et le libellé d'état suivent le pipeline via assist_set_pipeline_state() (tab5_assist.cpp), appelée par les cinq callbacks voice_assistant: de tab5-assist.yaml.)
4. Le paradigme Push¶
L'appareil n'initie jamais de requête réseau. Tout le flux de données va dans un seul sens :
Home Assistant Tab5 (ESP32-P4)
────────────── ───────────────
Changement d'état détecté
→ automatisation déclenchée
→ appel service : esphome.tab5_maj_previsions_jours_bulk(payload)
→ gestionnaire API reçoit payload ────────────────────→
parse_and_update_jours_bulk() s'exécute
labels LVGL mis à jour
Pas de pause côté HA (01/10/2026) : l'automatisation de push envoie ses blocs de service à la suite ; la séquence garde leur ordre. Les pauses d'une seconde qu'elle insérait dataient de l'époque où une poussée faisait une vingtaine d'appels en boucle, et ne faisaient que retarder l'écran de 6 s à chaque redémarrage. Les payloads groupés restent découpés en blocs : la tablette refuse un payload de plus de 2048 octets.
5. Compression de données¶
Pour les prévisions journalières sur 15 jours, 15 × 4+ points de données (jour, condition, temp max, temp min…) représenteraient des dizaines d'appels de service séparés. À la place, HA construit une chaîne :
"0;Soleil;27;14;1;Nuageux;24;12;2;Pluie;19;11;..."
Le tokenizer C++ découpe sur ; en un seul passage — O(n) sur la longueur de chaîne, pas O(n) sur le nombre d'appels. La mise à jour LVGL se fait ensuite une seule fois, de façon atomique, sans redraws intermédiaires.
Même schéma pour les prévisions horaires (tab5_maj_previsions_heures_bulk) et le payload de vigilance Météo-France (tab5_maj_alerte_meteo_france, 11 à 13 champs délimités par | : brouillard et feux de forêt, en fin, sont facultatifs).
6. Couche jeux (Arcade — expérimental)¶
Les 8 consoles sont des sous-modules isolés qui ne partagent aucun état avec le dashboard HMI. Elles suivent une architecture stricte :
- Une page LVGL dédiée plein écran 1280×720 par jeu (
page_marble,page_chess… déclaréesskip: truepour que le swipe ne puisse pas y naviguer) — et non un overlay empilé surpage_main. Seule exception documentée à la règle du chrome modal (ADR-0009) - YAML = conteneurs vides — chaque
*_game.yamlne déclare que 2 à 4 conteneurslv_obj; tout le contenu visuel est construit en C++ - Cycle de vie
lv_timer— créé à l'ouverture, détruit à la fermeture → coût CPU nul quand aucun jeu ne tourne - Pool LVGL préalloué — sprites et labels créés une fois à la première ouverture, puis recyclés par
show/hide+set_pos→ zéro allocation dans la boucle de jeu - Persistance NVS — chaque jeu a sa propre struct de sauvegarde via
esphome::global_preferences(validée par un magic) - Zéro dépendance HA / réseau — les jeux fonctionnent entièrement hors ligne
- Polling IMU adaptatif —
tab5-imu.yamlpasse de 100 ms (10 Hz) au repos à 33 ms (30 Hz) quand un jeu à l'inclinaison est ouvert
La navigation passe par lvgl.page.show: (YAML) ou lv_scr_load() (C++) ; la page sélecteur page_arcade est le point d'entrée unique, ouverte par le script tab5_arcade_open (tap du bouton manette, « Jeux » dans la roue de navigation ; le tap sur la température de la serre change la zone à gauche de l'horloge depuis l'ADR-0051). Les fichiers C++ sont inclus via esphome: includes: dans le module des jeux, tab5-arcade.yaml (ADR-0065 ; un firmware sans jeux n'en compile aucun). Chaque jeu vit dans son propre namespace (Marble, Arkanoid, Pinball, Lode, Go, Trivia, Draughts, Chess) avec une API uniforme : open(), close(), is_open(), on_imu(ax, ay, az). Ces points d'entrée sont listés une seule fois, dans Tab5/ecran/tab5_registry.cpp (GameRegistry::kGames, avec un drapeau imu_fast par console) : la fermeture globale, le poll IMU adaptatif, le dispatch IMU et le capteur HA « Écran courant » lisent cette table au lieu de nommer les jeux (ADR-0013). Le même fichier porte ModalRegistry, la liste unique des fenêtres modales, remplie une fois par le script tab5_modal_registry_init.
Une exception à l'orientation : Pinball::open() bascule LVGL en portrait 720×1280 et Pinball::close() restaure rotation: 270. C'est la seule console qui touche à l'orientation — voir le bloc [AI-CONTEXT] « ORIENTATION » en tête de pinball_game.cpp.
Statut : prototypes précoces. Premiers jets générés par IA pour tester les capacités de génération de code embarqué — fonctionnels mais non finalisés visuellement.