From 3c03e3fbf1dca6dc7025dc3502e7dc14bae95da8 Mon Sep 17 00:00:00 2001 From: nicoboy Date: Tue, 4 Aug 2026 23:11:35 +0200 Subject: [PATCH] Migre l'IHM OLED vers u8g2 (dashboard graphique, splash anime, economiseur d'ecran) et active la liaison TMS320 reelle - TMS_DUMMY_MODE=false : reception/emission UART reelle avec le TMS320 (au lieu de la simulation), echo debug optionnel sur Serial. - Remplace le driver OLED maison (legacy/er_oled.*) par u8g2 : dashboard graphique avec bargraphs, icones d'alerte/danger, splash logo anime, economiseur d'ecran (balle rebondissante) qui se reactive sur activite HT ou consultation du dashboard web. - Renomme le SSID WiFi en "HIVY", documente le protocole UART et les problemes d'affichage SSD1322/u8g2 rencontres (doc/OLED-u8g2.md). Co-Authored-By: Claude Sonnet 5 --- doc/ESP32-UART.md | 24 +- doc/OLED-u8g2.md | 66 +++++ include/config.h | 14 +- {src => legacy}/er_oled.cpp | 0 {src => legacy}/er_oled.h | 0 platformio.ini | 9 + src/lebel_logo_xbm.h | 269 +++++++++++++++++++ src/oled_ui.cpp | 503 ++++++++++++++++++++++++++++++++++-- src/oled_ui.h | 6 - src/tms_link.cpp | 18 +- src/warning_icon_xbm.h | 24 ++ src/web_ui.cpp | 12 +- src/web_ui.h | 8 + 13 files changed, 909 insertions(+), 44 deletions(-) create mode 100644 doc/OLED-u8g2.md rename {src => legacy}/er_oled.cpp (100%) rename {src => legacy}/er_oled.h (100%) create mode 100644 src/lebel_logo_xbm.h create mode 100644 src/warning_icon_xbm.h diff --git a/doc/ESP32-UART.md b/doc/ESP32-UART.md index b01bade..5f52703 100644 --- a/doc/ESP32-UART.md +++ b/doc/ESP32-UART.md @@ -137,10 +137,22 @@ trame si ca ne correspond pas (trame corrompue). `updateSimulation()`, qui simule des valeurs realistes (utile comme reference de plage de valeurs attendues : `Vin` ~400V, `Iin` ~1.2A, `V1`/`Vout` ~200V, `I1`/`I2` ~2.5A, `T1`/`T2` ~40°C). -- `include/config.h` : `TMS_DUMMY_MODE` (actuellement `true`) fait - tourner l'ESP32 sur la simulation interne au lieu de lire l'UART reel. - **A passer a `false` une fois le firmware TMS320 pret et teste**, pour - activer la reception reelle des trames `$T,...*XX`. +- `include/config.h` : `TMS_DUMMY_MODE` (actuellement `false`, liaison + reelle active) fait tourner l'ESP32 sur la simulation interne au lieu + de lire l'UART reel quand mis a `true` (utile pour developper l'IHM + sans hardware TMS320 branche). + +## A faire cote protocole (pas encore implemente) + +- **Trame de defaut** : le TMS doit signaler lui-meme une coupure de + securite des sorties PWM (court-circuit, surintensite I1/I2, + temperature critique >80°C) — pas de champ dedie actuellement, la + telemetrie ne donne que les mesures brutes. Cote ESP32, + `OledUi::drawDashboard()` (`src/oled_ui.cpp`) affiche pour l'instant + DEFAUT simplement quand HT n'est pas commande (`!cmd.ht`), en attendant + cette trame. Quand elle existera, ajouter le champ correspondant dans + `Telemetry` (`include/protocol.h`) et brancher la vraie condition dans + `drawDashboard()`. ## Points de vigilance @@ -149,7 +161,7 @@ trame si ca ne correspond pas (trame corrompue). pas d'erreur visible — la telemetrie n'avancera simplement pas). - Terminer chaque trame par `\n` uniquement (le `\r` est tolere/ignore cote ESP32 mais pas necessaire). -- Ne pas depasser une longueur de ligne de 160 caracteres (buffer fixe - cote ESP32, `g_lineBuf[160]` dans `tms_link.cpp`). +- Ne pas depasser une longueur de ligne de 200 caracteres (buffer fixe + cote ESP32, `g_lineBuf[200]` dans `tms_link.cpp`). - Verifier au multimetre/oscilloscope le niveau logique du TMS320 avant de le relier a l'ESP32-C3 (3,3V attendu). diff --git a/doc/OLED-u8g2.md b/doc/OLED-u8g2.md new file mode 100644 index 0000000..394c9e6 --- /dev/null +++ b/doc/OLED-u8g2.md @@ -0,0 +1,66 @@ +# Ecran OLED : migration vers u8g2 + +L'IHM OLED (`src/oled_ui.cpp`) utilise la librairie u8g2 +(`U8G2_SSD1322_NHD_256X64_F_4W_HW_SPI`) depuis la migration du driver +maison (conserve dans `legacy/er_oled.cpp` / `legacy/er_oled.h` pour +reference, non compile). + +## SPI matériel sur broches personnalisees + +Le SSD1322 est cable sur des broches qui ne sont pas les broches SPI +par defaut de l'ESP32-C3. `SPI.begin(PIN_OLED_CLK, -1, PIN_OLED_MOSI, +PIN_OLED_CS)` doit etre appele **avant** `u8g2.begin()` pour que le bus +HW SPI d'u8g2 utilise ces broches (voir `OledUi::begin()`). + +## Registre remap (0xA0) + +Le remap par defaut d'u8g2 pour ce controleur (`0x06` dans +`u8x8_d_ssd1322.c`) ne correspond pas au cablage de notre panneau +(image en miroir horizontal). On reapplique apres `u8g2.begin()` la +valeur validee sur l'ancien driver maison : `u8g2.sendF("caa", 0xA0, +0x14, 0x11)` (COM remap + nibble remap actifs, column remap desactive). + +**Ne pas desactiver le nibble remap** (`0x10` au lieu de `0x14`) : deja +teste, ca fait reapparaitre un miroir vertical et rend l'affichage +illisible. u8g2 lui-meme active ce bit dans son propre remap par +defaut (`0x06`), donc il est necessaire quelle que soit la config. + +## Logo splash coupe en deux moities (128px) + +Le logo de demarrage (256x64, dessine en un seul `drawXBMP`) s'affichait +coupe en deux moities de 128px inversees, alors que le texte du +dashboard (courtes sequences, meme chemin de dessin u8g2 sous-jacent) +s'affichait correctement. Plusieurs hypotheses testees sur le hardware, +**aucune n'a resolu le probleme individuellement** : + +1. Bit "nibble remap" du registre 0xA0 -- écarté (voir ci-dessus, a + empire le probleme). +2. Optimisation RLE de `u8g2_DrawXBMP` (regroupe les runs de pixels en + longs segments qui traversent la frontiere interne a 128px du + SSD1322, puce a double controleur de colonnes) -- dessin pixel par + pixel teste, n'a pas resolu le probleme non plus. +3. `u8g2_uint_t` tronque a 8 bits sur les largeurs >= 256 si + `U8G2_16BIT` n'est pas actif -- **verifie non applicable** : dans + u8g2 >= 2.36 (celle utilisee ici, voir `platformio.ini`), + `U8G2_16BIT` est defini inconditionnellement (fix upstream de + l'issue olikraus/u8g2#1222), confirme par un `static_assert(sizeof( + u8g2_uint_t) == 2)` qui compile sans erreur. Le flag `-D U8G2_16BIT` + est neanmoins ajoute en dur dans `platformio.ini` par precaution, + au cas ou une future version de la lib restreindrait a nouveau la + detection auto par architecture (ESP32-C3 = RISC-V, absent de la + liste `__arm__`/`__xtensa__`/... citee par cette issue). + +**Cause racine non identifiee formellement.** Contournement retenu : +dessiner le logo en deux moities de 128px (`lebel_logo_left` / +`lebel_logo_right` dans `src/lebel_logo_xbm.h`) via deux `drawXBMP` +successifs (`OledUi::showSplash()`), aucun appel ne depassant alors la +frontiere des 128px quelle que soit sa cause exacte. Le tableau +`lebel_logo_bits` (image complete 256x64) reste dans le fichier a +titre de reference mais n'est plus utilise et n'est pas garanti complet +(voir taille reelle du tableau si besoin de le reactiver un jour). + +**A confirmer sur le hardware** : si le contournement en deux moities +fonctionne, tant mieux. Si le probleme persiste malgre tout, la cause +est ailleurs (registre remap plus subtil, alimentation, cablage) et il +faudra reprendre l'investigation cote registre SSD1322 avec plus de +prudence (deja regresse une fois en touchant ce registre a l'aveugle). diff --git a/include/config.h b/include/config.h index 94a4a42..5fb3bef 100644 --- a/include/config.h +++ b/include/config.h @@ -15,12 +15,16 @@ #define PIN_OLED_RST 3 #define PIN_OLED_DC 1 -// Le firmware TMS320 n'est pas encore programme : on simule la telemetrie -// en local le temps que le hardware TMS320 tourne. Passer a false quand le -// TMS320 emet reellement des trames $T,...*XX sur l'UART. -#define TMS_DUMMY_MODE true +// Le firmware TMS320 emet reellement des trames $T,...*XX sur l'UART : +// on lit/ecrit le vrai Serial1 au lieu de simuler la telemetrie en local. +// Repasser a true pour developper l'IHM sans hardware TMS320 branche. +#define TMS_DUMMY_MODE false + +// Recopie sur Serial (USB, moniteur PlatformIO) de chaque trame TMS320 +// recue/envoyee sur Serial1, pour debug sans adaptateur UART externe. +#define TMS_DEBUG_ECHO true // WiFi/serveur web de l'IHM : l'ESP32 cree son propre point d'acces // (mode AP), pas de dependance a un reseau local existant. -#define WIFI_AP_SSID "AlimHT" +#define WIFI_AP_SSID "HIVY" #define WIFI_AP_PASSWORD "Milou1234" diff --git a/src/er_oled.cpp b/legacy/er_oled.cpp similarity index 100% rename from src/er_oled.cpp rename to legacy/er_oled.cpp diff --git a/src/er_oled.h b/legacy/er_oled.h similarity index 100% rename from src/er_oled.h rename to legacy/er_oled.h diff --git a/platformio.ini b/platformio.ini index ddb6c6c..eefdf0f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -13,3 +13,12 @@ platform = espressif32 board = esp32-c3-devkitm-1 framework = arduino monitor_speed = 115200 +lib_deps = + olikraus/U8g2@^2.35.19 +; Deja actif inconditionnellement dans u8g2 >= 2.36 (fix upstream de +; l'issue #1222), mais force explicitement au cas ou une future version +; de la lib restreindrait a nouveau la detection auto par architecture +; (ESP32-C3 = RISC-V, absent de la liste __arm__/__xtensa__/... citee +; par cette issue). +build_flags = + -D U8G2_16BIT diff --git a/src/lebel_logo_xbm.h b/src/lebel_logo_xbm.h new file mode 100644 index 0000000..741dfdc --- /dev/null +++ b/src/lebel_logo_xbm.h @@ -0,0 +1,269 @@ +// Logo LEBEL 256x64, format XBM pour u8g2 (drawXBMP). +// LSB = pixel le plus a gauche. +// +// Deux variantes fournies : +// - lebel_logo_bits : image complete 256x64 (necessite U8G2_16BIT) +// - lebel_logo_left/right : deux moities 128x64, sures meme en 8 bits +#pragma once + +#define LEBEL_LOGO_WIDTH 256 +#define LEBEL_LOGO_HEIGHT 64 +#define LEBEL_LOGO_HALF_W 128 + +static const unsigned char lebel_logo_bits[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0x00, + 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x00, + 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, + 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, + 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, + 0x00, 0x0C, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xF8, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0xF9, 0xFF, 0xFF, 0x01, 0xC0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0xFE, 0x0F, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFC, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0xF9, 0xFF, 0xFF, 0x07, 0xE0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0xFC, 0x1F, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0xF9, 0xFF, 0xFF, 0x0F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0xF8, 0x3F, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0xF0, 0x7F, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0xE0, 0xFF, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0xC0, 0xFF, 0x01, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0x80, 0xFF, 0x03, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x01, 0x00, 0xC0, 0x1F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0x00, 0xFF, 0x07, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x01, 0x00, 0xC0, 0x1F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0x00, 0xFE, 0x0F, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x01, 0x00, 0xC0, 0x1F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0x00, 0xFC, 0x1F, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFE, + 0xFF, 0xFF, 0xFF, 0x1F, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0x00, 0xF8, 0x3F, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFE, + 0xFF, 0xFF, 0xFF, 0x1F, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0x00, 0xF0, 0x7F, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFE, + 0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0x00, 0xE0, 0xFF, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFE, + 0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0x00, 0xC0, 0xFF, 0xF9, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFE, + 0xFF, 0xFF, 0xFF, 0x1F, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0x00, 0x80, 0xFF, 0xF9, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFE, + 0xFF, 0xFF, 0xFF, 0x1F, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0x00, 0x00, 0xFF, 0xF9, 0x07, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x01, 0x00, 0xC0, 0x1F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x01, 0x00, 0xC0, 0x1F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x01, 0x00, 0xC0, 0x1F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xF9, 0xE7, 0xFF, 0xFF, 0x3F, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0xC7, 0xFF, 0xFF, 0x7F, 0x30, 0x00, + 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xF9, 0xE7, 0xFF, 0xFF, 0x3F, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0xC7, 0xFF, 0xFF, 0x7F, 0x30, 0x00, + 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xF9, 0xE7, 0xFF, 0xFF, 0x3F, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0xC7, 0xFF, 0xFF, 0x7F, 0x30, 0x00, + 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xF9, 0xE7, 0xFF, 0xFF, 0x3F, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0xC7, 0xFF, 0xFF, 0x7F, 0x30, 0x00, + 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xF9, 0xE7, 0xFF, 0xFF, 0x3F, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0xF9, 0xFF, 0xFF, 0x0F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0xC7, 0xFF, 0xFF, 0x7F, 0x30, 0x00, + 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xF9, 0xE7, 0xFF, 0xFF, 0x3F, 0xFC, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0xF9, 0xFF, 0xFF, 0x07, 0xE0, 0xE7, 0xFF, 0xFF, 0x7F, 0xE0, 0xC7, 0xFF, 0xFF, 0x7F, 0x30, 0x00, + 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xF9, 0xE7, 0xFF, 0xFF, 0x3F, 0xF8, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0xF9, 0xFF, 0xFF, 0x01, 0xC0, 0xE7, 0xFF, 0xFF, 0x7F, 0xC0, 0xC7, 0xFF, 0xFF, 0x7F, 0x30, 0x00, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, + 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, + 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, + 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, + 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x00, + 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const unsigned char lebel_logo_left[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0C, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xF8, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0xFE, 0x0F, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFC, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0xFC, 0x1F, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0xF8, 0x3F, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0xF0, 0x7F, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0xE0, 0xFF, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0xC0, 0xFF, 0x01, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0x80, 0xFF, 0x03, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x00, 0x0C, 0x00, 0xFF, 0x07, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x00, 0x0C, 0x00, 0xFE, 0x0F, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x00, 0x0C, 0x00, 0xFC, 0x1F, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0x00, 0xF8, 0x3F, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0x00, 0xF0, 0x7F, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0x00, 0xE0, 0xFF, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0x00, 0xC0, 0xFF, 0xF9, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0x00, 0x80, 0xFF, 0xF9, 0x07, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0x00, 0x00, 0xFF, 0xF9, 0x07, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x00, 0x0C, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x00, 0x0C, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xF9, 0xE7, 0xFF, 0xFF, 0x3F, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xF9, 0xE7, 0xFF, 0xFF, 0x3F, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xF9, 0xE7, 0xFF, 0xFF, 0x3F, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xF9, 0xE7, 0xFF, 0xFF, 0x3F, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xF9, 0xE7, 0xFF, 0xFF, 0x3F, 0xFE, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xF9, 0xE7, 0xFF, 0xFF, 0x3F, 0xFC, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xF9, 0xE7, 0xFF, 0xFF, 0x3F, 0xF8, 0xF8, 0xFF, 0xFF, 0x0F, 0xFE, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const unsigned char lebel_logo_right[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, + 0xF9, 0xFF, 0xFF, 0x01, 0xC0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0xF9, 0xFF, 0xFF, 0x07, 0xE0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0xF9, 0xFF, 0xFF, 0x0F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x01, 0x00, 0xC0, 0x1F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x01, 0x00, 0xC0, 0x1F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x01, 0x00, 0xC0, 0x1F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0xFF, 0xFF, 0xFF, 0x1F, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0xFF, 0xFF, 0xFF, 0x1F, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0xFF, 0xFF, 0xFF, 0x1F, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0xFF, 0xFF, 0xFF, 0x1F, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x01, 0x00, 0xC0, 0x1F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x01, 0x00, 0xC0, 0x1F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x01, 0x00, 0xC0, 0x1F, 0xF0, 0x07, 0x00, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x30, 0x00, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0xC7, 0xFF, 0xFF, 0x7F, 0x30, 0x00, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0xC7, 0xFF, 0xFF, 0x7F, 0x30, 0x00, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0xC7, 0xFF, 0xFF, 0x7F, 0x30, 0x00, + 0xF9, 0xFF, 0xFF, 0x1F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0xC7, 0xFF, 0xFF, 0x7F, 0x30, 0x00, + 0xF9, 0xFF, 0xFF, 0x0F, 0xF0, 0xE7, 0xFF, 0xFF, 0x7F, 0xF0, 0xC7, 0xFF, 0xFF, 0x7F, 0x30, 0x00, + 0xF9, 0xFF, 0xFF, 0x07, 0xE0, 0xE7, 0xFF, 0xFF, 0x7F, 0xE0, 0xC7, 0xFF, 0xFF, 0x7F, 0x30, 0x00, + 0xF9, 0xFF, 0xFF, 0x01, 0xC0, 0xE7, 0xFF, 0xFF, 0x7F, 0xC0, 0xC7, 0xFF, 0xFF, 0x7F, 0x30, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; diff --git a/src/oled_ui.cpp b/src/oled_ui.cpp index 541920d..7242131 100644 --- a/src/oled_ui.cpp +++ b/src/oled_ui.cpp @@ -1,54 +1,509 @@ #include "oled_ui.h" #include +#include +#include +#include -#include "er_oled.h" +#include "config.h" // PIN_OLED_MOSI / CLK / CS / DC / RST, WIFI_AP_SSID #include "tms_link.h" +#include "web_ui.h" // WebUi::lastActivityMs(), reveil ecran si dashboard web consulte +#include "lebel_logo_xbm.h" // logo splash 256x64 (deux moities 128px) +#include "warning_icon_xbm.h" // icone danger principale 40x40 namespace OledUi { - namespace { -const uint32_t REFRESH_PERIOD_MS = 500; +// Memes broches que dans l'ancien er_oled_begin() : SPI matériel avec +// broches personnalisees (SCK/MOSI ne sont pas les pins HW SPI par +// defaut de l'ESP32-C3, d'ou l'appel explicite a SPI.begin() dans begin() +// ci-dessous, avant u8g2.begin()). +// +// NB : "_1_"/"_2_"/"_F_" dans le nom du constructeur u8g2 designent le +// mode de buffer (taille memoire), pas une variante de cablage du +// panneau -- il n'y a donc qu'un seul constructeur "full buffer" HW SPI +// pour ce controleur. Le remap segment/COM par defaut d'u8g2 ne +// correspondait pas au cablage de notre panneau (image coupee en deux +// moities gauche/droite inversees + miroir horizontal) ; corrige dans +// begin() en reappliquant le registre remap connu-fonctionnel de +// l'ancien driver maison (voir legacy/er_oled.cpp). +U8G2_SSD1322_NHD_256X64_F_4W_HW_SPI u8g2( + U8G2_R0, + /* cs=*/ PIN_OLED_CS, + /* dc=*/ PIN_OLED_DC, + /* reset=*/ PIN_OLED_RST); +const uint32_t REFRESH_PERIOD_MS = 300; uint32_t g_lastRefreshMs = 0; -void drawLine(uint8_t y, const char *text) { - char padded[33]; - snprintf(padded, sizeof(padded), "%-32s", text); - er_oled_string(0, y, padded, 0); +// Port du serveur web de l'IHM -- doit rester synchronise avec +// `WebServer server(80)` dans web_ui.cpp (pas d'accesseur dedie pour +// l'instant, les deux sont a changer ensemble si le port change). +const uint16_t WEB_UI_PORT = 80; + +// --------------------------------------------------------------------- +// Splash logo (voir doc/OLED-u8g2.md pour l'historique des problemes +// d'affichage rencontres et resolus sur ce panneau). +// --------------------------------------------------------------------- + +// Logo remonte de 4px : drawXBMP ne prend pas de y negatif (u8g2_uint_t +// est non signe), donc on saute les 4 premieres lignes de la source +// (deja du blanc) et on reduit la hauteur d'autant. +const uint8_t LOGO_Y_SHIFT = 4; +const uint8_t LOGO_HALF_STRIDE = LEBEL_LOGO_HALF_W / 8; +const uint8_t LOGO_DRAW_H = LEBEL_LOGO_HEIGHT - LOGO_Y_SHIFT; + +const uint32_t SPLASH_ANIM_MS = 500; +const uint8_t SPLASH_ANIM_FRAMES = 20; +const uint32_t SPLASH_HOLD_MS = 4000; + +void drawLogo(uint16_t x) { + const uint8_t *leftBits = lebel_logo_left + LOGO_Y_SHIFT * LOGO_HALF_STRIDE; + const uint8_t *rightBits = lebel_logo_right + LOGO_Y_SHIFT * LOGO_HALF_STRIDE; + u8g2.drawXBMP(x, 0, LEBEL_LOGO_HALF_W, LOGO_DRAW_H, leftBits); + u8g2.drawXBMP(x + LEBEL_LOGO_HALF_W, 0, LEBEL_LOGO_HALF_W, LOGO_DRAW_H, rightBits); +} + +// Dessine "s" caractere par caractere a partir d'un x signe : les +// caracteres dont la position tombe encore hors ecran (x < 0) ne sont +// pas dessines. u8g2.drawStr() n'accepte pas de x negatif +// (u8g2_uint_t est non signe) -- c'est le seul moyen de faire "entrer" +// du texte depuis en dehors de l'ecran. +void drawStrFromOffscreen(int16_t x, uint8_t y, const char *s) { + u8g2_t *raw = u8g2.getU8g2(); + int16_t cx = x; + for (const char *p = s; *p; p++) { + uint8_t w = (uint8_t)u8g2_GetGlyphWidth(raw, (uint8_t)*p); + if (cx >= 0) u8g2.drawGlyph((uint16_t)cx, y, (uint8_t)*p); + cx += w; + } +} + +void showSplash() { + u8g2.setFont(u8g2_font_6x10_tr); + u8g2.setFontPosTop(); + const char *title = "HIVY - Alimentation Haute Tension 2026"; + int16_t titleW = u8g2.getStrWidth(title); + int16_t titleFinalX = (LEBEL_LOGO_WIDTH - titleW) / 2; + if (titleFinalX < 0) titleFinalX = 0; + + // Phase 1 (~500ms) : le logo arrive de la droite (x: 256 -> 0). + for (uint8_t frame = 0; frame <= SPLASH_ANIM_FRAMES; frame++) { + float t = (float)frame / SPLASH_ANIM_FRAMES; + uint16_t logoX = (uint16_t)(LEBEL_LOGO_WIDTH * (1.0f - t)); + u8g2.clearBuffer(); + drawLogo(logoX); + u8g2.sendBuffer(); + delay(SPLASH_ANIM_MS / SPLASH_ANIM_FRAMES); + } + + // Phase 2 (~500ms) : le logo est arrive, la phrase arrive ensuite de + // la gauche, depuis entierement hors ecran (x: -titleW -> centree). + for (uint8_t frame = 0; frame <= SPLASH_ANIM_FRAMES; frame++) { + float t = (float)frame / SPLASH_ANIM_FRAMES; + int16_t textX = (int16_t)(-titleW + (titleW + titleFinalX) * t); + u8g2.clearBuffer(); + drawLogo(0); + drawStrFromOffscreen(textX, 51, title); + u8g2.sendBuffer(); + delay(SPLASH_ANIM_MS / SPLASH_ANIM_FRAMES); + } + + delay(SPLASH_HOLD_MS); +} + +// --------------------------------------------------------------------- +// Dashboard -- echelles d'affichage (VALEURS PROVISOIRES a corriger +// avec les vraies specs de l'alim, cf. doc/parametre-liaisonUART-TMS320-ESP32.txt +// et la calibration ADC dans doc/ESP32-UART.md pour les ordres de grandeur +// deja connus : VIN 30V max, IIN 3A max, VOUT 450V max, IOUT 50mA max). +// --------------------------------------------------------------------- +const float VIN_MIN = 0.0f, VIN_MAX = 25.0f; +const float IIN_MIN = 0.0f, IIN_MAX = 3.0f; +const float VOUT_MIN = 200.0f, VOUT_MAX = 500.0f; +const float IOUT_MIN = 0.0f, IOUT_MAX = 0.050f; // 50 mA +const float PWR_MIN = 0.0f, PWR_MAX = 30.0f; // W, PIN/POUT +const float RDT_MIN = 50.0f, RDT_MAX = 100.0f; // %, rendement + +const float ALERT_THRESHOLD = 0.90f; // 90% de l'echelle -> icone attention + +// DEBUG PROVISOIRE : force l'affichage de toutes les icones (alerte + +// danger principale) pour verifier leur positionnement, independamment +// des seuils/etat reels. Positions validees -> desactive. +const bool DEBUG_FORCE_ALL_ICONS = false; + +// Seuils temperature -- PROVISOIRES, a corriger avec les vraies specs. +const float T1_ALERT_C = 70.0f; +const float T2_ALERT_C = 70.0f; + +// Icone danger principale : affichee seulement si HT actif ET Vout > 50V +// (jamais avec DEFAUT, meme si Vout resterait > 50V par inertie). +const float VOUT_DANGER_THRESHOLD = 50.0f; + +// --------------------------------------------------------------------- +// Layout colonnes (256x64 reel) +// --------------------------------------------------------------------- +const int SCREEN_W = 256; +const int COL1_X = 0; +const int COL2_X = 110; +const int COL3_X = 205; + +// Bargraphs + valeurs min/max + mesures descendus de 2px (BAR_Y 16->18). +const int BAR_Y = 18; +const int BAR_H = 32; +const int BAR_W = 8; + +// Ligne verticale entre colonne 1 et 2, decalee de 2px vers la gauche +// par rapport a COL2_X (qui reste la reference des elements colonne 2). +const int COL1_COL2_DIVIDER_X = COL2_X - 2; + +// Clignotement 1s ON / 1s OFF, partage par toutes les icones d'alerte +// et les mots d'etat de defaut (DEFAUT, SYS:KO). +bool Blink1Hz() { return (millis() / 1000) % 2 == 0; } + +// Mini icone triangle + "!" (dessin vectoriel, trop petit pour un bitmap net) +void drawAlertIcon(int x, int y) { + u8g2.drawTriangle(x + 5, y, x, y + 9, x + 10, y + 9); + u8g2.drawVLine(x + 4, y + 3, 3); + u8g2.drawPixel(x + 4, y + 7); +} + +// Icone danger principale a moitie taille (20x20) : drawXBMP ne +// redimensionne pas, donc on ne prend qu'un pixel source sur deux +// (nearest-neighbor) sur le bitmap 40x40 existant plutot que de +// fabriquer un second asset. +void drawWarningIconHalf(int x, int y) { + const uint8_t stride = WARNING_ICON_WIDTH / 8; + for (int dy = 0; dy < WARNING_ICON_HEIGHT / 2; dy++) { + for (int dx = 0; dx < WARNING_ICON_WIDTH / 2; dx++) { + uint8_t b = pgm_read_byte(&warning_icon_bits[dy * 2 * stride + (dx * 2) / 8]); + if (b & (1 << ((dx * 2) % 8))) u8g2.drawPixel(x + dx, y + dy); + } + } +} + +// Bargraph vertical avec limites affichees a droite et icone d'alerte +// optionnelle si valeur >= ALERT_THRESHOLD de l'echelle. +// Valeurs min/max remontees de 4px par rapport au haut de la barre +// (LABEL_Y_SHIFT) pour ne plus mordre sur le cadre du bargraph -- ce +// shift suit BAR_Y (descend avec le bargraph). Le TITRE (nom de la +// grandeur, ex. "VIN") ne doit lui pas bouger quand BAR_Y descend, donc +// TITLE_Y_SHIFT = LABEL_Y_SHIFT + (delta BAR_Y) pour compenser. +const int LABEL_Y_SHIFT = 4; +const int TITLE_Y_SHIFT = 6; + +// labelScale : facteur applique uniquement a l'affichage des bornes +// min/max (ex. IOUT_MAX=0.050 A -> affiche "50" avec labelScale=1000, +// pour matcher l'unite mA affichee ailleurs). Le remplissage de la +// barre reste calcule sur value/vmin/vmax tels quels (unite d'origine). +// labelXOffset : decalage horizontal du seul texte du label (le nom de +// la grandeur, ex. "RENDEMENT"), independant du bar/valeurs. +void drawVBar(int x, const char *label, float value, float vmin, float vmax, + bool showAlert, int alertXOffset = 0, float labelScale = 1.0f, + int labelXOffset = 0) { + u8g2.setFont(u8g2_font_4x6_tr); + u8g2.drawStr(x + labelXOffset, BAR_Y - 3 - TITLE_Y_SHIFT, label); + + u8g2.drawFrame(x, BAR_Y, BAR_W, BAR_H); + float pct = (value - vmin) / (vmax - vmin); + if (pct < 0) pct = 0; + if (pct > 1) pct = 1; + int fillH = (int)(pct * (BAR_H - 2)); + if (fillH > 0) { + u8g2.drawBox(x + 1, BAR_Y + (BAR_H - 1 - fillH), BAR_W - 2, fillH); + } + + char buf[8]; + u8g2.setFont(u8g2_font_4x6_tr); + snprintf(buf, sizeof(buf), "%.0f", vmax * labelScale); + u8g2.drawStr(x + BAR_W + 3, BAR_Y + 5 - LABEL_Y_SHIFT, buf); + snprintf(buf, sizeof(buf), "%.0f", vmin * labelScale); + u8g2.drawStr(x + BAR_W + 3, BAR_Y + BAR_H - LABEL_Y_SHIFT, buf); + + if (showAlert && (DEBUG_FORCE_ALL_ICONS || pct >= ALERT_THRESHOLD) && Blink1Hz()) { + drawAlertIcon(x + BAR_W + 16 + alertXOffset, BAR_Y + BAR_H / 2 - 4); + } +} + +// --------------------------------------------------------------------- +// Economiseur d'ecran : balle rebondissante avec trainee de 20px qui +// s'estompe progressivement (utilise les 16 niveaux de gris du SSD1322). +// --------------------------------------------------------------------- + +const uint32_t IDLE_TIMEOUT_MS = 5UL * 60UL * 1000UL; // 5 min sans activite +const uint32_t BALL_STEP_MS = 40; // ~25 fps +const int BALL_SIZE = 3; // balle 3x3 px +const int TRAIL_LEN = 40; // longueur de la trainee (~40px) + +float g_ballX = 40, g_ballY = 20; +float g_ballVX = 1.3f, g_ballVY = 0.9f; + +// buffer circulaire des dernieres positions (centre de la balle) +int g_trailX[TRAIL_LEN]; +int g_trailY[TRAIL_LEN]; +int g_trailCount = 0; // nb de positions valides (monte jusqu'a TRAIL_LEN) +int g_trailHead = 0; // index de la position la plus recente + +uint32_t g_lastActivityMs = 0; +uint32_t g_lastBallStepMs = 0; +bool g_screensaverActive = false; + +bool g_lastHtState = false; +bool g_htStateInit = false; + +void resetActivityTimer() { + g_lastActivityMs = millis(); +} + +void checkActivity(const Telemetry & /*t*/, bool /*linkOk*/, bool htOn) { + if (!g_htStateInit) { + g_lastHtState = htOn; + g_htStateInit = true; + resetActivityTimer(); + return; + } + if (htOn != g_lastHtState) { + g_lastHtState = htOn; + resetActivityTimer(); + } +} + +void pushTrailPoint(int x, int y) { + g_trailHead = (g_trailHead + 1) % TRAIL_LEN; + g_trailX[g_trailHead] = x; + g_trailY[g_trailHead] = y; + if (g_trailCount < TRAIL_LEN) g_trailCount++; +} + +void stepBall() { + pushTrailPoint((int)g_ballX + BALL_SIZE / 2, (int)g_ballY + BALL_SIZE / 2); + + g_ballX += g_ballVX; + g_ballY += g_ballVY; + + if (g_ballX <= 0 || g_ballX >= SCREEN_W - BALL_SIZE) g_ballVX = -g_ballVX; + if (g_ballY <= 0 || g_ballY >= 64 - BALL_SIZE) g_ballVY = -g_ballVY; + + if (g_ballX < 0) g_ballX = 0; + if (g_ballY < 0) g_ballY = 0; + if (g_ballX > SCREEN_W - BALL_SIZE) g_ballX = SCREEN_W - BALL_SIZE; + if (g_ballY > 64 - BALL_SIZE) g_ballY = 64 - BALL_SIZE; +} + +void drawScreensaver() { + uint32_t now = millis(); + if (now - g_lastBallStepMs >= BALL_STEP_MS) { + g_lastBallStepMs = now; + stepBall(); + } + + u8g2.clearBuffer(); + + // trainee : fade par densite, pas par niveau de gris. Le buffer u8g2 + // pour ce driver (u8g2_ll_hvline_vertical_top_lsb, u8g2_ll_hvline.c) + // est 1 bit/pixel -- setDrawColor(2..15) y fait un XOR/toggle, pas un + // niveau de gris intermediaire (seuls 0/1 sont on/off reels). Les 16 + // niveaux de gris du SSD1322 sont une capacite materielle (contraste), + // pas exposee en 4bpp par pixel par cette configuration u8g2. On + // simule donc le fade en n'affichant qu'un point sur deux dans la + // moitie la plus ancienne de la trainee (dense pres de la balle, + // clairsemee derriere). + u8g2.setDrawColor(1); + for (int i = 0; i < g_trailCount; i++) { + int idx = (g_trailHead - g_trailCount + 1 + i + TRAIL_LEN) % TRAIL_LEN; + if (i < TRAIL_LEN / 2 && (i % 2) != 0) continue; + u8g2.drawPixel(g_trailX[idx], g_trailY[idx]); + } + + u8g2.setDrawColor(1); // pas 15 : cf. remarque ci-dessus, XOR/toggle sinon + u8g2.drawBox((int)g_ballX, (int)g_ballY, BALL_SIZE, BALL_SIZE); + + u8g2.sendBuffer(); } -// Tout sur un seul ecran (4 lignes de 32 caracteres) : statut liaison et -// commandes, electrique d'entree, puis un resume par etage. void drawDashboard(const Telemetry &t, bool linkOk) { - char l0[33], l1[33], l2[33], l3[33]; const CommandState &cmd = TmsLink::command(); - snprintf(l0, sizeof(l0), "TMS:%s HT:%s P1:%s P2:%s", linkOk ? "OK" : "KO", - cmd.ht ? "ON" : "OFF", cmd.pwm1 ? "ON" : "OFF", cmd.pwm2 ? "ON" : "OFF"); - snprintf(l1, sizeof(l1), "Vin=%.0fV Iin=%.2fA Iout=%.2fA", t.vin, t.iin, t.iout); - snprintf(l2, sizeof(l2), "E1 F%.0fk D%.0f%% V%.0fV I%.2fA T%.0fC", t.freq1 / 1000.0f, - t.duty1, t.v1, t.i1, t.t1); - snprintf(l3, sizeof(l3), "E2 F%.0fk D%.0f%% V%.0fV I%.2fA T%.0fC", t.freq2 / 1000.0f, - t.duty2, t.vout, t.i2, t.t2); - drawLine(0, l0); - drawLine(16, l1); - drawLine(32, l2); - drawLine(48, l3); + u8g2.clearBuffer(); + // setDrawColor(1) explicite : l'economiseur d'ecran laisse le contexte + // u8g2 sur un niveau de gris partiel (degrade de la trainee) apres son + // dernier appel -- sans ca, le dashboard heriterait de ce niveau au + // lieu du blanc plein apres une sortie de veille. + u8g2.setDrawColor(1); + char buf[40]; + + // --- bandeau haut : HIVY (gauche) / SYS:OK (centre) / WIFI:... (droite) --- + // SSID dynamique (config.h, maintenant "HIVY") + IP reelle (WiFi.softAPIP(), + // attribuee par WiFi.softAP() dans web_ui.cpp) + port (WEB_UI_PORT ci-dessus). + u8g2.setFont(u8g2_font_5x8_tr); + u8g2.setFontPosTop(); + u8g2.drawStr(0, 0, "HIVY"); + + // SYS:KO clignote (Blink1Hz) pour attirer l'attention ; SYS:OK reste fixe. + if (linkOk || Blink1Hz()) { + snprintf(buf, sizeof(buf), "SYS:%s", linkOk ? "OK" : "KO"); + int wSys = u8g2.getStrWidth(buf); + u8g2.drawStr((SCREEN_W - wSys) / 2 - 40, 0, buf); // historique : +20, -40, -20 = -40 + } + + IPAddress ip = WiFi.softAPIP(); + snprintf(buf, sizeof(buf), "WIFI:%s %d.%d.%d.%d:%u", WIFI_AP_SSID, ip[0], ip[1], + ip[2], ip[3], WEB_UI_PORT); + int wWifi = u8g2.getStrWidth(buf); + u8g2.drawStr(SCREEN_W - wWifi, 0, buf); + + // --- colonne 1 : VIN / IIN / VOUT / IOUT --- + // Logo 4 (IOUT) : -10 encore -3px = -13. + drawVBar(COL1_X + 2, "VIN", t.vin, VIN_MIN, VIN_MAX, true, -14); + drawVBar(COL1_X + 29, "IIN", t.iin, IIN_MIN, IIN_MAX, true, -14); + drawVBar(COL1_X + 56, "VOUT", t.vout, VOUT_MIN, VOUT_MAX, true, -14); + // labelScale=1000 : bornes affichees en mA (0/50) au lieu de A (0/0.05). + drawVBar(COL1_X + 83, "IOUT", t.iout, IOUT_MIN, IOUT_MAX, true, -13, 1000.0f); + + u8g2.setFont(u8g2_font_6x12_tr); + snprintf(buf, sizeof(buf), "%.1fV", t.vin); + u8g2.drawStr(COL1_X, BAR_Y + BAR_H + 4, buf); + snprintf(buf, sizeof(buf), "%.1fA", t.iin); // 2 digits (X.Y) + u8g2.drawStr(COL1_X + 27, BAR_Y + BAR_H + 4, buf); + snprintf(buf, sizeof(buf), "%.0fV", t.vout); + u8g2.drawStr(COL1_X + 54, BAR_Y + BAR_H + 4, buf); + snprintf(buf, sizeof(buf), "%.0fmA", t.iout * 1000.0f); + u8g2.drawStr(COL1_X + 81, BAR_Y + BAR_H + 4, buf); + + u8g2.drawVLine(COL1_COL2_DIVIDER_X, BAR_Y - 6, BAR_H + 20); + + // --- colonne 2 : RENDEMENT (50-100%, pas d'alerte) / PIN / POUT --- + float pin = t.vin * t.iin; + float pout = t.vout * t.iout; + float eff = (pin > 0.01f) ? (pout / pin * 100.0f) : 0.0f; + if (eff > 100.0f) eff = 100.0f; + if (eff < 0.0f) eff = 0.0f; + + // RENDEMENT (bar+valeur) decale de 6px a droite ; PIN/POUT (bar+valeur) + // decales de 4px a droite. Logos 5 et 6 (PIN/POUT) suivent + // automatiquement puisque l'offset icone est relatif au x du bar. + // Seul le mot "RENDEMENT" est ensuite recentre 6px vers la gauche + // (labelXOffset), independamment du bar/valeur qui restent en place. + drawVBar(COL2_X + 9, "RENDEMENT", eff, RDT_MIN, RDT_MAX, false, 0, 1.0f, -6); // pas d'icone (showAlert=false) + drawVBar(COL2_X + 43, "PIN", pin, PWR_MIN, PWR_MAX, true, -14); + drawVBar(COL2_X + 70, "POUT", pout, PWR_MIN, PWR_MAX, true, -14); + + u8g2.setFont(u8g2_font_6x12_tr); + snprintf(buf, sizeof(buf), "%.0f%%", eff); + u8g2.drawStr(COL2_X + 7, BAR_Y + BAR_H + 4, buf); + snprintf(buf, sizeof(buf), "%.1fW", pin); + u8g2.drawStr(COL2_X + 41, BAR_Y + BAR_H + 4, buf); + snprintf(buf, sizeof(buf), "%.1fW", pout); + u8g2.drawStr(COL2_X + 68, BAR_Y + BAR_H + 4, buf); + + u8g2.drawVLine(COL3_X, BAR_Y - 6, BAR_H + 20); + + // --- colonne 3 : I1/T1, I2/T2, HT/OFF/DEFAUT, icone danger --- + // Tout le contenu de la colonne remonte de 5px. + u8g2.setFont(u8g2_font_5x8_tr); + snprintf(buf, sizeof(buf), "I1 %.1fA", t.i1); // 2 digits (X.Y) + u8g2.drawStr(COL3_X + 4, 11, buf); + snprintf(buf, sizeof(buf), "T1 %.0fC", t.t1); + u8g2.drawStr(COL3_X + 4, 21, buf); + // Icone T1 completement a droite (flush ecran) ; le haut du triangle + // (apex, "y" dans drawAlertIcon) aligne sur le haut de la ligne I1 (y=11). + if ((DEBUG_FORCE_ALL_ICONS || t.t1 >= T1_ALERT_C) && Blink1Hz()) drawAlertIcon(SCREEN_W - 10, 11); + + // I2/T2 remontes de 2px supplementaires (35->33, 45->43). + snprintf(buf, sizeof(buf), "I2 %.1fA", t.i2); // 2 digits (X.Y) + u8g2.drawStr(COL3_X + 4, 33, buf); + snprintf(buf, sizeof(buf), "T2 %.0fC", t.t2); + u8g2.drawStr(COL3_X + 4, 43, buf); + // Icone T2 completement a droite (flush ecran) ; la base du triangle + // (y+9 dans drawAlertIcon) aligne sur la base de la ligne I2 (33+8=41, + // donc y=41-9=32). + if ((DEBUG_FORCE_ALL_ICONS || t.t2 >= T2_ALERT_C) && Blink1Hz()) drawAlertIcon(SCREEN_W - 10, 32); + + u8g2.setFont(u8g2_font_7x13B_tr); + // HT/OFF/DEFAUT (coherent avec le libelle du bouton "HT" de la page + // web, plutot que "RUN") : HT si HT_EN actif, OFF si inactif, DEFAUT + // (clignotant) si un probleme est detecte. HT/OFF restent fixes, + // seul DEFAUT clignote (Blink1Hz). + // + // TODO(protocole) : la detection de probleme n'existe pas encore -- + // problemDetected reste a false pour l'instant. Le TMS doit signaler + // lui-meme le defaut (court-circuit, surintensite I1/I2, temperature + // critique >80C) via une trame dediee, pas encore implementee cote TMS + // (cf. discussion). Quand cette trame existera (ajouter un champ + // correspondant dans Telemetry/protocol.h), c'est ici qu'il faudra + // brancher la vraie condition, plutot que de deviner des seuils cote + // ESP32 sans donnee du TMS pour les confirmer. + bool problemDetected = false; + if (problemDetected) { + if (Blink1Hz()) u8g2.drawStr(COL3_X + 2, 53, "DEFAUT"); + } else { + u8g2.drawStr(COL3_X + 2, 53, cmd.ht ? "HT" : "OFF"); + } + + // icone danger principale (20x20), clignotante (Blink1Hz, 1s ON/1s OFF). + // Regle reelle : HT actif (cmd.ht) ET Vout > 50V -- jamais avec DEFAUT, + // meme si Vout mesure encore > 50V par inertie a ce moment-la. + // Calee tout en bas a droite de la colonne 3 (= bord ecran). + bool dangerActive = cmd.ht && t.vout > VOUT_DANGER_THRESHOLD; + if ((DEBUG_FORCE_ALL_ICONS || dangerActive) && Blink1Hz()) { + drawWarningIconHalf(SCREEN_W - WARNING_ICON_WIDTH / 2, 64 - WARNING_ICON_HEIGHT / 2); + } + + u8g2.sendBuffer(); } } // namespace void begin() { - er_oled_begin(); - er_oled_clear(); + // Broches SPI personnalisees (comme l'ancien driver er_oled.cpp) : + // sck, miso (non cable), mosi, ss. A faire avant u8g2.begin() pour + // que le bus HW SPI d'u8g2 utilise ces pins plutot que les pins par + // defaut de l'ESP32-C3. + SPI.begin(PIN_OLED_CLK, -1, PIN_OLED_MOSI, PIN_OLED_CS); + + u8g2.begin(); + + // Re-applique le registre "Set Re-map / Dual COM Line Mode" (0xA0) tel + // que valide sur l'ancien driver maison (legacy/er_oled.cpp) : COM + // remap + nibble remap actives, column remap desactive. Le remap par + // defaut d'u8g2 pour ce controleur ne correspond pas au cablage de ce + // panneau (image en miroir horizontal sinon). + // + // NB : desactiver le nibble remap (0x10 au lieu de 0x14) a ete essaye + // pour tenter de corriger le logo coupe en deux, mais a fait + // reapparaitre le miroir vertical et rendu l'affichage illisible -- + // donc le nibble remap doit rester actif ici, contrairement a + // l'hypothese initiale. + u8g2.sendF("caa", 0xA0, 0x14, 0x11); + + showSplash(); } void update(const Telemetry &t, bool linkOk) { + const CommandState &cmd = TmsLink::command(); + checkActivity(t, linkOk, cmd.ht); + + // Page web consultee recemment (chargement de page ou poll /api/state, + // qui revient toutes les 1s tant que la page reste ouverte dans un + // navigateur) -> compte comme de l'activite, reactive/maintient le + // dashboard OLED. + if (millis() - WebUi::lastActivityMs() < IDLE_TIMEOUT_MS) { + resetActivityTimer(); + } + + bool idle = (millis() - g_lastActivityMs) >= IDLE_TIMEOUT_MS; + if (idle) { + g_screensaverActive = true; + drawScreensaver(); + return; + } + if (g_screensaverActive) { + g_screensaverActive = false; + g_lastRefreshMs = 0; // force un redraw complet du dashboard au reveil + } + uint32_t now = millis(); if (now - g_lastRefreshMs < REFRESH_PERIOD_MS) return; g_lastRefreshMs = now; - drawDashboard(t, linkOk); } diff --git a/src/oled_ui.h b/src/oled_ui.h index 54ca08a..f88cca2 100644 --- a/src/oled_ui.h +++ b/src/oled_ui.h @@ -1,13 +1,7 @@ #pragma once - #include "protocol.h" namespace OledUi { - void begin(); - -// A appeler en boucle. Redessine l'ecran periodiquement et fait tourner -// les pages (entree/commandes, etage 1, etage 2). void update(const Telemetry &t, bool linkOk); - } // namespace OledUi diff --git a/src/tms_link.cpp b/src/tms_link.cpp index 38ebeca..0257384 100644 --- a/src/tms_link.cpp +++ b/src/tms_link.cpp @@ -14,7 +14,7 @@ CommandState g_command; uint32_t g_lastValidFrameMs = 0; uint32_t g_lastCommandSentMs = 0; -char g_lineBuf[160]; +char g_lineBuf[200]; uint8_t g_lineLen = 0; const uint32_t COMMAND_SEND_PERIOD_MS = 500; @@ -75,12 +75,25 @@ bool parseTelemetryLine(char *line) { void pollUart() { while (Serial1.available()) { char c = (char)Serial1.read(); + if (c == '\n') { g_lineBuf[g_lineLen] = '\0'; - if (parseTelemetryLine(g_lineBuf)) { + + // Copie avant parsing : parseTelemetryLine() decoupe g_lineBuf en + // place (strtok_r, '=' -> '\0'), donc l'afficher apres coup ne + // montrerait que le premier fragment tronque. + char display[sizeof(g_lineBuf)]; + if (TMS_DEBUG_ECHO) memcpy(display, g_lineBuf, g_lineLen + 1); + + bool ok = parseTelemetryLine(g_lineBuf); + if (ok) { g_telemetry.lastUpdateMs = millis(); g_lastValidFrameMs = g_telemetry.lastUpdateMs; } + if (TMS_DEBUG_ECHO) { + Serial.print(ok ? "TMS<< " : "TMS<< [rejete] "); + Serial.println(display); + } g_lineLen = 0; } else if (c != '\r') { if (g_lineLen < sizeof(g_lineBuf) - 1) { @@ -136,6 +149,7 @@ void sendCommandFrame() { char frame[64]; snprintf(frame, sizeof(frame), "$%s*%02X\n", body, cs); Serial1.print(frame); + if (TMS_DEBUG_ECHO) Serial.printf("TMS>> %s", frame); } } // namespace diff --git a/src/warning_icon_xbm.h b/src/warning_icon_xbm.h new file mode 100644 index 0000000..38b42a6 --- /dev/null +++ b/src/warning_icon_xbm.h @@ -0,0 +1,24 @@ +// Icone danger haute tension (triangle + eclair), 40x40, format XBM pour u8g2.drawXBMP +#pragma once + +#define WARNING_ICON_WIDTH 40 +#define WARNING_ICON_HEIGHT 40 + +static const unsigned char warning_icon_bits[] PROGMEM = { + // 1er octet corrige (etait 0x01) : un pixel isole allumait le coin + // superieur gauche du bitmap, sans aucun pixel voisin -- defaut de la + // donnee source, sans rapport avec le code de dessin. + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x08, 0x00, 0x80, 0x00, 0x00, 0x18, 0x00, 0x80, 0x00, + 0x00, 0x1C, 0x00, 0x80, 0x00, 0x00, 0x34, 0x00, 0x80, 0x00, 0x00, 0x36, 0x00, 0x80, 0x00, 0x00, + 0x62, 0x00, 0x80, 0x00, 0x00, 0x63, 0x00, 0x80, 0x00, 0x00, 0xC1, 0x00, 0x80, 0x00, 0x80, 0xC1, + 0x00, 0x80, 0x00, 0x80, 0x80, 0x01, 0x80, 0x00, 0xC0, 0x80, 0x01, 0x80, 0x00, 0x40, 0x1C, 0x03, + 0x80, 0x00, 0x60, 0x1C, 0x03, 0x80, 0x00, 0x20, 0x1C, 0x06, 0x80, 0x00, 0x30, 0x1C, 0x06, 0x80, + 0x00, 0x10, 0x1C, 0x0C, 0x80, 0x00, 0x18, 0x0C, 0x0C, 0x80, 0x00, 0x08, 0x0E, 0x18, 0x80, 0x00, + 0x0C, 0x0E, 0x18, 0x80, 0x00, 0x04, 0x0E, 0x30, 0x80, 0x00, 0x06, 0x8E, 0x30, 0x80, 0x00, 0x02, + 0xFE, 0x60, 0x80, 0x00, 0x03, 0x7E, 0x60, 0x80, 0x00, 0x01, 0x7E, 0xC0, 0x80, 0x80, 0x01, 0x62, + 0xC0, 0x80, 0x80, 0x01, 0x60, 0x80, 0x81, 0xC0, 0x00, 0x30, 0x80, 0x81, 0xC0, 0x00, 0x30, 0x00, + 0x83, 0x60, 0x00, 0x30, 0x00, 0x83, 0x60, 0x00, 0x30, 0x00, 0x86, 0x30, 0x00, 0x38, 0x00, 0x86, + 0x30, 0x00, 0x3C, 0x00, 0x8C, 0x18, 0x00, 0x38, 0x00, 0x8C, 0x18, 0x00, 0x18, 0x00, 0x98, 0x0C, + 0x00, 0x08, 0x00, 0x98, 0x04, 0x00, 0x00, 0x00, 0xB0, 0xFE, 0xFF, 0xFF, 0xFF, 0xBF, 0xFE, 0xFF, + 0xFF, 0xFF, 0xBF, 0xFD, 0xFF, 0xFF, 0xFF, 0xBF, +}; diff --git a/src/web_ui.cpp b/src/web_ui.cpp index 4f52e88..6319c52 100644 --- a/src/web_ui.cpp +++ b/src/web_ui.cpp @@ -13,6 +13,9 @@ namespace { WebServer server(80); +uint32_t g_lastActivityMs = 0; +void markActivity() { g_lastActivityMs = millis(); } + const char INDEX_HTML[] PROGMEM = R"html( AlimHT @@ -121,9 +124,13 @@ refresh(); )html"; -void handleRoot() { server.send_P(200, "text/html", INDEX_HTML); } +void handleRoot() { + markActivity(); + server.send_P(200, "text/html", INDEX_HTML); +} void handleState() { + markActivity(); const Telemetry &t = TmsLink::telemetry(); const CommandState &cmd = TmsLink::command(); char buf[400]; @@ -139,6 +146,7 @@ void handleState() { } void handleCommand() { + markActivity(); CommandState cmd; cmd.ht = server.arg("ht") == "1"; cmd.pwm1 = server.arg("pwm1") == "1"; @@ -164,4 +172,6 @@ void begin() { void poll() { server.handleClient(); } +uint32_t lastActivityMs() { return g_lastActivityMs; } + } // namespace WebUi diff --git a/src/web_ui.h b/src/web_ui.h index d0dc8ea..6132d72 100644 --- a/src/web_ui.h +++ b/src/web_ui.h @@ -1,5 +1,7 @@ #pragma once +#include + // Serveur web de l'IHM : l'ESP32 cree son propre point d'acces WiFi // (voir config.h) et sert un dashboard + controles HT/PWM1/PWM2. @@ -8,4 +10,10 @@ namespace WebUi { void begin(); void poll(); +// millis() de la derniere requete HTTP servie (page, /api/state ou +// /api/command) -- permet a l'IHM OLED de savoir si le dashboard web +// est actuellement consulte (ex. pour reactiver l'ecran OLED depuis +// l'economiseur). +uint32_t lastActivityMs(); + } // namespace WebUi