Module comscan
[hide private]
[frames] | no frames]

Source Code for Module comscan

  1  #!/usr/bin/env python 
  2   
  3  ### BITPIM 
  4  ### 
  5  ### Copyright (C) 2003-2004 Roger Binns <rogerb@rogerbinns.com> 
  6  ### 
  7  ### This program is free software; you can redistribute it and/or modify 
  8  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
  9  ### 
 10  ### $Id: comscan.py 4368 2007-08-19 23:33:52Z djpham $ 
 11   
 12   
 13  """Detect and enumerate com(serial) ports 
 14   
 15  You should close all com ports you have open before calling any 
 16  functions in this module.  If you don't they will be detected as in 
 17  use. 
 18   
 19  Call the comscan() function It returns a list with each entry being a 
 20  dictionary of useful information.  See the platform notes for what is 
 21  in each one. 
 22   
 23  For your convenience the entries in the list are also sorted into 
 24  an order that would make sense to the user. 
 25   
 26  Platform Notes: 
 27  =============== 
 28   
 29  w  Windows9x 
 30  W  WindowsNT/2K/XP 
 31  L  Linux 
 32  M  Mac 
 33   
 34  wWLM name               string   Serial device name 
 35  wWLM available          Bool     True if it is possible to open this device 
 36  wW   active             Bool     Is the driver actually running?  An example of when this is False 
 37                                   is USB devices or Bluetooth etc that aren't currently plugged in. 
 38                                   If you are presenting stuff for users, do not show entries where 
 39                                   this is false 
 40  w    driverstatus       dict     status is some random number, problem is non-zero if there is some 
 41                                   issue (eg device disabled) 
 42  wW   hardwareinstance   string   instance of the device in the registry as named by Windows 
 43  wWLM description        string   a friendly name to show users 
 44  wW   driverdate         tuple    (year, month, day) 
 45   W   driverversion      string   version string 
 46  wW   driverprovider     string   the manufacturer of the device driver 
 47  wW   driverdescription  string   some generic description of the driver 
 48    L  device             tuple    (major, minor) device specification 
 49    L  driver             string   the driver name from /proc/devices (eg ttyS or ttyUSB) 
 50  """ 
 51   
 52  from __future__ import with_statement 
 53   
 54  version="$Revision: 4368 $" 
 55   
 56  import sys 
 57  import os 
 58  import time 
 59  import glob 
 60   
