ftd2xx icon indicating copy to clipboard operation
ftd2xx copied to clipboard

Device status is not handled properly

Open PalladinoMarco opened this issue 2 years ago • 8 comments

Describe the bug When a device is opened using open() or openEx() the status always returns to "INVALID_HANDLE", and when the device is closed the status returns to "OK". If the serial number is not corrected an exception error will be raised "DEVICE_NOT_FOUND", it works well in this case, but it could be a case (the function called returns 2). In defines.py there is the enumerator class "Status" that defines the error constants:

@unique
class Status(IntEnum):
    OK = 0
    INVALID_HANDLE = 1
    DEVICE_NOT_FOUND = 2
    DEVICE_NOT_OPENED = 3
    IO_ERROR = 4
    INSUFFICIENT_RESOURCES = 5
    INVALID_PARAMETER = 6
    INVALID_BAUD_RATE = 7
    DEVICE_NOT_OPENED_FOR_ERASE = 8
    DEVICE_NOT_OPENED_FOR_WRITE = 9
    FAILED_TO_WRITE_DEVICE = 10
    EEPROM_READ_FAILED = 11
    EEPROM_WRITE_FAILED = 12
    EEPROM_ERASE_FAILED = 13
    EEPROM_NOT_PRESENT = 14
    EEPROM_NOT_PROGRAMMED = 15
    INVALID_ARGS = 16
    NOT_SUPPORTED = 17
    OTHER_ERROR = 18

I tried as follow to see the effect, but it is just an example:

class FTD2XX(AbstractContextManager):
    """Class for communicating with an FTDI device"""

    handle: _ft.FT_HANDLE
    status: int

    def __init__(self, handle: _ft.FT_HANDLE, update: bool = True):
        """Create an instance of the FTD2XX class with the given device handle
        and populate the device info in the instance dictionary.

        Args:
            update (bool): Set False to disable automatic (slow) call to
            createDeviceInfoList

        """
        self.handle = handle
        self.status = 0 # OK
        # createDeviceInfoList is slow, only run if update is True
        if update:
            createDeviceInfoList()
        self.__dict__.update(self.getDeviceInfo())

    def close(self) -> None:
        """Close the device handle"""
        call_ft(_ft.FT_Close, self.handle)
        self.status = 3 # DEVICE_NOT_OPENED

To Reproduce

>>> import ftd2xx as ft
>>> from ftd2xx.defines import Status
>>> ft.listDevices()
[b'FT5SAZB5A', b'FT5SAZB5B', b'FT5SAZB5C', b'FT5SAZB5D']
>>> portb = ft.openEx(b'FT5SAZB5B')
>>> portb.status
1
>>> Status(portb.status)
<Status.INVALID_HANDLE: 1>
>>> portb.close()
>>> portb.status
0
>>> Status(portb.status)
<Status.OK: 0>
>>> portb = interface.openEx(b'FT5SAZB')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:\...\ftd2xx\ftd2xx.py", line 243, in openEx
    call_ft(_ft.FT_OpenEx, id_str, _ft.DWORD(flags), c.byref(h))
  File "c:\...\ftd2xx\ftd2xx.py", line 133, in call_ft
    raise DeviceError(status)
ftd2xx.ftd2xx.DeviceError: DEVICE_NOT_FOUND

Expected behavior I expect that the current state of the device is described with the right message.

Desktop:

  • OS: Windows, Linux (Raspberry Pi)
  • OS Version: Windows 10, Raspbian
  • Python Version: 3.7.5, 3.10
  • FTDI driver version: 2.12.36.4, 1.4.27 ARMv6

PalladinoMarco avatar Jan 18 '23 13:01 PalladinoMarco

Maybe status should be a property that calls FT_GetStatus (doesn't matter too much which function) to return current status.

krister-ts avatar Jan 20 '23 19:01 krister-ts

Thank you @krister-ts! Yes maybe. However if I use getstatus() or getEventStatus() I shouldn’t have an exception to get the event status message. For example, if I closed the connection and I would like to know the status using one of the above mentioned methods, the message 'INVALID_HANDLE' will be the exception message.

>>> import ftd2xx as ft
>>> mydevice = ft.openEx(b'FT5SAZB5B')
mydevice.getEventStatus()
0
>>> mydevice.close()
>>> mydevice.getEventStatus()
Traceback (most recent call last):
  ...
  File "c:\...\ftd2xx\ftd2xx.py", line 133, in call_ft
    raise DeviceError(status)
ftd2xx.ftd2xx.DeviceError: INVALID_HANDLE

I would expect instead 'DEVICE_NOT_OPEN', however this way I would capture the exception to know the status. Why? I was hoping I could use the status property to know the status by avoiding exceptions, but it is only used in three cases and in different way.

PalladinoMarco avatar Jan 23 '23 14:01 PalladinoMarco

@PalladinoMarco This is how the library is supposed to work. The handle is invalid after close.

You should use try except block where you know this could raise an exception.

I was saying, this could be done in the status member so that you can avoid the try except in user code.

krister-ts avatar Jan 23 '23 15:01 krister-ts

@krister-ts I wrote an overload method of my ftd2xx child class:

def getEventStatus(self):
        """
        Summary
        -------
        Gets the current device event status.
        """
        try:
            return Status(self.ftdi.getEventStatus()).name
        except ftd2xx.DeviceError as errorMessage:
            return str(errorMessage)

Thanks a lot for your support.

PalladinoMarco avatar Jan 23 '23 15:01 PalladinoMarco

@snmishra Should we change the status member of the class to a @property that will not raise an exception?

krister-ts avatar Jan 23 '23 16:01 krister-ts

@krister-ts I don't think the current status field field is very useful. But changing to a property would be a breaking change. May be something like deviceStatus?

snmishra avatar Jan 23 '23 19:01 snmishra

@krister-ts I don't think the current status field field is very useful. But changing to a property would be a breaking change. May be something like deviceStatus?

I would maybe use snake case so that no one thinks it is a real function from the API (we are keeping camel case on those).

krister-ts avatar Jan 23 '23 21:01 krister-ts

@krister-ts Sounds good. Want to do a PR?

snmishra avatar Jan 28 '23 22:01 snmishra