Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python
      2 
      3 # portable serial port access with python
      4 #
      5 # This is a module that gathers a list of serial ports including details on OSX
      6 #
      7 # code originally from https://github.com/makerbot/pyserial/tree/master/serial/tools
      8 # with contributions from cibomahto, dgs3, FarMcKon, tedbrandston
      9 # and modifications by cliechti
     10 #
     11 # this is distributed under a free software license, see license.txt
     12 
     13 
     14 
     15 # List all of the callout devices in OS/X by querying IOKit.
     16 
     17 # See the following for a reference of how to do this:
     18 # http://developer.apple.com/library/mac/#documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html#//apple_ref/doc/uid/TP30000384-CIHGEAFD
     19 
     20 # More help from darwin_hid.py
     21 
     22 # Also see the 'IORegistryExplorer' for an idea of what we are actually searching
     23 
     24 import ctypes
     25 from ctypes import util
     26 import re
     27 
     28 iokit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('IOKit'))
     29 cf = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation'))
     30 
     31 kIOMasterPortDefault = ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault")
     32 kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault")
     33 
     34 kCFStringEncodingMacRoman = 0
     35 
     36 iokit.IOServiceMatching.restype = ctypes.c_void_p
     37 
     38 iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
     39 iokit.IOServiceGetMatchingServices.restype = ctypes.c_void_p
     40 
     41 iokit.IORegistryEntryGetParentEntry.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
     42 
     43 iokit.IORegistryEntryCreateCFProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32]
     44 iokit.IORegistryEntryCreateCFProperty.restype = ctypes.c_void_p
     45 
     46 iokit.IORegistryEntryGetPath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
     47 iokit.IORegistryEntryGetPath.restype = ctypes.c_void_p
     48 
     49 iokit.IORegistryEntryGetName.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
     50 iokit.IORegistryEntryGetName.restype = ctypes.c_void_p
     51 
     52 iokit.IOObjectGetClass.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
     53 iokit.IOObjectGetClass.restype = ctypes.c_void_p
     54 
     55 iokit.IOObjectRelease.argtypes = [ctypes.c_void_p]
     56 
     57 
     58 cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int32]
     59 cf.CFStringCreateWithCString.restype = ctypes.c_void_p
     60 
     61 cf.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
     62 cf.CFStringGetCStringPtr.restype = ctypes.c_char_p
     63 
     64 cf.CFNumberGetValue.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p]
     65 cf.CFNumberGetValue.restype = ctypes.c_void_p
     66 
     67 def get_string_property(device_t, property):
     68     """ Search the given device for the specified string property
     69 
     70     @param device_t Device to search
     71     @param property String to search for.
     72     @return Python string containing the value, or None if not found.
     73     """
     74     key = cf.CFStringCreateWithCString(
     75         kCFAllocatorDefault,
     76         property.encode("mac_roman"),
     77         kCFStringEncodingMacRoman
     78     )
     79 
     80     CFContainer = iokit.IORegistryEntryCreateCFProperty(
     81         device_t,
     82         key,
     83         kCFAllocatorDefault,
     84         0
     85     );
     86 
     87     output = None
     88 
     89     if CFContainer:
     90         output = cf.CFStringGetCStringPtr(CFContainer, 0)
     91 
     92     return output
     93 
     94 def get_int_property(device_t, property):
     95     """ Search the given device for the specified string property
     96 
     97     @param device_t Device to search
     98     @param property String to search for.
     99     @return Python string containing the value, or None if not found.
    100     """
    101     key = cf.CFStringCreateWithCString(
    102         kCFAllocatorDefault,
    103         property.encode("mac_roman"),
    104         kCFStringEncodingMacRoman
    105     )
    106 
    107     CFContainer = iokit.IORegistryEntryCreateCFProperty(
    108         device_t,
    109         key,
    110         kCFAllocatorDefault,
    111         0
    112     );
    113 
    114     number = ctypes.c_uint16()
    115 
    116     if CFContainer:
    117         output = cf.CFNumberGetValue(CFContainer, 2, ctypes.byref(number))
    118 
    119     return number.value
    120 
    121 def IORegistryEntryGetName(device):
    122     pathname = ctypes.create_string_buffer(100) # TODO: Is this ok?
    123     iokit.IOObjectGetClass(
    124         device,
    125         ctypes.byref(pathname)
    126     )
    127 
    128     return pathname.value
    129 
    130 def GetParentDeviceByType(device, parent_type):
    131     """ Find the first parent of a device that implements the parent_type
    132         @param IOService Service to inspect
    133         @return Pointer to the parent type, or None if it was not found.
    134     """
    135     # First, try to walk up the IOService tree to find a parent of this device that is a IOUSBDevice.
    136     while IORegistryEntryGetName(device) != parent_type:
    137         parent = ctypes.c_void_p()
    138         response = iokit.IORegistryEntryGetParentEntry(
    139             device,
    140             "IOService".encode("mac_roman"),
    141             ctypes.byref(parent)
    142         )
    143 
    144         # If we weren't able to find a parent for the device, we're done.
    145         if response != 0:
    146             return None
    147 
    148         device = parent
    149 
    150     return device
    151 
    152 def GetIOServicesByType(service_type):
    153     """
    154     """
    155     serial_port_iterator = ctypes.c_void_p()
    156 
    157     response = iokit.IOServiceGetMatchingServices(
    158         kIOMasterPortDefault,
    159         iokit.IOServiceMatching(service_type),
    160         ctypes.byref(serial_port_iterator)
    161     )
    162 
    163     services = []
    164     while iokit.IOIteratorIsValid(serial_port_iterator):
    165         service = iokit.IOIteratorNext(serial_port_iterator)
    166         if not service:
    167             break
    168         services.append(service)
    169 
    170     iokit.IOObjectRelease(serial_port_iterator)
    171 
    172     return services
    173 
    174 def comports():
    175     # Scan for all iokit serial ports
    176     services = GetIOServicesByType('IOSerialBSDClient')
    177 
    178     ports = []
    179     for service in services:
    180         info = []
    181 
    182         # First, add the callout device file.
    183         info.append(get_string_property(service, "IOCalloutDevice"))
    184 
    185         # If the serial port is implemented by a
    186         usb_device = GetParentDeviceByType(service, "IOUSBDevice")
    187         if usb_device != None:
    188             info.append(get_string_property(usb_device, "USB Product Name"))
    189 
    190             info.append(
    191                 "USB VID:PID=%x:%x SNR=%s"%(
    192                 get_int_property(usb_device, "idVendor"),
    193                 get_int_property(usb_device, "idProduct"),
    194                 get_string_property(usb_device, "USB Serial Number"))
    195             )
    196         else:
    197            info.append('n/a')
    198            info.append('n/a')
    199 
    200         ports.append(info)
    201 
    202     return ports
    203 
    204 # test
    205 if __name__ == '__main__':
    206     for port, desc, hwid in sorted(comports()):
    207         print "%s: %s [%s]" % (port, desc, hwid)
    208 
    209