#include #include "esp_event.h" #include "esp_log.h" #include "esp_netif.h" #include "esp_system.h" #include "esp_timer.h" #include "esp_wifi.h" #include "freertos/FreeRTOS.h" #include "freertos/event_groups.h" #include "nvs_flash.h" #include "app_config.h" #include "wifi_manager.h" static const char *TAG = "wifi_manager"; #define WIFI_CONNECTED_BIT BIT0 #define WIFI_FAIL_BIT BIT1 #define WIFI_MAX_RETRIES 8 #define WIFI_CONNECT_TIMEOUT_MS 30000 /* After the fast retries are spent, keep trying at this interval forever -- * this is a wall appliance, so an AP outage must not require a power cycle. */ #define WIFI_RECONNECT_DELAY_MS 30000 static EventGroupHandle_t s_wifi_event_group = NULL; static esp_timer_handle_t s_reconnect_timer = NULL; static int s_retry_count = 0; static void reconnect_timer_cb(void *arg) { ESP_LOGI(TAG, "Slow reconnect attempt"); esp_wifi_connect(); } static void wifi_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(); return; } if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { /* Without this, wifi_is_connected() keeps reporting true after a * drop and callers never notice the link is gone. */ xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT); /* * A bounded burst of immediate retries covers transient drops; * signalling failure on the first disconnect (as this previously did) * meant a single blip was reported as a hard failure. Once the burst * is spent, fall back to a slow periodic retry instead of giving up: * FAIL_BIT only unblocks the boot-time wait in wifi_init_sta(). */ if (s_retry_count < WIFI_MAX_RETRIES) { s_retry_count++; ESP_LOGW(TAG, "Disconnected, retry %d/%d", s_retry_count, WIFI_MAX_RETRIES); esp_wifi_connect(); } else { ESP_LOGW(TAG, "Still down after %d fast retries; retrying every %d s", WIFI_MAX_RETRIES, WIFI_RECONNECT_DELAY_MS / 1000); xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); esp_timer_stop(s_reconnect_timer); esp_timer_start_once(s_reconnect_timer, (uint64_t)WIFI_RECONNECT_DELAY_MS * 1000); } return; } if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { const ip_event_got_ip_t *event = (const ip_event_got_ip_t *)event_data; ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip)); s_retry_count = 0; xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT); xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); } } esp_err_t wifi_init_sta(void) { if (WIFI_SSID[0] == '\0') { ESP_LOGE(TAG, "WIFI_SSID is empty -- set it in main/app_config.h"); return ESP_ERR_INVALID_STATE; } s_wifi_event_group = xEventGroupCreate(); if (s_wifi_event_group == NULL) { return ESP_ERR_NO_MEM; } const esp_timer_create_args_t timer_args = { .callback = reconnect_timer_cb, .name = "wifi_reconnect", }; ESP_ERROR_CHECK(esp_timer_create(&timer_args, &s_reconnect_timer)); esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } ESP_ERROR_CHECK(ret); /* The TCP/IP stack and the default STA netif were never set up here, so * IP_EVENT_STA_GOT_IP could never fire and every connect timed out. */ ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(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_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL)); ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL)); /* wifi_config_t.sta.ssid is a uint8_t array; it cannot be initialised * from a string literal in a designated initialiser. */ wifi_config_t wifi_config = { .sta = { .threshold.authmode = WIFI_AUTH_WPA2_PSK, .sae_pwe_h2e = WPA3_SAE_PWE_BOTH, }, }; strlcpy((char *)wifi_config.sta.ssid, WIFI_SSID, sizeof(wifi_config.sta.ssid)); strlcpy((char *)wifi_config.sta.password, WIFI_PASSWORD, 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()); /* Modem sleep adds hundreds of ms of latency per round trip, which is * enough to push a TLS handshake past its timeout on this board. */ ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); const EventBits_t bits = xEventGroupWaitBits( s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(WIFI_CONNECT_TIMEOUT_MS)); if (bits & WIFI_CONNECTED_BIT) { ESP_LOGI(TAG, "Connected to SSID: %s", WIFI_SSID); return ESP_OK; } if (bits & WIFI_FAIL_BIT) { ESP_LOGE(TAG, "Failed to connect to SSID: %s", WIFI_SSID); return ESP_FAIL; } ESP_LOGE(TAG, "Timed out waiting for WiFi connection"); return ESP_ERR_TIMEOUT; } bool wifi_is_connected(void) { if (s_wifi_event_group == NULL) { return false; } return (xEventGroupGetBits(s_wifi_event_group) & WIFI_CONNECTED_BIT) != 0; } void wifi_get_status(char *buf, size_t len) { if (buf == NULL || len == 0) { return; } if (!wifi_is_connected()) { snprintf(buf, len, "WiFi: not connected"); return; } wifi_ap_record_t ap; if (esp_wifi_sta_get_ap_info(&ap) == ESP_OK) { snprintf(buf, len, "WiFi: %s (%d dBm)", (char *)ap.ssid, ap.rssi); } else { snprintf(buf, len, "WiFi: connected"); } }