Add flash_and_config.py and WiFi reconfiguration system
This commit is contained in:
parent
f2abcf450b
commit
90e212fed6
|
|
@ -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())
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 Wi‑Fi config in NVS");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGI(TAG, "Applying Wi‑Fi 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);
|
||||
}
|
||||
|
|
@ -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 Wi‑Fi
|
||||
void wifi_cfg_force_dhcp(bool enable); // for testing
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Loading…
Reference in New Issue