PyXR

c:\projects\bitpim\src \ comscan.py



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