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