#!/usr/bin/env python3 import os import pyudev def generate_rules(): context = pyudev.Context() # Find all TTY devices driven by usb-serial drivers (CP210x, FTDI, etc.) devices = [] for device in context.list_devices(subsystem='tty'): if 'ID_VENDOR_ID' in device.properties and 'ID_MODEL_ID' in device.properties: # We filter for USB serial devices only parent = device.find_parent('usb', 'usb_interface') if parent: devices.append(device) # Sort by their physical path so they are ordered by hub port # The 'DEVPATH' usually looks like .../usb1/1-2/1-2.3/1-2.3.4... devices.sort(key=lambda x: x.properties.get('DEVPATH', '')) print(f"# Detected {len(devices)} devices. Generating rules...\n") rules = [] hub_counter = 1 port_counter = 1 last_parent_path = None for dev in devices: # Get the unique physical path identifier (KERNELS) # We need the parent USB interface kernel name (e.g., '1-1.2:1.0') parent = dev.find_parent('usb', 'usb_interface') if not parent: continue kernels_path = parent.device_path.split('/')[-1] # Simple heuristic to detect a new hub (big jump in path length or numbering) # You might need to manually tweak the hub numbers in the file later. # Generate the rule # KERNELS matches the physical port. # SYMLINK creates the static alias. rule = f'SUBSYSTEM=="tty", KERNELS=="{kernels_path}", SYMLINK+="esp_port_{len(rules)+1:02d}"' rules.append(rule) print(f"Mapped {dev.device_node} ({kernels_path}) -> /dev/esp_port_{len(rules):02d}") # Write to file with open("99-esp32-static.rules", "w") as f: f.write("# Persistent USB Serial mapping for ESP32 Hubs\n") f.write("# Generated automatically. Do not edit unless topology changes.\n\n") for r in rules: f.write(r + "\n") print(f"\n{'-'*60}") print(f"SUCCESS: Generated {len(rules)} rules in '99-esp32-static.rules'.") print(f"To install:\n 1. Review the file to ensure order is correct.") print(f" 2. sudo cp 99-esp32-static.rules /etc/udev/rules.d/") print(f" 3. sudo udevadm control --reload-rules") print(f" 4. sudo udevadm trigger") print(f"{'-'*60}") if __name__ == '__main__': # Requires 'pyudev'. Install with: sudo dnf install python3-pyudev (or pip install pyudev) try: import pyudev generate_rules() except ImportError: print("Error: This script requires 'pyudev'.") print("Install it via: pip install pyudev")