| #!/usr/bin/env python |
| |
| # portable serial port access with python |
| # |
| # This is a module that gathers a list of serial ports including details on OSX |
| # |
| # code originally from https://github.com/makerbot/pyserial/tree/master/serial/tools |
| # with contributions from cibomahto, dgs3, FarMcKon, tedbrandston |
| # and modifications by cliechti |
| # |
| # this is distributed under a free software license, see license.txt |
| |
| |
| |
| # List all of the callout devices in OS/X by querying IOKit. |
| |
| # See the following for a reference of how to do this: |
| # http://developer.apple.com/library/mac/#documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html#//apple_ref/doc/uid/TP30000384-CIHGEAFD |
| |
| # More help from darwin_hid.py |
| |
| # Also see the 'IORegistryExplorer' for an idea of what we are actually searching |
| |
| import ctypes |
| from ctypes import util |
| import re |
| |
| iokit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('IOKit')) |
| cf = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation')) |
| |
| kIOMasterPortDefault = ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault") |
| kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault") |
| |
| kCFStringEncodingMacRoman = 0 |
| |
| iokit.IOServiceMatching.restype = ctypes.c_void_p |
| |
| iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] |
| iokit.IOServiceGetMatchingServices.restype = ctypes.c_void_p |
| |
| iokit.IORegistryEntryGetParentEntry.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] |
| |
| iokit.IORegistryEntryCreateCFProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32] |
| iokit.IORegistryEntryCreateCFProperty.restype = ctypes.c_void_p |
| |
| iokit.IORegistryEntryGetPath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] |
| iokit.IORegistryEntryGetPath.restype = ctypes.c_void_p |
| |
| iokit.IORegistryEntryGetName.argtypes = [ctypes.c_void_p, ctypes.c_void_p] |
| iokit.IORegistryEntryGetName.restype = ctypes.c_void_p |
| |
| iokit.IOObjectGetClass.argtypes = [ctypes.c_void_p, ctypes.c_void_p] |
| iokit.IOObjectGetClass.restype = ctypes.c_void_p |
| |
| iokit.IOObjectRelease.argtypes = [ctypes.c_void_p] |
| |
| |
| cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int32] |
| cf.CFStringCreateWithCString.restype = ctypes.c_void_p |
| |
| cf.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p, ctypes.c_uint32] |
| cf.CFStringGetCStringPtr.restype = ctypes.c_char_p |
| |
| cf.CFNumberGetValue.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p] |
| cf.CFNumberGetValue.restype = ctypes.c_void_p |
| |
| def get_string_property(device_t, property): |
| """ Search the given device for the specified string property |
| |
| @param device_t Device to search |
| @param property String to search for. |
| @return Python string containing the value, or None if not found. |
| """ |
| key = cf.CFStringCreateWithCString( |
| kCFAllocatorDefault, |
| property.encode("mac_roman"), |
| kCFStringEncodingMacRoman |
| ) |
| |
| CFContainer = iokit.IORegistryEntryCreateCFProperty( |
| device_t, |
| key, |
| kCFAllocatorDefault, |
| 0 |
| ); |
| |
| output = None |
| |
| if CFContainer: |
| output = cf.CFStringGetCStringPtr(CFContainer, 0) |
| |
| return output |
| |
| def get_int_property(device_t, property): |
| """ Search the given device for the specified string property |
| |
| @param device_t Device to search |
| @param property String to search for. |
| @return Python string containing the value, or None if not found. |
| """ |
| key = cf.CFStringCreateWithCString( |
| kCFAllocatorDefault, |
| property.encode("mac_roman"), |
| kCFStringEncodingMacRoman |
| ) |
| |
| CFContainer = iokit.IORegistryEntryCreateCFProperty( |
| device_t, |
| key, |
| kCFAllocatorDefault, |
| 0 |
| ); |
| |
| number = ctypes.c_uint16() |
| |
| if CFContainer: |
| output = cf.CFNumberGetValue(CFContainer, 2, ctypes.byref(number)) |
| |
| return number.value |
| |
| def IORegistryEntryGetName(device): |
| pathname = ctypes.create_string_buffer(100) # TODO: Is this ok? |
| iokit.IOObjectGetClass( |
| device, |
| ctypes.byref(pathname) |
| ) |
| |
| return pathname.value |
| |
| def GetParentDeviceByType(device, parent_type): |
| """ Find the first parent of a device that implements the parent_type |
| @param IOService Service to inspect |
| @return Pointer to the parent type, or None if it was not found. |
| """ |
| # First, try to walk up the IOService tree to find a parent of this device that is a IOUSBDevice. |
| while IORegistryEntryGetName(device) != parent_type: |
| parent = ctypes.c_void_p() |
| response = iokit.IORegistryEntryGetParentEntry( |
| device, |
| "IOService".encode("mac_roman"), |
| ctypes.byref(parent) |
| ) |
| |
| # If we weren't able to find a parent for the device, we're done. |
| if response != 0: |
| return None |
| |
| device = parent |
| |
| return device |
| |
| def GetIOServicesByType(service_type): |
| """ |
| """ |
| serial_port_iterator = ctypes.c_void_p() |
| |
| response = iokit.IOServiceGetMatchingServices( |
| kIOMasterPortDefault, |
| iokit.IOServiceMatching(service_type), |
| ctypes.byref(serial_port_iterator) |
| ) |
| |
| services = [] |
| while iokit.IOIteratorIsValid(serial_port_iterator): |
| service = iokit.IOIteratorNext(serial_port_iterator) |
| if not service: |
| break |
| services.append(service) |
| |
| iokit.IOObjectRelease(serial_port_iterator) |
| |
| return services |
| |
| def comports(): |
| # Scan for all iokit serial ports |
| services = GetIOServicesByType('IOSerialBSDClient') |
| |
| ports = [] |
| for service in services: |
| info = [] |
| |
| # First, add the callout device file. |
| info.append(get_string_property(service, "IOCalloutDevice")) |
| |
| # If the serial port is implemented by a |
| usb_device = GetParentDeviceByType(service, "IOUSBDevice") |
| if usb_device != None: |
| info.append(get_string_property(usb_device, "USB Product Name")) |
| |
| info.append( |
| "USB VID:PID=%x:%x SNR=%s"%( |
| get_int_property(usb_device, "idVendor"), |
| get_int_property(usb_device, "idProduct"), |
| get_string_property(usb_device, "USB Serial Number")) |
| ) |
| else: |
| info.append('n/a') |
| info.append('n/a') |
| |
| ports.append(info) |
| |
| return ports |
| |
| # test |
| if __name__ == '__main__': |
| for port, desc, hwid in sorted(comports()): |
| print "%s: %s [%s]" % (port, desc, hwid) |
| |