// ========================= // wifi.c (ESP-IDF v5.4.2) // ========================= #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" #include "esp_log.h" #include "esp_err.h" #include "esp_wifi.h" #include "esp_event.h" #include "esp_netif.h" #include "esp_mac.h" #include "mdns.h" #include "network_events.h" #include "network.h" // NEW: #include "storage_service.h" // ----------------------------------------------------------------------------- // Config // ----------------------------------------------------------------------------- #define AP_SSID "plx-%02x%02x%02x" #define MDNS_SSID "plx%02x" #define NVS_NAMESPACE "wifi" #define NVS_ENABLED "enabled" #define NVS_SSID "ssid" #define NVS_PASSWORD "password" // Comprimentos com terminador #define SSID_MAX_LEN 32 #define PASS_MAX_LEN 64 #define SSID_BUF_SZ (SSID_MAX_LEN + 1) #define PASS_BUF_SZ (PASS_MAX_LEN + 1) static const char *TAG = "wifi"; // Storage timeouts #define STORE_TO pdMS_TO_TICKS(800) #define STORE_FLUSH_TO pdMS_TO_TICKS(2000) // ----------------------------------------------------------------------------- // Estado global // ----------------------------------------------------------------------------- static esp_netif_t *sta_netif; static esp_netif_t *ap_netif; EventGroupHandle_t wifi_event_group; // Backoff simples para reconexão STA static int s_retry_count = 0; static const int s_retry_max = 7; // ----------------------------------------------------------------------------- // Helpers storage (robustos) // ----------------------------------------------------------------------------- static esp_err_t store_flush_best_effort(void) { esp_err_t e = storage_flush_sync(STORE_FLUSH_TO); if (e != ESP_OK) ESP_LOGW(TAG, "storage_flush_sync failed: %s", esp_err_to_name(e)); return e; } static esp_err_t store_set_u8_best_effort(const char *ns, const char *key, uint8_t v) { for (int attempt = 0; attempt < 3; ++attempt) { esp_err_t e = storage_set_u8_async(ns, key, v); if (e == ESP_OK) return ESP_OK; if (e == ESP_ERR_TIMEOUT) { (void)store_flush_best_effort(); vTaskDelay(pdMS_TO_TICKS(10)); continue; } return e; } return ESP_ERR_TIMEOUT; } static esp_err_t store_set_str_best_effort(const char *ns, const char *key, const char *s) { for (int attempt = 0; attempt < 3; ++attempt) { esp_err_t e = storage_set_str_async(ns, key, s); if (e == ESP_OK) return ESP_OK; if (e == ESP_ERR_TIMEOUT) { (void)store_flush_best_effort(); vTaskDelay(pdMS_TO_TICKS(10)); continue; } return e; } return ESP_ERR_TIMEOUT; } // Lê string de forma segura (lê para buffer grande e depois trunca para out) // Nota: isto ajuda se houver lixo antigo no NVS com strings maiores do que o esperado. static esp_err_t store_get_str_safe(const char *ns, const char *key, char *out, size_t out_sz) { if (!out || out_sz == 0) return ESP_ERR_INVALID_ARG; out[0] = '\0'; // buffer grande (sem heap): usa o máximo definido no storage_service char tmp[STORAGE_MAX_VALUE_BYTES + 1]; memset(tmp, 0, sizeof(tmp)); esp_err_t e = storage_get_str_sync(ns, key, tmp, sizeof(tmp), STORE_TO); if (e == ESP_ERR_NOT_FOUND) { out[0] = '\0'; return ESP_OK; } if (e != ESP_OK) { out[0] = '\0'; return e; } size_t n = strnlen(tmp, out_sz - 1); // no máximo out_sz-1 memcpy(out, tmp, n); out[n] = '\0'; return ESP_OK; } // ----------------------------------------------------------------------------- // Eventos // ----------------------------------------------------------------------------- static void event_handler(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_AP_STACONNECTED: { ESP_LOGI(TAG, "AP: STA connected"); wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *)event_data; ESP_LOGI(TAG, "WiFi AP " MACSTR " join, AID=%d", MAC2STR(event->mac), event->aid); xEventGroupClearBits(wifi_event_group, WIFI_AP_DISCONNECTED_BIT); xEventGroupSetBits(wifi_event_group, WIFI_AP_CONNECTED_BIT); break; } case WIFI_EVENT_AP_STADISCONNECTED: { ESP_LOGI(TAG, "AP: STA disconnected"); wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data; ESP_LOGI(TAG, "WiFi AP " MACSTR " leave, AID=%d", MAC2STR(event->mac), event->aid); xEventGroupClearBits(wifi_event_group, WIFI_AP_CONNECTED_BIT); xEventGroupSetBits(wifi_event_group, WIFI_AP_DISCONNECTED_BIT); break; } case WIFI_EVENT_STA_START: { ESP_LOGI(TAG, "STA start"); s_retry_count = 0; esp_wifi_connect(); break; } case WIFI_EVENT_STA_CONNECTED: { ESP_LOGI(TAG, "STA associated (L2 connected)"); esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_STA_CONNECTED, NULL, 0, portMAX_DELAY); break; } case WIFI_EVENT_STA_DISCONNECTED: { xEventGroupClearBits(wifi_event_group, WIFI_STA_CONNECTED_BIT); xEventGroupSetBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT); wifi_event_sta_disconnected_t *ev = (wifi_event_sta_disconnected_t *)event_data; ESP_LOGW(TAG, "STA disconnected, reason=%d", ev ? ev->reason : -1); esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_STA_DISCONNECTED, ev, ev ? sizeof(*ev) : 0, portMAX_DELAY); if (s_retry_count < s_retry_max) { esp_err_t err = esp_wifi_connect(); if (err == ESP_OK) { s_retry_count++; ESP_LOGI(TAG, "Retrying connection (%d/%d)", s_retry_count, s_retry_max); } else { ESP_LOGW(TAG, "esp_wifi_connect failed (%s)", esp_err_to_name(err)); } } else { ESP_LOGE(TAG, "Max retries reached"); } break; } default: break; } } else if (event_base == IP_EVENT) { if (event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; ESP_LOGI(TAG, "WiFi STA got ip: " IPSTR, IP2STR(&event->ip_info.ip)); xEventGroupClearBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT); xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT); s_retry_count = 0; esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_STA_GOT_IP, &event->ip_info, sizeof(event->ip_info), portMAX_DELAY); } else if (event_id == IP_EVENT_STA_LOST_IP) { ESP_LOGW(TAG, "STA lost IP"); esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_STA_LOST_IP, NULL, 0, portMAX_DELAY); } } } // ----------------------------------------------------------------------------- // Config STA/AP // ----------------------------------------------------------------------------- static void sta_set_config(void) { ESP_LOGI(TAG, "sta_set_config"); if (!wifi_get_enabled()) return; wifi_config_t wifi_config = {0}; wifi_config.sta.pmf_cfg.capable = true; wifi_config.sta.pmf_cfg.required = false; char ssid_buf[SSID_BUF_SZ] = {0}; char pass_buf[PASS_BUF_SZ] = {0}; wifi_get_ssid(ssid_buf); wifi_get_password(pass_buf); size_t ssid_len = strnlen(ssid_buf, SSID_MAX_LEN); memcpy(wifi_config.sta.ssid, ssid_buf, ssid_len); size_t pass_len = strnlen(pass_buf, 63); memcpy(wifi_config.sta.password, pass_buf, pass_len); if (pass_len <= 63) wifi_config.sta.password[pass_len] = '\0'; ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); } static void ap_set_config(void) { ESP_LOGI(TAG, "ap_set_config"); wifi_config_t wifi_ap_config = {0}; wifi_ap_config.ap.max_connection = 1; wifi_ap_config.ap.authmode = WIFI_AUTH_OPEN; uint8_t mac[6]; esp_wifi_get_mac(WIFI_IF_AP, mac); snprintf((char *)wifi_ap_config.ap.ssid, sizeof(wifi_ap_config.ap.ssid), AP_SSID, mac[3], mac[4], mac[5]); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_ap_config)); } // ----------------------------------------------------------------------------- // Arranque STA // ----------------------------------------------------------------------------- static void sta_try_start(void) { ESP_LOGI(TAG, "sta_try_start"); sta_set_config(); if (wifi_get_enabled()) { ESP_LOGI(TAG, "Starting STA"); esp_err_t e = esp_wifi_start(); if (e != ESP_OK && e != ESP_ERR_WIFI_CONN) { ESP_LOGW(TAG, "esp_wifi_start returned %s", esp_err_to_name(e)); } xEventGroupSetBits(wifi_event_group, WIFI_STA_MODE_BIT); } } // ----------------------------------------------------------------------------- // API pública // ----------------------------------------------------------------------------- void wifi_ini(void) { ESP_LOGI(TAG, "Wifi init"); // garante storage pronto (não assume NVS handle aberto aqui) esp_err_t se = storage_service_init(); if (se != ESP_OK) ESP_LOGW(TAG, "storage_service_init failed: %s", esp_err_to_name(se)); wifi_event_group = xEventGroupCreate(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ap_netif = esp_netif_create_default_wifi_ap(); sta_netif = esp_netif_create_default_wifi_sta(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); uint8_t mac[6]; esp_wifi_get_mac(WIFI_IF_STA, mac); char chargeid[16]; snprintf(chargeid, sizeof(chargeid), MDNS_SSID, 0); ESP_ERROR_CHECK(esp_netif_set_hostname(sta_netif, chargeid)); ESP_ERROR_CHECK(esp_netif_set_hostname(ap_netif, chargeid)); ESP_ERROR_CHECK(mdns_init()); ESP_ERROR_CHECK(mdns_hostname_set(chargeid)); ESP_ERROR_CHECK(mdns_instance_name_set("EVSE controller")); sta_try_start(); } esp_netif_t *wifi_get_sta_netif(void) { return sta_netif; } esp_netif_t *wifi_get_ap_netif(void) { return ap_netif; } esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password) { ESP_LOGI(TAG, "wifi_set_config(enabled=%d)", enabled); // Validação if (enabled) { if (ssid && (strlen(ssid) == 0 || strlen(ssid) > SSID_MAX_LEN)) { ESP_LOGE(TAG, "SSID out of range"); return ESP_ERR_INVALID_ARG; } if (!ssid) { char cur_ssid[SSID_BUF_SZ] = {0}; wifi_get_ssid(cur_ssid); if (strlen(cur_ssid) == 0) { ESP_LOGE(TAG, "Required SSID"); return ESP_ERR_INVALID_ARG; } } if (password) { size_t lp = strlen(password); if (lp != 0 && (lp < 8 || lp > 63)) { ESP_LOGE(TAG, "Password length must be 8..63"); return ESP_ERR_INVALID_ARG; } } } // Persiste via storage_service esp_err_t err = store_set_u8_best_effort(NVS_NAMESPACE, NVS_ENABLED, enabled ? 1 : 0); if (err != ESP_OK) return err; if (ssid) { err = store_set_str_best_effort(NVS_NAMESPACE, NVS_SSID, ssid); if (err != ESP_OK) return err; } if (password) { err = store_set_str_best_effort(NVS_NAMESPACE, NVS_PASSWORD, password); if (err != ESP_OK) return err; } // Força persistência (para sobreviver a reboot imediato) (void)store_flush_best_effort(); // Reinicia modo ESP_LOGI(TAG, "Stopping AP/STA"); xEventGroupClearBits(wifi_event_group, WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT); esp_err_t e = esp_wifi_stop(); if (e != ESP_OK && e != ESP_ERR_WIFI_NOT_INIT && e != ESP_ERR_WIFI_STOP_STATE) { ESP_LOGW(TAG, "esp_wifi_stop returned %s", esp_err_to_name(e)); } sta_try_start(); return ESP_OK; } uint16_t wifi_scan(wifi_scan_ap_t *scan_aps) { ESP_LOGI(TAG, "wifi_scan"); if (!scan_aps) return 0; uint16_t number = WIFI_SCAN_SCAN_LIST_SIZE; wifi_ap_record_t ap_info[WIFI_SCAN_SCAN_LIST_SIZE]; uint16_t ap_count = 0; memset(ap_info, 0, sizeof(ap_info)); esp_err_t err = esp_wifi_scan_start(NULL, true); if (err != ESP_OK) { ESP_LOGW(TAG, "esp_wifi_scan_start failed (%s)", esp_err_to_name(err)); return 0; } err = esp_wifi_scan_get_ap_records(&number, ap_info); if (err != ESP_OK) { ESP_LOGW(TAG, "esp_wifi_scan_get_ap_records failed (%s)", esp_err_to_name(err)); return 0; } err = esp_wifi_scan_get_ap_num(&ap_count); if (err != ESP_OK) { ESP_LOGW(TAG, "esp_wifi_scan_get_ap_num failed (%s)", esp_err_to_name(err)); return 0; } ESP_LOGI(TAG, "wifi_scan --- %d", ap_count); for (int i = 0; (i < WIFI_SCAN_SCAN_LIST_SIZE) && (i < ap_count); i++) { snprintf(scan_aps[i].ssid, sizeof(scan_aps[i].ssid), "%s", (const char *)ap_info[i].ssid); scan_aps[i].rssi = ap_info[i].rssi; scan_aps[i].auth = ap_info[i].authmode != WIFI_AUTH_OPEN; } return ap_count; } bool wifi_get_enabled(void) { uint8_t value = 0; esp_err_t e = storage_get_u8_sync(NVS_NAMESPACE, NVS_ENABLED, &value, STORE_TO); if (e == ESP_ERR_NOT_FOUND) return false; if (e != ESP_OK) { ESP_LOGW(TAG, "storage_get_u8_sync(enabled) failed (%s), assuming disabled", esp_err_to_name(e)); return false; } return (value != 0); } void wifi_get_ssid(char *value) { if (!value) return; (void)store_get_str_safe(NVS_NAMESPACE, NVS_SSID, value, SSID_BUF_SZ); } void wifi_get_password(char *value) { if (!value) return; (void)store_get_str_safe(NVS_NAMESPACE, NVS_PASSWORD, value, PASS_BUF_SZ); } void wifi_ap_start(void) { ESP_LOGI(TAG, "Starting AP"); esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_AP_STARTED, NULL, 0, portMAX_DELAY); xEventGroupClearBits(wifi_event_group, WIFI_STA_MODE_BIT); esp_wifi_stop(); ap_set_config(); esp_err_t e = esp_wifi_start(); if (e != ESP_OK && e != ESP_ERR_WIFI_CONN) { ESP_LOGW(TAG, "esp_wifi_start (AP) returned %s", esp_err_to_name(e)); } xEventGroupSetBits(wifi_event_group, WIFI_AP_MODE_BIT); } void wifi_ap_stop(void) { ESP_LOGI(TAG, "Stopping AP"); esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_AP_STOP, NULL, 0, portMAX_DELAY); xEventGroupClearBits(wifi_event_group, WIFI_AP_MODE_BIT); esp_wifi_stop(); sta_try_start(); } bool wifi_is_ap(void) { if (!wifi_event_group) return false; EventBits_t bits = xEventGroupGetBits(wifi_event_group); return (bits & WIFI_AP_MODE_BIT) != 0; }