ftdi2.py 11.9 KB
#!/usr/bin/env python
'''This module is the first implementation of FTD2xx driver for the
FTDI USB chips. Initial implementation is for functions from the
dll required for present project'''
# (from: http://fluidmotion.dyndns.org/zenphoto/index.php?p=news&title=Python-interface-to-FTDI-driver-chip)

import sys
import ctypes as c


__FT_VERSION__ = '1.1'
__FT_LICENCE__ = 'LGPL3'
__FT_AUTHOR__ = 'Jonathan Roadley-Battin'


MAX_DESCRIPTION_SIZE = 256

FT_OK = 0
FT_LIST_NUMBER_ONLY = 0x80000000
FT_LIST_BY_INDEX = 0x40000000
FT_LIST_ALL = 0x20000000
FT_OPEN_BY_SERIAL_NUMBER = 1
FT_PURGE_RX = 1
FT_PURGE_TX = 2

class FtdiBitModes: # added by CJBH
    RESET         = 0x0
    ASYNC_BITBANG = 0x1
    MPSSE         = 0x2
    SYNC_BITBANG  = 0x4
    MCU_HOST      = 0x8
    FAST_SERIAL   = 0x10

ft_messages = ['OK',
                'INVALID_HANDLE',
                'DEVICE_NOT_FOUND',
                'DEVICE_NOT_OPENED',
                'IO_ERROR',
                'INSUFFICIENT_RESOURCES',
                'INVALID_PARAMETER',
                'INVALID_BAUD_RATE',
                'DEVICE_NOT_OPENED_FOR_ERASE',
                'DEVICE_NOT_OPENED_FOR_WRITE',
                'FAILED_TO_WRITE_DEVICE0',
                'EEPROM_READ_FAILED',
                'EEPROM_WRITE_FAILED',
                'EEPROM_ERASE_FAILED',
                'EEPROM_NOT_PRESENT',
                'EEPROM_NOT_PROGRAMMED',
                'INVALID_ARGS',
                'NOT_SUPPORTED',
                'OTHER_ERROR']


if sys.platform == 'win32':
    ft = c.windll.ftd2xx
else:
    ft = c.CDLL('libftd2xx.so')


######################################
##      FTDI exception classes      ##
######################################
class FTDeviceError(Exception):
    '''Exception class for FTDI function returns'''
    def __init__(self,msgnum):
        self.parameter = ft_messages[msgnum]
        self.status = msgnum
    def __str__(self):
        return repr(self.parameter)

#####################################
# CTYPES structure for DeviceInfo   #
#####################################
class DeviceListInfoNode(c.Structure):
    _fields_ = [    ('Flags',c.c_ulong),
                    ('Type',c.c_ulong),
                    ('ID',c.c_ulong),
                    ('LocID',c.c_ulong),
                    ('SerialNumber',(c.c_char * 16)),
                    ('Description',(c.c_char * 64)),
                    ('none',c.c_void_p),
                ]




####################################################
# Shared Lib functions via python function decorator
# for i in $(strings /usr/lib/libftd2xx.so.0.4.16 | grep FT_);do echo -e "@ftExceptionDecorator\ndef _${i/FT/PY}(*args):\n    return ft.$i(*args)\n";done
# Allows common exception routine to be performed on each fn
# Via Bash-liner additional fn can easily be added and specific pythonic fn added when needed
####################################################
def ftExceptionDecorator(f):
    def fn_wrap(*args):
        status = f(*args)
        if status == None:
            status = 18
        if status != FT_OK:
            raise FTDeviceError,status
    return fn_wrap


@ftExceptionDecorator
def _PY_GetDeviceInfo(*args):
    return ft.FT_GetDeviceInfo(*args)

@ftExceptionDecorator
def _PY_OpenEx(*args):
    return ft.FT_OpenEx(*args)

@ftExceptionDecorator
def _PY_Open(*args):
    return ft.FT_Open(*args)

@ftExceptionDecorator
def _PY_ListDevices(*args):
    return ft.FT_ListDevices(*args)

@ftExceptionDecorator
def _PY_Close(*args):
    return ft.FT_Close(*args)

@ftExceptionDecorator
def _PY_Read(*args):
    return ft.FT_Read(*args)

@ftExceptionDecorator
def _PY_Write(*args):
    return ft.FT_Write(*args)

@ftExceptionDecorator
def _PY_SetBaudRate(*args):
    return ft.FT_SetBaudRate(*args)

@ftExceptionDecorator
def _PY_ResetDevice(*args):
    return ft.FT_ResetDevice(*args)

