Conectividade WiFi e Networking 📶

Configuração e Gerenciamento WiFi

O WiFi representa funcionalidade essencial para maioria das aplicações IoT modernas, permitindo conectividade sem fio com internet e redes locais. O ESP32 integra pilha WiFi completa com suporte para 802.11 b/g/n, oferecendo performance adequada para streaming de dados, atualizações OTA e comunicação em tempo real com servidores cloud.

O ESP-IDF oferece API WiFi abrangente que abstrai complexidade do hardware RF e protocolos de baixo nível, expondo interface de alto nível para conectar redes, gerenciar credenciais e monitorar status de conexão. O driver WiFi opera como componente do sistema gerenciado por event loop, disparando eventos quando mudanças importantes ocorrem como conexão estabelecida ou perda de link.

A configuração WiFi envolve vários estágios incluindo inicialização do hardware, configuração de modo operacional, especificação de credenciais e estabelecimento de conexão. Cada estágio requer chamadas de API específicas em ordem correta, e o sistema de eventos permite que sua aplicação reaja apropriadamente a mudanças no estado da conexão.

⚠️ Modos de Operação WiFi

O ESP32 suporta três modos principais de operação WiFi que determinam como o dispositivo interage com redes wireless. Compreender estes modos e quando usar cada um é fundamental para arquitetar aplicações IoT efetivas.

O modo Station conecta o ESP32 a access point existente como qualquer dispositivo WiFi convencional. Este é o modo mais comum para aplicações IoT onde dispositivo precisa conectar à rede doméstica ou corporativa para acessar internet. No modo Station, o ESP32 recebe endereço IP do roteador via DHCP e pode comunicar com outros dispositivos na rede.

O modo Access Point transforma o ESP32 em ponto de acesso ao qual outros dispositivos podem conectar. Este modo é útil para configuração inicial de dispositivos IoT onde usuário conecta smartphone diretamente ao ESP32 para fornecer credenciais WiFi da rede principal. Também é valioso para criar redes ad-hoc entre dispositivos ESP32 sem infraestrutura externa.

O modo Station mais Access Point permite que ESP32 opere simultaneamente nos dois modos, conectando a rede existente enquanto oferece seu próprio access point. Este modo híbrido facilita configuração remota e update de dispositivos já implantados sem perder conectividade com rede principal.

#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 "lwip/err.h"
#include "lwip/sys.h"

static const char *TAG = "WIFI_EXAMPLE";

// Credenciais WiFi - em produção, armazene de forma segura
#define WIFI_SSID      "SuaRedeWiFi"
#define WIFI_PASS      "SuaSenha"
#define WIFI_MAX_RETRY 5

// Event group para sinalizar quando conectado
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1

static int s_retry_num = 0;

/**
 * Handler de eventos WiFi
 * 
 * Esta função é chamada automaticamente pelo sistema de eventos
 * quando ocorrem mudanças no estado do WiFi
 */
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) {
        // WiFi iniciado, tentar conectar
        ESP_LOGI(TAG, "WiFi iniciado, conectando...");
        esp_wifi_connect();
        
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        // Desconectado, tentar reconectar se não excedeu limite
        if (s_retry_num < WIFI_MAX_RETRY) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "Tentando reconectar... (%d/%d)", 
                     s_retry_num, WIFI_MAX_RETRY);
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
            ESP_LOGE(TAG, "Falha ao conectar após %d tentativas", WIFI_MAX_RETRY);
        }
        
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        // Obteve endereço IP, conexão estabelecida
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "Conectado! IP obtido: " IPSTR, 
                 IP2STR(&event->ip_info.ip));
        
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

/**
 * Inicializar WiFi em modo Station
 * 
 * Configura todos os componentes necessários e inicia conexão
 */
esp_err_t wifi_init_sta(void)
{
    ESP_LOGI(TAG, "Inicializando WiFi em modo Station");
    
    // Criar event group para sincronização
    s_wifi_event_group = xEventGroupCreate();
    
    // Inicializar stack TCP/IP
    ESP_ERROR_CHECK(esp_netif_init());
    
    // Criar event loop padrão
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    
    // Criar interface de rede padrão para station
    esp_netif_create_default_wifi_sta();
    
    // Inicializar WiFi com configuração padrão
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    
    // Registrar handlers de eventos
    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,
                                                         &wifi_event_handler,
                                                         NULL,
                                                         &instance_any_id));
    
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                         IP_EVENT_STA_GOT_IP,
                                                         &wifi_event_handler,
                                                         NULL,
                                                         &instance_got_ip));
    
    // Configurar parâmetros de conexão
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = WIFI_SSID,
            .password = WIFI_PASS,
            // Configurações de segurança e otimização
            .threshold.authmode = WIFI_AUTH_WPA2_PSK,
            .pmf_cfg = {
                .capable = true,
                .required = false
            },
        },
    };
    
    // Configurar modo e aplicar configuração
    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_LOGI(TAG, "WiFi configurado, aguardando conexão...");
    
    // Aguardar até conectar ou falhar
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
                                            WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
                                            pdFALSE,
                                            pdFALSE,
                                            portMAX_DELAY);
    
    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "Conectado ao AP SSID:%s", WIFI_SSID);
        return ESP_OK;
    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGE(TAG, "Falha ao conectar ao AP SSID:%s", WIFI_SSID);
        return ESP_FAIL;
    } else {
        ESP_LOGE(TAG, "Evento inesperado");
        return ESP_FAIL;
    }
}

