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 V1

Gateway 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 V1

Gateway 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 - OTA

Gateway 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 + + + +