#include "WifiConfig.h" #include "config.h" #include #include #include // ------------------------------------------------------------------ NVS keys namespace { constexpr const char* NVS_NS = "voice-cfg"; constexpr const char* KEY_SSID = "ssid"; constexpr const char* KEY_PASS = "pass"; constexpr const char* KEY_HOST = "mqttHost"; constexpr const char* KEY_PORT = "mqttPort"; constexpr const char* KEY_USER = "mqttUser"; constexpr const char* KEY_MPWD = "mqttPass"; // ------------------------------------------------------------------ HTML // Encodage UTF-8 inline ; servi avec charset=utf-8. const char HTML_FORM[] = "" "" "" "Assistant vocal — configuration" "" "

Assistant vocal

" "

Renseignez le réseau Wi-Fi et l’adresse du broker MQTT," " puis cliquez sur Enregistrer.

" "
" "
Wi-Fi" "" "" "" "" "
" "
Broker MQTT" "" "" "" "" "" "" "" "" "
" "" "
"; const char HTML_OK[] = "" "Sauvegardé" "" "

✓

" "

Configuration sauvegardée

" "

L’assistant redémarre dans quelques secondes…

" ""; } // namespace WifiConfig* WifiConfig::_instance = nullptr; WifiConfig::WifiConfig() { _instance = this; // Par défaut, l'utilisateur MQTT est DEVICE_ID : les règles ACL du broker // (pattern voice/%u/...) l'exigent. Ce repli garantit que MqttLink ne // stocke pas un pointeur vide avant que begin() soit appelé. strncpy(_mqttUser, DEVICE_ID, sizeof(_mqttUser) - 1); } WifiConfig::~WifiConfig() { delete _server; } // ------------------------------------------------------------------ NVS I/O bool WifiConfig::loadFromNvs() { Preferences prefs; if (!prefs.begin(NVS_NS, true)) { return false; } if (!prefs.isKey(KEY_SSID)) { prefs.end(); return false; } prefs.getString(KEY_SSID, _ssid, sizeof(_ssid)); prefs.getString(KEY_PASS, _password, sizeof(_password)); prefs.getString(KEY_HOST, _mqttHost, sizeof(_mqttHost)); _mqttPort = prefs.getUShort(KEY_PORT, 1883); prefs.getString(KEY_USER, _mqttUser, sizeof(_mqttUser)); prefs.getString(KEY_MPWD, _mqttPassword, sizeof(_mqttPassword)); prefs.end(); // Utilisateur vide → repli sur DEVICE_ID (requis par le broker ACL). if (_mqttUser[0] == '\0') { strncpy(_mqttUser, DEVICE_ID, sizeof(_mqttUser) - 1); } // SSID et hôte non vides : configuration valide. return _ssid[0] != '\0' && _mqttHost[0] != '\0'; } void WifiConfig::saveToNvs(const char* ssid, const char* password, const char* mqttHost, uint16_t mqttPort, const char* mqttUser, const char* mqttPassword) { Preferences prefs; if (!prefs.begin(NVS_NS, false)) { Serial.println("# config : erreur d'ecriture NVS"); return; } prefs.putString(KEY_SSID, ssid); prefs.putString(KEY_PASS, password); prefs.putString(KEY_HOST, mqttHost); prefs.putUShort(KEY_PORT, mqttPort); prefs.putString(KEY_USER, mqttUser); prefs.putString(KEY_MPWD, mqttPassword); prefs.end(); Serial.println("# config : identifiants sauvegardes en NVS"); } // ------------------------------------------------------------------ begin void WifiConfig::begin(bool forcePortal, const char* fbSsid, const char* fbPassword, const char* fbMqttHost, uint16_t fbMqttPort, const char* fbMqttUser, const char* fbMqttPass) { if (!forcePortal && loadFromNvs()) { Serial.printf("# config : Wi-Fi \"%s\", broker %s:%u (NVS)\n", _ssid, _mqttHost, _mqttPort); return; } // Repli depuis secrets.h : utilisé si les deux champs essentiels sont renseignés, // sans être copié en NVS (l'utilisateur reste maître de la migration). if (!forcePortal && fbSsid[0] != '\0' && fbMqttHost[0] != '\0') { strncpy(_ssid, fbSsid, sizeof(_ssid) - 1); strncpy(_password, fbPassword, sizeof(_password) - 1); strncpy(_mqttHost, fbMqttHost, sizeof(_mqttHost) - 1); _mqttPort = fbMqttPort; const char* user = (fbMqttUser[0] != '\0') ? fbMqttUser : DEVICE_ID; strncpy(_mqttUser, user, sizeof(_mqttUser) - 1); strncpy(_mqttPassword, fbMqttPass, sizeof(_mqttPassword) - 1); Serial.printf("# config : Wi-Fi \"%s\", broker %s:%u (secrets.h)\n", _ssid, _mqttHost, _mqttPort); return; } Serial.println(forcePortal ? "# config : portail force (bouton PTT tenu au demarrage)" : "# config : aucun identifiant disponible, lancement du portail"); runPortal(); // ne retourne pas } // ------------------------------------------------------------------ Portail void WifiConfig::onRoot() { String page = HTML_FORM; page.replace("%SSID%", _instance->_ssid); page.replace("%HOST%", _instance->_mqttHost); char portStr[8]; snprintf(portStr, sizeof(portStr), "%u", _instance->_mqttPort); page.replace("%PORT%", portStr); page.replace("%USER%", _instance->_mqttUser); _instance->_server->send(200, "text/html; charset=utf-8", page); } void WifiConfig::onSave() { WebServer* srv = _instance->_server; const String ssid = srv->arg("ssid"); const String mqttHost = srv->arg("mqttHost"); if (ssid.isEmpty()) { srv->send(400, "text/plain", "SSID manquant"); return; } if (mqttHost.isEmpty()) { srv->send(400, "text/plain", "Adresse broker manquante"); return; } const String pass = srv->arg("pass"); const uint16_t port = static_cast(srv->arg("mqttPort").toInt()); const String mqttUser = srv->arg("mqttUser"); const String mqttPass = srv->arg("mqttPass"); _instance->saveToNvs( ssid.c_str(), pass.c_str(), mqttHost.c_str(), port ? port : uint16_t(1883), mqttUser.c_str(), mqttPass.c_str()); srv->send(200, "text/html; charset=utf-8", HTML_OK); delay(1500); ESP.restart(); } void WifiConfig::onNotFound() { _instance->_server->sendHeader("Location", "/", true); _instance->_server->send(302, "text/plain", ""); } void WifiConfig::runPortal() { WiFi.disconnect(true); WiFi.mode(WIFI_AP); if (!WiFi.softAP(WIFI_CONFIG_AP_SSID)) { Serial.println("# portail : echec softAP, redemarrage dans 5 s"); delay(5000); ESP.restart(); } Serial.printf( "# portail : AP \"%s\" actif " "-- connectez-vous au Wi-Fi puis allez sur http://192.168.4.1\n", WIFI_CONFIG_AP_SSID); // LED : clignotement rapide (100 ms) pendant toute la durée du portail. // La broche est déjà configurée en OUTPUT par led.begin() avant begin(). pinMode(PIN_STATUS_LED, OUTPUT); _server = new WebServer(80); _server->on("/", HTTP_GET, onRoot); _server->on("/save", HTTP_POST, onSave); _server->onNotFound(onNotFound); _server->begin(); Serial.println("# portail : en attente de configuration..."); uint32_t lastToggle = 0; bool ledOn = false; for (;;) { _server->handleClient(); if (millis() - lastToggle >= 100) { lastToggle = millis(); ledOn = !ledOn; digitalWrite(PIN_STATUS_LED, ledOn ? HIGH : LOW); } } }