/**
 * Obter informações sobre conexão WiFi atual
 * 
 * Útil para monitoramento e diagnóstico
 */
void wifi_print_connection_info(void)
{
    wifi_ap_record_t ap_info;
    esp_err_t ret = esp_wifi_sta_get_ap_info(&ap_info);
    
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "=== Informações da Conexão ===");
        ESP_LOGI(TAG, "SSID: %s", ap_info.ssid);
        ESP_LOGI(TAG, "Canal: %d", ap_info.primary);
        ESP_LOGI(TAG, "RSSI: %d dBm", ap_info.rssi);
        ESP_LOGI(TAG, "Autenticação: %d", ap_info.authmode);
        ESP_LOGI(TAG, "MAC do AP: %02x:%02x:%02x:%02x:%02x:%02x",
                 ap_info.bssid[0], ap_info.bssid[1], ap_info.bssid[2],
                 ap_info.bssid[3], ap_info.bssid[4], ap_info.bssid[5]);
        
        // Interpretar força do sinal
        if (ap_info.rssi > -50) {
            ESP_LOGI(TAG, "Qualidade do sinal: Excelente");
        } else if (ap_info.rssi > -60) {
            ESP_LOGI(TAG, "Qualidade do sinal: Boa");
        } else if (ap_info.rssi > -70) {
            ESP_LOGI(TAG, "Qualidade do sinal: Regular");
        } else {
            ESP_LOGI(TAG, "Qualidade do sinal: Fraca");
        }
    } else {
        ESP_LOGW(TAG, "Não foi possível obter informações do AP");
    }
    
    // Obter endereço IP atual
    esp_netif_ip_info_t ip_info;
    esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
    
    if (netif && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK) {
        ESP_LOGI(TAG, "Endereço IP: " IPSTR, IP2STR(&ip_info.ip));
        ESP_LOGI(TAG, "Máscara de rede: " IPSTR, IP2STR(&ip_info.netmask));
        ESP_LOGI(TAG, "Gateway: " IPSTR, IP2STR(&ip_info.gw));
    }
}

/**
 * Escanear redes WiFi disponíveis
 * 
 * Útil para descobrir redes e avaliar qualidade de sinal
 */
void wifi_scan_networks(void)
{
    ESP_LOGI(TAG, "Iniciando scan de redes WiFi...");
    
    // Configurar parâmetros de scan
    wifi_scan_config_t scan_config = {
        .ssid = NULL,
        .bssid = NULL,
        .channel = 0,
        .show_hidden = true,
        .scan_type = WIFI_SCAN_TYPE_ACTIVE,
        .scan_time.active.min = 100,
        .scan_time.active.max = 300,
    };
    
    esp_err_t ret = esp_wifi_scan_start(&scan_config, true);
    
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Falha ao iniciar scan: %s", esp_err_to_name(ret));
        return;
    }
    
    // Obter número de redes encontradas
    uint16_t num_aps = 0;
    esp_wifi_scan_get_ap_num(&num_aps);
    
    if (num_aps == 0) {
        ESP_LOGW(TAG, "Nenhuma rede encontrada");
        return;
    }
    
    ESP_LOGI(TAG, "%d rede(s) encontrada(s)", num_aps);
    
    // Alocar buffer para resultados
    wifi_ap_record_t *ap_records = malloc(sizeof(wifi_ap_record_t) * num_aps);
    
    if (ap_records == NULL) {
        ESP_LOGE(TAG, "Falha ao alocar memória para scan");
        return;
    }
    
    // Obter resultados do scan
    esp_wifi_scan_get_ap_records(&num_aps, ap_records);
    
    // Exibir informações de cada rede
    ESP_LOGI(TAG, "=== Redes Disponíveis ===");
    
    for (int i = 0; i < num_aps; i++) {
        ESP_LOGI(TAG, "%2d: SSID: %-32s Canal: %2d RSSI: %3d dBm Auth: %d",
                 i + 1,
                 ap_records[i].ssid,
                 ap_records[i].primary,
                 ap_records[i].rssi,
                 ap_records[i].authmode);
    }
    
    free(ap_records);
}

