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
from collections import namedtuple

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

from pylibftdi._base import FtdiError, LibraryMissingError


[docs]class libusb_version_struct(Structure): _fields_ = [('major', c_uint16), ('minor', c_uint16), ('micro', c_uint16), ('nano', c_uint16), ('rc', c_char_p), ('describe', c_char_p)]
libusb_version = namedtuple('libusb_version', 'major minor micro nano rc describe')
[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)]
libftdi_version = namedtuple('libftdi_version', 'major minor micro version_str snapshot_str') # 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. """ # prefer libftdi1 if available. Windows uses 'lib' prefix. _lib_search = { 'libftdi': ('ftdi1', 'libftdi1', 'ftdi', 'libftdi'), 'libusb': ('usb-1.0', 'libusb-1.0') }
[docs] def __init__(self, libftdi_search=None): """ :param libftdi_search: force a particular version of libftdi to be used can specify either library name(s) or path(s) :type libftdi_search: string or sequence of strings """ if libftdi_search is not None: self._lib_search['libftdi'] = libftdi_search
def _load_library(self, name, search_list=None): """ find and load the requested library :param name: library name :param search_list: sequence or string referring to library names library names or paths can be given :return: a CDLL object referring to the requested library """ if search_list is None: search_list = self._lib_search.get(name, ()) if isinstance(search_list, (str, bytes)): search_list = (search_list,) lib = None for dll in search_list: try: # Windows in particular can have find_library # not find things which work fine directly on # cdll access. lib = getattr(cdll, dll) break except OSError: lib_path = find_library(dll) if lib_path is not None: lib = getattr(cdll, lib_path) break if lib is None: raise LibraryMissingError('{} library not found (search: {})'.format( name, search_list)) return lib @property def _libusb(self): """ ctypes DLL referencing the libusb library, if it exists Note this is not normally used directly by pylibftdi, and is available primarily for diagnostic purposes. """ if self._libusb_dll is None: self._libusb_dll = self._load_library('libusb') self._libusb_dll.libusb_get_version.restype = POINTER(libusb_version_struct) return self._libusb_dll _libusb_dll = None
[docs] def libusb_version(self): """ :return: namedtuple containing version info on libusb """ ver = self._libusb.libusb_get_version().contents return libusb_version(ver.major, ver.minor, ver.micro, ver.nano, ver.rc, ver.describe)
@property def fdll(self): """ ctypes DLL referencing the libftdi library This is the main interface to FTDI functionality. """ if self._fdll is None: self._fdll = self._load_library('libftdi') # 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) return self._fdll _fdll = None
[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 libftdi_version(version.major, version.minor, version.micro, version.version_str, version.snapshot_str) else: # library versions <1.0 don't support this function... return libftdi_version(0, 0, 0, '< 1.0 - 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