#include "wifi_config_portal.h" #include #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_wifi.h" #include "esp_log.h" #include "esp_event.h" #include "esp_netif.h" #include "esp_timer.h" #include "esp_http_server.h" #include "esp_system.h" #include "esp_err.h" #include "nvs_flash.h" #include "nvs.h" #include "dns_server.h" #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif // ===== Ajustes ===== #define FORCE_PORTAL_DEFAULT 0 // 1 = força sempre portal #define PORTAL_AP_PASS "12345678" // =================== static const char *TAG = "WIFI_PORTAL"; static httpd_handle_t server = NULL; static wifi_connected_cb_t g_on_connected = NULL; static esp_timer_handle_t wifi_watchdog_timer = NULL; static bool got_ip = false; // ✅ Flag: quando true estamos em portal (APSTA só para scan), NÃO conectar static bool g_portal_mode = false; /* ========================= NVS init seguro ========================= */ static void ensure_nvs_ready(void) { esp_err_t err = nvs_flash_init(); if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_LOGW(TAG, "NVS (%s) -> apagar e reiniciar", esp_err_to_name(err)); ESP_ERROR_CHECK(nvs_flash_erase()); ESP_ERROR_CHECK(nvs_flash_init()); } else if (err != ESP_OK) { ESP_LOGE(TAG, "nvs_flash_init falhou: %s", esp_err_to_name(err)); } } /* ========================= URL decode ========================= */ static void url_decode(char *dst, const char *src) { char a, b; while (*src) { if (*src == '%' && (a = src[1]) && (b = src[2]) && isxdigit((unsigned char)a) && isxdigit((unsigned char)b)) { if (a >= 'a') a -= 'a' - 'A'; if (a >= 'A') a -= ('A' - 10); else a -= '0'; if (b >= 'a') b -= 'a' - 'A'; if (b >= 'A') b -= ('A' - 10); else b -= '0'; *dst++ = 16 * a + b; src += 3; } else if (*src == '+') { *dst++ = ' '; src++; } else { *dst++ = *src++; } } *dst = '\0'; } /* ========================= Watchdog STA ========================= */ static void wifi_watchdog_cb(void *arg) { (void)arg; if (!got_ip) { ESP_LOGW(TAG, "⏱️ 60 s sem IP — reconectar..."); esp_wifi_disconnect(); vTaskDelay(pdMS_TO_TICKS(1000)); esp_wifi_connect(); } } /* ========================= Eventos WiFi ========================= */ static void on_wifi_event(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { (void)arg; if (event_base == WIFI_EVENT) { switch (event_id) { case WIFI_EVENT_STA_START: ESP_LOGI(TAG, "📡 STA start"); // ✅ Em portal mode: NÃO conectar, só scan if (!g_portal_mode) { esp_wifi_connect(); } else { ESP_LOGI(TAG, "🛑 Portal mode: não faço connect, só scan."); } break; case WIFI_EVENT_STA_CONNECTED: ESP_LOGI(TAG, "🔗 STA conectado, esperando IP..."); break; case WIFI_EVENT_STA_DISCONNECTED: { wifi_event_sta_disconnected_t *d = (wifi_event_sta_disconnected_t *)event_data; ESP_LOGW(TAG, "⚠️ STA desconectado (motivo %d)", d ? d->reason : -1); got_ip = false; // ✅ só tenta reconectar no modo normal, não no portal if (!g_portal_mode) { esp_wifi_connect(); } break; } default: break; } } } static void wifi_start_app_task(void *arg) { (void)arg; if (g_on_connected) g_on_connected(); vTaskDelete(NULL); } static void on_got_ip(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { (void)arg; (void)event_base; (void)event_id; (void)event_data; got_ip = true; ESP_LOGI(TAG, "🌐 IP obtido"); if (wifi_watchdog_timer) esp_timer_stop(wifi_watchdog_timer); xTaskCreate(wifi_start_app_task, "wifi_start_app", 6144, NULL, 5, NULL); } /* ========================= NVS Credenciais (teu namespace) ========================= */ static bool wifi_load_creds(char *ssid, char *pass) { nvs_handle_t nvs; if (nvs_open("wifi", NVS_READONLY, &nvs) != ESP_OK) return false; size_t ssid_len = 32, pass_len = 64; esp_err_t e1 = nvs_get_str(nvs, "ssid", ssid, &ssid_len); esp_err_t e2 = nvs_get_str(nvs, "pass", pass, &pass_len); nvs_close(nvs); if (e1 != ESP_OK || e2 != ESP_OK) return false; if (ssid[0] == '\0') return false; return true; } static void wifi_save_creds(const char *ssid, const char *pass) { nvs_handle_t nvs; if (nvs_open("wifi", NVS_READWRITE, &nvs) != ESP_OK) return; nvs_set_str(nvs, "ssid", ssid); nvs_set_str(nvs, "pass", pass); nvs_commit(nvs); nvs_close(nvs); } // ✅ Exportável: apaga as tuas credenciais (namespace "wifi") void wifi_clear_creds(void) { nvs_handle_t nvs; esp_err_t err = nvs_open("wifi", NVS_READWRITE, &nvs); if (err != ESP_OK) { ESP_LOGE(TAG, "❌ NVS open falhou: %s", esp_err_to_name(err)); return; } nvs_erase_key(nvs, "ssid"); nvs_erase_key(nvs, "pass"); nvs_commit(nvs); nvs_close(nvs); ESP_LOGW(TAG, "🧹 Credenciais Wi-Fi apagadas (namespace wifi)"); } /* ========================= SCAN SSID (APSTA) ========================= */ #define MAX_APS 20 static wifi_ap_record_t g_aps[MAX_APS]; static uint16_t g_ap_count = 0; static void html_escape(char *dst, size_t dstlen, const char *src) { size_t o = 0; for (size_t i = 0; src[i] && o + 6 < dstlen; i++) { unsigned char c = (unsigned char)src[i]; if (c == '&') { memcpy(dst+o, "&", 5); o += 5; } else if (c == '<') { memcpy(dst+o, "<", 4); o += 4; } else if (c == '>') { memcpy(dst+o, ">", 4); o += 4; } else if (c < 32) { /* ignora */ } else { dst[o++] = (char)c; } } dst[o] = 0; } static int wifi_scan_try(void) { g_ap_count = 0; ESP_LOGI(TAG, "📶 Scan: a iniciar..."); wifi_scan_config_t sc = { .ssid = 0, .bssid = 0, .channel = 0, .show_hidden = true }; esp_err_t err = esp_wifi_scan_start(&sc, true); // bloqueante if (err != ESP_OK) { ESP_LOGW(TAG, "📶 Scan: scan_start falhou: %s", esp_err_to_name(err)); return 0; } uint16_t n = MAX_APS; err = esp_wifi_scan_get_ap_records(&n, g_aps); if (err != ESP_OK) { ESP_LOGW(TAG, "📶 Scan: get_ap_records falhou: %s", esp_err_to_name(err)); return 0; } g_ap_count = n; ESP_LOGI(TAG, "📶 Scan: encontrei %d redes", (int)n); if (n > 0) ESP_LOGI(TAG, "📶 Exemplo SSID: %s", (char*)g_aps[0].ssid); return (int)n; } /* ========================= HTTP handlers ========================= */ static esp_err_t handle_root(httpd_req_t *req) { ESP_LOGI(TAG, "📥 GET %s", req->uri); // tenta scan (se falhar, continua com input manual) int n = wifi_scan_try(); char *html = malloc(9000); if (!html) return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Sem memória"); int o = 0; o += snprintf(html+o, 9000-o, "" "" "Configuração Wi-Fi" "" "

