From 4d08be2656d5599caf60ab5163a9d2305b397daf Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Thu, 13 Aug 2026 11:09:03 -0600 Subject: Re-detect serial device ports by identity when a profile loads on another machine COM/tty numbers aren't stable across machines or replugs. Devices now save their USB serial number and OS description alongside the port; on profile load, if the saved port isn't present, ArduinoLayer.find_port_by_identity() looks for a match and remaps it. Falls back to the saved port (device shows ERROR) if nothing matches. Co-Authored-By: Claude Sonnet 5 --- api_layers/arduino_layer.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'api_layers/arduino_layer.py') diff --git a/api_layers/arduino_layer.py b/api_layers/arduino_layer.py index a3c3584..07520d6 100644 --- a/api_layers/arduino_layer.py +++ b/api_layers/arduino_layer.py @@ -575,6 +575,46 @@ class ArduinoLayer: return desc return "" + @staticmethod + def port_serial_number(port: str) -> str: + """USB serial number for a currently-connected port, if the OS/driver + exposes one — empty string if unavailable or the port isn't enumerable.""" + if not _SERIAL_AVAILABLE: + return "" + try: + for p in serial.tools.list_ports.comports(): + if p.device == port: + return getattr(p, "serial_number", None) or "" + except Exception as e: + print(f"[ArduinoLayer] port_serial_number() error: {e}") + return "" + + @staticmethod + def find_port_by_identity(description: str = "", serial_number: str = "") -> Optional[str]: + """Re-map a device saved in a profile to whatever port it enumerates + as on *this* machine — COM/tty numbers aren't stable across machines + (or even across replugs on Linux). Prefers USB serial number (unique + even between two identical board models); falls back to an exact + description-string match. Returns None if nothing matches — caller + should fall back to the saved port as-is (device will show ERROR + until the operator fixes it manually).""" + if not _SERIAL_AVAILABLE: + return None + try: + ports = list(serial.tools.list_ports.comports()) + except Exception as e: + print(f"[ArduinoLayer] find_port_by_identity() error: {e}") + return None + if serial_number: + for p in ports: + if (getattr(p, "serial_number", None) or "") == serial_number: + return p.device + if description: + for p in ports: + if (p.description or "") == description: + return p.device + return None + def __repr__(self): mode = "SIM" if self.simulate else f"HW:{self.port}@{self.baud}" return f"" -- cgit v1.2.3