#!/usr/bin/env python3 """ ESP32 WiFi Configuration Tool - With verbose mode """ import serial import time import sys import argparse def log_verbose(message, verbose=False): """Print message only if verbose is enabled""" if verbose: print(f"[VERBOSE] {message}") def config_device(port, ip, ssid="ClubHouse2G", password="ez2remember", gateway="192.168.1.1", netmask="255.255.255.0", band="2.4G", bandwidth="HT20", reboot=True, verbose=False): """Configure ESP32 device via serial""" print(f"\n{'='*70}") print(f"ESP32 WiFi Configuration") print(f"{'='*70}") print(f"Port: {port}") print(f"SSID: {ssid}") print(f"Password: {'*' * len(password)}") print(f"IP: {ip}") print(f"Gateway: {gateway}") print(f"Netmask: {netmask}") print(f"Band: {band}") print(f"Bandwidth: {bandwidth}") print(f"Reboot: {'Yes' if reboot else 'No'}") print(f"Verbose: {verbose}") print(f"{'='*70}\n") try: # Open serial connection log_verbose(f"Opening serial port {port} at 115200 baud...", verbose) ser = serial.Serial(port, 115200, timeout=0.5, write_timeout=0.5) log_verbose(f"Serial port opened successfully", verbose) log_verbose(f"Port settings: {ser}", verbose) time.sleep(0.2) # Check if there's any data waiting if ser.in_waiting: log_verbose(f"{ser.in_waiting} bytes waiting in buffer", verbose) existing = ser.read(ser.in_waiting).decode('utf-8', errors='ignore') log_verbose(f"Existing data: {existing[:100]}", verbose) # Build config message config_lines = [ "CFG", f"SSID:{ssid}", f"PASS:{password}", f"IP:{ip}", f"MASK:{netmask}", f"GW:{gateway}", "DHCP:0", f"BAND:{band}", f"BW:{bandwidth}", "END" ] config = '\n'.join(config_lines) + '\n' log_verbose(f"Config message size: {len(config)} bytes", verbose) if verbose: print("[VERBOSE] Config message:") for line in config_lines: display_line = line if not line.startswith("PASS:") else "PASS:********" print(f"[VERBOSE] {display_line}") # Send config print("Sending configuration...") print("\nConfiguration being sent:") for line in config_lines: display_line = line if not line.startswith("PASS:") else "PASS:********" print(f" {display_line}") print() start_time = time.time() bytes_written = ser.write(config.encode('utf-8')) ser.flush() send_time = time.time() - start_time log_verbose(f"Wrote {bytes_written} bytes in {send_time:.3f}s", verbose) print(f"Sent {bytes_written} bytes") print("Waiting for response...") time.sleep(3) # Give more time for response # Read response if ser.in_waiting: response_size = ser.in_waiting print(f"\n✓ Response received: {response_size} bytes") response = ser.read(response_size).decode('utf-8', errors='ignore') print("\nDevice response:") print("-" * 70) for line in response.split('\n')[:30]: # Show first 30 lines if line.strip(): print(f" {line}") print("-" * 70) # Check for key indicators success_indicators = [] warning_indicators = [] if "OK" in response: success_indicators.append("✓ Configuration acknowledged (OK)") if "Config saved" in response or "saved to NVS" in response: success_indicators.append("✓ Config saved to NVS") if "got ip:" in response.lower(): success_indicators.append("✓ Device connected to WiFi!") # Extract IP from response import re ip_match = re.search(r'got ip:(\d+\.\d+\.\d+\.\d+)', response, re.IGNORECASE) if ip_match: success_indicators.append(f" Assigned IP: {ip_match.group(1)}") if "connected" in response.lower(): success_indicators.append("✓ WiFi connection established") if "failed" in response.lower() or "disconnect" in response.lower(): warning_indicators.append("⚠ WiFi connection may have failed") if "error" in response.lower(): warning_indicators.append("⚠ Error detected in response") if success_indicators: print("\nStatus indicators:") for indicator in success_indicators: print(f" {indicator}") if warning_indicators: print("\nWarnings:") for warning in warning_indicators: print(f" {warning}") else: print("\n⚠ No response from device") print(" This could mean:") print(" - Device is not running config handler") print(" - Wrong serial port") print(" - Baud rate mismatch") # Reboot device if requested if reboot: print("\n" + "="*70) print("Rebooting device...") print("="*70) log_verbose("Performing hardware reset via DTR/RTS", verbose) # Standard ESP32 reset sequence ser.dtr = False # DTR low ser.rts = True # RTS high (EN low) time.sleep(0.1) ser.rts = False # RTS low (EN high) time.sleep(0.1) ser.dtr = True # DTR high print("✓ Reset signal sent - waiting for boot...") # Wait for boot messages time.sleep(3) # Longer wait for full boot if ser.in_waiting: boot_msg = ser.read(ser.in_waiting).decode('utf-8', errors='ignore') print("\nBoot messages:") print("-" * 70) for line in boot_msg.split('\n')[:40]: # Show more lines if line.strip(): print(f" {line}") print("-" * 70) # Check boot status boot_success = [] boot_warnings = [] if "WiFi config loaded from NVS" in boot_msg: boot_success.append("✓ Config successfully loaded from NVS") elif "No WiFi config" in boot_msg or "YELLOW LED" in boot_msg: boot_warnings.append("✗ NO CONFIG found in NVS - this is the problem!") boot_warnings.append(" Device does not see saved config") if "got ip:" in boot_msg.lower(): boot_success.append("✓ Device connected to WiFi!") import re ip_match = re.search(r'got ip:(\d+\.\d+\.\d+\.\d+)', boot_msg, re.IGNORECASE) if ip_match: boot_success.append(f" Connected with IP: {ip_match.group(1)}") if "WiFi CONNECTED" in boot_msg: boot_success.append("✓ WiFi connection confirmed") if "BLUE LED solid" in boot_msg or "BLUE solid" in boot_msg: boot_success.append("✓ LED should be BLUE (connected)") elif "YELLOW solid" in boot_msg or "YELLOW LED" in boot_msg: boot_warnings.append("⚠ LED is YELLOW (no config)") elif "RED" in boot_msg and "blink" in boot_msg: boot_warnings.append("⚠ LED is RED (connection failed)") if boot_success: print("\nBoot Status - SUCCESS:") for msg in boot_success: print(f" {msg}") if boot_warnings: print("\nBoot Status - ISSUES:") for msg in boot_warnings: print(f" {msg}") else: print("\n⚠ No boot messages received") print(" Device may still be booting...") # Get final port stats if verbose: log_verbose(f"Input buffer: {ser.in_waiting} bytes", verbose) log_verbose(f"Output buffer empty: {ser.out_waiting == 0}", verbose) ser.close() log_verbose("Serial port closed", verbose) print(f"\n{'='*70}") print("Configuration Summary") print(f"{'='*70}") print(f"Port: {port}") print(f"Expected IP: {ip}") print(f"SSID: {ssid}") print(f"Band: {band}") print(f"Bandwidth: {bandwidth}") print(f"{'='*70}") print("\nNext steps:") print(f" 1. Check LED color:") print(f" - GREEN = Connected ✓") print(f" - YELLOW = No config saved ✗") print(f" - RED blinking = Connection failed") print(f" 2. Test connection:") print(f" ping {ip}") print(f" iperf -c {ip}") print(f"\nIf LED is YELLOW:") print(f" - Config is NOT being saved to NVS") print(f" - Check if wifi_cfg.c is handling BAND: and BW: fields") print(f" - Try running: idf.py monitor") print(f" to see what the device is actually receiving") return True except serial.SerialException as e: print(f"\n✗ Serial error: {e}") log_verbose(f"Serial exception details: {type(e).__name__}", verbose) print(" Is another program using this port? (idf.py monitor, screen, etc.)") return False except KeyboardInterrupt: print("\n\nConfiguration cancelled by user") if 'ser' in locals() and ser.is_open: ser.close() log_verbose("Serial port closed after interrupt", verbose) return False except Exception as e: print(f"\n✗ Error: {e}") if verbose: import traceback print("\n[VERBOSE] Full traceback:") traceback.print_exc() return False def main(): parser = argparse.ArgumentParser( description='Configure ESP32 WiFi via serial', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Basic configuration (2.4GHz) with auto-reboot %(prog)s -p /dev/ttyUSB0 -i 192.168.1.51 # 5GHz with HT40 bandwidth and auto-reboot %(prog)s -p /dev/ttyUSB0 -i 192.168.1.81 -s ClubHouse5G -b 5G -B HT40 # Configure without rebooting (manual reboot required) %(prog)s -p /dev/ttyUSB0 -i 192.168.1.51 -r # With verbose output to see boot messages %(prog)s -p /dev/ttyUSB0 -i 192.168.1.51 -v # Custom WiFi credentials on 2.4GHz %(prog)s -p /dev/ttyUSB0 -i 192.168.1.52 -s MyWiFi -P mypass # Custom gateway %(prog)s -p /dev/ttyUSB0 -i 10.0.0.100 -g 10.0.0.1 """ ) parser.add_argument('-p', '--port', required=True, help='Serial port (e.g., /dev/ttyUSB0)') parser.add_argument('-i', '--ip', required=True, help='Static IP address') parser.add_argument('-s', '--ssid', default='ClubHouse2G', help='WiFi SSID (default: ClubHouse2G)') parser.add_argument('-P', '--password', default='ez2remember', help='WiFi password (default: ez2remember)') parser.add_argument('-g', '--gateway', default='192.168.1.1', help='Gateway IP (default: 192.168.1.1)') parser.add_argument('-m', '--netmask', default='255.255.255.0', help='Netmask (default: 255.255.255.0)') parser.add_argument('-b', '--band', default='2.4G', choices=['2.4G', '5G'], help='WiFi band: 2.4G or 5G (default: 2.4G)') parser.add_argument('-B', '--bandwidth', default='HT20', choices=['HT20', 'HT40'], help='Channel bandwidth: HT20 or HT40 (default: HT20)') parser.add_argument('-r', '--no-reboot', action='store_true', help='Do NOT reboot device after configuration (default: will reboot)') parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output (show detailed debug info)') args = parser.parse_args() success = config_device( port=args.port, ip=args.ip, ssid=args.ssid, password=args.password, gateway=args.gateway, netmask=args.netmask, band=args.band, bandwidth=args.bandwidth, reboot=not args.no_reboot, # Invert since flag is --no-reboot verbose=args.verbose ) sys.exit(0 if success else 1) if __name__ == '__main__': main()