Skip to content
Merged

Dev #10

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +28 to +30

### 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`.*
Empty file added data/.gitkeep
Empty file.
1 change: 1 addition & 0 deletions data/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!DOCTYPE html><html lang="fr"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Gateway Lab V1</title><style>*{box-sizing:border-box;margin:0;padding:0;}body{font-family:'Segoe UI',system-ui,sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;display:flex;flex-direction:column;align-items:center;padding:2rem 1rem;}h1{font-size:1.8rem;font-weight:700;color:#06b6d4;margin-bottom:0.25rem;letter-spacing:0.05em;}.subtitle{font-size:0.8rem;color:#475569;margin-bottom:2rem;}.card{background:#1e293b;border-radius:12px;padding:1.5rem;width:100%;max-width:420px;box-shadow:0 4px 24px rgba(0,0,0,0.3);margin-bottom:1.5rem;}.card-title{font-size:0.7rem;text-transform:uppercase;letter-spacing:0.12em;color:#64748b;margin-bottom:1rem;font-weight:600;}.info-row{display:flex;justify-content:space-between;align-items:center;padding:0.6rem 0;border-bottom:1px solid #1e3a5f22;}.info-row:last-child{border-bottom:none;}.info-label{color:#94a3b8;font-size:0.875rem;}.info-value{color:#e2e8f0;font-size:0.875rem;font-weight:600;text-align:right;}.badge{padding:0.2rem 0.7rem;border-radius:999px;font-size:0.7rem;font-weight:700;letter-spacing:0.04em;}.badge-ok{background:#064e3b;color:#10b981;}.badge-warn{background:#451a03;color:#f59e0b;}.btn{display:block;width:100%;max-width:420px;padding:0.875rem;background:#0891b2;color:#fff;border:none;border-radius:10px;font-size:1rem;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:background 0.2s;}.btn:hover{background:#06b6d4;}.last-update{font-size:0.72rem;color:#334155;margin-top:1.2rem;}</style></head><body><h1>Gateway Lab V1</h1><p class="subtitle" id="version-label">v—</p><div class="card"><p class="card-title">Informations réseau</p><div class="info-row"><span class="info-label">SSID</span><span class="info-value" id="ssid">—</span></div><div class="info-row"><span class="info-label">IP locale</span><span class="info-value" id="ip">—</span></div><div class="info-row"><span class="info-label">Signal (RSSI)</span><span class="info-value" id="rssi">—</span></div><div class="info-row"><span class="info-label">mDNS</span><span class="info-value" id="hostname">—</span></div><div class="info-row"><span class="info-label">Uptime</span><span class="info-value" id="uptime">—</span></div><div class="info-row"><span class="info-label">Statut</span><span class="badge badge-ok" id="status-badge">Connecté</span></div></div><a class="btn" href="/update">Mise à jour OTA</a><p class="last-update" id="last-update"></p><script>function fmtUptime(ms) { var s = Math.floor(ms / 1000); var m = Math.floor(s / 60); s %= 60; var h = Math.floor(m / 60); m %= 60; var d = Math.floor(h / 24); h %= 24; return (d > 0 ? d + 'j ' : '') + h + 'h ' + m + 'm ' + s + 's'; } function rssiQuality(r) { if (r >= -50) return 'Excellent'; if (r >= -60) return 'Bon'; if (r >= -70) return 'Moyen'; return 'Faible'; } function refresh() { fetch('/api/status') .then(function(r) { return r.json(); }) .then(function(d) { document.getElementById('ssid').textContent = d.ssid || '—'; document.getElementById('ip').textContent = d.ip || '—'; document.getElementById('rssi').textContent = d.rssi + ' dBm (' + rssiQuality(d.rssi) + ')'; document.getElementById('hostname').textContent = d.hostname ? d.hostname + '.local' : '—'; document.getElementById('uptime').textContent = fmtUptime(d.uptime || 0); if (d.version) { document.getElementById('version-label').textContent = 'v' + d.version; } document.getElementById('status-badge').className = 'badge badge-ok'; document.getElementById('status-badge').textContent = 'Connecté'; document.getElementById('last-update').textContent = 'Dernière mise à jour : ' + new Date().toLocaleTimeString('fr-FR'); }) .catch(function() { document.getElementById('status-badge').className = 'badge badge-warn'; document.getElementById('status-badge').textContent = 'Erreur'; }); } refresh(); setInterval(refresh, 10000);</script></body></html>
3 changes: 2 additions & 1 deletion include/app_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions include/web_interface.h
Original file line number Diff line number Diff line change
@@ -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(
<!DOCTYPE html><html lang="fr"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Gateway Lab V1</title><style>*{box-sizing:border-box;margin:0;padding:0;}body{font-family:'Segoe UI',system-ui,sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;display:flex;flex-direction:column;align-items:center;padding:2rem 1rem;}h1{font-size:1.8rem;font-weight:700;color:#06b6d4;margin-bottom:0.25rem;letter-spacing:0.05em;}.subtitle{font-size:0.8rem;color:#475569;margin-bottom:2rem;}.card{background:#1e293b;border-radius:12px;padding:1.5rem;width:100%;max-width:420px;box-shadow:0 4px 24px rgba(0,0,0,0.3);margin-bottom:1.5rem;}.card-title{font-size:0.7rem;text-transform:uppercase;letter-spacing:0.12em;color:#64748b;margin-bottom:1rem;font-weight:600;}.info-row{display:flex;justify-content:space-between;align-items:center;padding:0.6rem 0;border-bottom:1px solid #1e3a5f22;}.info-row:last-child{border-bottom:none;}.info-label{color:#94a3b8;font-size:0.875rem;}.info-value{color:#e2e8f0;font-size:0.875rem;font-weight:600;text-align:right;}.badge{padding:0.2rem 0.7rem;border-radius:999px;font-size:0.7rem;font-weight:700;letter-spacing:0.04em;}.badge-ok{background:#064e3b;color:#10b981;}.badge-warn{background:#451a03;color:#f59e0b;}.btn{display:block;width:100%;max-width:420px;padding:0.875rem;background:#0891b2;color:#fff;border:none;border-radius:10px;font-size:1rem;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:background 0.2s;}.btn:hover{background:#06b6d4;}.last-update{font-size:0.72rem;color:#334155;margin-top:1.2rem;}</style></head><body><h1>Gateway Lab V1</h1><p class="subtitle" id="version-label">v—</p><div class="card"><p class="card-title">Informations réseau</p><div class="info-row"><span class="info-label">SSID</span><span class="info-value" id="ssid">—</span></div><div class="info-row"><span class="info-label">IP locale</span><span class="info-value" id="ip">—</span></div><div class="info-row"><span class="info-label">Signal (RSSI)</span><span class="info-value" id="rssi">—</span></div><div class="info-row"><span class="info-label">mDNS</span><span class="info-value" id="hostname">—</span></div><div class="info-row"><span class="info-label">Uptime</span><span class="info-value" id="uptime">—</span></div><div class="info-row"><span class="info-label">Statut</span><span class="badge badge-ok" id="status-badge">Connecté</span></div></div><a class="btn" href="/update">Mise à jour OTA</a><p class="last-update" id="last-update"></p><script>function fmtUptime(ms) { var s = Math.floor(ms / 1000); var m = Math.floor(s / 60); s %= 60; var h = Math.floor(m / 60); m %= 60; var d = Math.floor(h / 24); h %= 24; return (d > 0 ? d + 'j ' : '') + h + 'h ' + m + 'm ' + s + 's'; } function rssiQuality(r) { if (r >= -50) return 'Excellent'; if (r >= -60) return 'Bon'; if (r >= -70) return 'Moyen'; return 'Faible'; } function refresh() { fetch('/api/status') .then(function(r) { return r.json(); }) .then(function(d) { document.getElementById('ssid').textContent = d.ssid || '—'; document.getElementById('ip').textContent = d.ip || '—'; document.getElementById('rssi').textContent = d.rssi + ' dBm (' + rssiQuality(d.rssi) + ')'; document.getElementById('hostname').textContent = d.hostname ? d.hostname + '.local' : '—'; document.getElementById('uptime').textContent = fmtUptime(d.uptime || 0); if (d.version) { document.getElementById('version-label').textContent = 'v' + d.version; } document.getElementById('status-badge').className = 'badge badge-ok'; document.getElementById('status-badge').textContent = 'Connecté'; document.getElementById('last-update').textContent = 'Dernière mise à jour : ' + new Date().toLocaleTimeString('fr-FR'); }) .catch(function() { document.getElementById('status-badge').className = 'badge badge-warn'; document.getElementById('status-badge').textContent = 'Erreur'; }); } refresh(); setInterval(refresh, 10000);</script></body></html>
)HTML";
7 changes: 7 additions & 0 deletions include/web_interface_ota.h
Original file line number Diff line number Diff line change
@@ -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(
<!DOCTYPE html><html lang="fr"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Gateway Lab V1 - OTA</title><style>*{box-sizing:border-box;margin:0;padding:0;}body{font-family:'Segoe UI',system-ui,sans-serif;background:#0f172a;color:#e2e8f0;display:flex;flex-direction:column;align-items:center;padding:2rem 1rem;}h1{color:#06b6d4;margin-bottom:0.25rem;}p.sub{color:#475569;font-size:0.8rem;margin-bottom:2rem;}.card{background:#1e293b;border-radius:12px;padding:1.5rem;width:100%;max-width:420px;text-align:center;}label{display:block;color:#94a3b8;font-size:0.875rem;margin-bottom:0.75rem;}input[type=file]{width:100%;padding:0.5rem;background:#0f172a;border:1px solid #334155;border-radius:8px;color:#e2e8f0;margin-bottom:1rem;}.btn{display:block;width:100%;padding:0.875rem;background:#0891b2;color:#fff;border:none;border-radius:10px;font-size:1rem;font-weight:600;cursor:pointer;}.btn:hover{background:#06b6d4;}.progress{width:100%;background:#334155;border-radius:8px;height:12px;display:none;margin-top:1rem;overflow:hidden;}.bar{height:100%;background:#06b6d4;width:0;transition:width 0.2s;}#msg{margin-top:1rem;font-size:0.875rem;color:#94a3b8;}a{color:#06b6d4;font-size:0.8rem;margin-top:1.5rem;display:block;}</style></head><body><h1>Gateway Lab V1</h1><p class="sub">Mise à jour OTA</p><div class="card"><form id="f"><label>Sélectionnez le firmware (.bin)</label><input type="file" id="fw" accept=".bin"><button type="submit" class="btn">Mettre à jour</button></form><div class="progress" id="pg"><div class="bar" id="bar"></div></div><p id="msg"></p></div><a href="/">← Retour</a><script>document.getElementById('f').addEventListener('submit', function(e) { e.preventDefault(); var file = document.getElementById('fw').files[0]; if (!file) { document.getElementById('msg').textContent = 'Aucun fichier sélectionné.'; return; } var fd = new FormData(); fd.append('firmware', file); var xhr = new XMLHttpRequest(); xhr.upload.onprogress = function(e) { document.getElementById('pg').style.display = 'block'; document.getElementById('bar').style.width = (e.loaded / e.total * 100) + '%'; }; xhr.onload = function() { var m = document.getElementById('msg'); if (xhr.status === 200) { m.style.color = '#10b981'; m.textContent = 'Succès — redémarrage en cours…'; } else { m.style.color = '#ef4444'; m.textContent = 'Erreur : ' + xhr.responseText; } }; xhr.open('POST', '/update'); xhr.send(fd); });</script></body></html>
)HTML";
7 changes: 5 additions & 2 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Put the minifier script in the build environment

I checked the PlatformIO docs: [platformio] only lists core generic/directory options, while extra_scripts is documented under [env] advanced options as the PRE/POST script list. With this line in [platformio], the pre-build minifier will not be attached to esp32s3_n16r8, so edits to web_src/*.html can silently compile stale generated headers unless someone runs the script manually.

Useful? React with 👍 / 👎.


Comment on lines 14 to +18
[env:esp32s3_n16r8]
platform = espressif32
board = esp32-s3-devkitc-1-n16r8v
Expand All @@ -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
lib_deps =
bblanchon/ArduinoJson@^7.0.0
Loading