/**
 * Configurar WiFi Power Save
 * 
 * Economizar energia é essencial para dispositivos IoT alimentados por bateria.
 * O ESP32 oferece vários modos de economia de energia WiFi.
 */
esp_err_t wifi_configure_power_save(wifi_ps_type_t ps_mode)
{
    esp_err_t ret = esp_wifi_set_ps(ps_mode);
    
    if (ret == ESP_OK) {
        const char *mode_str[] = {"NONE", "MIN_MODEM", "MAX_MODEM"};
        ESP_LOGI(TAG, "Power save configurado: %s", 
                 mode_str[ps_mode < 3 ? ps_mode : 0]);
    } else {
        ESP_LOGE(TAG, "Falha ao configurar power save: %s", 
                 esp_err_to_name(ret));
    }
    
    return ret;
}

/**
 * Monitorar qualidade da conexão WiFi
 * 
 * Task que monitora continuamente força do sinal e reconecta se necessário
 */
void wifi_monitoring_task(void *pvParameters)
{
    ESP_LOGI(TAG, "Task de monitoramento WiFi iniciada");
    
    const int RSSI_THRESHOLD = -75;  // Reconectar se sinal ficar muito fraco
    int weak_signal_count = 0;
    
    while (1) {
        wifi_ap_record_t ap_info;
        esp_err_t ret = esp_wifi_sta_get_ap_info(&ap_info);
        
        if (ret == ESP_OK) {
            ESP_LOGD(TAG, "RSSI atual: %d dBm", ap_info.rssi);
            
            // Verificar qualidade do sinal
            if (ap_info.rssi < RSSI_THRESHOLD) {
                weak_signal_count++;
                ESP_LOGW(TAG, "Sinal fraco detectado (%d/3): %d dBm",
                         weak_signal_count, ap_info.rssi);
                
                // Se sinal fraco persistir, tentar reconectar
                if (weak_signal_count >= 3) {
                    ESP_LOGW(TAG, "Sinal persistentemente fraco, reconectando...");
                    esp_wifi_disconnect();
                    vTaskDelay(pdMS_TO_TICKS(1000));
                    esp_wifi_connect();
                    weak_signal_count = 0;
                }
            } else {
                weak_signal_count = 0;
            }
            
        } else {
            ESP_LOGW(TAG, "WiFi desconectado, aguardando reconexão...");
            weak_signal_count = 0;
        }
        
        vTaskDelay(pdMS_TO_TICKS(10000));  // Verificar a cada 10 segundos
    }
}

/**
 * Configurar IP estático ao invés de DHCP
 * 
 * Útil para servidores ou dispositivos que precisam IP fixo
 */
esp_err_t wifi_set_static_ip(const char *ip_addr, const char *gateway, 
                               const char *netmask)
{
    ESP_LOGI(TAG, "Configurando IP estático: %s", ip_addr);
    
    esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
    
    if (netif == NULL) {
        ESP_LOGE(TAG, "Interface de rede não encontrada");
        return ESP_FAIL;
    }
    
    // Parar DHCP client
    esp_netif_dhcpc_stop(netif);
    
    // Configurar IP estático
    esp_netif_ip_info_t ip_info;
    
    ip_info.ip.addr = ipaddr_addr(ip_addr);
    ip_info.gw.addr = ipaddr_addr(gateway);
    ip_info.netmask.addr = ipaddr_addr(netmask);
    
    esp_err_t ret = esp_netif_set_ip_info(netif, &ip_info);
    
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "IP estático configurado com sucesso");
    } else {
        ESP_LOGE(TAG, "Falha ao configurar IP estático: %s",
                 esp_err_to_name(ret));
    }
    
    return ret;
}

void app_main(void)
{
    ESP_LOGI(TAG, "Demonstrando conectividade WiFi");
    
    // Inicializar NVS (necessário para WiFi)
    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);
    
    // Fazer scan de redes disponíveis (opcional)
    // Necessário inicializar WiFi em modo NULL primeiro para scan
    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_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_start());
    
    wifi_scan_networks();
    
    // Agora conectar à rede
    ret = wifi_init_sta();
    
    if (ret == ESP_OK) {
        // Exibir informações da conexão
        wifi_print_connection_info();
        
        // Configurar power save para economizar energia
        wifi_configure_power_save(WIFI_PS_MIN_MODEM);
        
        // Criar task de monitoramento
        xTaskCreate(wifi_monitoring_task, "WiFi_Monitor",
                    3072, NULL, 3, NULL);
        
        ESP_LOGI(TAG, "Sistema WiFi pronto para uso");
    } else {
        ESP_LOGE(TAG, "Falha ao conectar WiFi");
    }
}