diff options
| author | Christian Kolset <ckolset@colostate.edu> | 2026-08-13 11:09:03 -0600 |
|---|---|---|
| committer | Christian Kolset <ckolset@colostate.edu> | 2026-08-13 11:09:03 -0600 |
| commit | 4d08be2656d5599caf60ab5163a9d2305b397daf (patch) | |
| tree | d78e764e21995b47eca79d184460f14eb52cdd22 /api_layers/arduino_layer.py | |
| parent | 012a1093e3cb9a1b376fc3beee4df859ccfb8da5 (diff) | |
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 <noreply@anthropic.com>
Diffstat (limited to 'api_layers/arduino_layer.py')
| -rw-r--r-- | api_layers/arduino_layer.py | 40 |
1 files changed, 40 insertions, 0 deletions
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"<ArduinoLayer {mode} pins={self.analog_pins}>" |