@ftExceptionDecorator
def _PY_Purge(*args):
    return ft.FT_Purge(*args)

@ftExceptionDecorator
def _PY_SetTimeouts(*args):
    return ft.FT_SetTimeouts(*args)

@ftExceptionDecorator
def _PY_SetBitMode(*args): # added by CJBH
    return ft.FT_SetBitMode(*args)

@ftExceptionDecorator
def _PY_GetQueueStatus(*args):
    return ft.FT_GetQueueStatus(*args)

@ftExceptionDecorator
def _PY_GetStatus(*args):
    return ft.FT_GetStatus(*args)

@ftExceptionDecorator
def _PY_SetLatencyTimer(*args):
    return ft.FT_SetLatencyTimer(*args)

@ftExceptionDecorator
def _PY_SetUSBParameters(*args):
    return ft.FT_SetUSBParameters(*args)

@ftExceptionDecorator
def _PY_ResetPort(*args):
    return ft.FT_ResetPort(*args)

@ftExceptionDecorator
def _PY_CyclePort(*args):
    return ft.FT_CyclePort(*args)

@ftExceptionDecorator
def _PY_CreateDeviceInfoList(*args):
    return ft.FT_CreateDeviceInfoList(*args)

@ftExceptionDecorator
def _PY_GetDeviceInfoList(*args):
    return ft.FT_GetDeviceInfoList(*args)

@ftExceptionDecorator
def _PY_GetDeviceInfoDetail(*args):
    return ft.FT_GetDeviceInfoDetail(*args)

@ftExceptionDecorator
def _PY_GetDriverVersion(*args):
    return ft.FT_GetDriverVersion(*args)

@ftExceptionDecorator
def _PY_GetLibraryVersion(*args):
    return ft.FT_GetLibraryVersion(*args)


################################################
# Start of pythonic functions for specific     #
# functionality around FTDI API                #
################################################
def list_devices():
    '''method to list devices connected.
    total connected and specific serial for a device position'''
    n = c.c_ulong()
    _PY_ListDevices(c.byref(n), None, c.c_ulong(FT_LIST_NUMBER_ONLY))

    if n.value:
        p_array = (c.c_char_p*(n.value + 1))()
        for i in xrange(n.value):
            p_array[i] = c.cast(c.c_buffer(64), c.c_char_p)
        _PY_ListDevices(p_array, c.byref(n), c.c_ulong(FT_LIST_ALL|FT_OPEN_BY_SERIAL_NUMBER ))
        return [ser for ser in p_array[:n.value]]
    else:
        return []
#------------------------------------------------------------------------------
def create_device_info_list():
    """Create the internal device info list and return number of entries"""
    lpdwNumDevs = c.c_ulong()
    _PY_CreateDeviceInfoList(c.byref(lpdwNumDevs))
    return lpdwNumDevs.value
#------------------------------------------------------------------------------
def get_device_info_detail(dev=0):
    """Get an entry from the internal device info list. """
    dwIndex = c.c_ulong(dev)
    lpdwFlags = c.c_ulong()
    lpdwType = c.c_ulong()
    lpdwID = c.c_ulong()
    lpdwLocId = c.c_ulong()
    pcSerialNumber = c.c_buffer(MAX_DESCRIPTION_SIZE)
    pcDescription = c.c_buffer(MAX_DESCRIPTION_SIZE)
    ftHandle = c.c_ulong()
    _PY_GetDeviceInfoDetail(dwIndex,
                                c.byref(lpdwFlags),
                                c.byref(lpdwType),
                                c.byref(lpdwID),
                                c.byref(lpdwLocId),
                                pcSerialNumber,
                                pcDescription,
                                c.byref(ftHandle))
    return {'Dev': dwIndex.value,
            'Flags': lpdwFlags.value,
            'Type': lpdwType.value,
            'ID': lpdwID.value,
            'LocId': lpdwLocId.value,
            'SerialNumber': pcSerialNumber.value,
            'Description': pcDescription.value,
            'ftHandle': ftHandle}
#------------------------------------------------------------------------------
def get_device_info_list():
    num_dev =  create_device_info_list()
    dev_info = DeviceListInfoNode * (num_dev + 1)
    pDest = c.pointer(dev_info())
    lpdwNumDevs = c.c_ulong()
    _PY_GetDeviceInfoList( pDest, c.byref(lpdwNumDevs))

    return_list = []
    data = pDest.contents
    for i in data:
        return_list.append({'Flags':i.Flags,'Type':i.Type,'LocID':i.LocID,'SerialNumber':i.SerialNumber,'Description':i.Description})
    return return_list[:-1]
