diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..9c8a242
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,104 @@
+# Changelog — Gateway Lab V1
+
+Toutes les modifications notables sont documentées ici.
+Format : [Semantic Versioning](https://semver.org/)
+
+---
+
+## [0.0.2] — 2026-06-14
+
+### Ajouté
+- **WiFiMulti** : gestion multi-réseaux depuis `include/secrets.h`, reconnexion automatique dans `loop()`
+- **ArduinoOTA** : mise à jour réseau via PlatformIO/IDE (hostname configurable)
+- **WebServer** (port 80) avec les routes :
+ - `GET /` → page d'accueil embarquée en PROGMEM
+ - `GET /api/status` → JSON `{ssid, ip, rssi, uptime, version, hostname}`
+ - `GET /update` → page de mise à jour OTA
+ - `POST /update` → upload firmware `.bin` (librairie `Update`)
+- **mDNS** : accès via `gateway-lab-v1.local` (configurable via `MDNS_HOSTNAME`)
+- **Page d'accueil** (`web_src/index.html`) : titre, cartouche réseau (SSID, IP, RSSI, mDNS, Uptime, Statut), bouton OTA, rafraîchissement automatique toutes les 10 s
+- **Page OTA** (`web_src/ota.html`) : formulaire upload firmware avec barre de progression
+- **Minification automatique** (`tools/minify_web.py`) : génère les headers PROGMEM avant chaque compilation PlatformIO
+ - `web_src/index.html` → `include/web_interface.h` (`INDEX_HTML`)
+ - `web_src/ota.html` → `include/web_interface_ota.h` (`OTA_PAGE`)
+- **`include/app_config.h`** : ajout de `MDNS_HOSTNAME`, `WEB_SERVER_PORT`
+- **`CHANGELOG.md`** : ce fichier
+
+### Modifié
+- `src/main.cpp` : implémentation complète (stub remplacé)
+- `platformio.ini` : `PROJECT_VERSION` → `0.0.2`, ajout `lib_deps` (ArduinoJson v7), `extra_scripts` (minification pre-build), suppression des chemins Windows
+- `tools/minify_web.py` : refonte complète — traite plusieurs pages, génère des headers PROGMEM paramétrables, dual-mode standalone/PlatformIO
+
+### Technique
+- **Stratégie de stockage HTML** : abandon de SPIFFS au profit de l'injection PROGMEM —
+ le HTML est compilé directement dans le firmware, éliminant la partition filesystem et
+ l'étape « Upload Filesystem Image »
+- **Serving sans RAM** : `server.send_P()` lit `INDEX_HTML` et `OTA_PAGE` depuis la flash
+ (PROGMEM) via DMA, sans copie en heap — empreinte RAM quasi nulle pour les pages web
+- **`/api/status` sans template** : les données dynamiques (SSID, IP, RSSI, uptime) sont
+ injectées côté client par `fetch()` toutes les 10 s, évitant la génération HTML serveur
+- **OTA dual-stack** : ArduinoOTA (UDP, port 3232) pour les mises à jour PlatformIO/réseau,
+ et `WebServer + Update` (HTTP POST multipart) pour les mises à jour via navigateur —
+ les deux coexistent et partagent le même hostname mDNS
+- **WiFiMulti** : l'ESP32 tente chaque SSID de `secrets.h` par ordre de signal, avec
+ timeout `WIFI_CONNECT_TIMEOUT` (15 s) ; la reconnexion dans `loop()` est non-bloquante
+ (`wifiMulti.run()` retourne immédiatement si déjà connecté)
+- **Sécurité secrets** : `include/secrets.h` listé dans `.gitignore` dès v0.0.1,
+ `secrets_example.h` versionné comme modèle sans donnée réelle
+
+### Infrastructure
+- **Pre-script PlatformIO** (`extra_scripts = pre:tools/minify_web.py`) : la minification
+ s'exécute automatiquement avant chaque `pio run` — le header C++ est toujours synchronisé
+ avec les sources HTML sans intervention manuelle
+- **Table `PAGES[]`** dans `minify_web.py` : architecture extensible pour ajouter de
+ nouvelles pages sans modifier la logique du script
+- **Fallback sans dépendances** : `minify_web.py` fonctionne sans `rcssmin`/`rjsmin` grâce
+ à des regex de substitution intégrées ; les librairies optionnelles améliorent simplement
+ le taux de compression
+- **Dual-mode du script** : détection automatique du contexte d'exécution
+ (`Import("env")` PlatformIO vs `__main__` standalone) — un seul fichier pour les deux usages
+- **`lib_deps` minimal** : seule dépendance externe ajoutée = `ArduinoJson v7` ;
+ WiFi, mDNS, OTA, WebServer, Update sont tous dans le SDK Arduino ESP32
+- **Suppression des chemins Windows** dans `platformio.ini` (`build_dir`, `build_cache_dir`) :
+ le projet est désormais portable Linux/macOS/Windows sans configuration locale
+
+### Architecture web
+```
+web_src/index.html ──minify──► include/web_interface.h (INDEX_HTML[] PROGMEM)
+web_src/ota.html ──minify──► include/web_interface_ota.h (OTA_PAGE[] PROGMEM)
+```
+Aucune étape « Upload Filesystem Image » requise — le HTML voyage avec le firmware.
+
+---
+
+## [0.0.1] — 2026-06-13
+
+### Ajouté
+- Structure initiale du projet PlatformIO (ESP32-S3 DevKitC-1 N16R8)
+- `include/board_config.h` : brochage complet de la carte (SPI, I2C, GPIO, NeoPixel, capteurs)
+- `include/app_config.h` : paramètres centralisés de l'application (timeouts, port, features)
+- `include/secrets_example.h` : modèle pour les identifiants WiFi multi-réseaux
+- `web_src/` : dossier sources HTML — seul endroit autorisé pour modifier le HTML
+- `tools/minify_web.py` : minificateur CSS/JS pour injection dans header C++
+- `tools/extract_web_sources.py` : extracteur/beautifier de sources depuis un header existant
+- `tools/validate_html.py` : validateur de structure HTML (balises, IDs, i18n)
+- `README.md` : roadmap versionnée (v0.0.1 → v1.0.0)
+- `BACKLOG.md` : liste des fonctionnalités futures
+- `.gitignore` : exclusion de `include/secrets.h`, binaires PlatformIO, caches Python
+
+### Technique
+- **Cible matérielle** : ESP32-S3 DevKitC-1 N16R8 (16 Mo flash, 8 Mo PSRAM, dual-core 240 MHz)
+- **Standard C++17** activé (`-std=gnu++17`) pour les fonctionnalités modernes du langage
+- **SPIFFS** activé en partition filesystem (remplacé en v0.0.2 par PROGMEM)
+- **Versioning unique** : `PROJECT_VERSION` défini exclusivement dans `platformio.ini`
+ via `-D PROJECT_VERSION='"x.y.z"'`, disponible dans tout le code C++ sans header dédié
+
+### Infrastructure
+- **Projet PlatformIO** : environnement `esp32s3_n16r8`, framework Arduino, upload 921 600 baud
+- **USB CDC** activé au boot (`board_build.usb_cdc_on_boot = 1`) pour le port série via USB natif
+- **Convention de dossiers** : `web_src/` (sources éditables), `include/` (headers générés),
+ `data/` (assets SPIFFS), `tools/` (scripts Python), `src/` (code C++)
+
+---
+
+*Ce projet suit la roadmap définie dans `README.md`.*
diff --git a/data/.gitkeep b/data/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/data/index.html b/data/index.html
new file mode 100644
index 0000000..6302d7e
--- /dev/null
+++ b/data/index.html
@@ -0,0 +1 @@
+
Gateway Lab V1Gateway Lab V1
v—
Informations réseau
SSID—
IP locale—
Signal (RSSI)
mDNS—
Uptime—
StatutConnecté
Mise à jour OTA
\ No newline at end of file
diff --git a/include/app_config.h b/include/app_config.h
index ccd9b2f..eda4bc8 100644
--- a/include/app_config.h
+++ b/include/app_config.h
@@ -6,7 +6,8 @@
#define NETWORK_SCAN_TIMEOUT 5000
// Network
-#define WEB_SERVER_PORT 80
+#define WEB_SERVER_PORT 80
+#define MDNS_HOSTNAME "gateway-lab-v1"
// Features
#define ENABLE_OTA
diff --git a/include/web_interface.h b/include/web_interface.h
new file mode 100644
index 0000000..0ad3bd9
--- /dev/null
+++ b/include/web_interface.h
@@ -0,0 +1,7 @@
+// Auto-generated by tools/minify_web.py — NE PAS MODIFIER MANUELLEMENT
+// Source : web_src/index.html
+#pragma once
+
+static const char INDEX_HTML[] PROGMEM = R"HTML(
+Gateway Lab V1Gateway Lab V1
v—
Informations réseau
SSID—
IP locale—
Signal (RSSI)
mDNS—
Uptime—
StatutConnecté
Mise à jour OTA
+)HTML";
diff --git a/include/web_interface_ota.h b/include/web_interface_ota.h
new file mode 100644
index 0000000..0d2ec32
--- /dev/null
+++ b/include/web_interface_ota.h
@@ -0,0 +1,7 @@
+// Auto-generated by tools/minify_web.py — NE PAS MODIFIER MANUELLEMENT
+// Source : web_src/ota.html
+#pragma once
+
+static const char OTA_PAGE[] PROGMEM = R"HTML(
+Gateway Lab V1 - OTAGateway Lab V1
Mise à jour OTA
← Retour
+)HTML";
diff --git a/platformio.ini b/platformio.ini
index 59ea65a..ce37c52 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -14,6 +14,8 @@ default_envs = esp32s3_n16r8
build_dir = C:/pio_builds/build
build_cache_dir = C:/pio_builds/cache
+extra_scripts = pre:tools/minify_web.py
+
[env:esp32s3_n16r8]
platform = espressif32
board = esp32-s3-devkitc-1-n16r8v
@@ -30,8 +32,9 @@ build_unflags =
build_flags =
-std=gnu++17
- -D PROJECT_VERSION='"0.0.1"'
+ -D PROJECT_VERSION='"0.0.2"'
-D PROJECT_NAME='"GatewayLabV1"'
-D TARGET_ESP32_S3
-board_build.filesystem = spiffs
\ No newline at end of file
+lib_deps =
+ bblanchon/ArduinoJson@^7.0.0
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index cb9fbba..8a474d6 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,18 +1,169 @@
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
-// put function declarations here:
-int myFunction(int, int);
+#include "secrets.h"
+#include "app_config.h"
+#include "board_config.h"
+#include "web_interface.h" // INDEX_HTML — généré par tools/minify_web.py
+#include "web_interface_ota.h" // OTA_PAGE — généré par tools/minify_web.py
+// ---------------------------------------------------------------------------
+// Globals
+// ---------------------------------------------------------------------------
+WiFiMulti wifiMulti;
+WebServer server(WEB_SERVER_PORT);
+
+// ---------------------------------------------------------------------------
+// WiFi
+// ---------------------------------------------------------------------------
+static void setupWiFi() {
+ WiFi.mode(WIFI_STA);
+ constexpr size_t N = sizeof(WIFI_NETWORKS) / sizeof(WIFI_NETWORKS[0]);
+ for (size_t i = 0; i < N; i++) {
+ wifiMulti.addAP(WIFI_NETWORKS[i][0], WIFI_NETWORKS[i][1]);
+ }
+
+ Serial.print("WiFi: connexion en cours");
+ unsigned long start = millis();
+ while (wifiMulti.run() != WL_CONNECTED &&
+ millis() - start < WIFI_CONNECT_TIMEOUT) {
+ delay(500);
+ Serial.print('.');
+ }
+ Serial.println();
+
+ if (WiFi.isConnected()) {
+ Serial.printf("WiFi: connecté à \"%s\"\n", WiFi.SSID().c_str());
+ Serial.printf("WiFi: IP = %s\n", WiFi.localIP().toString().c_str());
+ Serial.printf("WiFi: RSSI = %d dBm\n", WiFi.RSSI());
+ } else {
+ Serial.println("WiFi: échec de connexion");
+ }
+}
+
+// ---------------------------------------------------------------------------
+// ArduinoOTA (mise à jour via PlatformIO / IDE réseau)
+// ---------------------------------------------------------------------------
+#ifdef ENABLE_OTA
+static void setupArduinoOTA() {
+ ArduinoOTA.setHostname(MDNS_HOSTNAME);
+ ArduinoOTA.onStart([]() { Serial.println("OTA: début"); });
+ ArduinoOTA.onEnd([]() { Serial.println("\nOTA: terminé"); });
+ ArduinoOTA.onProgress([](unsigned int p, unsigned int t) {
+ Serial.printf("OTA: %u%%\r", p * 100 / t);
+ });
+ ArduinoOTA.onError([](ota_error_t err) {
+ Serial.printf("OTA: erreur [%u]\n", err);
+ });
+ ArduinoOTA.begin();
+ Serial.println("OTA: ArduinoOTA actif");
+}
+#endif
+
+// ---------------------------------------------------------------------------
+// Routes web
+// ---------------------------------------------------------------------------
+static void handleRoot() {
+ // Servir directement depuis PROGMEM — aucun SPIFFS requis
+ server.send_P(200, "text/html", INDEX_HTML);
+}
+
+static void handleApiStatus() {
+ JsonDocument doc;
+ doc["ssid"] = WiFi.SSID();
+ doc["ip"] = WiFi.localIP().toString();
+ doc["rssi"] = WiFi.RSSI();
+ doc["uptime"] = millis();
+ doc["version"] = PROJECT_VERSION;
+ doc["hostname"] = MDNS_HOSTNAME;
+
+ String json;
+ serializeJson(doc, json);
+ server.sendHeader("Cache-Control", "no-cache");
+ server.send(200, "application/json", json);
+}
+
+static void handleOtaGet() {
+ server.send_P(200, "text/html", OTA_PAGE);
+}
+
+static void handleOtaUploadDone() {
+ server.sendHeader("Connection", "close");
+ server.send(200, "text/plain", Update.hasError() ? "FAIL" : "OK");
+ delay(500);
+ ESP.restart();
+}
+
+static void handleOtaUpload() {
+ HTTPUpload& upload = server.upload();
+ if (upload.status == UPLOAD_FILE_START) {
+ Serial.printf("OTA Web: %s\n", upload.filename.c_str());
+ if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
+ Update.printError(Serial);
+ }
+ } else if (upload.status == UPLOAD_FILE_WRITE) {
+ if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
+ Update.printError(Serial);
+ }
+ } else if (upload.status == UPLOAD_FILE_END) {
+ if (Update.end(true)) {
+ Serial.printf("OTA Web: %u octets écrits\n", upload.totalSize);
+ } else {
+ Update.printError(Serial);
+ }
+ }
+}
+
+static void setupWebServer() {
+ server.on("/", HTTP_GET, handleRoot);
+ server.on("/api/status", HTTP_GET, handleApiStatus);
+ server.on("/update", HTTP_GET, handleOtaGet);
+ server.on("/update", HTTP_POST, handleOtaUploadDone, handleOtaUpload);
+
+ server.begin();
+ Serial.printf("Web: serveur démarré sur le port %d\n", WEB_SERVER_PORT);
+}
+
+// ---------------------------------------------------------------------------
+// Arduino entry points
+// ---------------------------------------------------------------------------
void setup() {
- // put your setup code here, to run once:
- int result = myFunction(2, 3);
+ Serial.begin(115200);
+ Serial.printf("\n=== %s v%s ===\n", PROJECT_NAME, PROJECT_VERSION);
+
+ setupWiFi();
+
+#ifdef ENABLE_OTA
+ if (WiFi.isConnected()) setupArduinoOTA();
+#endif
+
+#ifdef ENABLE_MDNS
+ if (WiFi.isConnected()) {
+ if (MDNS.begin(MDNS_HOSTNAME)) {
+ Serial.printf("mDNS: %s.local actif\n", MDNS_HOSTNAME);
+ }
+ }
+#endif
+
+#ifdef ENABLE_WEB_SERVER
+ if (WiFi.isConnected()) setupWebServer();
+#endif
}
void loop() {
- // put your main code here, to run repeatedly:
-}
+#ifdef ENABLE_OTA
+ ArduinoOTA.handle();
+#endif
+
+ if (WiFi.status() != WL_CONNECTED) {
+ wifiMulti.run();
+ }
-// put function definitions here:
-int myFunction(int x, int y) {
- return x + y;
-}
\ No newline at end of file
+ server.handleClient();
+}
diff --git a/tools/minify_web.py b/tools/minify_web.py
index 0663306..659e5cc 100644
--- a/tools/minify_web.py
+++ b/tools/minify_web.py
@@ -1,211 +1,170 @@
#!/usr/bin/env python3
"""
-Web Assets Minifier
+Web Assets Minifier — Gateway Lab V1
-This script minifies CSS and JavaScript files and generates the web_interface.h header file.
+Minifie les sources HTML et génère les headers C++ PROGMEM :
+ - web_src/index.html → include/web_interface.h (INDEX_HTML)
+ - web_src/ota.html → include/web_interface_ota.h (OTA_PAGE)
+ - data/index.html (copie minifiée, optionnelle)
-Usage:
+Peut être exécuté en standalone ou comme pre-script PlatformIO.
+
+Usage standalone :
python tools/minify_web.py
-The script will:
-1. Read source files from web_src/
-2. Minify CSS and JavaScript
-3. Update include/web_interface.h with minified content
-4. Preserve the structure and comments of the header file
+PlatformIO (extra_scripts = pre:tools/minify_web.py) :
+ Exécuté automatiquement avant chaque compilation.
-Requirements:
- pip install csscompressor rjsmin
+Requirements (optionnels — fallback intégré) :
+ pip install rcssmin rjsmin
"""
-import os
-import sys
import re
+import sys
from pathlib import Path
+# ---------------------------------------------------------------------------
+# Paths
+# ---------------------------------------------------------------------------
+SCRIPT_DIR = Path(__file__).parent
+PROJECT_ROOT = SCRIPT_DIR.parent
+WEB_SRC_DIR = PROJECT_ROOT / "web_src"
+DATA_DIR = PROJECT_ROOT / "data"
+INCLUDE_DIR = PROJECT_ROOT / "include"
+
+# ---------------------------------------------------------------------------
+# Pages à traiter : (source, header_dest, const_name)
+# ---------------------------------------------------------------------------
+PAGES = [
+ (
+ WEB_SRC_DIR / "index.html",
+ INCLUDE_DIR / "web_interface.h",
+ "INDEX_HTML",
+ DATA_DIR / "index.html", # copie data/ (None pour désactiver)
+ ),
+ (
+ WEB_SRC_DIR / "ota.html",
+ INCLUDE_DIR / "web_interface_ota.h",
+ "OTA_PAGE",
+ None,
+ ),
+]
+
+# ---------------------------------------------------------------------------
+# Optional minifiers (graceful fallback)
+# ---------------------------------------------------------------------------
try:
import rcssmin
+ HAS_RCSSMIN = True
+except ImportError:
+ HAS_RCSSMIN = False
+
+try:
import rjsmin
+ HAS_RJSMIN = True
except ImportError:
- print("ERROR: Required modules not installed.")
- print("Please install: pip install rcssmin rjsmin")
- sys.exit(1)
+ HAS_RJSMIN = False
+
+# ---------------------------------------------------------------------------
+# Minification helpers
+# ---------------------------------------------------------------------------
+def _minify_css_block(css: str) -> str:
+ if HAS_RCSSMIN:
+ return rcssmin.cssmin(css)
+ css = re.sub(r'/\*.*?\*/', '', css, flags=re.DOTALL)
+ css = re.sub(r'\s*([{}:;,>~+])\s*', r'\1', css)
+ css = re.sub(r'\s+', ' ', css).strip()
+ return css
+
+
+def _minify_js_block(js: str) -> str:
+ if HAS_RJSMIN:
+ return rjsmin.jsmin(js)
+ js = re.sub(r'//[^\n]*', '', js)
+ js = re.sub(r'\s+', ' ', js).strip()
+ return js
+
+
+def minify_html(html: str) -> str:
+ html = re.sub(r'', '', html, flags=re.DOTALL)
+ def _repl_style(m):
+ return ''
+ html = re.sub(r'', _repl_style, html, flags=re.DOTALL)
+ def _repl_script(m):
+ return ''
+ html = re.sub(r'', _repl_script, html, flags=re.DOTALL)
+ html = re.sub(r'>\s+<', '><', html)
+ html = re.sub(r'[ \t]{2,}', ' ', html)
+ html = re.sub(r'\n\s*\n', '\n', html)
+ return html.strip()
+
+
+# ---------------------------------------------------------------------------
+# Header generation
+# ---------------------------------------------------------------------------
+HEADER_TEMPLATE = """\
+// Auto-generated by tools/minify_web.py — NE PAS MODIFIER MANUELLEMENT
+// Source : {src}
+#pragma once
+
+static const char {const_name}[] PROGMEM = R"HTML(
+{html}
+)HTML";
+"""
-# Paths
-SCRIPT_DIR = Path(__file__).parent
-PROJECT_ROOT = SCRIPT_DIR.parent
-WEB_SRC_DIR = PROJECT_ROOT / "web_src"
-INCLUDE_DIR = PROJECT_ROOT / "include"
-WEB_INTERFACE_H = INCLUDE_DIR / "web_interface.h"
-
-# Source files
-CSS_FILE = WEB_SRC_DIR / "styles.css"
-JS_FILE_FULL = WEB_SRC_DIR / "app.js"
-JS_FILE_LITE = WEB_SRC_DIR / "app-lite.js"
-
-def read_file(filepath):
- """Read file content"""
- with open(filepath, 'r', encoding='utf-8') as f:
- return f.read()
-
-def write_file(filepath, content):
- """Write content to file"""
- with open(filepath, 'w', encoding='utf-8', newline='\n') as f:
- f.write(content)
-
-def minify_css(css_content):
- """Minify CSS content"""
- print(" Minifying CSS...")
- minified = rcssmin.cssmin(css_content)
- print(f" Original: {len(css_content)} bytes")
- print(f" Minified: {len(minified)} bytes")
- print(f" Saved: {len(css_content) - len(minified)} bytes ({100 * (1 - len(minified)/len(css_content)):.1f}%)")
- return minified
-
-def minify_js(js_content):
- """Minify JavaScript content"""
- print(" Minifying JavaScript...")
- minified = rjsmin.jsmin(js_content)
- print(f" Original: {len(js_content)} bytes")
- print(f" Minified: {len(minified)} bytes")
- print(f" Saved: {len(js_content) - len(minified)} bytes ({100 * (1 - len(minified)/len(js_content)):.1f}%)")
- return minified
-
-def escape_for_cpp_string(text):
- """Escape text for C++ string literal"""
- # Replace backslash first
- text = text.replace('\\', '\\\\')
- # Replace quotes
- text = text.replace('"', '\\"')
- # Replace newlines
- text = text.replace('\n', '\\n')
- text = text.replace('\r', '')
- return text
-
-def extract_css_from_header(header_content):
- """Extract CSS section from web_interface.h"""
- # Find the CSS section in generateHTML()
- pattern = r'(html \+= "";)'
- match = re.search(pattern, header_content, re.DOTALL)
- if match:
- return match.group(2)
- return None
-
-def extract_js_from_header(header_content, is_lite=False):
- """Extract JavaScript constant from web_interface.h"""
- if is_lite:
- pattern = r'(static const char PROGMEM DIAGNOSTIC_JS_STATIC_LITE\[\] = R"JS\()(.*?)(\)JS";)'
- else:
- pattern = r'(static const char PROGMEM DIAGNOSTIC_JS_STATIC\[\] = R"JS\()(.*?)(\)JS";)'
-
- match = re.search(pattern, header_content, re.DOTALL)
- if match:
- return match.group(2)
- return None
-
-def inject_css_into_header(header_content, minified_css):
- """Inject minified CSS into web_interface.h"""
- # Escape for C++ string
- escaped_css = escape_for_cpp_string(minified_css)
-
- # Split into manageable chunks (to avoid too long lines)
- max_line_length = 200
- css_lines = []
- current_line = ""
-
- for char in escaped_css:
- current_line += char
- if len(current_line) >= max_line_length and char in [';', '}']:
- css_lines.append(' html += "' + current_line + '";')
- current_line = ""
-
- if current_line:
- css_lines.append(' html += "' + current_line + '";')
-
- css_block = '\n'.join(css_lines)
-
- # Replace CSS section - ensure tag is preserved
- pattern = r'(html \+= "";)'
-
- # Build replacement with explicit tag
- replacement = r'\1' + '\n' + css_block + '\n html += "";'
-
- new_content = re.sub(pattern, replacement, header_content, flags=re.DOTALL)
- return new_content
-
-def inject_js_into_header(header_content, minified_js, is_lite=False):
- """Inject minified JavaScript into web_interface.h"""
- if is_lite:
- pattern = r'(static const char PROGMEM DIAGNOSTIC_JS_STATIC_LITE\[\] = R"JS\()(.*?)(\)JS";)'
- else:
- pattern = r'(static const char PROGMEM DIAGNOSTIC_JS_STATIC\[\] = R"JS\()(.*?)(\)JS";)'
-
- replacement = r'\1' + '\n' + minified_js + '\n' + r'\3'
- new_content = re.sub(pattern, replacement, header_content, flags=re.DOTALL)
- return new_content
-
-def update_web_interface_header():
- """Main function to update web_interface.h"""
- print("=" * 60)
- print("Web Assets Minifier")
- print("=" * 60)
-
- # Check if source files exist
- if not CSS_FILE.exists():
- print(f"ERROR: {CSS_FILE} not found!")
- print("Please create readable CSS source in web_src/styles.css")
- return False
-
- if not WEB_INTERFACE_H.exists():
- print(f"ERROR: {WEB_INTERFACE_H} not found!")
- return False
-
- print(f"\n1. Reading source files...")
- print(f" - CSS: {CSS_FILE}")
- if JS_FILE_FULL.exists():
- print(f" - JS (Full): {JS_FILE_FULL}")
- if JS_FILE_LITE.exists():
- print(f" - JS (Lite): {JS_FILE_LITE}")
-
- # Read source files
- css_content = read_file(CSS_FILE)
- header_content = read_file(WEB_INTERFACE_H)
-
- # Minify CSS
- print("\n2. Minifying CSS...")
- minified_css = minify_css(css_content)
-
- # Update header with minified CSS
- print("\n3. Injecting CSS into web_interface.h...")
- header_content = inject_css_into_header(header_content, minified_css)
-
- # Process JavaScript if available
- if JS_FILE_FULL.exists():
- print("\n4. Processing JavaScript (Full)...")
- js_content = read_file(JS_FILE_FULL)
- minified_js = minify_js(js_content)
- header_content = inject_js_into_header(header_content, minified_js, is_lite=False)
-
- if JS_FILE_LITE.exists():
- print("\n5. Processing JavaScript (Lite)...")
- js_lite_content = read_file(JS_FILE_LITE)
- minified_js_lite = minify_js(js_lite_content)
- header_content = inject_js_into_header(header_content, minified_js_lite, is_lite=True)
-
- # Write updated header
- print("\n6. Writing updated web_interface.h...")
- write_file(WEB_INTERFACE_H, header_content)
-
- print("\n" + "=" * 60)
- print("✅ Web interface header updated successfully!")
- print("=" * 60)
- print("\nNext steps:")
- print(" 1. Compile your project with PlatformIO")
- print(" 2. Upload to ESP32")
- print(" 3. Verify web interface works correctly")
- print("\n")
-
- return True
-
-if __name__ == "__main__":
- success = update_web_interface_header()
- sys.exit(0 if success else 1)
+
+def generate_header(src_path: Path, const_name: str, minified_html: str) -> str:
+ return HEADER_TEMPLATE.format(
+ src=src_path.relative_to(PROJECT_ROOT),
+ const_name=const_name,
+ html=minified_html,
+ )
+
+
+# ---------------------------------------------------------------------------
+# Main
+# ---------------------------------------------------------------------------
+def run():
+ print("=" * 55)
+ print("Gateway Lab V1 — Minification HTML")
+ print("=" * 55)
+
+ INCLUDE_DIR.mkdir(parents=True, exist_ok=True)
+ DATA_DIR.mkdir(parents=True, exist_ok=True)
+
+ ok = True
+ for src, header_dst, const_name, data_dst in PAGES:
+ if not src.exists():
+ print(f" ERREUR : {src.relative_to(PROJECT_ROOT)} introuvable — ignoré")
+ ok = False
+ continue
+
+ raw = src.read_text(encoding='utf-8')
+ minified = minify_html(raw)
+ ratio = 100 * (1 - len(minified) / len(raw))
+
+ header = generate_header(src, const_name, minified)
+ header_dst.write_text(header, encoding='utf-8')
+
+ print(f"\n [{src.name}]")
+ print(f" Source : {len(raw):,} octets")
+ print(f" Minifié : {len(minified):,} octets (gain {ratio:.1f}%)")
+ print(f" Header : {header_dst.relative_to(PROJECT_ROOT)}")
+
+ if data_dst is not None:
+ data_dst.write_text(minified, encoding='utf-8')
+ print(f" Data : {data_dst.relative_to(PROJECT_ROOT)}")
+
+ print("\nOK\n" if ok else "\nTerminé avec des erreurs\n")
+ return ok
+
+
+# ---------------------------------------------------------------------------
+# Entry points
+# ---------------------------------------------------------------------------
+try:
+ Import("env") # type: ignore # noqa: F821
+ run()
+except NameError:
+ if __name__ == "__main__":
+ sys.exit(0 if run() else 1)
diff --git a/web_src/index.html b/web_src/index.html
new file mode 100644
index 0000000..2674f5d
--- /dev/null
+++ b/web_src/index.html
@@ -0,0 +1,173 @@
+
+
+
+
+
+ Gateway Lab V1
+
+
+
+ Gateway Lab V1
+ v—
+
+
+
Informations réseau
+
+ SSID
+ —
+
+
+ IP locale
+ —
+
+
+ Signal (RSSI)
+
+
+
+ mDNS
+ —
+
+
+ Uptime
+ —
+
+
+ Statut
+ Connecté
+
+
+
+ Mise à jour OTA
+
+
+
+
+
diff --git a/web_src/ota.html b/web_src/ota.html
new file mode 100644
index 0000000..e5f5657
--- /dev/null
+++ b/web_src/ota.html
@@ -0,0 +1,109 @@
+
+
+
+
+
+ Gateway Lab V1 - OTA
+
+
+
+ Gateway Lab V1
+ Mise à jour OTA
+
+ ← Retour
+
+
+
+