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