#------------------------------------------------------------------------------
def open_ex(serial=''):
    '''open's FTDI-device by EEPROM-serial (prefered method).
    Serial fetched by the ListDevices fn'''
    ftHandle = c.c_ulong()
    dw_flags = c.c_ulong(FT_OPEN_BY_SERIAL_NUMBER)
    _PY_OpenEx(serial, dw_flags, c.byref(ftHandle))
    return FTD2XX(ftHandle)
#------------------------------------------------------------------------------



######################################
##     FTDI ctypes DLL wrapper      ##
######################################
class FTD2XX(object):
    '''class that implements a ctype interface to the FTDI d2xx driver'''
    def __init__(self, ftHandle):
        '''setup initial ctypes link and some varabled'''
        self.ftHandle = ftHandle
#------------------------------------------------------------------------------
    def set_baud_rate(self, dwBaudRate=921600):
        '''Set baud rate of driver, non-intelgent checking of allowed BAUD'''
        _PY_SetBaudRate(self.ftHandle, c.c_ulong(dwBaudRate))
        return None
#------------------------------------------------------------------------------
    def set_timeouts(self, dwReadTimeout=100, dwWriteTimeout=100):
        '''setup timeout times for TX and RX'''
        _PY_SetTimeouts(self.ftHandle, c.c_ulong(dwReadTimeout), c.c_ulong(dwWriteTimeout))
        return None
#------------------------------------------------------------------------------
    def set_latency_timer(self, ucTimer=16): # added by CJBH
        '''setup latency timer'''
        _PY_SetLatencyTimer(self.ftHandle, c.c_ubyte(ucTimer))
        return None
#------------------------------------------------------------------------------
    def set_bit_mode(self, ucMask=0, ucMode=0): # added by CJBH
        '''setup bit mode'''
        _PY_SetBitMode(self.ftHandle, c.c_ubyte(ucMask), c.c_ubyte(ucMode))
        return None
#------------------------------------------------------------------------------
    def set_usb_parameters(self, dwInTransferSize=4096, dwOutTransferSize=0):
        '''set the drivers input and output buffer size'''
        _PY_SetUSBParameters(self.ftHandle, c.c_ulong(dwInTransferSize), c.c_ulong(dwOutTransferSize))
        return None
#------------------------------------------------------------------------------
    def purge(self, to_purge= 'TXRX'):
        '''purge the in and out buffer of driver.
            Valid arguement = TX,RX,TXRX'''
        if to_purge == 'TXRX':
            dwMask = c.c_ulong(FT_PURGE_RX|FT_PURGE_TX)
        elif to_purge == 'TX':
            dwMask = c.c_ulong(FT_PURGE_TX)
        elif to_purge == 'RX':
            dwMask = c.c_ulong(FT_PURGE_RX)

        _PY_Purge(self.ftHandle, dwMask)
        return None
#------------------------------------------------------------------------------
    def get_queue_status(self):
        '''returns the number of bytes in the RX buffer
        else raises an exception'''
        lpdwAmountInRxQueue = c.c_ulong()
        _PY_GetQueueStatus(self.ftHandle, c.byref(lpdwAmountInRxQueue))
        return lpdwAmountInRxQueue.value
#------------------------------------------------------------------------------
    def write(self, lpBuffer=''):
        '''writes the string-type "data" to the opened port.'''
        lpdwBytesWritten = c.c_ulong()
        _PY_Write(self.ftHandle, lpBuffer, len(lpBuffer), c.byref(lpdwBytesWritten))
        return lpdwBytesWritten.value
#------------------------------------------------------------------------------
    def read(self, dwBytesToRead, raw=True):
        '''Read in int-type of bytes. Returns either the data
        or raises an exception'''
        lpdwBytesReturned = c.c_ulong()
        lpBuffer = c.c_buffer(dwBytesToRead)
        _PY_Read(self.ftHandle, lpBuffer, dwBytesToRead, c.byref(lpdwBytesReturned))
        return lpBuffer.raw[:lpdwBytesReturned.value] if raw else lpBuffer.value[:lpdwBytesReturned.value]
#------------------------------------------------------------------------------
    def reset_device(self):
        '''closes the port.'''
        _PY_ResetDevice(self.ftHandle)
        return None
#------------------------------------------------------------------------------
    def close(self):
        '''closes the port.'''
        _PY_Close(self.ftHandle)
        return None