Configuração Wi-Fi

" "
" ); if (n > 0) { o += snprintf(html+o, 9000-o, "
Redes encontradas (scan):
" ""); } else { o += snprintf(html+o, 9000-o, "
Scan indisponível. Escreve o SSID manualmente.
" ); } o += snprintf(html+o, 9000-o, "SSID:

" "Senha:

" "" "
" "" ); httpd_resp_set_type(req, "text/html; charset=UTF-8"); esp_err_t r = httpd_resp_send(req, html, HTTPD_RESP_USE_STRLEN); free(html); return r; } static esp_err_t handle_save(httpd_req_t *req) { ESP_LOGI(TAG, "📥 POST %s len=%d", req->uri, (int)req->content_len); int total = req->content_len; int cur = 0; char buf[256]; if (total <= 0 || total >= (int)sizeof(buf)) { return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Body inválido/grande"); } while (cur < total) { int r = httpd_req_recv(req, buf + cur, total - cur); if (r <= 0) return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Falha recv"); cur += r; } buf[cur] = 0; char ssid_enc[32] = {0}, pass_enc[64] = {0}; char ssid[32] = {0}, pass[64] = {0}; sscanf(buf, "ssid=%31[^&]&pass=%63s", ssid_enc, pass_enc); url_decode(ssid, ssid_enc); url_decode(pass, pass_enc); wifi_save_creds(ssid, pass); ESP_LOGI(TAG, "💾 Guardado SSID=%s", ssid); const char *resp = "" "

✅ Credenciais guardadas

" "

Reiniciando...

"; httpd_resp_set_type(req, "text/html; charset=UTF-8"); httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN); vTaskDelay(pdMS_TO_TICKS(1200)); esp_restart(); return ESP_OK; } static esp_err_t handle_captive(httpd_req_t *req) { ESP_LOGI(TAG, "📥 GET %s -> redirect /", req->uri); httpd_resp_set_status(req, "302 Found"); httpd_resp_set_hdr(req, "Location", "/"); return httpd_resp_send(req, NULL, 0); } /* ========================= Webserver ========================= */ static void start_webserver(void) { ESP_LOGI(TAG, "🌍 start_webserver() chamado"); if (server) { ESP_LOGW(TAG, "⚠️ HTTP server já existia, a parar..."); httpd_stop(server); server = NULL; } httpd_config_t cfg = HTTPD_DEFAULT_CONFIG(); cfg.stack_size = 8192; cfg.server_port = 80; cfg.uri_match_fn = httpd_uri_match_wildcard; esp_err_t err = httpd_start(&server, &cfg); ESP_LOGI(TAG, "httpd_start -> %s", esp_err_to_name(err)); if (err != ESP_OK) { ESP_LOGE(TAG, "❌ HTTP server não arrancou"); return; } httpd_uri_t root = { .uri="/", .method=HTTP_GET, .handler=handle_root }; httpd_uri_t save = { .uri="/save", .method=HTTP_POST, .handler=handle_save }; httpd_uri_t captive = { .uri="/*", .method=HTTP_GET, .handler=handle_captive }; ESP_LOGI(TAG, "register / -> %s", esp_err_to_name(httpd_register_uri_handler(server, &root))); ESP_LOGI(TAG, "register /save -> %s", esp_err_to_name(httpd_register_uri_handler(server, &save))); ESP_LOGI(TAG, "register /* -> %s", esp_err_to_name(httpd_register_uri_handler(server, &captive))); } /* ========================= API ========================= */ void wifi_config_portal_init(wifi_connected_cb_t cb, bool have_creds) { bool force_portal = (FORCE_PORTAL_DEFAULT != 0) || have_creds; ESP_LOGW(TAG, "🔥 ENTREI no wifi_config_portal_init() force_portal=%d", (int)force_portal); ensure_nvs_ready(); g_on_connected = cb; ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); // STA netif sempre esp_netif_create_default_wifi_sta(); wifi_init_config_t wcfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&wcfg)); ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &on_wifi_event, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &on_got_ip, NULL)); // Watchdog STA const esp_timer_create_args_t tcfg = { .callback = wifi_watchdog_cb, .name = "wifi_watchdog" }; ESP_ERROR_CHECK(esp_timer_create(&tcfg, &wifi_watchdog_timer)); ESP_ERROR_CHECK(esp_timer_start_periodic(wifi_watchdog_timer, 60000000)); char ssid[32] = {0}, pass[64] = {0}; bool stored = wifi_load_creds(ssid, pass); if (force_portal) stored = false; if (stored) { g_portal_mode = false; ESP_LOGI(TAG, "✅ Credenciais encontradas: SSID=%s", ssid); wifi_config_t w = {0}; strncpy((char *)w.sta.ssid, ssid, sizeof(w.sta.ssid)); strncpy((char *)w.sta.password, pass, sizeof(w.sta.password)); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &w)); ESP_ERROR_CHECK(esp_wifi_start()); return; } // ----------- PORTAL AP ----------- g_portal_mode = true; ESP_LOGW(TAG, "⚠️ Sem credenciais válidas -> iniciar PORTAL"); esp_netif_create_default_wifi_ap(); wifi_config_t ap = {0}; ap.ap.max_connection = 4; ap.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK; strncpy((char*)ap.ap.password, PORTAL_AP_PASS, sizeof(ap.ap.password)); uint8_t mac[6]; ESP_ERROR_CHECK(esp_wifi_get_mac(WIFI_IF_AP, mac)); snprintf((char *)ap.ap.ssid, sizeof(ap.ap.ssid), "ESP32_%02X%02X%02X", mac[3], mac[4], mac[5]); // ✅ portal em APSTA para scan ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA)); // ✅ no portal, usa RAM storage para não gravar config interna ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap)); ESP_ERROR_CHECK(esp_wifi_start()); // watchdog não faz sentido em AP esp_timer_stop(wifi_watchdog_timer); // HTTP primeiro ESP_LOGI(TAG, "🚀 Vou iniciar HTTP agora!"); start_webserver(); // DNS em task ESP_LOGI(TAG, "🚀 Vou iniciar DNS agora!"); start_dns_server(); ESP_LOGI(TAG, "✅ Portal ativo (HTTP+DNS+SCAN)"); }