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 24import ctypes 25from ctypes import util 26import re 27 28iokit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('IOKit')) 29cf = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation')) 30 31kIOMasterPortDefault = ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault") 32kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault") 33 34kCFStringEncodingMacRoman = 0 35 36iokit.IOServiceMatching.restype = ctypes.c_void_p 37 38iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] 39iokit.IOServiceGetMatchingServices.restype = ctypes.c_void_p 40 41iokit.IORegistryEntryGetParentEntry.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] 42 43iokit.IORegistryEntryCreateCFProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32] 44iokit.IORegistryEntryCreateCFProperty.restype = ctypes.c_void_p 45 46iokit.IORegistryEntryGetPath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] 47iokit.IORegistryEntryGetPath.restype = ctypes.c_void_p 48 49iokit.IORegistryEntryGetName.argtypes = [ctypes.c_void_p, ctypes.c_void_p] 50iokit.IORegistryEntryGetName.restype = ctypes.c_void_p 51 52iokit.IOObjectGetClass.argtypes = [ctypes.c_void_p, ctypes.c_void_p] 53iokit.IOObjectGetClass.restype = ctypes.c_void_p 54 55iokit.IOObjectRelease.argtypes = [ctypes.c_void_p] 56 57 58cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int32] 59cf.CFStringCreateWithCString.restype = ctypes.c_void_p 60 61cf.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p, ctypes.c_uint32] 62cf.CFStringGetCStringPtr.restype = ctypes.c_char_p 63 64cf.CFNumberGetValue.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p] 65cf.CFNumberGetValue.restype = ctypes.c_void_p 66 67def 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 94def 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 121def 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 130def 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 152def 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 174def 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 205if __name__ == '__main__': 206 for port, desc, hwid in sorted(comports()): 207 print "%s: %s [%s]" % (port, desc, hwid) 208 209