ESP32/components/wifi_cfg/wifi_cfg.c

383 lines
14 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// wifi_cfg_dualmode.c — ESP-IDF v5.3.x
// Listens for CFG/END WiFi config on BOTH UART(console/COM) and USBSerial/JTAG concurrently.
// - UART path uses stdio (fgets on stdin) — works with /dev/ttyUSB*
// - USB path uses driver API (usb_serial_jtag_read_bytes / write_bytes) — works with /dev/ttyACM*
// - Tolerates ESP_ERR_INVALID_STATE on repeated inits
// - Supports DHCP or static IP, persists to NVS, applies immediately
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdatomic.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_netif.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_check.h"
#include "esp_event.h"
#include "driver/usb_serial_jtag.h" // direct read/write API (no VFS remap)
#include "wifi_cfg.h"
// wifi_cfg.c
static const char *TAG = "wifi_cfg";
static esp_netif_t *sta_netif = NULL;
static bool cfg_dhcp = true;
static void trim(char *s){
int n = strlen(s);
while(n>0 && (s[n-1]=='\r' || s[n-1]=='\n' || isspace((unsigned char)s[n-1]))) s[--n]=0;
while(*s && isspace((unsigned char)*s)){
memmove(s, s+1, strlen(s));
}
}
static esp_err_t nvs_set_str2(nvs_handle_t h, const char *key, const char *val){
return val ? nvs_set_str(h, key, val) : nvs_erase_key(h, key);
}
static void save_cfg(const char* ssid, const char* pass, const char* ip, const char* mask, const char* gw, bool dhcp, const char* band, const char* bw){
nvs_handle_t h;
if (nvs_open("netcfg", NVS_READWRITE, &h) != ESP_OK) return;
if (ssid) nvs_set_str2(h, "ssid", ssid);
if (pass) nvs_set_str2(h, "pass", pass);
if (ip) nvs_set_str2(h, "ip", ip);
if (mask) nvs_set_str2(h, "mask", mask);
if (gw) nvs_set_str2(h, "gw", gw);
if (band) nvs_set_str2(h, "band", band);
if (bw) nvs_set_str2(h, "bw", bw);
nvs_set_u8(h, "dhcp", dhcp ? 1 : 0);
nvs_commit(h);
nvs_close(h);
cfg_dhcp = dhcp;
ESP_LOGI(TAG, "Config saved to NVS: SSID=%s Band=%s BW=%s", ssid?ssid:"", band?band:"", bw?bw:"");
}
static bool load_cfg(char* ssid, size_t ssz, char* pass, size_t psz,
char* ip, size_t isz, char* mask, size_t msz, char* gw, size_t gsz,
char* band, size_t bsz, char* bw, size_t bwsz, bool* dhcp){
nvs_handle_t h;
if (nvs_open("netcfg", NVS_READONLY, &h) != ESP_OK) return false;
size_t len;
esp_err_t e;
if ((e = nvs_get_str(h, "ssid", NULL, &len)) != ESP_OK){ nvs_close(h); return false; }
if (len >= ssz){ nvs_close(h); return false; }
nvs_get_str(h, "ssid", ssid, &len);
len = psz; e = nvs_get_str(h, "pass", pass, &len); if (e!=ESP_OK) pass[0]=0;
len = isz; e = nvs_get_str(h, "ip", ip, &len); if (e!=ESP_OK) ip[0]=0;
len = msz; e = nvs_get_str(h, "mask", mask, &len); if (e!=ESP_OK) mask[0]=0;
len = gsz; e = nvs_get_str(h, "gw", gw, &len); if (e!=ESP_OK) gw[0]=0;
len = bsz; e = nvs_get_str(h, "band", band, &len); if (e!=ESP_OK) strcpy(band, "2.4G");
len = bwsz; e = nvs_get_str(h, "bw", bw, &len); if (e!=ESP_OK) strcpy(bw, "HT20");
uint8_t d=1; nvs_get_u8(h, "dhcp", &d); *dhcp = (d!=0);
nvs_close(h);
return true;
}
void wifi_cfg_force_dhcp(bool enable){ cfg_dhcp = enable; }
/* --- One-time net stack bring-up (thread-safe) --- */
static atomic_bool s_net_stack_ready = false;
static esp_err_t ensure_net_stack_once(void)
{
bool expected = false;
if (atomic_compare_exchange_strong(&s_net_stack_ready, &expected, true)) {
ESP_RETURN_ON_ERROR(esp_netif_init(), TAG, "esp_netif_init");
esp_err_t err = esp_event_loop_create_default();
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_RETURN_ON_ERROR(err, TAG, "event loop create");
}
}
return ESP_OK;
}
/* --- One-time Wi-Fi driver init (thread-safe, idempotent) --- */
static atomic_bool s_wifi_inited = false;
esp_err_t wifi_ensure_inited(void)
{
bool expected = false;
if (!atomic_compare_exchange_strong(&s_wifi_inited, &expected, true)) {
// someone else already initialized (or is initializing and finished)
return ESP_OK;
}
ESP_RETURN_ON_ERROR(ensure_net_stack_once(), TAG, "net stack");
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_err_t err = esp_wifi_init(&cfg);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
// roll back the flag so a later attempt can retry
atomic_store(&s_wifi_inited, false);
ESP_RETURN_ON_ERROR(err, TAG, "esp_wifi_init");
}
// Optional: set default interface and mode now or let caller do it
// ESP_RETURN_ON_ERROR(esp_wifi_set_mode(WIFI_MODE_STA), TAG, "set mode");
return ESP_OK;
}
static void apply_ip_static(const char* ip, const char* mask, const char* gw){
if (!sta_netif) return;
esp_netif_ip_info_t info = {0};
esp_netif_dhcpc_stop(sta_netif);
info.ip.addr = esp_ip4addr_aton(ip);
info.netmask.addr = esp_ip4addr_aton(mask);
info.gw.addr = esp_ip4addr_aton(gw);
ESP_ERROR_CHECK( esp_netif_set_ip_info(sta_netif, &info) );
}
bool wifi_cfg_apply_from_nvs(void) {
char ssid[64]={0}, pass[64]={0}, ip[32]={0}, mask[32]={0}, gw[32]={0};
char band[16]={0}, bw[16]={0};
bool dhcp = true;
if (!load_cfg(ssid,sizeof(ssid), pass,sizeof(pass), ip,sizeof(ip), mask,sizeof(mask), gw,sizeof(gw),
band,sizeof(band), bw,sizeof(bw), &dhcp)){
ESP_LOGW(TAG, "No WiFi config in NVS");
return false;
}
ESP_LOGI(TAG, "Applying WiFi config: SSID=%s DHCP=%d IP=%s Band=%s BW=%s", ssid, dhcp, ip, band, bw);
static bool inited = false;
if (!inited){
// NVS (with recovery)
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
nvs_flash_erase();
err = nvs_flash_init();
}
if (err != ESP_OK) { ESP_ERROR_CHECK(err); }
// Netif + default event loop (tolerate already-initialized state)
do{ esp_err_t __e = esp_netif_init(); if(__e!=ESP_OK && __e!=ESP_ERR_INVALID_STATE){ ESP_ERROR_CHECK(__e);} }while(0);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { ESP_ERROR_CHECK(err); }
do{ esp_err_t __e = esp_event_loop_create_default(); if(__e!=ESP_OK && __e!=ESP_ERR_INVALID_STATE){ ESP_ERROR_CHECK(__e);} }while(0);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { ESP_ERROR_CHECK(err); }
if (sta_netif == NULL) {
sta_netif = esp_netif_create_default_wifi_sta();
}
ESP_ERROR_CHECK(err=wifi_ensure_inited());
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { ESP_ERROR_CHECK(err); }
inited = true;
}
wifi_config_t wcfg = (wifi_config_t){0};
strncpy((char*)wcfg.sta.ssid, ssid, sizeof(wcfg.sta.ssid)-1);
strncpy((char*)wcfg.sta.password, pass, sizeof(wcfg.sta.password)-1);
wcfg.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
wcfg.sta.sae_pwe_h2e = WPA3_SAE_PWE_BOTH;
// Set scan method to search all channels
wcfg.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
// Log and configure band preference
if (strcmp(band, "5G") == 0) {
ESP_LOGI(TAG, "Configuring for 5GHz operation");
// For 5GHz preference, scan both but prefer 5GHz channels
wcfg.sta.channel = 0; // Scan all channels (both 2.4GHz and 5GHz)
} else {
ESP_LOGI(TAG, "Configuring for 2.4GHz operation (default)");
wcfg.sta.channel = 0; // Scan all channels
}
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
// Enable WiFi 6 (802.11ax) and all protocols for best compatibility
wifi_protocols_t protocols = {
// 2.4 GHz: b/g/n/ax
.ghz_2g = WIFI_PROTOCOL_11B |
WIFI_PROTOCOL_11G |
WIFI_PROTOCOL_11N |
WIFI_PROTOCOL_11AX,
// 5 GHz: a/n/ac/ax
.ghz_5g = WIFI_PROTOCOL_11A |
WIFI_PROTOCOL_11N |
WIFI_PROTOCOL_11AC |
WIFI_PROTOCOL_11AX,
// .ghz_6g will be zero-initialized (not used on C5)
};
ESP_ERROR_CHECK( esp_wifi_set_protocols(WIFI_IF_STA, &protocols) );
ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wcfg) );
if (!dhcp && ip[0] && mask[0] && gw[0]){
apply_ip_static(ip, mask, gw);
} else {
if (sta_netif) esp_netif_dhcpc_start(sta_netif);
}
esp_err_t err2 = esp_wifi_start();
if (err2 != ESP_OK && err2 != ESP_ERR_INVALID_STATE) { ESP_ERROR_CHECK(err2); }
// Set bandwidth AFTER WiFi is started but BEFORE connect
// For ESP32-C5 dual-band, use esp_wifi_set_bandwidths() with struct
wifi_bandwidths_t bandwidths = {
.ghz_2g = WIFI_BW_HT20,
.ghz_5g = WIFI_BW_HT20
};
if (strcmp(bw, "HT40") == 0) {
bandwidths.ghz_2g = WIFI_BW_HT40;
bandwidths.ghz_5g = WIFI_BW_HT40;
ESP_LOGI(TAG, "Setting bandwidth to HT40 (40MHz) for both bands");
} else {
ESP_LOGI(TAG, "Setting bandwidth to HT20 (20MHz) for both bands");
}
// Use dual-band API with struct pointer
err2 = esp_wifi_set_bandwidths(WIFI_IF_STA, &bandwidths);
if (err2 != ESP_OK) {
ESP_LOGW(TAG, "Failed to set bandwidths: %s", esp_err_to_name(err2));
}
err2 = esp_wifi_connect();
if (err2 != ESP_OK && err2 != ESP_ERR_WIFI_NOT_INIT && err2 != ESP_ERR_INVALID_STATE) { ESP_ERROR_CHECK(err2); }
return true;
}
// -------------------- Dual input paths --------------------
typedef struct {
// emit() should write a short line back to the same channel (optional)
void (*emit)(const char *s, void *ctx);
void *ctx;
// fetch_line() should fill buf with a single line (without trailing CR/LF) and return true if a line was read
bool (*fetch_line)(char *buf, size_t sz, void *ctx);
} cfg_io_t;
static void on_cfg_line(const char *line, char *ssid, char *pass, char *ip, char *mask, char *gw, char *band, char *bw, bool *dhcp){
if (strncmp(line, "SSID:",5)==0){ strncpy(ssid, line+5, 63); ssid[63]=0; return; }
if (strncmp(line, "PASS:",5)==0){ strncpy(pass, line+5, 63); pass[63]=0; return; }
if (strncmp(line, "IP:",3)==0){ strncpy(ip, line+3, 31); ip[31]=0; return; }
if (strncmp(line, "MASK:",5)==0){ strncpy(mask, line+5, 31); mask[31]=0; return; }
if (strncmp(line, "GW:",3)==0){ strncpy(gw, line+3, 31); gw[31]=0; return; }
if (strncmp(line, "BAND:",5)==0){ strncpy(band, line+5, 15); band[15]=0; return; }
if (strncmp(line, "BW:",3)==0){ strncpy(bw, line+3, 15); bw[15]=0; return; }
if (strncmp(line, "DHCP:",5)==0){ *dhcp = atoi(line+5) ? true:false; return; }
}
static void cfg_worker(const cfg_io_t *io){
char line[160];
char ssid[64]={0}, pass[64]={0}, ip[32]={0}, mask[32]={0}, gw[32]={0};
char band[16]={0}, bw[16]={0};
bool dhcp = true;
bool in_cfg = false;
for(;;){
if (!io->fetch_line(line, sizeof(line), io->ctx)){
vTaskDelay(pdMS_TO_TICKS(20));
continue;
}
trim(line);
if (!in_cfg){
if (strcmp(line, "CFG")==0){
in_cfg = true;
ssid[0]=pass[0]=ip[0]=mask[0]=gw[0]=0;
band[0]=bw[0]=0;
dhcp = true;
}
continue;
}
if (strcmp(line, "END")==0){
// Set defaults if not specified
if (!band[0]) strcpy(band, "2.4G");
if (!bw[0]) strcpy(bw, "HT20");
save_cfg(ssid, pass, ip, mask, gw, dhcp, band, bw);
if (io->emit) io->emit("OK\n", io->ctx);
wifi_cfg_apply_from_nvs();
in_cfg = false;
continue;
}
on_cfg_line(line, ssid, pass, ip, mask, gw, band, bw, &dhcp);
}
}
// ---- UART(stdin) path ----
static bool uart_fetch_line(char *buf, size_t sz, void *ctx){
(void)ctx;
if (!fgets(buf, sz, stdin)) return false;
return true;
}
static void uart_emit(const char *s, void *ctx){
(void)ctx;
fputs(s, stdout);
fflush(stdout);
}
static void cfg_listener_uart_task(void *arg){
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
cfg_io_t io = {.emit = uart_emit, .ctx = NULL, .fetch_line = uart_fetch_line};
cfg_worker(&io);
}
// ---- USB-Serial/JTAG path ----
typedef struct { int dummy; } usb_ctx_t;
static bool usb_fetch_line(char *buf, size_t sz, void *ctx){
(void)ctx;
static char acc[256];
static size_t acc_len = 0;
uint8_t tmp[64];
int n = usb_serial_jtag_read_bytes(tmp, sizeof(tmp), pdMS_TO_TICKS(10));
if (n <= 0){
return false;
}
for (int i=0; i<n; ++i){
char c = (char)tmp[i];
if (c == '\n' || c == '\r'){
acc[acc_len] = 0;
strncpy(buf, acc, sz-1);
buf[sz-1]=0;
acc_len = 0;
return true;
}else if (acc_len < sizeof(acc)-1){
acc[acc_len++] = c;
}
}
return false;
}
static void usb_emit(const char *s, void *ctx){
(void)ctx;
usb_serial_jtag_write_bytes((const uint8_t*)s, strlen(s), pdMS_TO_TICKS(50));
}
static void cfg_listener_usb_task(void *arg){
// Install USB-Serial/JTAG driver (no VFS remap: stdio stays on UART)
usb_serial_jtag_driver_config_t d = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT();
ESP_ERROR_CHECK(usb_serial_jtag_driver_install(&d));
usb_ctx_t uctx = {0};
cfg_io_t io = {.emit = usb_emit, .ctx = &uctx, .fetch_line = usb_fetch_line};
cfg_worker(&io);
}
void wifi_cfg_init(void){
// Make sure NVS exists
nvs_flash_init();
// Spawn both listeners
xTaskCreatePinnedToCore(cfg_listener_uart_task, "cfg_uart",
6144, NULL, 5, NULL, tskNO_AFFINITY);
xTaskCreatePinnedToCore(cfg_listener_usb_task, "cfg_usb",
6144, NULL, 5, NULL, tskNO_AFFINITY);
}