diff --git a/flash_all_serial_config.py b/flash_all_serial_config.py new file mode 100644 index 0000000..d97010a --- /dev/null +++ b/flash_all_serial_config.py @@ -0,0 +1,203 @@ + +#!/usr/bin/env python3 +import argparse, os, sys, time, fnmatch +from pathlib import Path +from typing import List, Dict + +import serial, serial.tools.list_ports as list_ports +import subprocess + +KNOWN_VIDS = {0x0403, 0x10C4, 0x1A86, 0x067B, 0x303A} + +def run(cmd, cwd=None, check=True): + print(">>", " ".join(cmd)) + p = subprocess.run(cmd, cwd=cwd) + if check and p.returncode != 0: + raise RuntimeError(f"Command failed ({p.returncode}): {' '.join(cmd)}") + return p.returncode + +def detect_chip_type(port: str) -> str: + try: + out = subprocess.check_output(["esptool.py", "--port", port, "chip_id"], stderr=subprocess.STDOUT, text=True, timeout=6) + if "ESP32-S3" in out.upper(): return "ESP32-S3" + if "ESP32-S2" in out.upper(): return "ESP32-S2" + if "ESP32-C3" in out.upper(): return "ESP32-C3" + if "ESP32-C6" in out.upper(): return "ESP32-C6" + if "ESP32" in out.upper(): return "ESP32" + except Exception: + pass + return "Unknown" + +def map_chip_to_idf_target(chip: str) -> str: + s = chip.upper() + if s.startswith("ESP32-S3"): return "esp32s3" + if s.startswith("ESP32-S2"): return "esp32s2" + if s.startswith("ESP32-C3"): return "esp32c3" + if s.startswith("ESP32-C6"): return "esp32c6" + if s.startswith("ESP32-H2"): return "esp32h2" + if s.startswith("ESP32"): return "esp32" + return "unknown" + +def list_ports_filtered(patterns: List[str] = None): + ports = list_ports.comports() + out = [] + for p in ports: + dev = p.device + if patterns: + if not any(fnmatch.fnmatch(dev, pat) for pat in patterns): + continue + else: + if not (dev.startswith("/dev/ttyUSB") or dev.startswith("/dev/ttyACM")): + continue + vid = getattr(p, "vid", None) + if vid is not None and vid not in KNOWN_VIDS: + continue + out.append(p) + return out + +def ensure_target(project_dir: str, target: str): + if not target or target == "unknown": + raise ValueError("Unknown IDF target; cannot set-target") + run(["idf.py", "set-target", target], cwd=project_dir, check=True) + +def flash_device(project_dir: str, port: str, idf_target: str, baud: int = 460800) -> bool: + try: + ensure_target(project_dir, idf_target) + run(["idf.py", "-p", port, "-b", str(baud), "flash"], cwd=project_dir, check=True) + return True + except Exception as e: + print(f" Flash failed on {port}: {e}") + return False + +def toggle_reset(ser): + try: + ser.dtr = False + ser.rts = True + time.sleep(0.05) + ser.dtr = True + ser.rts = False + time.sleep(0.05) + except Exception: + pass + +def send_wifi_config(port: str, ssid: str, password: str, ip: str, mask: str, gw: str, dhcp: bool, baud: int = 115200, retries: int = 3) -> bool: + for attempt in range(1, retries+1): + try: + print(f" [{port}] opening serial @ {baud} (attempt {attempt}/{retries})") + with serial.Serial(port, baudrate=baud, timeout=2) as ser: + time.sleep(0.3) + toggle_reset(ser) + time.sleep(0.8) + ser.reset_input_buffer() + ser.reset_output_buffer() + + lines = [ + "CFG\n", + f"SSID:{ssid}\n" if ssid else "", + f"PASS:{password}\n" if password else "", + f"IP:{ip}\n" if ip else "", + f"MASK:{mask}\n" if mask else "", + f"GW:{gw}\n" if gw else "", + f"DHCP:{1 if dhcp else 0}\n", + "END\n", + ] + payload = "".join([l for l in lines if l]) + ser.write(payload.encode("utf-8")) + ser.flush() + + t0 = time.time() + buf = b"" + while time.time() - t0 < 3.0: + chunk = ser.read(64) + if chunk: + buf += chunk + if b"OK" in buf: + print(f" [{port}] config applied: {buf.decode(errors='ignore').strip()}") + return True + print(f" [{port}] no OK from device, got: {buf.decode(errors='ignore').strip()}") + except Exception as e: + print(f" [{port}] serial error: {e}") + time.sleep(0.6) + return False + +def main(): + ap = argparse.ArgumentParser(description="Mass flash ESP32 devices and push Wi‑Fi/IP config over serial.") + ap.add_argument("--project", required=True, help="Path to ESP‑IDF project") + ap.add_argument("--ssid", required=True, help="Wi‑Fi SSID") + ap.add_argument("--password", required=True, help="Wi‑Fi password") + ap.add_argument("--start-ip", default="192.168.1.50", help="Base IP (only used if --dhcp=0)") + ap.add_argument("--mask", default="255.255.255.0", help="Netmask for static IP") + ap.add_argument("--gw", default="192.168.1.1", help="Gateway for static IP") + ap.add_argument("--dhcp", type=int, choices=[0,1], default=1, help="1=use DHCP, 0=set static IPs") + ap.add_argument("--baud", type=int, default=460800, help="Flashing baud rate") + ap.add_argument("--cfg-baud", type=int, default=115200, help="Serial baud for config exchange") + ap.add_argument("--ports", help="Comma-separated globs to override port selection, e.g. '/dev/ttyUSB*,/dev/ttyACM*'") + ap.add_argument("--dry-run", action="store_true", help="Plan only; do not flash or configure") + args = ap.parse_args() + + project_dir = os.path.abspath(args.project) + if not os.path.isdir(project_dir): + print(f"Project directory not found: {project_dir}") + sys.exit(1) + + patterns = [p.strip() for p in args.ports.split(",")] if args.ports else None + devices = list_ports_filtered(patterns) + if not devices: + print("No candidate USB serial ports found.") + sys.exit(2) + print(f"Found {len(devices)} devices.") + + base = args.start_ip.split(".") + try: + base = [int(x) for x in base] + except Exception: + base = [192,168,1,50] + + plan = [] + for idx, dev in enumerate(devices, 1): + port = dev.device + chip = detect_chip_type(port) + target = map_chip_to_idf_target(chip) + ip = f"{base[0]}.{base[1]}.{base[2]}.{base[3] + idx - 1}" if args.dhcp == 0 else None + plan.append(dict(idx=idx, port=port, chip=chip, target=target, ip=ip)) + + print("\nPlan:") + for d in plan: + print(f" {d['idx']:2d} {d['port']} {d['chip']} -> target {d['target']} IP:{d['ip'] or 'DHCP'}") + + if args.dry_run: + print("\nDry run only.") + return + + failed = [] + for d in plan: + if d['target'] == 'unknown': + print(f"\nERROR: Unknown IDF target for {d['port']} (chip '{d['chip']}'). Skipping.") + failed.append(d['idx']) + continue + print(f"\nFlashing {d['port']} as {d['target']}...") + if not flash_device(project_dir, d['port'], d['target'], baud=args.baud): + failed.append(d['idx']) + continue + + print(f"Configuring Wi‑Fi on {d['port']}...") + ok = send_wifi_config( + d['port'], + args.ssid, + args.password, + d['ip'], + args.mask if d['ip'] else None, + args.gw if d['ip'] else None, + dhcp=(args.dhcp == 1), + baud=args.cfg_baud, + ) + if not ok: + print(f" WARN: config not acknowledged on {d['port']}") + + if failed: + print(f"\nCompleted with flashing failures on: {failed}") + sys.exit(3) + print("\nAll done.") + +if __name__ == "__main__": + main() diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 8182962..dee35e7 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,2 +1,8 @@ -idf_component_register(SRCS "main.c" "iperf.c" - INCLUDE_DIRS ".") +idf_component_register( + SRCS + "main.c" + "iperf.c" + "wifi_cfg.c" + INCLUDE_DIRS + "." + ) diff --git a/main/main.c b/main/main.c index 88911d7..c3d4ba8 100644 --- a/main/main.c +++ b/main/main.c @@ -1,137 +1,41 @@ +// main.c — Add support for wifi_cfg and ip address assignment + #include #include #include + #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 "esp_mac.h" + #include "nvs_flash.h" #include "esp_console.h" #include "argtable3/argtable3.h" + #include "esp_netif.h" #include "lwip/inet.h" -#include "iperf.h" -#define WIFI_SSID CONFIG_WIFI_SSID -#define WIFI_PASS CONFIG_WIFI_PASSWORD -#define WIFI_MAXIMUM_RETRY CONFIG_WIFI_MAXIMUM_RETRY +#include "iperf.h" +#include "wifi_cfg.h" static const char *TAG = "main"; -static EventGroupHandle_t s_wifi_event_group; -#define WIFI_CONNECTED_BIT BIT0 -#define WIFI_FAIL_BIT BIT1 - -static int s_retry_num = 0; - static void event_handler(void* arg, esp_event_base_t event_base, - int32_t event_id, void* event_data) + int32_t event_id, void* event_data) { - if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { - esp_wifi_connect(); - } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) { - wifi_event_sta_connected_t* event = (wifi_event_sta_connected_t*) event_data; - - // Get RSSI - wifi_ap_record_t ap_info; - esp_wifi_sta_get_ap_info(&ap_info); - - ESP_LOGI(TAG, "Connected to AP SSID:%.*s, BSSID:" MACSTR " Channel:%d RSSI:%d dBm", - event->ssid_len, (const char *)event->ssid, MAC2STR(event->bssid), event->channel, ap_info.rssi); -} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { - if (s_retry_num < WIFI_MAXIMUM_RETRY) { - esp_wifi_connect(); - s_retry_num++; - ESP_LOGI(TAG, "retry to connect to the AP"); - } else { - xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); - } - ESP_LOGI(TAG,"connect to the AP fail"); - } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; - ESP_LOGI(TAG, "got ip:" IPSTR " gw:" IPSTR " netmask:" IPSTR, + ESP_LOGI(TAG, "got ip:" IPSTR " gw:" IPSTR " netmask:" IPSTR, IP2STR(&event->ip_info.ip), IP2STR(&event->ip_info.gw), IP2STR(&event->ip_info.netmask)); - s_retry_num = 0; - xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); } } -void wifi_init_sta(void) -{ - s_wifi_event_group = xEventGroupCreate(); - - ESP_ERROR_CHECK(esp_netif_init()); - ESP_ERROR_CHECK(esp_event_loop_create_default()); - - esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta(); - -#ifdef CONFIG_USE_STATIC_IP - // Configure static IP - ESP_LOGI(TAG, "Configuring static IP: %s", CONFIG_STATIC_IP_ADDR); - - esp_netif_dhcpc_stop(sta_netif); - - esp_netif_ip_info_t ip_info; - memset(&ip_info, 0, sizeof(esp_netif_ip_info_t)); - - ip_info.ip.addr = ipaddr_addr(CONFIG_STATIC_IP_ADDR); - ip_info.gw.addr = ipaddr_addr(CONFIG_STATIC_GATEWAY_ADDR); - ip_info.netmask.addr = ipaddr_addr(CONFIG_STATIC_NETMASK_ADDR); - - ESP_ERROR_CHECK(esp_netif_set_ip_info(sta_netif, &ip_info)); -#endif - - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(esp_wifi_init(&cfg)); - - 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, - &event_handler, - NULL, - &instance_any_id)); - ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, - IP_EVENT_STA_GOT_IP, - &event_handler, - NULL, - &instance_got_ip)); - - wifi_config_t wifi_config = { - .sta = { - .ssid = WIFI_SSID, - .password = WIFI_PASS, - .threshold.authmode = WIFI_AUTH_WPA2_PSK, - }, - }; - 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_init_sta finished."); - - 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, "WiFi connection successful"); - } else if (bits & WIFI_FAIL_BIT) { - ESP_LOGI(TAG, "Failed to connect to SSID:%s", WIFI_SSID); - } else { - ESP_LOGE(TAG, "UNEXPECTED EVENT"); - } -} - -/* Console command structure for iperf */ static struct { struct arg_str *ip; struct arg_lit *server; @@ -145,74 +49,6 @@ static struct { struct arg_end *end; } iperf_args; -/* Console command structure for wifi config */ -static struct { - struct arg_str *ssid; - struct arg_str *password; - struct arg_end *end; -} wifi_args; - -static int cmd_wifi(int argc, char **argv) -{ - int nerrors = arg_parse(argc, argv, (void **)&wifi_args); - if (nerrors != 0) { - arg_print_errors(stderr, wifi_args.end, argv[0]); - return 1; - } - - if (wifi_args.ssid->count == 0) { - ESP_LOGE(TAG, "Please provide SSID with -s option"); - return 1; - } - - wifi_config_t wifi_config; - memset(&wifi_config, 0, sizeof(wifi_config_t)); - - // Set SSID - strncpy((char *)wifi_config.sta.ssid, wifi_args.ssid->sval[0], sizeof(wifi_config.sta.ssid) - 1); - - // Set password if provided - if (wifi_args.password->count > 0) { - strncpy((char *)wifi_config.sta.password, wifi_args.password->sval[0], sizeof(wifi_config.sta.password) - 1); - wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; - } - - ESP_LOGI(TAG, "Updating WiFi configuration..."); - ESP_LOGI(TAG, "SSID: %s", wifi_config.sta.ssid); - - // Stop WiFi - esp_wifi_disconnect(); - esp_wifi_stop(); - - // Update config - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); - - // Restart WiFi - ESP_ERROR_CHECK(esp_wifi_start()); - esp_wifi_connect(); - - ESP_LOGI(TAG, "WiFi configuration updated. Attempting to connect..."); - - return 0; -} - -static void register_wifi(void) -{ - wifi_args.ssid = arg_str1("s", "ssid", "", "WiFi SSID"); - wifi_args.password = arg_str0("p", "password", "", "WiFi password"); - wifi_args.end = arg_end(1); - - const esp_console_cmd_t wifi_cmd = { - .command = "wifi", - .help = "Configure WiFi credentials (wifi -s -p )", - .hint = NULL, - .func = &cmd_wifi, - .argtable = &wifi_args - }; - - ESP_ERROR_CHECK(esp_console_cmd_register(&wifi_cmd)); -} - static int cmd_iperf(int argc, char **argv) { int nerrors = arg_parse(argc, argv, (void **)&iperf_args); @@ -221,7 +57,6 @@ static int cmd_iperf(int argc, char **argv) return 1; } - /* Check if needs to abort */ if (iperf_args.abort->count != 0) { iperf_stop(); return 0; @@ -230,10 +65,8 @@ static int cmd_iperf(int argc, char **argv) iperf_cfg_t cfg; memset(&cfg, 0, sizeof(cfg)); - /* Set protocol type */ cfg.flag |= (iperf_args.udp->count == 0) ? IPERF_FLAG_TCP : IPERF_FLAG_UDP; - /* Set server/client mode */ if (iperf_args.server->count != 0) { cfg.flag |= IPERF_FLAG_SERVER; cfg.sport = IPERF_DEFAULT_PORT; @@ -246,7 +79,7 @@ static int cmd_iperf(int argc, char **argv) if (iperf_args.port->count != 0) { cfg.dport = iperf_args.port->ival[0]; } - + if (iperf_args.ip->count == 0) { ESP_LOGE(TAG, "Please input destination IP address"); return 1; @@ -258,84 +91,61 @@ static int cmd_iperf(int argc, char **argv) } } - /* Set test time */ cfg.time = IPERF_DEFAULT_TIME; if (iperf_args.time->count != 0) { cfg.time = iperf_args.time->ival[0]; } - /* Set bandwidth limit for UDP */ cfg.bw_lim = 0; if (iperf_args.bw->count != 0) { cfg.bw_lim = iperf_args.bw->ival[0]; } - ESP_LOGI(TAG, "mode=%s-%s sport=%u, dip=%u.%u.%u.%u:%u, interval=%" PRIu32 ", time=%" PRIu32, - (cfg.flag & IPERF_FLAG_TCP) ? "tcp" : "udp", - (cfg.flag & IPERF_FLAG_SERVER) ? "server" : "client", - cfg.sport, - cfg.dip & 0xFF, (cfg.dip >> 8) & 0xFF, (cfg.dip >> 16) & 0xFF, (cfg.dip >> 24) & 0xFF, cfg.dport, - cfg.interval, cfg.time); + if (iperf_args.interval->count != 0) { + cfg.interval = iperf_args.interval->ival[0]; + } iperf_start(&cfg); + return 0; } static void register_iperf(void) { - iperf_args.ip = arg_str0("c", "client", "", "run in client mode, connecting to "); - iperf_args.server = arg_lit0("s", "server", "run in server mode"); - iperf_args.udp = arg_lit0("u", "udp", "use UDP rather than TCP"); - iperf_args.port = arg_int0("p", "port", "", "server port to listen on/connect to"); - iperf_args.interval = arg_int0("i", "interval", "", "seconds between periodic bandwidth reports"); - iperf_args.time = arg_int0("t", "time", "