15d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#!/usr/bin/env python
25d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
35d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# portable serial port access with python
45d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#
55d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# This is a module that gathers a list of serial ports including details on OSX
65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#
75d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# code originally from https://github.com/makerbot/pyserial/tree/master/serial/tools
85d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# with contributions from cibomahto, dgs3, FarMcKon, tedbrandston
95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# and modifications by cliechti
105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#
115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# this is distributed under a free software license, see license.txt
125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# List all of the callout devices in OS/X by querying IOKit.
165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# See the following for a reference of how to do this:
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# http://developer.apple.com/library/mac/#documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html#//apple_ref/doc/uid/TP30000384-CIHGEAFD
195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# More help from darwin_hid.py
215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Also see the 'IORegistryExplorer' for an idea of what we are actually searching
235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import ctypes
255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from ctypes import util
265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import re
275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)iokit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('IOKit'))
295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)cf = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation'))
305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)kIOMasterPortDefault = ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault")
325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault")
335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)kCFStringEncodingMacRoman = 0
355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)iokit.IOServiceMatching.restype = ctypes.c_void_p
375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)iokit.IOServiceGetMatchingServices.restype = ctypes.c_void_p
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)iokit.IORegistryEntryGetParentEntry.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)iokit.IORegistryEntryCreateCFProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32]
445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)iokit.IORegistryEntryCreateCFProperty.restype = ctypes.c_void_p
455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)iokit.IORegistryEntryGetPath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)iokit.IORegistryEntryGetPath.restype = ctypes.c_void_p
485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)iokit.IORegistryEntryGetName.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)iokit.IORegistryEntryGetName.restype = ctypes.c_void_p
515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)iokit.IOObjectGetClass.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)iokit.IOObjectGetClass.restype = ctypes.c_void_p
545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)iokit.IOObjectRelease.argtypes = [ctypes.c_void_p]
565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int32]
595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)cf.CFStringCreateWithCString.restype = ctypes.c_void_p
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)cf.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)cf.CFStringGetCStringPtr.restype = ctypes.c_char_p
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)cf.CFNumberGetValue.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p]
655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)cf.CFNumberGetValue.restype = ctypes.c_void_p
665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def get_string_property(device_t, property):
685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """ Search the given device for the specified string property
695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    @param device_t Device to search
715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    @param property String to search for.
725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    @return Python string containing the value, or None if not found.
735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    key = cf.CFStringCreateWithCString(
755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        kCFAllocatorDefault,
765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        property.encode("mac_roman"),
775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        kCFStringEncodingMacRoman
785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    )
795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    CFContainer = iokit.IORegistryEntryCreateCFProperty(
815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        device_t,
825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        key,
835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        kCFAllocatorDefault,
845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        0
855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    );
865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    output = None
885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if CFContainer:
905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        output = cf.CFStringGetCStringPtr(CFContainer, 0)
915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return output
935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def get_int_property(device_t, property):
955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """ Search the given device for the specified string property
965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    @param device_t Device to search
985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    @param property String to search for.
995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    @return Python string containing the value, or None if not found.
1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    key = cf.CFStringCreateWithCString(
1025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        kCFAllocatorDefault,
1035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        property.encode("mac_roman"),
1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        kCFStringEncodingMacRoman
1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    )
1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    CFContainer = iokit.IORegistryEntryCreateCFProperty(
1085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        device_t,
1095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        key,
1105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        kCFAllocatorDefault,
1115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        0
1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    );
1135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    number = ctypes.c_uint16()
1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if CFContainer:
1175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        output = cf.CFNumberGetValue(CFContainer, 2, ctypes.byref(number))
1185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return number.value
1205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def IORegistryEntryGetName(device):
1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    pathname = ctypes.create_string_buffer(100) # TODO: Is this ok?
1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    iokit.IOObjectGetClass(
1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        device,
1255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        ctypes.byref(pathname)
1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    )
1275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return pathname.value
1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def GetParentDeviceByType(device, parent_type):
1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """ Find the first parent of a device that implements the parent_type
1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        @param IOService Service to inspect
1335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        @return Pointer to the parent type, or None if it was not found.
1345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
1355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # First, try to walk up the IOService tree to find a parent of this device that is a IOUSBDevice.
1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    while IORegistryEntryGetName(device) != parent_type:
1375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        parent = ctypes.c_void_p()
1385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        response = iokit.IORegistryEntryGetParentEntry(
1395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            device,
1405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            "IOService".encode("mac_roman"),
1415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            ctypes.byref(parent)
1425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        )
1435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # If we weren't able to find a parent for the device, we're done.
1455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if response != 0:
1465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            return None
1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        device = parent
1495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return device
1515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def GetIOServicesByType(service_type):
1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
1555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    serial_port_iterator = ctypes.c_void_p()
1565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    response = iokit.IOServiceGetMatchingServices(
1585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        kIOMasterPortDefault,
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        iokit.IOServiceMatching(service_type),
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        ctypes.byref(serial_port_iterator)
1615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    )
1625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    services = []
1645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    while iokit.IOIteratorIsValid(serial_port_iterator):
1655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        service = iokit.IOIteratorNext(serial_port_iterator)
1665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if not service:
1675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            break
1685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        services.append(service)
1695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    iokit.IOObjectRelease(serial_port_iterator)
1715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return services
1735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def comports():
1755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Scan for all iokit serial ports
1765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    services = GetIOServicesByType('IOSerialBSDClient')
1775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    ports = []
1795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    for service in services:
1805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        info = []
1815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # First, add the callout device file.
1835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        info.append(get_string_property(service, "IOCalloutDevice"))
1845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # If the serial port is implemented by a
1865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        usb_device = GetParentDeviceByType(service, "IOUSBDevice")
1875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if usb_device != None:
1885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            info.append(get_string_property(usb_device, "USB Product Name"))
1895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            info.append(
1915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                "USB VID:PID=%x:%x SNR=%s"%(
1925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                get_int_property(usb_device, "idVendor"),
1935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                get_int_property(usb_device, "idProduct"),
1945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                get_string_property(usb_device, "USB Serial Number"))
1955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            )
1965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        else:
1975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           info.append('n/a')
1985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)           info.append('n/a')
1995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        ports.append(info)
2015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return ports
2035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# test
2055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)if __name__ == '__main__':
2065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    for port, desc, hwid in sorted(comports()):
2075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        print "%s: %s [%s]" % (port, desc, hwid)
2085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
209