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 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 23:11:35 +02:00
parent 6b4b5c932d
commit 3c03e3fbf1
13 changed files with 909 additions and 44 deletions

View File

@ -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).

66
doc/OLED-u8g2.md Normal file
View File

@ -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).

View File

@ -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"

View File

@ -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

269
src/lebel_logo_xbm.h Normal file
View File

@ -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,
};

View File

@ -1,54 +1,509 @@
#include "oled_ui.h"
#include <Arduino.h>
#include <U8g2lib.h>
#include <SPI.h>
#include <WiFi.h>
#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);
}

View File

@ -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

View File

@ -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

24
src/warning_icon_xbm.h Normal file
View File

@ -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,
};

View File

@ -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(<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>AlimHT</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
@ -121,9 +124,13 @@ refresh();
</body></html>
)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

View File

@ -1,5 +1,7 @@
#pragma once
#include <Arduino.h>
// 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