253 lines
8.9 KiB
C
253 lines
8.9 KiB
C
|
|
#include "wifi_config_portal.h"
|
|||
|
|
#include "esp_wifi.h"
|
|||
|
|
#include "esp_log.h"
|
|||
|
|
#include "esp_event.h"
|
|||
|
|
#include "esp_netif.h"
|
|||
|
|
#include "nvs_flash.h"
|
|||
|
|
#include "esp_timer.h"
|
|||
|
|
#include "esp_http_server.h"
|
|||
|
|
#include <string.h>
|
|||
|
|
#include <stdio.h>
|
|||
|
|
#include <stdlib.h>
|
|||
|
|
#include "dns_server.h"
|
|||
|
|
|
|||
|
|
#ifndef MIN
|
|||
|
|
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
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;
|
|||
|
|
|
|||
|
|
// --- WATCHDOG: tenta reconectar se ficar 60 s sem IP ---
|
|||
|
|
static void wifi_watchdog_cb(void *arg) {
|
|||
|
|
if (!got_ip) {
|
|||
|
|
ESP_LOGW(TAG, "⏱️ 60 s sem IP — a tentar reconectar Wi-Fi...");
|
|||
|
|
esp_wifi_disconnect();
|
|||
|
|
vTaskDelay(pdMS_TO_TICKS(1000)); // espera 1 s
|
|||
|
|
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) {
|
|||
|
|
if (event_base == WIFI_EVENT) {
|
|||
|
|
switch (event_id) {
|
|||
|
|
case WIFI_EVENT_STA_START:
|
|||
|
|
ESP_LOGI(TAG, "📡 STA start");
|
|||
|
|
esp_wifi_connect();
|
|||
|
|
break;
|
|||
|
|
case WIFI_EVENT_STA_CONNECTED:
|
|||
|
|
ESP_LOGI(TAG, "🔗 STA conectado ao AP, aguardando IP...");
|
|||
|
|
break;
|
|||
|
|
case WIFI_EVENT_STA_DISCONNECTED: {
|
|||
|
|
wifi_event_sta_disconnected_t *disconn = (wifi_event_sta_disconnected_t *) event_data;
|
|||
|
|
ESP_LOGW(TAG, "⚠️ STA desconectado (motivo %d)", disconn->reason);
|
|||
|
|
got_ip = false;
|
|||
|
|
esp_wifi_connect();
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
default:
|
|||
|
|
ESP_LOGI(TAG, "ℹ️ Evento Wi-Fi não tratado: %ld", (long)event_id);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- Task auxiliar para iniciar o app (MQTT, LEDs, etc.) ---
|
|||
|
|
static void wifi_start_app_task(void *arg) {
|
|||
|
|
if (g_on_connected)
|
|||
|
|
g_on_connected(); // chamada do callback da aplicação
|
|||
|
|
vTaskDelete(NULL);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- EVENTO IP OBTIDO ---
|
|||
|
|
static void on_got_ip(void *arg, esp_event_base_t event_base,
|
|||
|
|
int32_t event_id, void *event_data) {
|
|||
|
|
got_ip = true;
|
|||
|
|
ESP_LOGI(TAG, "🌐 STA obteve IP!");
|
|||
|
|
|
|||
|
|
if (wifi_watchdog_timer)
|
|||
|
|
esp_timer_stop(wifi_watchdog_timer);
|
|||
|
|
|
|||
|
|
// 🧠 executa o callback noutra task (stack própria)
|
|||
|
|
xTaskCreate(wifi_start_app_task, "wifi_start_app", 6144, NULL, 5, NULL);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- NVS: guardar e ler credenciais ---
|
|||
|
|
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 len1 = 32, len2 = 64;
|
|||
|
|
if (nvs_get_str(nvs, "ssid", ssid, &len1) != ESP_OK) { nvs_close(nvs); return false; }
|
|||
|
|
if (nvs_get_str(nvs, "pass", pass, &len2) != ESP_OK) { nvs_close(nvs); return false; }
|
|||
|
|
nvs_close(nvs);
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- Handlers HTTP ---
|
|||
|
|
static esp_err_t handle_root(httpd_req_t *req)
|
|||
|
|
{
|
|||
|
|
const char *html =
|
|||
|
|
"<!DOCTYPE html><html lang='pt'><head>"
|
|||
|
|
"<meta charset='UTF-8'>"
|
|||
|
|
"<title>Configuração Wi-Fi</title>"
|
|||
|
|
"<style>"
|
|||
|
|
"body{font-family:Arial,sans-serif;background:#eef2f3;text-align:center;}"
|
|||
|
|
"form{background:#fff;padding:20px;margin:50px auto;width:360px;"
|
|||
|
|
"border-radius:10px;box-shadow:0 0 10px rgba(0,0,0,0.2);}"
|
|||
|
|
"input{width:90%;padding:10px;margin:10px;font-size:16px;}"
|
|||
|
|
"button{background:#007bff;color:#fff;border:none;padding:10px 20px;"
|
|||
|
|
"border-radius:6px;font-size:16px;cursor:pointer;}"
|
|||
|
|
"button:hover{background:#0056b3;}"
|
|||
|
|
"</style></head><body>"
|
|||
|
|
"<h2>Configuração Wi-Fi</h2>"
|
|||
|
|
"<form id='wifiForm' action='/save' method='POST'>"
|
|||
|
|
"SSID:<br><input name='ssid' maxlength='31' required><br>"
|
|||
|
|
"Senha:<br><input name='pass' type='password' maxlength='63' required><br><br>"
|
|||
|
|
"<button type='submit'>Guardar</button>"
|
|||
|
|
"</form>"
|
|||
|
|
"<script>"
|
|||
|
|
"document.getElementById('wifiForm').onsubmit=function(e){"
|
|||
|
|
"e.preventDefault();"
|
|||
|
|
"fetch('/save',{method:'POST',body:new URLSearchParams(new FormData(this))})"
|
|||
|
|
".then(()=>alert('Credenciais enviadas! Reinicie o dispositivo.'));"
|
|||
|
|
"};"
|
|||
|
|
"</script>"
|
|||
|
|
"</body></html>";
|
|||
|
|
|
|||
|
|
httpd_resp_set_type(req, "text/html; charset=UTF-8");
|
|||
|
|
httpd_resp_set_hdr(req, "Cache-Control", "no-store, no-cache, must-revalidate");
|
|||
|
|
return httpd_resp_send(req, html, HTTPD_RESP_USE_STRLEN);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static esp_err_t handle_save(httpd_req_t *req)
|
|||
|
|
{
|
|||
|
|
char buf[128];
|
|||
|
|
int ret = httpd_req_recv(req, buf, MIN(req->content_len, sizeof(buf) - 1));
|
|||
|
|
if (ret <= 0) {
|
|||
|
|
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Nada recebido");
|
|||
|
|
}
|
|||
|
|
buf[ret] = '\0';
|
|||
|
|
|
|||
|
|
char ssid[32] = {0}, pass[64] = {0};
|
|||
|
|
sscanf(buf, "ssid=%31[^&]&pass=%63s", ssid, pass);
|
|||
|
|
|
|||
|
|
wifi_save_creds(ssid, pass);
|
|||
|
|
ESP_LOGI(TAG, "💾 Credenciais salvas: SSID=%s", ssid);
|
|||
|
|
|
|||
|
|
const char *resp =
|
|||
|
|
"<!DOCTYPE html><html lang='pt'><meta charset='UTF-8'>"
|
|||
|
|
"<body style='font-family:sans-serif;text-align:center;margin-top:50px;'>"
|
|||
|
|
"<h2>✅ Credenciais guardadas!</h2>"
|
|||
|
|
"<p>O dispositivo vai reiniciar em 2 segundos...</p>"
|
|||
|
|
"</body></html>";
|
|||
|
|
|
|||
|
|
httpd_resp_set_type(req, "text/html; charset=UTF-8");
|
|||
|
|
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
|
|||
|
|
|
|||
|
|
vTaskDelay(pdMS_TO_TICKS(2000));
|
|||
|
|
esp_restart();
|
|||
|
|
|
|||
|
|
return ESP_OK;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- Inicia o servidor HTTP ---
|
|||
|
|
static void start_webserver(void) {
|
|||
|
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
|||
|
|
config.stack_size = 8192;
|
|||
|
|
config.server_port = 80;
|
|||
|
|
config.max_uri_handlers = 8;
|
|||
|
|
config.uri_match_fn = httpd_uri_match_wildcard;
|
|||
|
|
config.recv_wait_timeout = 10;
|
|||
|
|
config.send_wait_timeout = 10;
|
|||
|
|
config.max_resp_headers = 20;
|
|||
|
|
|
|||
|
|
if (httpd_start(&server, &config) == ESP_OK) {
|
|||
|
|
httpd_uri_t root = { .uri = "/", .method = HTTP_GET, .handler = handle_root };
|
|||
|
|
httpd_uri_t save = { .uri = "/save", .method = HTTP_POST, .handler = handle_save };
|
|||
|
|
httpd_register_uri_handler(server, &root);
|
|||
|
|
httpd_register_uri_handler(server, &save);
|
|||
|
|
ESP_LOGI(TAG, "🌐 Servidor HTTP iniciado na porta %d", config.server_port);
|
|||
|
|
} else {
|
|||
|
|
ESP_LOGE(TAG, "❌ Falha ao iniciar servidor HTTP!");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- Inicialização principal ---
|
|||
|
|
void wifi_config_portal_init(wifi_connected_cb_t cb, bool have_creds)
|
|||
|
|
{
|
|||
|
|
g_on_connected = cb;
|
|||
|
|
esp_netif_init();
|
|||
|
|
esp_event_loop_create_default();
|
|||
|
|
esp_netif_create_default_wifi_sta();
|
|||
|
|
|
|||
|
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
|||
|
|
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
|||
|
|
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));
|
|||
|
|
|
|||
|
|
const esp_timer_create_args_t timer_args = {
|
|||
|
|
.callback = &wifi_watchdog_cb,
|
|||
|
|
.name = "wifi_watchdog"
|
|||
|
|
};
|
|||
|
|
esp_timer_create(&timer_args, &wifi_watchdog_timer);
|
|||
|
|
esp_timer_start_periodic(wifi_watchdog_timer, 60000000); // 60 s
|
|||
|
|
|
|||
|
|
char ssid[32] = {0}, pass[64] = {0};
|
|||
|
|
bool stored = wifi_load_creds(ssid, pass);
|
|||
|
|
|
|||
|
|
if (have_creds || stored) {
|
|||
|
|
ESP_LOGI(TAG, "🔄 A ligar à rede guardada...");
|
|||
|
|
wifi_config_t wifi_cfg = {0};
|
|||
|
|
strcpy((char *)wifi_cfg.sta.ssid, ssid);
|
|||
|
|
strcpy((char *)wifi_cfg.sta.password, pass);
|
|||
|
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
|||
|
|
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg));
|
|||
|
|
ESP_ERROR_CHECK(esp_wifi_start());
|
|||
|
|
esp_wifi_connect();
|
|||
|
|
} else {
|
|||
|
|
ESP_LOGW(TAG, "❌ Sem credenciais — modo AP de configuração...");
|
|||
|
|
esp_netif_create_default_wifi_ap();
|
|||
|
|
|
|||
|
|
wifi_config_t ap_cfg = {
|
|||
|
|
.ap = {
|
|||
|
|
.ssid_len = 0,
|
|||
|
|
.password = "12345678",
|
|||
|
|
.max_connection = 4,
|
|||
|
|
.authmode = WIFI_AUTH_WPA_WPA2_PSK
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
uint8_t mac[6];
|
|||
|
|
esp_wifi_get_mac(WIFI_IF_AP, mac);
|
|||
|
|
snprintf((char *)ap_cfg.ap.ssid, sizeof(ap_cfg.ap.ssid),
|
|||
|
|
"ESP32_%02X%02X%02X", mac[3], mac[4], mac[5]);
|
|||
|
|
if (strlen((char *)ap_cfg.ap.password) == 0)
|
|||
|
|
ap_cfg.ap.authmode = WIFI_AUTH_OPEN;
|
|||
|
|
|
|||
|
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
|
|||
|
|
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_cfg));
|
|||
|
|
ESP_ERROR_CHECK(esp_wifi_start());
|
|||
|
|
|
|||
|
|
if (wifi_watchdog_timer)
|
|||
|
|
esp_timer_stop(wifi_watchdog_timer);
|
|||
|
|
|
|||
|
|
start_dns_server();
|
|||
|
|
start_webserver();
|
|||
|
|
|
|||
|
|
ESP_LOGI(TAG, "🌐 Criado AP SSID=%s Senha=%s", ap_cfg.ap.ssid, ap_cfg.ap.password);
|
|||
|
|
}
|
|||
|
|
}
|