61 -def _IsWindows():
62 return sys.platform=='win32'
63
64 -def _IsLinux():
65 return sys.platform.startswith('linux')
66
67 -def _IsMac():
68 return sys.platform.startswith('darwin')
69 70 if _IsWindows(): 71 import _winreg 72 import win32file 73 import win32con 74
75 - class RegistryAccess:
76 """A class that is significantly easier to use to access the Registry"""
77 - def __init__(self, hive=_winreg.HKEY_LOCAL_MACHINE):
78 self.rootkey=_winreg.ConnectRegistry(None, hive)
79
80 - def getchildren(self, key):
81 """Returns a list of the child nodes of a key""" 82 k=_winreg.OpenKey(self.rootkey, key) 83 index=0 84 res=[] 85 while 1: 86 try: 87 subkey=_winreg.EnumKey(k, index) 88 res.append(subkey) 89 index+=1 90 except: 91 # ran out of keys 92 break 93 return res
94
95 - def safegetchildren(self, key):
96 """Doesn't throw exception if doesn't exist 97 98 @return: A list of zero or more items""" 99 try: 100 k=_winreg.OpenKey(self.rootkey, key) 101 except: 102 return [] 103 index=0 104 res=[] 105 while 1: 106 try: 107 subkey=_winreg.EnumKey(k, index) 108 res.append(subkey) 109 index+=1 110 except WindowsError,e: 111 if e[0]==259: # No more data is available 112 break 113 elif e[0]==234: # more data is available 114 index+=1 115 continue 116 raise 117 return res
118 119
120 - def getvalue(self, key, node):
121 """Gets a value 122 123 The result is returned as the correct type (string, int, etc)""" 124 k=_winreg.OpenKey(self.rootkey, key) 125 v,t=_winreg.QueryValueEx(k, node) 126 if t==2: 127 return int(v) 128 if t==3: 129 # lsb data 130 res=0 131 mult=1 132 for i in v: 133 res+=ord(i)*mult 134 mult*=256 135 return res 136 # un unicode if possible 137 if isinstance(v, unicode): 138 try: 139 return str(v) 140 except: 141 pass 142 return v
143
144 - def safegetvalue(self, key, node, default=None):
145 """Gets a value and if nothing is found returns the default""" 146 try: 147 return self.getvalue(key, node) 148 except: 149 return default
150
151 - def findkey(self, start, lookfor, prependresult=""):
152 """Searches for the named key""" 153 res=[] 154 for i in self.getchildren(start): 155 if i==lookfor: 156 res.append(prependresult+i) 157 else: 158 l=self.findkey(start+"\\"+i, lookfor, prependresult+i+"\\") 159 res.extend(l) 160 return res
161
162 - def getallchildren(self, start, prependresult=""):
163 """Returns a list of all child nodes in the hierarchy""" 164 res=[] 165 for i in self.getchildren(start): 166 res.append(prependresult+i) 167 l=self.getallchildren(start+"\\"+i, prependresult+i+"\\") 168 res.extend(l) 169 return res
170
171 -def _comscanwindows():
172 """Get detail about all com ports on Windows 173 174 This code functions on both win9x and nt/2k/xp""" 175 # give results back 176 results={} 177 resultscount=0 178 179 # list of active drivers on win98 180 activedrivers={} 181 182 reg=RegistryAccess(_winreg.HKEY_DYN_DATA) 183 k=r"Config Manager\Enum" 184 for device in reg.safegetchildren(k): 185 hw=reg.safegetvalue(k+"\\"+device, "hardwarekey") 186 if hw is None: 187 continue 188 status=reg.safegetvalue(k+"\\"+device, "status", -1) 189 problem=reg.safegetvalue(k+"\\"+device, "problem", -1) 190 activedrivers[hw.upper()]={ 'status': status, 'problem': problem } 191 192 # list of active drivers on winXP. Apparently Win2k is different? 193 reg=RegistryAccess(_winreg.HKEY_LOCAL_MACHINE) 194 k=r"SYSTEM\CurrentControlSet\Services" 195 for service in reg.safegetchildren(k): 196 # we will just take largest number 197 count=int(reg.safegetvalue(k+"\\"+service+"\\Enum", "Count", 0)) 198 next=int(reg.safegetvalue(k+"\\"+service+"\\Enum", "NextInstance", 0)) 199 for id in range(max(count,next)): 200 hw=reg.safegetvalue(k+"\\"+service+"\\Enum", `id`) 201 if hw is None: 202 continue 203 activedrivers[hw.upper()]=None 204 205 206 # scan through everything listed in Enum. Enum is the key containing a list of all 207 # running hardware 208 reg=RegistryAccess(_winreg.HKEY_LOCAL_MACHINE) 209 210 # The three keys are: 211 # 212 # - where to find hardware 213 # This then has three layers of children. 214 # Enum 215 # +-- Category (something like BIOS, PCI etc) 216 # +-- Driver (vendor/product ids etc) 217 # +-- Instance (An actual device. You may have more than one instance) 218 # 219 # - where to find information about drivers. The driver name is looked up in the instance 220 # (using the key "driver") and then looked for as a child key of driverlocation to find 221 # out more about the driver 222 # 223 # - where to look for the portname key. Eg in Win98 it is directly in the instance whereas 224 # in XP it is below "Device Parameters" subkey of the instance 225 226 for enumstr, driverlocation, portnamelocation in ( 227 (r"SYSTEM\CurrentControlSet\Enum", r"SYSTEM\CurrentControlSet\Control\Class", r"\Device Parameters"), # win2K/XP 228 (r"Enum", r"System\CurrentControlSet\Services\Class", ""), # win98 229 ): 230 for category in reg.safegetchildren(enumstr): 231 catstr=enumstr+"\\"+category 232 for driver in reg.safegetchildren(catstr): 233 drvstr=catstr+"\\"+driver 234 for instance in reg.safegetchildren(drvstr): 235 inststr=drvstr+"\\"+instance 236 237 # see if there is a portname 238 name=reg.safegetvalue(inststr+portnamelocation, "PORTNAME", "") 239 240 # We only want com ports 241 if len(name)<4 or name.lower()[:3]!="com": 242 continue 243 244 # Get rid of phantom devices 245 phantom=reg.safegetvalue(inststr, "Phantom", 0) 246 if phantom: 247 continue 248 249 # Lookup the class 250 klassguid=reg.safegetvalue(inststr, "ClassGUID") 251 if klassguid is not None: 252 # win2k uses ClassGuid 253 klass=reg.safegetvalue(driverlocation+"\\"+klassguid, "Class") 254 else: 255 # Win9x and WinXP use Class 256 klass=reg.safegetvalue(inststr, "Class") 257 258 if klass is None: 259 continue 260 klass=klass.lower() 261 if klass=='ports': 262 klass='serial' 263 elif klass=='modem': 264 klass='modem' 265 else: 266 continue 267 268 # verify COM is followed by digits only 269 try: 270 portnum=int(name[3:]) 271 except: 272 continue 273 274 # we now have some sort of match 275 res={} 276 277 res['name']=name.upper() 278 res['class']=klass 279 280 # is the device active? 281 kp=inststr[len(enumstr)+1:].upper() 282 if kp in activedrivers: 283 res['active']=True 284 if activedrivers[kp] is not None: 285 res['driverstatus']=activedrivers[kp] 286 else: 287 res['active']=False 288 289 # available? 290 if res['active']: 291 try: 292 usename=name 293 if sys.platform=='win32' and name.lower().startswith("com"): 294 usename="\\\\?\\"+name 295 print "scanning " +usename 296 ComPort = win32file.CreateFile(usename, 297 win32con.GENERIC_READ | win32con.GENERIC_WRITE, 0, None, 298 win32con.OPEN_EXISTING, win32con.FILE_ATTRIBUTE_NORMAL, None) 299 win32file.CloseHandle(ComPort) 300 res['available']=True 301 except Exception,e: 302 print usename,"is not available",e 303 res['available']=False 304 else: 305 res['available']=False 306 307 # hardwareinstance 308 res['hardwareinstance']=kp 309 310 # friendly name 311 res['description']=reg.safegetvalue(inststr, "FriendlyName", "<No Description>") 312 313 # driver information key 314 drv=reg.safegetvalue(inststr, "Driver") 315 316 if drv is not None: 317 driverkey=driverlocation+"\\"+drv 318 319 # get some useful driver information 320 for subkey, reskey in \ 321 ("driverdate", "driverdate"), \ 322 ("providername", "driverprovider"), \ 323 ("driverdesc", "driverdescription"), \ 324 ("driverversion", "driverversion"): 325 val=reg.safegetvalue(driverkey, subkey, None) 326 if val is None: 327 continue 328 if reskey=="driverdate": 329 try: 330 val2=val.split('-') 331 val=int(val2[2]), int(val2[0]), int(val2[1]) 332 except: 333 # ignroe wierd dates 334 continue 335 res[reskey]=val 336 337 results[resultscount]=res 338 resultscount+=1 339 340 341 return results
342 343 # There follows a demonstration of how user friendly Linux is. 344 # Users are expected by some form of magic to know exactly what 345 # the names of their devices are. We can't even tell the difference 346 # between a serial port not existing, and there not being sufficient 347 # permission to open it
348 -def _comscanlinux(maxnum=9):
349 """Get all the ports on Linux 350 351 Note that Linux doesn't actually provide any way to enumerate actual ports. 352 Consequently we just look for device nodes. It still isn't possible to 353 establish if there are actual device drivers behind them. The availability 354 testing is done however. 355 356 @param maxnum: The highest numbered device to look for (eg maxnum of 17 357 will look for ttyS0 ... ttys17) 358 """ 359 360 # track of mapping majors to drivers 361 drivers={} 362 363 # get the list of char drivers from /proc/drivers 364 with file('/proc/devices', 'r') as f: 365 f.readline() # skip "Character devices:" header 366 for line in f.readlines(): 367 line=line.split() 368 if len(line)!=2: 369 break # next section 370 major,driver=line 371 drivers[int(major)]=driver 372 373 # device nodes we have seen so we don't repeat them in listing 374 devcache={} 375 376 resultscount=0 377 results={} 378 for prefix, description, klass in ( 379 ("/dev/cua", "Standard serial port", "serial"), 380 ("/dev/ttyUSB", "USB to serial convertor", "serial"), 381 ("/dev/ttyACM", "USB modem", "modem"), 382 ("/dev/rfcomm", "Bluetooth", "modem"), 383 ("/dev/usb/ttyUSB", "USB to serial convertor", "serial"), 384 ("/dev/usb/tts/", "USB to serial convertor", "serial"), 385 ("/dev/usb/acm/", "USB modem", "modem"), 386 ("/dev/input/ttyACM", "USB modem", "modem") 387 ): 388 for num in range(maxnum+1): 389 name=prefix+`num` 390 if not os.path.exists(name): 391 continue 392 res={} 393 res['name']=name 394 res['class']=klass 395 res['description']=description+" ("+name+")" 396 dev=os.stat(name).st_rdev 397 try: 398 with file(name, 'rw'): 399 res['available']=True 400 except: 401 res['available']=False 402 # linux specific, and i think they do funky stuff on kernel 2.6 403 # there is no way to get these 'normally' from the python library 404 major=(dev>>8)&0xff 405 minor=dev&0xff 406 res['device']=(major, minor) 407 if drivers.has_key(major): 408 res['driver']=drivers[major] 409 410 if res['available']: 411 if dev not in devcache or not devcache[dev][0]['available']: 412 results[resultscount]=res 413 resultscount+=1 414 devcache[dev]=[res] 415 continue 416 # not available, so add 417 try: 418 devcache[dev].append(res) 419 except: 420 devcache[dev]=[res] 421 # add in one failed device type per major/minor 422 for dev in devcache: 423 if devcache[dev][0]['available']: 424 continue 425 results[resultscount]=devcache[dev][0] 426 resultscount+=1 427 return results
428 429
430 -def _comscanmac():
431 """Get all the ports on Mac 432 433 Just look for /dev/cu.* entries, they all seem to populate here whether 434 USB->Serial, builtin, bluetooth, etc... 435 436 """ 437 438 resultscount=0 439 results={} 440 for name in glob.glob("/dev/cu.*"): 441 res={} 442 res['name']=name 443 if name.upper().rfind("MODEM") >= 0: 444 res['description']="Modem"+" ("+name+")" 445 res['class']="modem" 446 else: 447 res['description']="Serial"+" ("+name+")" 448 res['class']="serial" 449 try: 450 with file(name, 'rw'): 451 res['available']=True 452 except: 453 res['available']=False 454 results[resultscount]=res 455 resultscount+=1 456 return results
457 458 ##def availableports(): 459 ## """Gets list of available ports 460 461 ## It is verified that the ports can be opened. 462 463 ## @note: You must close any ports you have open before calling this function, otherwise they 464 ## will not be considered available. 465 466 ## @return: List of tuples. Each tuple is (port name, port description) - the description is user 467 ## friendly. The list is sorted. 468 ## """ 469 ## pass 470
471 -def _stringint(str):
472 """Seperate a string and trailing number into a tuple 473 474 For example "com10" returns ("com", 10) 475 """ 476 prefix=str 477 suffix="" 478 479 while len(prefix) and prefix[-1]>='0' and prefix[-1]<='9': 480 suffix=prefix[-1]+suffix 481 prefix=prefix[:-1] 482 483 if len(suffix): 484 return (prefix, int(suffix)) 485 else: 486 return (prefix, None)
487
488 -def _cmpfunc(a,b):
489 """Comparison function for two port names 490 491 In particular it looks for a number on the end, and sorts by the prefix (as a 492 string operation) and then by the number. This function is needed because 493 "com9" needs to come before "com10" 494 """ 495 496 aa=_stringint(a[0]) 497 bb=_stringint(b[0]) 498 499 if aa==bb: 500 if a[1]==b[1]: 501 return 0 502 if a[1]<b[1]: 503 return -1 504 return 1 505 if aa<bb: return -1 506 return 1
507
508 -def comscan(*args, **kwargs):
509 """Call platform specific version of comscan function""" 510 res={} 511 if _IsWindows(): 512 res=_comscanwindows(*args, **kwargs) 513 elif _IsLinux(): 514 res=_comscanlinux(*args, **kwargs) 515 elif _IsMac(): 516 res=_comscanmac(*args, **kwargs) 517 else: 518 raise Exception("unknown platform "+sys.platform) 519 520 # sort by name 521 keys=res.keys() 522 declist=[ (res[k]['name'], k) for k in keys] 523 declist.sort(_cmpfunc) 524 525 return [res[k[1]] for k in declist]
526 527 528 if __name__=="__main__": 529 res=comscan() 530 531 output="ComScan "+version+"\n\n" 532 533 for r in res: 534 rkeys=r.keys() 535 rkeys.sort() 536 537 output+=r['name']+":\n" 538 offset=0 539 for rk in rkeys: 540 if rk=='name': continue 541 v=r[rk] 542 if not isinstance(v, type("")): v=`v` 543 op=' %s: %s ' % (rk, v) 544 if offset+len(op)>78: 545 output+="\n"+op 546 offset=len(op)+1 547 else: 548 output+=op 549 offset+=len(op) 550 551 if output[-1]!="\n": 552 output+="\n" 553 output+="\n" 554 offset=0 555 556 print output 557