1import ctypes 2import re 3 4def ValidHandle(value, func, arguments): 5 if value == 0: 6 raise ctypes.WinError() 7 return value 8 9import serial 10from serial.win32 import ULONG_PTR, is_64bit 11from ctypes.wintypes import HANDLE 12from ctypes.wintypes import BOOL 13from ctypes.wintypes import HWND 14from ctypes.wintypes import DWORD 15from ctypes.wintypes import WORD 16from ctypes.wintypes import LONG 17from ctypes.wintypes import ULONG 18from ctypes.wintypes import LPCSTR 19from ctypes.wintypes import HKEY 20from ctypes.wintypes import BYTE 21 22NULL = 0 23HDEVINFO = ctypes.c_void_p 24PCTSTR = ctypes.c_char_p 25PTSTR = ctypes.c_void_p 26CHAR = ctypes.c_char 27LPDWORD = PDWORD = ctypes.POINTER(DWORD) 28#~ LPBYTE = PBYTE = ctypes.POINTER(BYTE) 29LPBYTE = PBYTE = ctypes.c_void_p # XXX avoids error about types 30 31ACCESS_MASK = DWORD 32REGSAM = ACCESS_MASK 33 34 35def byte_buffer(length): 36 """Get a buffer for a string""" 37 return (BYTE*length)() 38 39def string(buffer): 40 s = [] 41 for c in buffer: 42 if c == 0: break 43 s.append(chr(c & 0xff)) # "& 0xff": hack to convert signed to unsigned 44 return ''.join(s) 45 46 47class GUID(ctypes.Structure): 48 _fields_ = [ 49 ('Data1', DWORD), 50 ('Data2', WORD), 51 ('Data3', WORD), 52 ('Data4', BYTE*8), 53 ] 54 def __str__(self): 55 return "{%08x-%04x-%04x-%s-%s}" % ( 56 self.Data1, 57 self.Data2, 58 self.Data3, 59 ''.join(["%02x" % d for d in self.Data4[:2]]), 60 ''.join(["%02x" % d for d in self.Data4[2:]]), 61 ) 62 63class SP_DEVINFO_DATA(ctypes.Structure): 64 _fields_ = [ 65 ('cbSize', DWORD), 66 ('ClassGuid', GUID), 67 ('DevInst', DWORD), 68 ('Reserved', ULONG_PTR), 69 ] 70 def __str__(self): 71 return "ClassGuid:%s DevInst:%s" % (self.ClassGuid, self.DevInst) 72PSP_DEVINFO_DATA = ctypes.POINTER(SP_DEVINFO_DATA) 73 74PSP_DEVICE_INTERFACE_DETAIL_DATA = ctypes.c_void_p 75 76setupapi = ctypes.windll.LoadLibrary("setupapi") 77SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList 78SetupDiDestroyDeviceInfoList.argtypes = [HDEVINFO] 79SetupDiDestroyDeviceInfoList.restype = BOOL 80 81SetupDiClassGuidsFromName = setupapi.SetupDiClassGuidsFromNameA 82SetupDiClassGuidsFromName.argtypes = [PCTSTR, ctypes.POINTER(GUID), DWORD, PDWORD] 83SetupDiClassGuidsFromName.restype = BOOL 84 85SetupDiEnumDeviceInfo = setupapi.SetupDiEnumDeviceInfo 86SetupDiEnumDeviceInfo.argtypes = [HDEVINFO, DWORD, PSP_DEVINFO_DATA] 87SetupDiEnumDeviceInfo.restype = BOOL 88 89SetupDiGetClassDevs = setupapi.SetupDiGetClassDevsA 90SetupDiGetClassDevs.argtypes = [ctypes.POINTER(GUID), PCTSTR, HWND, DWORD] 91SetupDiGetClassDevs.restype = HDEVINFO 92SetupDiGetClassDevs.errcheck = ValidHandle 93 94SetupDiGetDeviceRegistryProperty = setupapi.SetupDiGetDeviceRegistryPropertyA 95SetupDiGetDeviceRegistryProperty.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD] 96SetupDiGetDeviceRegistryProperty.restype = BOOL 97 98SetupDiGetDeviceInstanceId = setupapi.SetupDiGetDeviceInstanceIdA 99SetupDiGetDeviceInstanceId.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, PTSTR, DWORD, PDWORD] 100SetupDiGetDeviceInstanceId.restype = BOOL 101 102SetupDiOpenDevRegKey = setupapi.SetupDiOpenDevRegKey 103SetupDiOpenDevRegKey.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM] 104SetupDiOpenDevRegKey.restype = HKEY 105 106advapi32 = ctypes.windll.LoadLibrary("Advapi32") 107RegCloseKey = advapi32.RegCloseKey 108RegCloseKey.argtypes = [HKEY] 109RegCloseKey.restype = LONG 110 111RegQueryValueEx = advapi32.RegQueryValueExA 112RegQueryValueEx.argtypes = [HKEY, LPCSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD] 113RegQueryValueEx.restype = LONG 114 115 116DIGCF_PRESENT = 2 117DIGCF_DEVICEINTERFACE = 16 118INVALID_HANDLE_VALUE = 0 119ERROR_INSUFFICIENT_BUFFER = 122 120SPDRP_HARDWAREID = 1 121SPDRP_FRIENDLYNAME = 12 122DICS_FLAG_GLOBAL = 1 123DIREG_DEV = 0x00000001 124KEY_READ = 0x20019 125 126# workaround for compatibility between Python 2.x and 3.x 127Ports = serial.to_bytes([80, 111, 114, 116, 115]) # "Ports" 128PortName = serial.to_bytes([80, 111, 114, 116, 78, 97, 109, 101]) # "PortName" 129 130def comports(): 131 GUIDs = (GUID*8)() # so far only seen one used, so hope 8 are enough... 132 guids_size = DWORD() 133 if not SetupDiClassGuidsFromName( 134 Ports, 135 GUIDs, 136 ctypes.sizeof(GUIDs), 137 ctypes.byref(guids_size)): 138 raise ctypes.WinError() 139 140 # repeat for all possible GUIDs 141 for index in range(guids_size.value): 142 g_hdi = SetupDiGetClassDevs( 143 ctypes.byref(GUIDs[index]), 144 None, 145 NULL, 146 DIGCF_PRESENT) # was DIGCF_PRESENT|DIGCF_DEVICEINTERFACE which misses CDC ports 147 148 devinfo = SP_DEVINFO_DATA() 149 devinfo.cbSize = ctypes.sizeof(devinfo) 150 index = 0 151 while SetupDiEnumDeviceInfo(g_hdi, index, ctypes.byref(devinfo)): 152 index += 1 153 154 # get the real com port name 155 hkey = SetupDiOpenDevRegKey( 156 g_hdi, 157 ctypes.byref(devinfo), 158 DICS_FLAG_GLOBAL, 159 0, 160 DIREG_DEV, # DIREG_DRV for SW info 161 KEY_READ) 162 port_name_buffer = byte_buffer(250) 163 port_name_length = ULONG(ctypes.sizeof(port_name_buffer)) 164 RegQueryValueEx( 165 hkey, 166 PortName, 167 None, 168 None, 169 ctypes.byref(port_name_buffer), 170 ctypes.byref(port_name_length)) 171 RegCloseKey(hkey) 172 173 # unfortunately does this method also include parallel ports. 174 # we could check for names starting with COM or just exclude LPT 175 # and hope that other "unknown" names are serial ports... 176 if string(port_name_buffer).startswith('LPT'): 177 continue 178 179 # hardware ID 180 szHardwareID = byte_buffer(250) 181 # try to get ID that includes serial number 182 if not SetupDiGetDeviceInstanceId( 183 g_hdi, 184 ctypes.byref(devinfo), 185 ctypes.byref(szHardwareID), 186 ctypes.sizeof(szHardwareID) - 1, 187 None): 188 # fall back to more generic hardware ID if that would fail 189 if not SetupDiGetDeviceRegistryProperty( 190 g_hdi, 191 ctypes.byref(devinfo), 192 SPDRP_HARDWAREID, 193 None, 194 ctypes.byref(szHardwareID), 195 ctypes.sizeof(szHardwareID) - 1, 196 None): 197 # Ignore ERROR_INSUFFICIENT_BUFFER 198 if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER: 199 raise ctypes.WinError() 200 # stringify 201 szHardwareID_str = string(szHardwareID) 202 203 # in case of USB, make a more readable string, similar to that form 204 # that we also generate on other platforms 205 if szHardwareID_str.startswith('USB'): 206 m = re.search(r'VID_([0-9a-f]{4})&PID_([0-9a-f]{4})(\\(\w+))?', szHardwareID_str, re.I) 207 if m: 208 if m.group(4): 209 szHardwareID_str = 'USB VID:PID=%s:%s SNR=%s' % (m.group(1), m.group(2), m.group(4)) 210 else: 211 szHardwareID_str = 'USB VID:PID=%s:%s' % (m.group(1), m.group(2)) 212 213 # friendly name 214 szFriendlyName = byte_buffer(250) 215 if not SetupDiGetDeviceRegistryProperty( 216 g_hdi, 217 ctypes.byref(devinfo), 218 SPDRP_FRIENDLYNAME, 219 #~ SPDRP_DEVICEDESC, 220 None, 221 ctypes.byref(szFriendlyName), 222 ctypes.sizeof(szFriendlyName) - 1, 223 None): 224 # Ignore ERROR_INSUFFICIENT_BUFFER 225 #~ if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER: 226 #~ raise IOError("failed to get details for %s (%s)" % (devinfo, szHardwareID.value)) 227 # ignore errors and still include the port in the list, friendly name will be same as port name 228 yield string(port_name_buffer), 'n/a', szHardwareID_str 229 else: 230 yield string(port_name_buffer), string(szFriendlyName), szHardwareID_str 231 232 SetupDiDestroyDeviceInfoList(g_hdi) 233 234# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 235# test 236if __name__ == '__main__': 237 import serial 238 239 for port, desc, hwid in sorted(comports()): 240 print "%s: %s [%s]" % (port, desc, hwid) 241