Source code for pylibftdi.driver

"""
pylibftdi.driver - interface to the libftdi library

Copyright (c) 2010-2014 Ben Bass <benbass@codedstructure.net>
See LICENSE file for details and (absence of) warranty

pylibftdi: http://bitbucket.org/codedstructure/pylibftdi

"""

import itertools

# be disciplined so pyflakes can check us...
from ctypes import (CDLL, byref, c_int, c_char_p, c_void_p, cast,
                    create_string_buffer, Structure, pointer, POINTER)
from ctypes.util import find_library

from pylibftdi._base import FtdiError


[docs]class ftdi_device_list(Structure): _fields_ = [('next', c_void_p), ('dev', c_void_p)]
[docs]class ftdi_version_info(Structure): _fields_ = [('major', c_int), ('minor', c_int), ('micro', c_int), ('version_str', c_char_p), ('snapshot_str', c_char_p)] # Note I gave up on attempts to use ftdi_new/ftdi_free (just using # ctx instead of byref(ctx) in first param of most ftdi_* functions) as # (at least for 64-bit) they only worked if argtypes was declared # (c_void_p for ctx), and that's too much like hard work to maintain. # So I've reverted to using create_string_buffer for memory management, # byref(ctx) to pass in the context instance, and ftdi_init() / # ftdi_deinit() pair to manage the driver resources. It's very nice # how layered the libftdi code is, with access to each layer. # These constants determine what type of flush operation to perform
FLUSH_BOTH = 1 FLUSH_INPUT = 2 FLUSH_OUTPUT = 3 # Device Modes BITMODE_RESET = 0x00 BITMODE_BITBANG = 0x01 # Opening / searching for a device uses this list of IDs to search # by default. These can be extended directly after import if required. FTDI_VENDOR_ID = 0x0403 USB_VID_LIST = [FTDI_VENDOR_ID] USB_PID_LIST = [0x6001, 0x6010, 0x6011, 0x6014] FTDI_ERROR_DEVICE_NOT_FOUND = -3
[docs]class Driver(object): """ This is where it all happens... We load the libftdi library, and use it. """ _instance = None _need_init = True # prefer libftdi1 if available. Windows uses 'lib' prefix. _dll_list = ('ftdi1', 'libftdi1', 'ftdi', 'libftdi')
[docs] def __init__(self, libftdi_search=None): """ :param libftdi_search: force a particular version of libftdi to be used :type libftdi_search: string or sequence of strings """ self._libftdi_path = self._find_libftdi(libftdi_search) self.fdll = CDLL(self._libftdi_path) # most args/return types are fine with the implicit # int/void* which ctypes uses, but some need setting here self.fdll.ftdi_get_error_string.restype = c_char_p self.fdll.ftdi_usb_get_strings.argtypes = ( c_void_p, c_void_p, c_char_p, c_int, c_char_p, c_int, c_char_p, c_int)
def _find_libftdi(self, libftdi_search=None): """ find the libftdi path, suitable for ctypes.CDLL() :param libftdi_search: string or sequence of strings use to force a particular version of libftdi to be used """ if libftdi_search is None: search_list = self._dll_list elif isinstance(libftdi_search, (str, bytes)): search_list = (libftdi_search,) else: search_list = libftdi_search ftdi_lib = None for dll in search_list: ftdi_lib = find_library(dll) if ftdi_lib is not None: break if ftdi_lib is None: raise FtdiError('libftdi library not found (search: {})'.format(search_list)) return ftdi_lib
[docs] def libftdi_version(self): """ :return: the version of the underlying library being used :rtype: tuple (major, minor, micro, version_string, snapshot_string) """ if hasattr(self.fdll, 'ftdi_get_library_version'): version = ftdi_version_info() self.fdll.ftdi_get_library_version(byref(version)) return (version.major, version.minor, version.micro, version.version_str, version.snapshot_str) else: # library versions <1.0 don't support this function... return (0, 0, 0, 'unknown - no ftdi_get_library_version()', 'unknown')
[docs] def list_devices(self): """ :return: (manufacturer, description, serial#) for each attached device, e.g.: [('FTDI', 'UM232R USB <-> Serial', 'FTE4FFVQ'), ('FTDI', 'UM245R', 'FTE00P4L')] :rtype: a list of string triples the serial number can be used to open specific devices """ # ftdi_usb_find_all sets dev_list_ptr to a linked list # (*next/*usb_device) of usb_devices, each of which can # be passed to ftdi_usb_get_strings() to get info about # them. # this will contain the device info to return devices = [] manuf = create_string_buffer(128) desc = create_string_buffer(128) serial = create_string_buffer(128) devlistptrtype = POINTER(ftdi_device_list) dev_list_ptr = devlistptrtype() # create context for doing the enumeration ctx = create_string_buffer(1024) if self.fdll.ftdi_init(byref(ctx)) != 0: msg = self.fdll.ftdi_get_error_string(byref(ctx)) raise FtdiError(msg) try: for usb_vid, usb_pid in itertools.product(USB_VID_LIST, USB_PID_LIST): res = self.fdll.ftdi_usb_find_all(byref(ctx), byref(dev_list_ptr), usb_vid, usb_pid) if res < 0: err_msg = self.fdll.ftdi_get_error_string(byref(ctx)) msg = "%s (%d)" % (err_msg, res) raise FtdiError(msg) elif res > 0: # take a copy of the dev_list for subsequent list_free dev_list_base = pointer(dev_list_ptr.contents) # traverse the linked list... try: while dev_list_ptr: res = self.fdll.ftdi_usb_get_strings( byref(ctx), dev_list_ptr.contents.dev, manuf, 127, desc, 127, serial, 127) # don't error on failure to get all the data # error codes: -7: manuf, -8: desc, -9: serial if res < 0 and res not in (-7, -8, -9): err_msg = self.fdll.ftdi_get_error_string(byref(ctx)) msg = "%s (%d)" % (err_msg, res) raise FtdiError(msg) devices.append((manuf.value, desc.value, serial.value)) # step to next in linked-list dev_list_ptr = cast(dev_list_ptr.contents.next, devlistptrtype) finally: self.fdll.ftdi_list_free(dev_list_base) finally: self.fdll.ftdi_deinit(byref(ctx)) return devices