@@ -31,20 +31,24 @@ class MillenniaEv25Device(LaserSourceDevice, OnOffControl, ShutterControl, Power
3131
3232 # The lab eV25s enumerates its back-panel USB port as a native STM32
3333 # USB-CDC device: VID 0x0483, PID 0x5740 (confirmed on the bench, firmware
34- # SW214-00.004.096). MillenniaEv25Device is still instantiated by portPath
35- # (like CoboltDevice) rather than discovered by VID/PID, because 0x0483:0x5740
36- # is STMicroelectronics' *generic* STM32 Virtual COM Port identity, shared by
37- # many unrelated STM32-based USB-CDC boards; matching on it via
38- # SerialPort.matchAnyPort would risk binding to the wrong device. If a host
39- # only ever has this one STM32 CDC port, set classIdVendor = 0x0483 and
40- # classIdProduct = 0x5740 to enable discovery; otherwise pin the portPath.
41- # To re-derive on another unit:
34+ # SW214-00.004.096). doInitializeDevice discovers the port by this identity
35+ # when no portPath is given.
36+ #
37+ # Caveat: 0x0483:0x5740 is STMicroelectronics' *generic* STM32 Virtual COM
38+ # Port identity, shared by many unrelated STM32-based USB-CDC boards. On a
39+ # host that also has another STM32 CDC device, discovery could bind to the
40+ # wrong port; pin it with an explicit portPath (or narrow with a
41+ # serialNumber) in that case. A given portPath always wins over discovery.
42+ # To re-derive the identity on another unit:
4243 #
4344 # system_profiler SPUSBDataType | grep -A 12 -i millennia
4445 # .venv/bin/python -c "from serial.tools.list_ports import comports; \
4546 # [print(p.device, hex(p.vid or 0), hex(p.pid or 0), p.serial_number) \
4647 # for p in comports()]"
4748
49+ classIdVendor = 0x0483 # STMicroelectronics
50+ classIdProduct = 0x5740 # STM32 Virtual COM Port (generic STM32 CDC identity)
51+
4852 defaultBaudRate = 115200
4953 commandTerminator = "\r " # the eV accepts <CR>, <LF>, or both
5054 minPower = 0.05 # documented eV/Pro-s lower bound
@@ -76,8 +80,23 @@ def __init__(self, portPath=None, serialNumber=None, idProduct=None, idVendor=No
7680 self .laserSerialNumber = None
7781
7882 def doInitializeDevice (self ):
79- try :
83+ if self . portPath is not None :
8084 self .port = SerialPort (portPath = self .portPath )
85+ else :
86+ # Discover by the STM32 USB-CDC identity. serialNumber is a regex
87+ # (PhysicalDevice turns the default into ".*"); pass it only when it
88+ # actually narrows, so SerialPort.matchPorts stays on its vid/pid
89+ # path and never regex-matches against a possibly-None descriptor
90+ # serial.
91+ serialNumber = None if self .serialNumber in (None , ".*" ) else self .serialNumber
92+ self .port = SerialPort (idVendor = self .idVendor , idProduct = self .idProduct ,
93+ serialNumber = serialNumber )
94+ if self .port .portPath is None :
95+ raise PhysicalDevice .UnableToInitialize (
96+ "No Millennia eV found by USB identity {0:#06x}:{1:#06x}. "
97+ "Pass an explicit portPath if several STM32 USB-CDC ports "
98+ "are present." .format (self .idVendor , self .idProduct ))
99+ try :
81100 self .port .open (baudRate = self .defaultBaudRate )
82101 self .readIdentity ()
83102 except Exception as error :
0 commit comments