diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 5cd0879..c454bc8 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,5 +1,5 @@ -idf_component_register(SRCS "main.c" +idf_component_register(SRCS "main.c" "wifi_manager.c" INCLUDE_DIRS "." EMBED_TXTFILES "index.html" "index.js" - REQUIRES esp_http_server esp_adc nvs_flash esp_wifi driver esp_netif freertos esp_event) + REQUIRES esp_http_server esp_adc nvs_flash esp_wifi driver esp_netif freertos esp_event esp_timer) diff --git a/main/Kconfig b/main/Kconfig index a479314..1e607c6 100644 --- a/main/Kconfig +++ b/main/Kconfig @@ -5,16 +5,11 @@ menu "espScope Configuration" help LED IO pin - config ESP_WIFI_SSID - string "WiFi SSID" - default "myssid" + config BSP_CONFIG_GPIO + int "Factory Reset Pin (GPIO)" + default 9 help - Default SSID (network name) to connect to. You can change this onece connected via the web interface. - - config ESP_WIFI_PASSWORD - string "WiFi Password" - default "mypassword" - help - Default SSID password. You can change this onece connected via the web interface. + GPIO pin number to hold low on boot to erase NVS (Factory Reset). + Default is 9 (BOOT button on Seeed XIAO ESP32C6). endmenu diff --git a/main/index.html b/main/index.html index ca27a62..a050e4b 100644 --- a/main/index.html +++ b/main/index.html @@ -3,180 +3,316 @@ - + espScope
-
-
+ +
+
-
-
+ + + + + +
Connecting...
+ style="display:none; background:#eab308; color:#000; padding:4px 8px; font-size:0.8rem;">Reconnect
+ + +
-
+ +
-
- + +
+
-
- + + -
- + +
+
-
-
- - -
+ +
+ +
-
- - + +
+ + +
+
-
+ + diff --git a/main/index.js b/main/index.js index a0dfaaa..abeacd3 100644 --- a/main/index.js +++ b/main/index.js @@ -461,5 +461,44 @@ document.getElementById('resetBtn').addEventListener('click', () => { window.location.reload(); }); document.getElementById('powerOff').addEventListener('click', () => window.location.href = "/poweroff"); + +function setupWifiListeners() { + const modal = document.getElementById('wifiModal'); + const btn = document.getElementById('wifiBtn'); + const closeBtn = document.getElementById('closeWifi'); + const saveBtn = document.getElementById('saveWifi'); + + if (btn) btn.onclick = () => { + modal.style.display = "flex"; + document.getElementById('wifiSsid').focus(); + }; + if (closeBtn) closeBtn.onclick = () => modal.style.display = "none"; + if (saveBtn) saveBtn.onclick = () => { + const ssid = document.getElementById('wifiSsid').value; + const pass = document.getElementById('wifiPass').value; + + if (!ssid) { + alert("SSID is required"); + return; + } + + saveBtn.innerText = "Saving..."; + fetch('/api/save_wifi', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ ssid, password: pass }) + }) + .then(res => res.text()) + .then(text => { + alert(text); + window.location.reload(); + }) + .catch(err => { + alert("Error: " + err); + saveBtn.innerText = "Save"; + }); + }; +} +setupWifiListeners(); setParams(); connect(); diff --git a/main/main.c b/main/main.c index 24a0d3f..4a0cc11 100644 --- a/main/main.c +++ b/main/main.c @@ -14,29 +14,20 @@ #include "esp_sleep.h" #include "esp_system.h" #include "esp_wifi.h" +#include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/event_groups.h" #include "freertos/task.h" +#include "wifi_manager.h" #include "nvs_flash.h" // Tag for logging static const char* TAG = "ESP-SCOPE"; -// WiFi configuration from Kconfig -/* See untracked wifi-credentials.h */ -#ifndef ESP_WIFI_SSID -#define ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID -#define ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD -#endif - #define ESP_MAXIMUM_RETRY 5 /* FreeRTOS event group to signal when we are connected*/ -static EventGroupHandle_t s_wifi_event_group; -#define WIFI_CONNECTED_BIT BIT0 -#define WIFI_FAIL_BIT BIT1 -static int s_retry_num = 0; // Embedded index.html extern const uint8_t index_html_start[] asm("_binary_index_html_start"); @@ -45,7 +36,6 @@ extern const uint8_t index_js_start[] asm("_binary_index_js_start"); extern const uint8_t index_js_end[] asm("_binary_index_js_end"); // Forward declarations -static void wifi_init_sta(void); static void start_webserver(void); // ADC Configuration @@ -62,7 +52,7 @@ static void start_webserver(void); * Web Server Configuration */ static httpd_handle_t s_server = NULL; - +static bool is_ap = false; #define ADC_READ_LEN 4096 static adc_continuous_handle_t adc_handle = NULL; @@ -78,13 +68,6 @@ static adc_bitwidth_t s_bit_width = ADC_BIT_WIDTH; static uint16_t s_test_hz = 100; // Forward declarations -static void wifi_init_sta(void); -static void continuous_adc_init(adc_channel_t* channel, uint8_t channel_num, - adc_continuous_handle_t* out_handle); -static esp_err_t ws_handler(httpd_req_t* req); - -// Forward declarations -static void wifi_init_sta(void); static void continuous_adc_init(adc_channel_t* channel, uint8_t channel_num, adc_continuous_handle_t* out_handle); static esp_err_t ws_handler(httpd_req_t* req); @@ -287,14 +270,44 @@ static void start_test_signal(uint32_t hz) { } static void show_status_led() { -#ifdef CONFIG_LED_BUILTIN + #ifdef CONFIG_BSP_CONFIG_GPIO + gpio_config_t rst_conf = { + .pin_bit_mask = (1ULL << CONFIG_BSP_CONFIG_GPIO), + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&rst_conf); + #endif + + int64_t reset_pressed_time = 0; while (true) { - vTaskDelay(pdMS_TO_TICKS(100)); + int64_t now = esp_timer_get_time() / 1000; + #ifdef CONFIG_LED_BUILTIN + vTaskDelay(pdMS_TO_TICKS(is_ap ? 500 : 100)); gpio_set_level(CONFIG_LED_BUILTIN, 1); vTaskDelay(pdMS_TO_TICKS(s_ws_client_fd == -1 ? 900 : 200)); gpio_set_level(CONFIG_LED_BUILTIN, 0); + #else + vTaskDelay(pdMS_TO_TICKS(1000)); + #endif + + // Check Reset Pin + #ifdef CONFIG_BSP_CONFIG_GPIO + if (!is_ap && gpio_get_level(CONFIG_BSP_CONFIG_GPIO) == 0) { + if (now - reset_pressed_time > 1000) { + ESP_LOGW(TAG, "Factory Reset Triggered via GPIO %d", CONFIG_BSP_CONFIG_GPIO); + wifi_manager_erase_config(); + esp_restart(); + } else { + reset_pressed_time = now; + } + } else { + reset_pressed_time = 0; + } + #endif } -#endif } void app_main(void) { @@ -318,7 +331,6 @@ void app_main(void) { gpio_set_level(CONFIG_LED_BUILTIN, 0); #endif - ESP_LOGI(TAG, "ESP_WIFI_MODE_STA"); /* Board-specific WiFi init (if any) */ // Seeed XIAO ESP32C6: Configure GPIO 3 and GPIO 14 as outputs gpio_config_t board_io_conf = { @@ -334,82 +346,21 @@ void app_main(void) { gpio_set_level(14, 0); /* end board-specific WiFi init (if any) */ - wifi_init_sta(); + is_ap = wifi_manager_init_wifi(); + start_test_signal(100); xTaskCreate(adc_read_task, "adc_read_task", 8192 + ADC_READ_LEN, NULL, 5, NULL); - // Wait for WiFi connection - EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, - WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, - pdFALSE, pdFALSE, portMAX_DELAY); + // Wait for WiFi connection (Not strict blocking anymore, manager handles it) + // But let's keep it to print status + // Note: s_wifi_event_group is local to this file, we removed it in place of manager's. + // Proper way: expose event group from manager or just dont block here. + // For now, let's just proceed. The webserver will start and wait for connections. - if (bits & WIFI_CONNECTED_BIT) { - ESP_LOGI(TAG, "connected to ap SSID:%s", ESP_WIFI_SSID); - start_webserver(); - show_status_led(); - } else if (bits & WIFI_FAIL_BIT) { - ESP_LOGI(TAG, "Failed to connect to SSID:%s", ESP_WIFI_SSID); - } else { - ESP_LOGE(TAG, "UNEXPECTED EVENT"); - } + start_webserver(); + show_status_led(); } -static void event_handler(void* arg, esp_event_base_t event_base, - int32_t event_id, void* event_data) { - if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { - esp_wifi_connect(); - } else if (event_base == WIFI_EVENT && - event_id == WIFI_EVENT_STA_DISCONNECTED) { - if (s_retry_num < ESP_MAXIMUM_RETRY) { - esp_wifi_connect(); - s_retry_num++; - ESP_LOGI(TAG, "retry to connect to the AP"); - } else { - xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); - } - ESP_LOGI(TAG, "connect to the AP fail"); - } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { - ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data; - ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); - s_retry_num = 0; - xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); - } -} - -static void wifi_init_sta(void) { - s_wifi_event_group = xEventGroupCreate(); - - ESP_ERROR_CHECK(esp_netif_init()); - - ESP_ERROR_CHECK(esp_event_loop_create_default()); - esp_netif_t* netif = esp_netif_create_default_wifi_sta(); - esp_netif_set_hostname(netif, "esp-scope"); // Set hostname for the STA interface - - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(esp_wifi_init(&cfg)); - - esp_event_handler_instance_t instance_any_id; - esp_event_handler_instance_t instance_got_ip; - ESP_ERROR_CHECK(esp_event_handler_instance_register( - WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id)); - ESP_ERROR_CHECK(esp_event_handler_instance_register( - IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip)); - - wifi_config_t wifi_config = { - .sta = - { - .ssid = ESP_WIFI_SSID, - .password = ESP_WIFI_PASS, - .threshold.authmode = WIFI_AUTH_WPA2_PSK, - }, - }; - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); - ESP_ERROR_CHECK(esp_wifi_start()); - ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); - - ESP_LOGI(TAG, "wifi_init_sta finished."); -} /* * WebSocket Handler @@ -562,6 +513,16 @@ static esp_err_t power_handler(httpd_req_t* req) { static const httpd_uri_t uri_power = { .uri = "/poweroff", .method = HTTP_GET, .handler = power_handler, .user_ctx = NULL}; +/* Error handler for 404 - Redirects to captive portal */ +static esp_err_t http_404_error_handler(httpd_req_t *req, httpd_err_code_t err) +{ + /* Set status 302 Redirect */ + httpd_resp_set_status(req, "302 Found"); + httpd_resp_set_hdr(req, "Location", "/"); + httpd_resp_send(req, NULL, 0); // No body needed + return ESP_OK; +} + static void start_webserver(void) { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.lru_purge_enable = true; @@ -573,7 +534,13 @@ static void start_webserver(void) { httpd_register_uri_handler(s_server, &uri_index_js); httpd_register_uri_handler(s_server, &uri_ws); httpd_register_uri_handler(s_server, &uri_params); + + // Register WiFi Manager endpoints + wifi_manager_register_uri(s_server); httpd_register_uri_handler(s_server, &uri_power); + + // Register 404 handler for Captive Portal redirection + httpd_register_err_handler(s_server, HTTPD_404_NOT_FOUND, http_404_error_handler); } else { ESP_LOGI(TAG, "Error starting server!"); } diff --git a/main/wifi_manager.c b/main/wifi_manager.c new file mode 100644 index 0000000..264386d --- /dev/null +++ b/main/wifi_manager.c @@ -0,0 +1,354 @@ +#include "wifi_manager.h" +#include +#include "esp_mac.h" +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "nvs.h" +#include "lwip/err.h" +#include "lwip/sys.h" +#include "lwip/sockets.h" +#include "cJSON.h" + +// Logs +static const char *TAG = "WIFI_MGR"; + +// Event Group +static EventGroupHandle_t s_wifi_event_group; +#define WIFI_CONNECTED_BIT BIT0 +#define WIFI_FAIL_BIT BIT1 + +// Server Handle for registering handlers +static httpd_handle_t s_server_handle = NULL; + +// DNS Port +#define DNS_PORT 53 + +// SoftAP Config +#define AP_SSID "ESP-Scope" +#define AP_PASS "" + +// Forward decls + +static void wifi_init_softap(void); +static void wifi_init_station(const char* ssid, const char* pass); + +// NVS Keys +#define NVS_NAMESPACE "wifi_cfg" +#define NVS_KEY_SSID "ssid" +#define NVS_KEY_PASS "pass" + +// WiFi Event Handler +static void event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) { + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + esp_wifi_connect(); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + ESP_LOGI(TAG, "Retry connecting to AP..."); + esp_wifi_connect(); + // In a real product, we might count retries and switch to AP mode if failing. + // For now, infinite retry is simple. + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip)); + xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) { + wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *)event_data; + ESP_LOGI(TAG, "station "MACSTR" join, AID=%d", MAC2STR(event->mac), event->aid); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) { + wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data; + ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d", MAC2STR(event->mac), event->aid); + } +} + +// ------------------------------------------------------------------------- +// DNS Server Task (Captive Portal) +// ------------------------------------------------------------------------- +static void dns_server_task(void *pvParameters) { + int sock; + struct sockaddr_in server_addr; + struct sockaddr_in client_addr; + socklen_t client_addr_len = sizeof(client_addr); + uint8_t rx_buffer[128]; + uint8_t tx_buffer[128]; + + // Create UDP socket + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sock < 0) { + ESP_LOGE(TAG, "Unable to create DNS socket: errno %d", errno); + vTaskDelete(NULL); + } + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(DNS_PORT); + server_addr.sin_addr.s_addr = htonl(INADDR_ANY); + + if (bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { + ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); + close(sock); + vTaskDelete(NULL); + } + + ESP_LOGI(TAG, "DNS Server started on port 53"); + + while (1) { + int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer), 0, (struct sockaddr *)&client_addr, &client_addr_len); + if (len < 0) { + ESP_LOGE(TAG, "recvfrom failed: errno %d", errno); + break; + } + + // Basic DNS Header Parsing + // We just want to spoof the answer for any A-record query. + // Transaction ID: bytes 0-1 (Copy) + // Flags: bytes 2-3 (Standard Query -> Standard Response) + + if (len > 12) { + // Copy Transaction ID + tx_buffer[0] = rx_buffer[0]; + tx_buffer[1] = rx_buffer[1]; + + // Flags: Response (QR=1), Opcode=0, AA=0, TC=0, RD=1, RA=0, Z=0, RCODE=0 + // 0x8180 (Standard query response, No error) + tx_buffer[2] = 0x81; + tx_buffer[3] = 0x80; + + // Questions Count: Copy (should be 1) + tx_buffer[4] = rx_buffer[4]; + tx_buffer[5] = rx_buffer[5]; + + // Answer RRs: 1 + tx_buffer[6] = 0x00; + tx_buffer[7] = 0x01; + + // Authority RRs: 0 + tx_buffer[8] = 0x00; + tx_buffer[9] = 0x00; + + // Additional RRs: 0 + tx_buffer[10] = 0x00; + tx_buffer[11] = 0x00; + + // Copy Question Section + // In a real server we'd parse this length. For simplicity in captive portal, + // we assume the question fits in the buffer and just echo it. + // Point to end of question to append answer. + int question_len = len - 12; + if (question_len > 0 && (12 + question_len + 16 < sizeof(tx_buffer))) { + memcpy(&tx_buffer[12], &rx_buffer[12], question_len); + + // Answer Section + int offset = 12 + question_len; + + // Name Pointer (0xC00C -> Point to start of packet header + 12 = start of question name) + tx_buffer[offset++] = 0xC0; + tx_buffer[offset++] = 0x0C; + + // Type: A (Host Address) = 1 + tx_buffer[offset++] = 0x00; + tx_buffer[offset++] = 0x01; + + // Class: IN (Internet) = 1 + tx_buffer[offset++] = 0x00; + tx_buffer[offset++] = 0x01; + + // TTL: 60 seconds + tx_buffer[offset++] = 0x00; + tx_buffer[offset++] = 0x00; + tx_buffer[offset++] = 0x00; + tx_buffer[offset++] = 0x3C; + + // Data Length: 4 (IPv4) + tx_buffer[offset++] = 0x00; + tx_buffer[offset++] = 0x04; + + // Address: 192.168.4.1 (Default SoftAP IP) + tx_buffer[offset++] = 192; + tx_buffer[offset++] = 168; + tx_buffer[offset++] = 4; + tx_buffer[offset++] = 1; + + // Send + sendto(sock, tx_buffer, offset, 0, (struct sockaddr *)&client_addr, client_addr_len); + } + } + } + close(sock); + vTaskDelete(NULL); +} + +// ------------------------------------------------------------------------- +// Init Functions +// ------------------------------------------------------------------------- +static void wifi_init_softap(void) { + ESP_LOGI(TAG, "Starting SoftAP Provisioning Mode..."); + + // Explicitly set mode to NULL first to ensure clean state + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); + + wifi_config_t wifi_config = { + .ap = { + .ssid = AP_SSID, + .ssid_len = strlen(AP_SSID), + .channel = 1, + .password = AP_PASS, + .max_connection = 4, + .authmode = WIFI_AUTH_OPEN + }, + }; + if (strlen(AP_PASS) == 0) { + wifi_config.ap.authmode = WIFI_AUTH_OPEN; + } + + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + ESP_LOGI(TAG, "SoftAP Started. SSID: %s", AP_SSID); + + // Start DNS Server Task + xTaskCreate(dns_server_task, "dns_task", 2048, NULL, 5, NULL); +} + +static void wifi_init_station(const char* ssid, const char* pass) { + ESP_LOGI(TAG, "Starting Station Mode. Connecting to %s...", ssid); + + wifi_config_t wifi_config = { + .sta = { + .threshold.authmode = WIFI_AUTH_WPA2_PSK, + }, + }; + strncpy((char*)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid)); + strncpy((char*)wifi_config.sta.password, pass, sizeof(wifi_config.sta.password)); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); // Maintain low jitter optimization +} + +// ------------------------------------------------------------------------- +// HTTP Handler: /api/save_wifi +// ------------------------------------------------------------------------- +static esp_err_t save_wifi_handler(httpd_req_t *req) { + char buf[200]; // reasonable limit for JSON + int ret, remaining = req->content_len; + if (remaining >= sizeof(buf)) { + httpd_resp_send_500(req); + return ESP_FAIL; + } + + ret = httpd_req_recv(req, buf, remaining); + if (ret <= 0) return ESP_FAIL; + buf[ret] = '\0'; + + cJSON *root = cJSON_Parse(buf); + if (root) { + cJSON *ssid_item = cJSON_GetObjectItem(root, "ssid"); + cJSON *pass_item = cJSON_GetObjectItem(root, "password"); + + if (cJSON_IsString(ssid_item) && (cJSON_IsString(pass_item))) { + const char* ssid = ssid_item->valuestring; + const char* pass = pass_item->valuestring; + + ESP_LOGI(TAG, "Saving WiFi Credentials: SSID=%s", ssid); + + // Save to NVS + nvs_handle_t nvs_handle; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); + if (err == ESP_OK) { + nvs_set_str(nvs_handle, NVS_KEY_SSID, ssid); + nvs_set_str(nvs_handle, NVS_KEY_PASS, pass); + nvs_commit(nvs_handle); + nvs_close(nvs_handle); + + httpd_resp_send(req, "Saved. Rebooting...", HTTPD_RESP_USE_STRLEN); + + // Give time for response to flush + vTaskDelay(pdMS_TO_TICKS(1000)); + esp_restart(); + } else { + ESP_LOGE(TAG, "NVS Open Failed"); + httpd_resp_send_500(req); + } + } + cJSON_Delete(root); + } + return ESP_OK; +} + +static const httpd_uri_t uri_save_wifi = { + .uri = "/api/save_wifi", + .method = HTTP_POST, + .handler = save_wifi_handler, + .user_ctx = NULL +}; + +// ------------------------------------------------------------------------- +// Public API +// ------------------------------------------------------------------------- + +void wifi_manager_register_uri(httpd_handle_t server) { + s_server_handle = server; + if (s_server_handle) { + httpd_register_uri_handler(s_server_handle, &uri_save_wifi); + } +} + +bool wifi_manager_init_wifi(void) { + s_wifi_event_group = xEventGroupCreate(); + + // 1. Netif Init (Common) + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta(); + esp_netif_set_hostname(sta_netif, "esp-scope"); + esp_netif_create_default_wifi_ap(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, NULL); + esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, NULL); + + // 2. Check NVS + nvs_handle_t nvs_handle; + char ssid[33] = {0}; + char pass[64] = {0}; + size_t ssid_len = sizeof(ssid); + size_t pass_len = sizeof(pass); + bool has_creds = false; + + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle); + if (err == ESP_OK) { + if (nvs_get_str(nvs_handle, NVS_KEY_SSID, ssid, &ssid_len) == ESP_OK && + nvs_get_str(nvs_handle, NVS_KEY_PASS, pass, &pass_len) == ESP_OK) { + has_creds = true; + } + nvs_close(nvs_handle); + } + + // 3. Start Mode + if (has_creds && strlen(ssid) > 0) { + wifi_init_station(ssid, pass); + return false; + } else { + wifi_init_softap(); + return true; + } +} + +void wifi_manager_erase_config(void) { + ESP_LOGW(TAG, "Erasing WiFi Config from NVS..."); + nvs_handle_t nvs_handle; + if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle) == ESP_OK) { + nvs_erase_all(nvs_handle); + nvs_commit(nvs_handle); + nvs_close(nvs_handle); + } +} diff --git a/main/wifi_manager.h b/main/wifi_manager.h new file mode 100644 index 0000000..2f6ff47 --- /dev/null +++ b/main/wifi_manager.h @@ -0,0 +1,16 @@ +#ifndef WIFI_MANAGER_H +#define WIFI_MANAGER_H + +#include "esp_err.h" +#include "esp_http_server.h" + +// Initialize Wi-Fi (Checks NVS, starts AP or STA) +bool wifi_manager_init_wifi(void); + +// Register provisioning URI handlers to the server +void wifi_manager_register_uri(httpd_handle_t server); + +// Helper to erase NVS credentials (can be called by button handler) +void wifi_manager_erase_config(void); + +#endif // WIFI_MANAGER_H