0001 #!/usr/bin/env python 0002 0003 ### BITPIM 0004 ### 0005 ### Copyright (C) 2003-2004 Roger Binns <rogerb@rogerbinns.com> 0006 ### 0007 ### This program is free software; you can redistribute it and/or modify 0008 ### it under the terms of the BitPim license as detailed in the LICENSE file. 0009 ### 0010 ### $Id: comscan.py 4368 2007-08-19 23:33:52Z djpham $ 0011 0012 0013 """Detect and enumerate com(serial) ports 0014 0015 You should close all com ports you have open before calling any 0016 functions in this module. If you don't they will be detected as in 0017 use. 0018 0019 Call the comscan() function It returns a list with each entry being a 0020 dictionary of useful information. See the platform notes for what is 0021 in each one. 0022 0023 For your convenience the entries in the list are also sorted into 0024 an order that would make sense to the user. 0025 0026 Platform Notes: 0027 =============== 0028 0029 w Windows9x 0030 W WindowsNT/2K/XP 0031 L Linux 0032 M Mac 0033 0034 wWLM name string Serial device name 0035 wWLM available Bool True if it is possible to open this device 0036 wW active Bool Is the driver actually running? An example of when this is False 0037 is USB devices or Bluetooth etc that aren't currently plugged in. 0038 If you are presenting stuff for users, do not show entries where 0039 this is false 0040 w driverstatus dict status is some random number, problem is non-zero if there is some 0041 issue (eg device disabled) 0042 wW hardwareinstance string instance of the device in the registry as named by Windows 0043 wWLM description string a friendly name to show users 0044 wW driverdate tuple (year, month, day) 0045 W driverversion string version string 0046 wW driverprovider string the manufacturer of the device driver 0047 wW driverdescription string some generic description of the driver 0048 L device tuple (major, minor) device specification 0049 L driver string the driver name from /proc/devices (eg ttyS or ttyUSB) 0050 """ 0051 0052 from __future__ import with_statement 0053 0054 version="$Revision: 4368 $" 0055 0056 import sys 0057 import os 0058 import time 0059 import glob 0060 0061 def _IsWindows(): 0062 return sys.platform=='win32' 0063 0064 def _IsLinux(): 0065 return sys.platform.startswith('linux') 0066 0067 def _IsMac(): 0068 return sys.platform.startswith('darwin') 0069 0070 if _IsWindows(): 0071 import _winreg 0072 import win32file 0073 import win32con 0074 0075 class RegistryAccess: 0076 """A class that is significantly easier to use to access the Registry""" 0077 def __init__(self, hive=_winreg.HKEY_LOCAL_MACHINE): 0078 self.rootkey=_winreg.ConnectRegistry(None, hive) 0079 0080 def getchildren(self, key): 0081 """Returns a list of the child nodes of a key""" 0082 k=_winreg.OpenKey(self.rootkey, key) 0083 index=0 0084 res=[] 0085 while 1: 0086 try: 0087 subkey=_winreg.EnumKey(k, index) 0088 res.append(subkey) 0089 index+=1 0090 except: 0091 # ran out of keys 0092 break 0093 return res 0094 0095 def safegetchildren(self, key): 0096 """Doesn't throw exception if doesn't exist 0097 0098 @return: A list of zero or more items""" 0099 try: 0100 k=_winreg.OpenKey(self.rootkey, key) 0101 except: 0102 return [] 0103 index=0 0104 res=[] 0105 while 1: 0106 try: 0107 subkey=_winreg.EnumKey(k, index) 0108 res.append(subkey) 0109 index+=1 0110 except WindowsError,e: 0111 if e[0]==259: # No more data is available 0112 break 0113 elif e[0]==234: # more data is available 0114 index+=1 0115 continue 0116 raise 0117 return res 0118 0119 0120 def getvalue(self, key, node): 0121 """Gets a value 0122 0123 The result is returned as the correct type (string, int, etc)""" 0124 k=_winreg.OpenKey(self.rootkey, key) 0125 v,t=_winreg.QueryValueEx(k, node) 0126 if t==2: 0127 return int(v) 0128 if t==3: 0129 # lsb data 0130 res=0 0131 mult=1 0132 for i in v: 0133 res+=ord(i)*mult 0134 mult*=256 0135 return res 0136 # un unicode if possible 0137 if isinstance(v, unicode): 0138 try: 0139 return str(v) 0140 except: 0141 pass 0142 return v 0143 0144 def safegetvalue(self, key, node, default=None): 0145 """Gets a value and if nothing is found returns the default""" 0146 try: 0147 return self.getvalue(key, node) 0148 except: 0149 return default 0150 0151 def findkey(self, start, lookfor, prependresult=""): 0152 """Searches for the named key""" 0153 res=[] 0154 for i in self.getchildren(start): 0155 if i==lookfor: 0156 res.append(prependresult+i) 0157 else: 0158 l=self.findkey(start+"\\"+i, lookfor, prependresult+i+"\\") 0159 res.extend(l) 0160 return res 0161 0162 def getallchildren(self, start, prependresult=""): 0163 """Returns a list of all child nodes in the hierarchy""" 0164 res=[] 0165 for i in self.getchildren(start): 0166 res.append(prependresult+i) 0167 l=self.getallchildren(start+"\\"+i, prependresult+i+"\\") 0168 res.extend(l) 0169 return res 0170 0171 def _comscanwindows(): 0172 """Get detail about all com ports on Windows 0173 0174 This code functions on both win9x and nt/2k/xp""" 0175 # give results back 0176 results={} 0177 resultscount=0 0178 0179 # list of active drivers on win98 0180 activedrivers={} 0181 0182 reg=RegistryAccess(_winreg.HKEY_DYN_DATA) 0183 k=r"Config Manager\Enum" 0184 for device in reg.safegetchildren(k): 0185 hw=reg.safegetvalue(k+"\\"+device, "hardwarekey") 0186 if hw is None: 0187 continue 0188 status=reg.safegetvalue(k+"\\"+device, "status", -1) 0189 problem=reg.safegetvalue(k+"\\"+device, "problem", -1) 0190 activedrivers[hw.upper()]={ 'status': status, 'problem': problem } 0191 0192 # list of active drivers on winXP. Apparently Win2k is different? 0193 reg=RegistryAccess(_winreg.HKEY_LOCAL_MACHINE) 0194 k=r"SYSTEM\CurrentControlSet\Services" 0195 for service in reg.safegetchildren(k): 0196 # we will just take largest number 0197 count=int(reg.safegetvalue(k+"\\"+service+"\\Enum", "Count", 0)) 0198 next=int(reg.safegetvalue(k+"\\"+service+"\\Enum", "NextInstance", 0)) 0199 for id in range(max(count,next)): 0200 hw=reg.safegetvalue(k+"\\"+service+"\\Enum", `id`) 0201 if hw is None: 0202 continue 0203 activedrivers[hw.upper()]=None 0204 0205 0206 # scan through everything listed in Enum. Enum is the key containing a list of all 0207 # running hardware 0208 reg=RegistryAccess(_winreg.HKEY_LOCAL_MACHINE) 0209 0210 # The three keys are: 0211 # 0212 # - where to find hardware 0213 # This then has three layers of children. 0214 # Enum 0215 # +-- Category (something like BIOS, PCI etc) 0216 # +-- Driver (vendor/product ids etc) 0217 # +-- Instance (An actual device. You may have more than one instance) 0218 # 0219 # - where to find information about drivers. The driver name is looked up in the instance 0220 # (using the key "driver") and then looked for as a child key of driverlocation to find 0221 # out more about the driver 0222 # 0223 # - where to look for the portname key. Eg in Win98 it is directly in the instance whereas 0224 # in XP it is below "Device Parameters" subkey of the instance 0225 0226 for enumstr, driverlocation, portnamelocation in ( 0227 (r"SYSTEM\CurrentControlSet\Enum", r"SYSTEM\CurrentControlSet\Control\Class", r"\Device Parameters"), # win2K/XP 0228 (r"Enum", r"System\CurrentControlSet\Services\Class", ""), # win98 0229 ): 0230 for category in reg.safegetchildren(enumstr): 0231 catstr=enumstr+"\\"+category 0232 for driver in reg.safegetchildren(catstr): 0233 drvstr=catstr+"\\"+driver 0234 for instance in reg.safegetchildren(drvstr): 0235 inststr=drvstr+"\\"+instance 0236 0237 # see if there is a portname 0238 name=reg.safegetvalue(inststr+portnamelocation, "PORTNAME", "") 0239 0240 # We only want com ports 0241 if len(name)<4 or name.lower()[:3]!="com": 0242 continue 0243 0244 # Get rid of phantom devices 0245 phantom=reg.safegetvalue(inststr, "Phantom", 0) 0246 if phantom: 0247 continue 0248 0249 # Lookup the class 0250 klassguid=reg.safegetvalue(inststr, "ClassGUID") 0251 if klassguid is not None: 0252 # win2k uses ClassGuid 0253 klass=reg.safegetvalue(driverlocation+"\\"+klassguid, "Class") 0254 else: 0255 # Win9x and WinXP use Class 0256 klass=reg.safegetvalue(inststr, "Class") 0257 0258 if klass is None: 0259 continue 0260 klass=klass.lower() 0261 if klass=='ports': 0262 klass='serial' 0263 elif klass=='modem': 0264 klass='modem' 0265 else: 0266 continue 0267 0268 # verify COM is followed by digits only 0269 try: 0270 portnum=int(name[3:]) 0271 except: 0272 continue 0273 0274 # we now have some sort of match 0275 res={} 0276 0277 res['name']=name.upper() 0278 res['class']=klass 0279 0280 # is the device active? 0281 kp=inststr[len(enumstr)+1:].upper() 0282 if kp in activedrivers: 0283 res['active']=True 0284 if activedrivers[kp] is not None: 0285 res['driverstatus']=activedrivers[kp] 0286 else: 0287 res['active']=False 0288 0289 # available? 0290 if res['active']: 0291 try: 0292 usename=name 0293 if sys.platform=='win32' and name.lower().startswith("com"): 0294 usename="\\\\?\\"+name 0295 print "scanning " +usename 0296 ComPort = win32file.CreateFile(usename, 0297 win32con.GENERIC_READ | win32con.GENERIC_WRITE, 0, None, 0298 win32con.OPEN_EXISTING, win32con.FILE_ATTRIBUTE_NORMAL, None) 0299 win32file.CloseHandle(ComPort) 0300 res['available']=True 0301 except Exception,e: 0302 print usename,"is not available",e 0303 res['available']=False 0304 else: 0305 res['available']=False 0306 0307 # hardwareinstance 0308 res['hardwareinstance']=kp 0309 0310 # friendly name 0311 res['description']=reg.safegetvalue(inststr, "FriendlyName", "<No Description>") 0312 0313 # driver information key 0314 drv=reg.safegetvalue(inststr, "Driver") 0315 0316 if drv is not None: 0317 driverkey=driverlocation+"\\"+drv 0318 0319 # get some useful driver information 0320 for subkey, reskey in \ 0321 ("driverdate", "driverdate"), \ 0322 ("providername", "driverprovider"), \ 0323 ("driverdesc", "driverdescription"), \ 0324 ("driverversion", "driverversion"): 0325 val=reg.safegetvalue(driverkey, subkey, None) 0326 if val is None: 0327 continue 0328 if reskey=="driverdate": 0329 try: 0330 val2=val.split('-') 0331 val=int(val2[2]), int(val2[0]), int(val2[1]) 0332 except: 0333 # ignroe wierd dates 0334 continue 0335 res[reskey]=val 0336 0337 results[resultscount]=res 0338 resultscount+=1 0339 0340 0341 return results 0342 0343 # There follows a demonstration of how user friendly Linux is. 0344 # Users are expected by some form of magic to know exactly what 0345 # the names of their devices are. We can't even tell the difference 0346 # between a serial port not existing, and there not being sufficient 0347 # permission to open it 0348 def _comscanlinux(maxnum=9): 0349 """Get all the ports on Linux 0350 0351 Note that Linux doesn't actually provide any way to enumerate actual ports. 0352 Consequently we just look for device nodes. It still isn't possible to 0353 establish if there are actual device drivers behind them. The availability 0354 testing is done however. 0355 0356 @param maxnum: The highest numbered device to look for (eg maxnum of 17 0357 will look for ttyS0 ... ttys17) 0358 """ 0359 0360 # track of mapping majors to drivers 0361 drivers={} 0362 0363 # get the list of char drivers from /proc/drivers 0364 with file('/proc/devices', 'r') as f: 0365 f.readline() # skip "Character devices:" header 0366 for line in f.readlines(): 0367 line=line.split() 0368 if len(line)!=2: 0369 break # next section 0370 major,driver=line 0371 drivers[int(major)]=driver 0372 0373 # device nodes we have seen so we don't repeat them in listing 0374 devcache={} 0375 0376 resultscount=0 0377 results={} 0378 for prefix, description, klass in ( 0379 ("/dev/cua", "Standard serial port", "serial"), 0380 ("/dev/ttyUSB", "USB to serial convertor", "serial"), 0381 ("/dev/ttyACM", "USB modem", "modem"), 0382 ("/dev/rfcomm", "Bluetooth", "modem"), 0383 ("/dev/usb/ttyUSB", "USB to serial convertor", "serial"), 0384 ("/dev/usb/tts/", "USB to serial convertor", "serial"), 0385 ("/dev/usb/acm/", "USB modem", "modem"), 0386 ("/dev/input/ttyACM", "USB modem", "modem") 0387 ): 0388 for num in range(maxnum+1): 0389 name=prefix+`num` 0390 if not os.path.exists(name): 0391 continue 0392 res={} 0393 res['name']=name 0394 res['class']=klass 0395 res['description']=description+" ("+name+")" 0396 dev=os.stat(name).st_rdev 0397 try: 0398 with file(name, 'rw'): 0399 res['available']=True 0400 except: 0401 res['available']=False 0402 # linux specific, and i think they do funky stuff on kernel 2.6 0403 # there is no way to get these 'normally' from the python library 0404 major=(dev>>8)&0xff 0405 minor=dev&0xff 0406 res['device']=(major, minor) 0407 if drivers.has_key(major): 0408 res['driver']=drivers[major] 0409 0410 if res['available']: 0411 if dev not in devcache or not devcache[dev][0]['available']: 0412 results[resultscount]=res 0413 resultscount+=1 0414 devcache[dev]=[res] 0415 continue 0416 # not available, so add 0417 try: 0418 devcache[dev].append(res) 0419 except: 0420 devcache[dev]=[res] 0421 # add in one failed device type per major/minor 0422 for dev in devcache: 0423 if devcache[dev][0]['available']: 0424 continue 0425 results[resultscount]=devcache[dev][0] 0426 resultscount+=1 0427 return results 0428 0429 0430 def _comscanmac(): 0431 """Get all the ports on Mac 0432 0433 Just look for /dev/cu.* entries, they all seem to populate here whether 0434 USB->Serial, builtin, bluetooth, etc... 0435 0436 """ 0437 0438 resultscount=0 0439 results={} 0440 for name in glob.glob("/dev/cu.*"): 0441 res={} 0442 res['name']=name 0443 if name.upper().rfind("MODEM") >= 0: 0444 res['description']="Modem"+" ("+name+")" 0445 res['class']="modem" 0446 else: 0447 res['description']="Serial"+" ("+name+")" 0448 res['class']="serial" 0449 try: 0450 with file(name, 'rw'): 0451 res['available']=True 0452 except: 0453 res['available']=False 0454 results[resultscount]=res 0455 resultscount+=1 0456 return results 0457 0458 ##def availableports(): 0459 ## """Gets list of available ports 0460 0461 ## It is verified that the ports can be opened. 0462 0463 ## @note: You must close any ports you have open before calling this function, otherwise they 0464 ## will not be considered available. 0465 0466 ## @return: List of tuples. Each tuple is (port name, port description) - the description is user 0467 ## friendly. The list is sorted. 0468 ## """ 0469 ## pass 0470 0471 def _stringint(str): 0472 """Seperate a string and trailing number into a tuple 0473 0474 For example "com10" returns ("com", 10) 0475 """ 0476 prefix=str 0477 suffix="" 0478 0479 while len(prefix) and prefix[-1]>='0' and prefix[-1]<='9': 0480 suffix=prefix[-1]+suffix 0481 prefix=prefix[:-1] 0482 0483 if len(suffix): 0484 return (prefix, int(suffix)) 0485 else: 0486 return (prefix, None) 0487 0488 def _cmpfunc(a,b): 0489 """Comparison function for two port names 0490 0491 In particular it looks for a number on the end, and sorts by the prefix (as a 0492 string operation) and then by the number. This function is needed because 0493 "com9" needs to come before "com10" 0494 """ 0495 0496 aa=_stringint(a[0]) 0497 bb=_stringint(b[0]) 0498 0499 if aa==bb: 0500 if a[1]==b[1]: 0501 return 0 0502 if a[1]<b[1]: 0503 return -1 0504 return 1 0505 if aa<bb: return -1 0506 return 1 0507 0508 def comscan(*args, **kwargs): 0509 """Call platform specific version of comscan function""" 0510 res={} 0511 if _IsWindows(): 0512 res=_comscanwindows(*args, **kwargs) 0513 elif _IsLinux(): 0514 res=_comscanlinux(*args, **kwargs) 0515 elif _IsMac(): 0516 res=_comscanmac(*args, **kwargs) 0517 else: 0518 raise Exception("unknown platform "+sys.platform) 0519 0520 # sort by name 0521 keys=res.keys() 0522 declist=[ (res[k]['name'], k) for k in keys] 0523 declist.sort(_cmpfunc) 0524 0525 return [res[k[1]] for k in declist] 0526 0527 0528 if __name__=="__main__": 0529 res=comscan() 0530 0531 output="ComScan "+version+"\n\n" 0532 0533 for r in res: 0534 rkeys=r.keys() 0535 rkeys.sort() 0536 0537 output+=r['name']+":\n" 0538 offset=0 0539 for rk in rkeys: 0540 if rk=='name': continue 0541 v=r[rk] 0542 if not isinstance(v, type("")): v=`v` 0543 op=' %s: %s ' % (rk, v) 0544 if offset+len(op)>78: 0545 output+="\n"+op 0546 offset=len(op)+1 0547 else: 0548 output+=op 0549 offset+=len(op) 0550 0551 if output[-1]!="\n": 0552 output+="\n" 0553 output+="\n" 0554 offset=0 0555 0556 print output 0557 0558 0559
Generated by PyXR 0.9.4