#!/usr/bin/env python3 """ ESP32 USB Device Detection Script Detects and lists ESP32 devices connected via USB """ import serial.tools.list_ports import subprocess import sys import time import serial def detect_chip_type(port): """ Try to detect the ESP32 chip type using esptool.py Returns chip type string or 'Unknown' """ # Try to enter ROM bootloader via DTR/RTS before probing try_enter_bootloader(port) cmd = ['esptool.py', '--port', port, '--chip', 'auto', '--before', 'default_reset', '--after', 'no_reset', 'chip_id'] try : result = subprocess.run(cmd, capture_output=True, text=True, timeout=20) output = (result.stdout or '') + (result.stderr or '') if result.returncode != 0: last = output.strip().splitlines()[-1] if output.strip().splitlines() else 'esptool failed' print(f" esptool on {port} failed: {last}") return 'Unknown' # Prefer exact "Chip is ..." line (e.g., ESP32-D0WD-V3) for line in output.splitlines(): if line.startswith('Chip is '): return line.replace('Chip is ', '').split(' (', 1)[0].strip() # Fallback loose matches for name in ('ESP32-S3','ESP32-S2','ESP32-C6','ESP32-C3','ESP32-H2','ESP32'): if name in output: return name except FileNotFoundError: print(" esptool.py not found (pip install esptool)") except subprocess.TimeoutExpired: print(f" esptool on {port} timed out (check EN/IO0 wiring)") except Exception as e: print(f" Error running esptool on {port}: {e}") return 'Unknown' def try_enter_bootloader(port): """ Toggle DTR/RTS to enter download mode. Assumes RTS->EN (active-low) and DTR->IO0 (active-low on many adapters). Safe no-op if control lines aren't available. """ try: with serial.Serial(port, 115200, timeout=0.1) as ser: ser.rts = True # EN low ser.dtr = True # IO0 low time.sleep(0.05) ser.rts = False # EN high time.sleep(0.05) ser.dtr = False # IO0 high time.sleep(0.05) except Exception: pass def guess_chip_type_from_usb(vid, pid): """ Make an educated guess about chip type based on USB VID:PID Returns chip type string or None """ if vid == 0x303A: # Espressif USB if pid == 0x1001: return 'ESP32-S3/C3' # Built-in USB, could be either elif pid == 0x1002: return 'ESP32-S3/C3' # For CP210x, CH340, etc., we can't tell from USB alone return None def detect_esp32_devices(): """ Detect ESP32 devices connected via USB. Returns a list of tuples containing (port, description, hwid) """ esp32_devices = [] # Common USB vendor IDs used by ESP32 boards ESP32_VID_PIDS = [ ('10C4', 'EA60'), # Silicon Labs CP210x ('1A86', '7523'), # CH340 ('1A86', '55D4'), # CH9102 ('0403', '6001'), # FTDI FT232 ('0403', '6010'), # FTDI FT2232 ('303A', '1001'), # Espressif USB JTAG/serial debug unit (ESP32-C3, ESP32-S3) ('303A', '1002'), # Espressif USB JTAG/serial debug unit ] # Keywords that might appear in ESP32 device descriptions ESP32_KEYWORDS = [ 'CP210', 'CH340', 'CH9102', 'UART', 'USB-SERIAL', 'USB SERIAL', 'JTAG', 'ESP32', ] # Get all available ports ports = serial.tools.list_ports.comports() for port in ports: # Check by VID:PID if port.vid is not None and port.pid is not None: vid = f"{port.vid:04X}" pid = f"{port.pid:04X}" if any((vid == v and pid == p) for v, p in ESP32_VID_PIDS): esp32_devices.append(port) continue # Check by description keywords description_upper = port.description.upper() if any(keyword in description_upper for keyword in ESP32_KEYWORDS): esp32_devices.append(port) return esp32_devices def main(): import argparse parser = argparse.ArgumentParser(description='ESP32 USB Device Detection') parser.add_argument('--no-probe', action='store_true', help='Skip chip type probing (faster, less accurate)') args = parser.parse_args() print("=" * 60) print("ESP32 USB Device Detection") print("=" * 60) print() # Detect ESP32 devices esp32_devices = detect_esp32_devices() if esp32_devices: print(f"Found {len(esp32_devices)} ESP32 device(s):\n") for idx, device in enumerate(esp32_devices, 1): print(f"Device {idx}:") print(f" Port: {device.device}") print(f" Description: {device.description}") print(f" Hardware ID: {device.hwid}") if device.vid is not None and device.pid is not None: print(f" VID:PID: {device.vid:04X}:{device.pid:04X}") # Chip type detection (probe by default) chip_type = None if not args.no_probe: print(f" Probing chip type...", end='', flush=True) chip_type = detect_chip_type(device.device) print(f"\r Chip Type: {chip_type}") else: # Try to guess from USB VID:PID if device.vid is not None and device.pid is not None: chip_type = guess_chip_type_from_usb(device.vid, device.pid) if chip_type: print(f" Chip Guess: {chip_type} (remove --no-probe for exact type)") else: print(f" Chip Type: Unknown (remove --no-probe to detect)") if device.manufacturer: print(f" Manufacturer: {device.manufacturer}") if device.serial_number: print(f" Serial: {device.serial_number}") print() print("=" * 60) print(f"Total ESP32 devices detected: {len(esp32_devices)}") if args.no_probe: print("Tip: Remove --no-probe flag to detect exact chip types") print("=" * 60) else: print("No ESP32 devices detected.") print() print("Available USB serial devices:") all_ports = serial.tools.list_ports.comports() if all_ports: for port in all_ports: print(f" - {port.device}: {port.description}") if port.vid is not None and port.pid is not None: print(f" VID:PID = {port.vid:04X}:{port.pid:04X}") else: print(" No USB serial devices found.") if __name__ == "__main__": try: main() except KeyboardInterrupt: print("\n\nDetection interrupted by user.") except Exception as e: print(f"\nError: {e}") print("\nMake sure pyserial is installed: pip install pyserial")