Add flash_and_config.py and WiFi reconfiguration system

This commit is contained in:
Bob 2025-11-15 12:12:33 -08:00
parent f2abcf450b
commit 90e212fed6
4 changed files with 377 additions and 5 deletions

198
flash_and_config.py Executable file
View File

@ -0,0 +1,198 @@
#!/usr/bin/env python3
"""
Flash uniform firmware to all ESP32 devices, then reconfigure via serial
This is much faster than building unique firmwares for each device
"""
import os
import sys
import subprocess
import argparse
import glob
from pathlib import Path
import time
def detect_devices():
"""Detect all ESP32 devices"""
devices = sorted(glob.glob('/dev/ttyUSB*'))
return devices
def build_firmware(project_dir):
"""Build the firmware once"""
print("=" * 60)
print("Building firmware (one time)...")
print("=" * 60)
result = subprocess.run(
['idf.py', 'build'],
cwd=project_dir,
capture_output=False
)
if result.returncode != 0:
print("✗ Build failed!")
return False
print("✓ Build complete")
return True
def flash_device(port, project_dir):
"""Flash a single device"""
try:
result = subprocess.run(
['idf.py', '-p', port, 'flash'],
cwd=project_dir,
capture_output=True,
text=True,
timeout=120
)
return result.returncode == 0
except Exception as e:
return False
def flash_all_devices(devices, project_dir):
"""Flash all devices with the same firmware"""
print(f"\nFlashing {len(devices)} devices...")
success_count = 0
for idx, dev in enumerate(devices, 1):
print(f"[{idx:2d}/{len(devices)}] Flashing {dev}...", end='', flush=True)
if flash_device(dev, project_dir):
print("")
success_count += 1
else:
print("")
print(f"\nFlashed: {success_count}/{len(devices)}")
return success_count
def reconfigure_devices(ssid, password, start_ip, gateway="192.168.1.1"):
"""Reconfigure devices using the reconfig script"""
script_path = os.path.join(os.path.dirname(__file__), 'reconfig_simple.py')
if not os.path.exists(script_path):
print(f"Error: {script_path} not found!")
return False
print("\n" + "=" * 60)
print("Reconfiguring WiFi settings via serial...")
print("=" * 60)
cmd = [
'python3', script_path,
'-s', ssid,
'-p', password,
'--start-ip', start_ip,
'-g', gateway
]
result = subprocess.run(cmd)
return result.returncode == 0
def main():
parser = argparse.ArgumentParser(
description='Flash and configure all ESP32 devices',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
This script:
1. Builds firmware ONCE
2. Flashes the SAME firmware to all devices (fast!)
3. Reconfigures each device via serial with unique IP
Much faster than building 32 different firmwares!
Examples:
# Basic usage
%(prog)s --ssid MyWiFi --password mypass
# Custom IP range
%(prog)s --ssid MyWiFi --password mypass --start-ip 192.168.1.100
# Build only (no flash)
%(prog)s --build-only
# Reconfigure only (no flash)
%(prog)s --reconfig-only --ssid MyWiFi --password mypass
"""
)
parser.add_argument('--ssid', help='WiFi SSID')
parser.add_argument('--password', help='WiFi password')
parser.add_argument('--start-ip', default='192.168.1.50',
help='Starting IP address (default: 192.168.1.50)')
parser.add_argument('--gateway', default='192.168.1.1',
help='Gateway IP (default: 192.168.1.1)')
parser.add_argument('--project-dir', default=None,
help='ESP32 project directory (default: current dir)')
parser.add_argument('--build-only', action='store_true',
help='Only build, do not flash')
parser.add_argument('--reconfig-only', action='store_true',
help='Only reconfigure, do not build/flash')
parser.add_argument('--skip-build', action='store_true',
help='Skip build, use existing firmware')
args = parser.parse_args()
# Determine project directory
if args.project_dir:
project_dir = args.project_dir
else:
project_dir = os.path.dirname(os.path.abspath(__file__))
if not os.path.exists(os.path.join(project_dir, 'CMakeLists.txt')):
print(f"Error: Not an ESP-IDF project directory: {project_dir}")
return 1
# Detect devices
devices = detect_devices()
if not devices and not args.build_only:
print("Error: No devices found!")
return 1
print(f"Found {len(devices)} device(s)")
# Reconfigure only mode
if args.reconfig_only:
if not args.ssid or not args.password:
print("Error: --ssid and --password required for reconfigure mode")
return 1
return 0 if reconfigure_devices(args.ssid, args.password, args.start_ip, args.gateway) else 1
# Build firmware
if not args.skip_build:
if not build_firmware(project_dir):
return 1
if args.build_only:
print("\n Build complete. Use --skip-build to flash later.")
return 0
# Flash all devices
flash_count = flash_all_devices(devices, project_dir)
if flash_count == 0:
print("✗ No devices flashed successfully")
return 1
# Reconfigure if credentials provided
if args.ssid and args.password:
print("\nWaiting for devices to boot...")
time.sleep(5)
if not reconfigure_devices(args.ssid, args.password, args.start_ip, args.gateway):
print("✗ Reconfiguration failed")
return 1
else:
print("\n" + "=" * 60)
print("Flashing complete!")
print("=" * 60)
print("\nTo configure WiFi settings, run:")
print(f" python3 reconfig_simple.py -s YourSSID -p YourPassword --start-ip {args.start_ip}")
print("\n✓ Done!")
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -1,5 +1,3 @@
idf_component_register(
SRCS "main.c"
idf_component_register(SRCS "main.c" "iperf.c" "wifi_cfg.c"
INCLUDE_DIRS "."
PRIV_REQUIRES esp_driver_gpio
)
REQUIRES led_strip driver)

161
main/wifi_cfg.c Normal file
View File

@ -0,0 +1,161 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.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 "driver/uart.h"
#include "esp_vfs_dev.h"
#include "driver/usb_serial_jtag.h"
#include "esp_vfs_usb_serial_jtag.h"
static const char *TAG = "wifi_cfg";
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 bool cfg_dhcp = true;
static void save_cfg(const char* ssid, const char* pass, const char* ip, const char* mask, const char* gw, bool dhcp){
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);
nvs_set_u8(h, "dhcp", dhcp ? 1 : 0);
nvs_commit(h);
nvs_close(h);
cfg_dhcp = dhcp;
}
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, 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;
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; }
static esp_netif_t *sta_netif = NULL;
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);
esp_ip4addr_aton(ip, &info.ip.addr);
esp_ip4addr_aton(mask, &info.netmask.addr);
esp_ip4addr_aton(gw, &info.gw.addr);
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};
bool dhcp = true;
if (!load_cfg(ssid,sizeof(ssid), pass,sizeof(pass), ip,sizeof(ip), mask,sizeof(mask), gw,sizeof(gw), &dhcp)){
ESP_LOGW(TAG, "No WiFi config in NVS");
return false;
}
ESP_LOGI(TAG, "Applying WiFi config: SSID=%s DHCP=%d IP=%s", ssid, dhcp, ip);
static bool inited = false;
if (!inited){
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
sta_netif = esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
inited = true;
}
wifi_config_t wcfg = {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;
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
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_ERROR_CHECK( esp_wifi_start() );
ESP_ERROR_CHECK( esp_wifi_connect() );
return true;
}
static void cfg_listener_task(void *arg){
esp_vfs_usb_serial_jtag_use_driver();
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
char line[160];
char ssid[64]={0}, pass[64]={0}, ip[32]={0}, mask[32]={0}, gw[32]={0};
bool dhcp = true;
bool in_cfg = false;
while (1){
if (!fgets(line, sizeof(line), stdin)){
vTaskDelay(pdMS_TO_TICKS(50));
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;
dhcp = true;
}
continue;
}
if (strcmp(line, "END")==0){
save_cfg(ssid, pass, ip, mask, gw, dhcp);
printf("OK\n");
wifi_cfg_apply_from_nvs();
in_cfg = false;
continue;
}
if (strncmp(line, "SSID:",5)==0){ strncpy(ssid, line+5, sizeof(ssid)-1); continue; }
if (strncmp(line, "PASS:",5)==0){ strncpy(pass, line+5, sizeof(pass)-1); continue; }
if (strncmp(line, "IP:",3)==0){ strncpy(ip, line+3, sizeof(ip)-1); continue; }
if (strncmp(line, "MASK:",5)==0){ strncpy(mask, line+5, sizeof(mask)-1); continue; }
if (strncmp(line, "GW:",3)==0){ strncpy(gw, line+3, sizeof(gw)-1); continue; }
if (strncmp(line, "DHCP:",5)==0){ dhcp = atoi(line+5) ? true:false; continue; }
}
}
void wifi_cfg_init(void){
nvs_flash_init();
xTaskCreatePinnedToCore(cfg_listener_task, "cfg_listener", 4096, NULL, 5, NULL, tskNO_AFFINITY);
}

15
main/wifi_cfg.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
void wifi_cfg_init(void); // starts serial listener task
bool wifi_cfg_apply_from_nvs(void); // reads saved config and connects WiFi
void wifi_cfg_force_dhcp(bool enable); // for testing
#ifdef __cplusplus
}
#endif