Skip to content

FTDI¤

Prerequisites¤

Before communicating with equipment that use a Future Technology Devices International (FTDI) chip for the interface, either a libusb-compatible driver or the d2xx driver must be installed and the directory to the appropriate library file (libusb or ftd2xx) must be available on the PATH environment variable.

Tip

If using the d2xx driver, you can create a D2XX_LIBRARY environment variable with the value being the full path to a d2xx library file to load. This variable can also be defined as a <d2xx_library> element in a Configuration file.

The following instructions are intended to be a starting point if you are having issues communicating with an FTDI chip.

Windows¤

The choice of using a libusb or d2xx driver depends on whether the manufacturer of the equipment provides software that you also want to use to control the equipment and what driver their software uses. On Windows, this is typically the d2xx driver. If you don't want to use the manufacturer's software (or they don't provide software) then the choice of which driver to use does not matter.

If you want to use a libusb driver, follow these instructions.

If you want to use the d2xx driver, follow the installation guide (skip this step if the Windows Device Manager shows that the driver for the equipment has already been successfully installed), download the zip file that contains the ftd2xx library file then extract the zip file and copy the appropriate library file (i.e., amd64\ftd2xx64.dll if using 64-bit Python, i386\ftd2xx.dll if using 32-bit Python) to a directory that is on your PATH environment variable.

Debian/Ubuntu¤

If you want to use the libusb driver (recommended), follow these instructions.

If you want to use the d2xx driver, follow the installation guide, download the compressed-archive file that contains the libftd2xx.so library file then extract the archive and copy the library file (i.e., linux-x86_64\libftd2xx.so) to a directory that is on your PATH environment variable, and, finally create a udev rule to be able to access the equipment as a non-root user (follow the udev instructions from here).

macOS¤

If you want to use the libusb driver (recommended), follow these instructions.

If you want to use the d2xx driver, follow the installation guide, download the virtual-disk file that contains the libftd2xx.dylib library file then extract the file and copy the library file (i.e., release\build\libftd2xx.1.4.30.dylib) to a directory that is on your PATH environment variable, and, finally rename the library file to be libftd2xx.dylib (i.e., remove the version information).

FTDI (MessageBased) ¤

FTDI(equipment: Equipment)

Base class for equipment that use a Future Technology Devices International (FTDI) chip for communication.

Parameters:

Name Type Description Default
equipment Equipment

An Equipment instance.

required

A Connection instance supports the following properties for the FTDI communication protocol, as well as the properties defined in MessageBased. The DataBits, Parity and StopBits enumeration names or values may also be used. For properties that specify an alias, you may also use the alternative name as the property name. The read_termination and write_termination values are automatically set to None (termination characters are not used in the FTDI protocol).

Connection Properties:

Name Type Description
baud_rate int

The baud rate (alias: baudrate). Default: 9600

data_bits DataBits | str | int

The number of data bits: 7 or 8 (alias: bytesize). Default: 8

dsr_dtr bool

Whether to enable hardware (DSR/DTR) flow control (alias: dsrdtr). Default: False

latency int | None

The latency-timer value in milliseconds, between 1 and 255. If None, the latency timer is not set and the chip default is used, typically 16 ms. Default: None

parity Parity | str

Parity checking: NONE, ODD, EVEN, MARK or SPACE. Default: NONE

rts_cts bool

Whether to enable hardware (RTS/CTS) flow control (alias: rtscts). Default: False

stop_bits StopBits | str | float

The number of stop bits: 1, 1.5 or 2 (alias: stopbits). Default: 1

xon_xoff bool

Whether to enable software flow control (alias: xonxoff). Default: False

Source code in src/msl/equipment/interfaces/ftdi.py
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
def __init__(self, equipment: Equipment) -> None:
    """Base class for equipment that use a Future Technology Devices International (FTDI) chip for communication.

    Args:
        equipment: An [Equipment][] instance.

    A [Connection][msl.equipment.schema.Connection] instance supports the following _properties_
    for the FTDI communication protocol, as well as the _properties_ defined in
    [MessageBased][msl.equipment.interfaces.message_based.MessageBased]. The
    [DataBits][msl.equipment.enumerations.DataBits],
    [Parity][msl.equipment.enumerations.Parity] and [StopBits][msl.equipment.enumerations.StopBits]
    enumeration names or values may also be used. For properties that specify an _alias_, you
    may also use the alternative name as the property name. The
    [read_termination][msl.equipment.interfaces.message_based.MessageBased.read_termination] and
    [write_termination][msl.equipment.interfaces.message_based.MessageBased.write_termination] values
    are automatically set to `None` (termination characters are not used in the FTDI protocol).

    Attributes: Connection Properties:
        baud_rate (int): The baud rate (_alias:_ baudrate).
            _Default: `9600`_
        data_bits (DataBits | str | int): The number of data bits: 7 or 8 (_alias:_ bytesize).
            _Default: `8`_
        dsr_dtr (bool): Whether to enable hardware (DSR/DTR) flow control (_alias:_ dsrdtr).
            _Default: `False`_
        latency (int | None): The latency-timer value in milliseconds, between 1 and 255.
            If `None`, the latency timer is not set and the chip default is used, typically 16 ms.
            _Default: `None`_
        parity (Parity | str): Parity checking: NONE, ODD, EVEN, MARK or SPACE.
            _Default: `NONE`_
        rts_cts (bool): Whether to enable hardware (RTS/CTS) flow control (_alias:_ rtscts).
            _Default: `False`_
        stop_bits (StopBits | str | float): The number of stop bits: 1, 1.5 or 2 (_alias:_ stopbits).
            _Default: `1`_
        xon_xoff (bool): Whether to enable software flow control (_alias:_ xonxoff).
            _Default: `False`_
    """
    self._d2xx: _D2XX | None = None
    self._libusb: USB | None = None
    super().__init__(equipment)

    self.check_packet_for_errors: bool = True
    """Whether to check the bit mask of the `line` byte in a read packet for errors.

    This attribute is only used if *libusb* is used as the communication library.
    See [poll_status][msl.equipment.interfaces.ftdi.FTDI.poll_status] for more details
    about the `line` bit mask.
    """

    self._read_termination: bytes | None = None
    self._write_termination: bytes | None = None

    assert equipment.connection is not None  # noqa: S101
    parsed = parse_ftdi_address(equipment.connection.address)
    if parsed is None:
        msg = f"Invalid FTDI address {equipment.connection.address!r}"
        raise ValueError(msg)

    self._out_req_type: int = -1
    self._in_req_type: int = -1
    self._index: int = -1
    self._characteristics: int = 0
    self._buffer: bytearray = bytearray()

    if parsed.driver == 0:
        self._libusb = libusb = USB(equipment)
        libusb._str = self._str  # noqa: SLF001
        self._index = libusb.bulk_in_endpoint.interface_number + 1
        self._out_req_type = libusb.build_request_type(
            direction=libusb.CtrlDirection.OUT,
            type=libusb.CtrlType.VENDOR,
            recipient=libusb.CtrlRecipient.DEVICE,
        )
        self._in_req_type = libusb.build_request_type(
            direction=libusb.CtrlDirection.IN,
            type=libusb.CtrlType.VENDOR,
            recipient=libusb.CtrlRecipient.DEVICE,
        )
    elif parsed.driver == 2:  # noqa: PLR2004
        self._d2xx = _D2XX(errcheck=self._error_check)
        if not IS_WINDOWS:
            self._d2xx.set_vid_pid(parsed.vid, parsed.pid)

        if parsed.index is None:
            self._d2xx.open_ex(parsed.serial)
        else:
            self._d2xx.open(parsed.index)
    else:
        msg = f"Invalid FTDI driver number {parsed.driver}, must be either 0 or 2"
        raise ValueError(msg)

    self._set_interface_timeout()

    p = equipment.connection.properties

    self.set_baud_rate(p.get("baud_rate", p.get("baudrate", 9600)))

    self.set_data_characteristics(
        data_bits=p.get("data_bits", p.get("bytesize", DataBits.EIGHT)),
        parity=p.get("parity", Parity.NONE),
        stop_bits=p.get("stop_bits", p.get("stopbits", StopBits.ONE)),
    )

    latency: int | None = p.get("latency")
    if latency:
        self.set_latency_timer(latency)

    self.set_flow_control()  # set to None as default, the following may overwrite

    if p.get("xon_xoff", p.get("xonxoff", False)):
        self.set_flow_control("XON_XOFF", xon=17, xoff=19)  # pySerial uses 17 and 19

    if p.get("dsr_dtr", p.get("dsrdtr", False)):
        self.set_flow_control("DTR_DSR")

    if p.get("rts_cts", p.get("rtscts", False)):
        self.set_flow_control("RTS_CTS")

check_packet_for_errors instance-attribute ¤

check_packet_for_errors: bool = True

Whether to check the bit mask of the line byte in a read packet for errors.

This attribute is only used if libusb is used as the communication library. See poll_status for more details about the line bit mask.

encoding property writable ¤

encoding: str

The encoding that is used for read and write operations.

equipment property ¤

equipment: Equipment

The Equipment associated with the interface.

max_read_size property writable ¤

max_read_size: int

The maximum number of bytes that can be read.

read_termination property writable ¤

read_termination: bytes | None

The termination character sequence that is used for a read operation.

Reading stops when the equipment stops sending data or the read_termination character sequence is detected. If you set the read_termination to be equal to a variable of type str, it will be encoded as bytes.

rstrip property writable ¤

rstrip: bool

Whether to remove trailing whitespace from read messages.

timeout property writable ¤

timeout: float | None

The timeout, in seconds, for read and write operations.

A value <0 will set the timeout to be None (blocking mode).

write_termination property writable ¤

write_termination: bytes | None

The termination character sequence that is appended to write messages.

If you set the write_termination to be equal to a variable of type str, it will be encoded as bytes.

disconnect ¤

disconnect() -> None

Disconnect from the equipment.

Source code in src/msl/equipment/interfaces/ftdi.py
922
923
924
925
926
927
928
929
930
931
def disconnect(self) -> None:  # pyright: ignore[reportImplicitOverride]
    """Disconnect from the equipment."""
    if self._d2xx is not None:
        self._d2xx.close()
        self._d2xx = None
        super().disconnect()
    elif self._libusb is not None:
        self._libusb.disconnect()
        self._libusb = None
        super().disconnect()

get_latency_timer ¤

get_latency_timer() -> int

Get the latency-timer value from the FTDI chip.

Returns:

Type Description
int

The latency-timer value, in milliseconds.

Source code in src/msl/equipment/interfaces/ftdi.py
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
def get_latency_timer(self) -> int:
    """Get the latency-timer value from the FTDI chip.

    Returns:
        The latency-timer value, in milliseconds.
    """
    if self._d2xx is not None:
        return self._d2xx.get_latency_timer()

    assert self._libusb is not None  # noqa: S101

    # https://github.com/torvalds/linux/blob/5572ad8fddecd4a0db19801262072ff5916b7589/drivers/usb/serial/ftdi_sio.c#L1408
    if self._libusb.device_version <= FT232A:
        # The linux code returns an error, but FT_GetLatencyTimer in the D2xx programming manual states:
        #  "In the FT8U232AM and FT8U245AM devices, the receive buffer timeout that is used
        #   to flush remaining data from the receive buffer was fixed at 16 ms."
        return 16

    data = self._libusb.ctrl_transfer(
        request_type=self._in_req_type,
        request=0x0A,  # FTDI_SIO_GET_LATENCY_TIMER
        value=0,
        index=self._index,
        data_or_length=1,
    )
    return data[0]

poll_status ¤

poll_status() -> tuple[int, int]

Polls the modem and line status bytes from the FTDI chip.

Returns:

Type Description
tuple[int, int]

The (modem, line) status bytes.

Bit mask of the modem byte:

  • B0..3: Should be 0 (reserved)
  • B4: Clear To Send (CTS) = 0x10
  • B5: Data Set Ready (DSR) = 0x20
  • B6: Ring Indicator (RI) = 0x40
  • B7: Data Carrier Detect (DCD) = 0x80

Bit mask of the line byte:

  • B0: Data Ready (DR) = 0x01
  • B1: Overrun Error (OE) = 0x02
  • B2: Parity Error (PE) = 0x04
  • B3: Framing Error (FE) = 0x08
  • B4: Break Interrupt (BI) = 0x10
  • B5: Transmitter Holding Register Empty (THRE) = 0x20
  • B6: Transmitter Empty (TEMT) = 0x40
  • B7: Receiver FIFO Error (RCVE) = 0x80
Source code in src/msl/equipment/interfaces/ftdi.py
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
def poll_status(self) -> tuple[int, int]:
    """Polls the modem and line status bytes from the FTDI chip.

    Returns:
        The `(modem, line)` status bytes.

            Bit mask of the `modem` byte:

            - B0..3: Should be 0 (reserved)
            - B4: Clear To Send (CTS) = 0x10
            - B5: Data Set Ready (DSR) = 0x20
            - B6: Ring Indicator (RI) = 0x40
            - B7: Data Carrier Detect (DCD) = 0x80

            Bit mask of the `line` byte:

            - B0: Data Ready (DR) = 0x01
            - B1: Overrun Error (OE) = 0x02
            - B2: Parity Error (PE) = 0x04
            - B3: Framing Error (FE) = 0x08
            - B4: Break Interrupt (BI) = 0x10
            - B5: Transmitter Holding Register Empty (THRE) = 0x20
            - B6: Transmitter Empty (TEMT) = 0x40
            - B7: Receiver FIFO Error (RCVE) = 0x80

    """
    status: int
    if self._d2xx is not None:
        status = self._d2xx.get_line_modem_status()
    else:
        assert self._libusb is not None  # noqa: S101

        # https://github.com/torvalds/linux/blob/5572ad8fddecd4a0db19801262072ff5916b7589/drivers/usb/serial/ftdi_sio.c#L2744
        length, fmt = (1, "B") if self._libusb.device_version < FT232A else (2, "<H")
        data = self._libusb.ctrl_transfer(
            request_type=self._in_req_type,
            request=5,  # FTDI_SIO_GET_MODEM_STATUS
            value=0,
            index=self._index,
            data_or_length=length,
        )
        (status,) = unpack(fmt, data)

    line = (status >> 8) & 0xFF
    modem = status & 0xFF
    return modem, line

purge_buffers ¤

purge_buffers() -> None

Purge the receive (Rx) and transmit (Tx) buffers of the FTDI chip.

Source code in src/msl/equipment/interfaces/ftdi.py
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
def purge_buffers(self) -> None:
    """Purge the receive (Rx) and transmit (Tx) buffers of the FTDI chip."""
    if self._d2xx is not None:
        return self._d2xx.purge_buffers()

    assert self._libusb is not None  # noqa: S101
    # http://developer.intra2net.com/git/?p=libftdi;a=blob;f=src/ftdi.c;h=811f801feab8a04a62526c68ff2a93aae11feb2b;hb=HEAD#l1032
    # 0=SIO_RESET_REQUEST, 1=SIO_TCOFLUSH (host-to-ftdi), 2=SIO_TCIFLUSH (ftdi-to-host)
    _ = self._libusb.ctrl_transfer(request_type=self._out_req_type, request=0, value=1, index=self._index)
    _ = self._libusb.ctrl_transfer(request_type=self._out_req_type, request=0, value=2, index=self._index)
    self._buffer.clear()
    return None

query ¤

query(
    message: bytes | str,
    *,
    delay: float = 0.0,
    decode: Literal[True] = True,
    dtype: None = None,
    fmt: MessageFormat = ...,
    size: int | None = ...
) -> str
query(
    message: bytes | str,
    *,
    delay: float = 0.0,
    decode: Literal[False] = False,
    dtype: None = None,
    fmt: MessageFormat = ...,
    size: int | None = ...
) -> bytes
query(
    message: bytes | str,
    *,
    delay: float = 0.0,
    decode: bool = ...,
    dtype: MessageDataType = ...,
    fmt: MessageFormat = ...,
    size: int | None = ...
) -> NumpyArray1D
query(
    message: bytes | str,
    *,
    delay: float = 0.0,
    decode: bool = True,
    dtype: MessageDataType | None = None,
    fmt: MessageFormat = None,
    size: int | None = None
) -> bytes | str | NumpyArray1D

Convenience method for performing a write followed by a read.

Parameters:

Name Type Description Default
message bytes | str

The message to write to the equipment.

required
delay float

Time delay, in seconds, to wait between the write and read operations.

0.0
decode bool

Whether to decode the returned message (i.e., convert the message to a str) or keep the message as bytes. Ignored if dtype is not None.

True
dtype MessageDataType | None

The data type of the elements in the returned message. Can be any object that numpy dtype supports. For messages that are of scalar type (i.e., a single number) it is more efficient to not specify dtype but to pass the returned message to the int or float class to convert the message to the appropriate numeric type. See MessageDataType for more details.

None
fmt MessageFormat

The format that the returned message data is in. Ignored if dtype is None. See MessageFormat for more details.

None
size int | None

The number of bytes to read. Ignored if the value is None.

None

Returns:

Type Description
bytes | str | NumpyArray1D

The message from the equipment. If dtype is specified, then the message is returned as a numpy ndarray, if decode is True then the message is returned as a str, otherwise the message is returned as bytes.

Source code in src/msl/equipment/interfaces/message_based.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def query(
    self,
    message: bytes | str,
    *,
    delay: float = 0.0,
    decode: bool = True,
    dtype: MessageDataType | None = None,
    fmt: MessageFormat = None,
    size: int | None = None,
) -> bytes | str | NumpyArray1D:
    """Convenience method for performing a [write][msl.equipment.interfaces.message_based.MessageBased.write]
    followed by a [read][msl.equipment.interfaces.message_based.MessageBased.read].

    Args:
        message: The message to write to the equipment.
        delay: Time delay, in seconds, to wait between the _write_ and _read_ operations.
        decode: Whether to decode the returned message (i.e., convert the message to a [str][])
            or keep the message as [bytes][]. Ignored if `dtype` is not `None`.
        dtype: The data type of the elements in the returned message. Can be any object that numpy
            [dtype][numpy.dtype] supports. For messages that are of scalar type (i.e., a single number)
            it is more efficient to not specify `dtype` but to pass the returned message to the
            [int][] or [float][] class to convert the message to the appropriate numeric type.
            See [MessageDataType][msl.equipment._types.MessageDataType] for more details.
        fmt: The format that the returned message data is in. Ignored if `dtype` is `None`.
            See [MessageFormat][msl.equipment._types.MessageFormat] for more details.
        size: The number of bytes to read. Ignored if the value is `None`.

    Returns:
        The message from the equipment. If `dtype` is specified, then the message is
            returned as a numpy [ndarray][numpy.ndarray], if `decode` is `True` then the message
            is returned as a [str][], otherwise the message is returned as [bytes][].
    """  # noqa: D205
    _ = self.write(message)
    if delay > 0:
        time.sleep(delay)
    if dtype:
        return self.read(dtype=dtype, fmt=fmt, size=size)
    return self.read(decode=decode, size=size)

read ¤

read(
    *,
    decode: Literal[True] = True,
    dtype: None = None,
    fmt: MessageFormat = ...,
    size: int | None = ...
) -> str
read(
    *,
    decode: Literal[False] = False,
    dtype: None = None,
    fmt: MessageFormat = ...,
    size: int | None = ...
) -> bytes
read(
    *,
    decode: bool = ...,
    dtype: MessageDataType = ...,
    fmt: MessageFormat = ...,
    size: int | None = ...
) -> NumpyArray1D
read(
    *,
    decode: bool = True,
    dtype: MessageDataType | None = None,
    fmt: MessageFormat = None,
    size: int | None = None
) -> bytes | str | NumpyArray1D

Read a message from the equipment.

This method will block until one of the following conditions is fulfilled:

  1. size bytes have been received — only if size is not None.
  2. the read_termination byte(s) is(are) received — only if read_termination is not None.
  3. a timeout occurs — only if timeout is not None. If a timeout occurs, an MSLTimeoutError is raised.
  4. max_read_size bytes have been received. If the maximum number of bytes have been read, an MSLConnectionError is raised.

Tip

You may also want to set the rstrip value for the class instance.

Parameters:

Name Type Description Default
decode bool

Whether to decode the message (i.e., convert the message to a str) or keep the message as bytes. Ignored if dtype is not None.

True
dtype MessageDataType | None

The data type of the elements in the returned message. Can be any object that numpy dtype supports. For messages that are of scalar type (i.e., a single number) it is more efficient to not specify dtype but to pass the returned message to the int or float class to convert the message to the appropriate numeric type. See MessageDataType for more details.

None
fmt MessageFormat

The format that the returned message data is in. Ignored if dtype is None. See MessageFormat for more details.

None
size int | None

The number of bytes to read. Ignored if the value is None.

None

Returns:

Type Description
bytes | str | NumpyArray1D

The message from the equipment. If dtype is specified, then the message is returned as a numpy ndarray, if decode is True then the message is returned as a str, otherwise the message is returned as bytes.

Source code in src/msl/equipment/interfaces/message_based.py
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
def read(
    self,
    *,
    decode: bool = True,
    dtype: MessageDataType | None = None,
    fmt: MessageFormat = None,
    size: int | None = None,
) -> bytes | str | NumpyArray1D:
    """Read a message from the equipment.

    This method will block until one of the following conditions is fulfilled:

    1. `size` bytes have been received &mdash; only if `size` is not `None`.
    2. the [read_termination][msl.equipment.interfaces.message_based.MessageBased.read_termination]
       byte(s) is(are) received &mdash; only if
       [read_termination][msl.equipment.interfaces.message_based.MessageBased.read_termination]
       is not `None`.
    3. a timeout occurs &mdash; only if [timeout][msl.equipment.interfaces.message_based.MessageBased.timeout]
       is not `None`. If a timeout occurs, an
       [MSLTimeoutError][msl.equipment.interfaces.message_based.MSLTimeoutError] is raised.
    4. [max_read_size][msl.equipment.interfaces.message_based.MessageBased.max_read_size]
       bytes have been received. If the maximum number of bytes have been read, an
       [MSLConnectionError][msl.equipment.interfaces.message_based.MSLConnectionError] is raised.

    !!! tip
        You may also want to set the [rstrip][msl.equipment.interfaces.message_based.MessageBased.rstrip]
        value for the class instance.

    Args:
        decode: Whether to decode the message (i.e., convert the message to a [str][])
            or keep the message as [bytes][]. Ignored if `dtype` is not `None`.
        dtype: The data type of the elements in the returned message. Can be any object that numpy
            [dtype][numpy.dtype] supports. For messages that are of scalar type (i.e., a single number)
            it is more efficient to not specify `dtype` but to pass the returned message to the
            [int][] or [float][] class to convert the message to the appropriate numeric type.
            See [MessageDataType][msl.equipment._types.MessageDataType] for more details.
        fmt: The format that the returned message data is in. Ignored if `dtype` is `None`.
            See [MessageFormat][msl.equipment._types.MessageFormat] for more details.
        size: The number of bytes to read. Ignored if the value is `None`.

    Returns:
        The message from the equipment. If `dtype` is specified, then the message is returned
            as a numpy [ndarray][numpy.ndarray], if `decode` is `True` then the message
            is returned as a [str][], otherwise the message is returned as [bytes][].
    """
    if size is not None and size > self._max_read_size:
        msg = f"max_read_size is {self._max_read_size} bytes, requesting {size} bytes"
        raise MSLConnectionError(self, msg)

    try:
        message = self._read(size)
    except (serial.SerialTimeoutException, socket.timeout, TimeoutError, USBTimeoutError):
        raise MSLTimeoutError(self) from None
    except Exception as e:  # noqa: BLE001
        msg = f"{e.__class__.__name__}: {e}"
        raise MSLConnectionError(self, msg) from None

    if size is None:
        if dtype:
            logger.debug("%s.read(dtype=%r, fmt=%r) -> %r", self, dtype, fmt, message)
        else:
            logger.debug("%s.read() -> %r", self, message)
    else:
        if len(message) != size:
            msg = f"received {len(message)} bytes, requested {size} bytes"
            raise MSLConnectionError(self, msg)
        logger.debug("%s.read(size=%s) -> %r", self, size, message)

    if self._rstrip:
        message = message.rstrip()

    if dtype:
        return from_bytes(message, fmt=fmt, dtype=dtype)

    if decode:
        return message.decode(encoding=self._encoding)

    return message

reset_device ¤

reset_device() -> None

Sends a reset command to the FTDI chip.

Source code in src/msl/equipment/interfaces/ftdi.py
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
def reset_device(self) -> None:
    """Sends a reset command to the FTDI chip."""
    if self._d2xx is not None:
        return self._d2xx.reset_device()

    # http://developer.intra2net.com/git/?p=libftdi;a=blob;f=src/ftdi.c;h=811f801feab8a04a62526c68ff2a93aae11feb2b;hb=HEAD#l1006
    assert self._libusb is not None  # noqa: S101
    # SIO_RESET=0, SIO_RESET_SIO=0
    _ = self._libusb.ctrl_transfer(request_type=self._out_req_type, request=0, value=0, index=self._index)
    return None

set_baud_rate ¤

set_baud_rate(rate: int) -> None

Set the baud rate for the FTDI chip.

Parameters:

Name Type Description Default
rate int

The baud rate.

required
Source code in src/msl/equipment/interfaces/ftdi.py
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
def set_baud_rate(self, rate: int) -> None:
    """Set the baud rate for the FTDI chip.

    Args:
        rate: The baud rate.
    """
    if self._d2xx is not None:
        return self._d2xx.set_baud_rate(rate)

    # https://github.com/torvalds/linux/blob/5572ad8fddecd4a0db19801262072ff5916b7589/drivers/usb/serial/ftdi_sio.c#L1342
    assert self._libusb is not None  # noqa: S101
    dv = self._libusb.device_version
    divisor = _get_ftdi_divisor(baudrate=rate, device_version=dv)
    value = divisor & 0xFFFF
    index = (divisor >> 16) & 0xFFFF
    if dv >= FT2232H or dv == FT2232C:
        index <<= 8
        index |= self._index

    _ = self._libusb.ctrl_transfer(
        request_type=self._out_req_type,
        request=3,  # FTDI_SIO_SET_BAUDRATE_REQUEST
        value=value,
        index=index,
    )
    return None

set_break ¤

set_break(state: bool) -> None

Set the BREAK condition for the FTDI chip.

Parameters:

Name Type Description Default
state bool

The state of the BREAK condition. If True, the transmit (Tx) line is driven low until this method is called with False, which resets or de-asserts the BREAK condition returning the transmit line to its normal operating state.

required
Source code in src/msl/equipment/interfaces/ftdi.py
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
def set_break(self, state: bool) -> None:  # noqa: FBT001
    """Set the BREAK condition for the FTDI chip.

    Args:
        state: The state of the BREAK condition. If `True`, the transmit (Tx) line is driven
            low until this method is called with `False`, which resets or de-asserts the
            BREAK condition returning the transmit line to its normal operating state.
    """
    if self._d2xx is not None:
        return self._d2xx.set_break_on() if state else self._d2xx.set_break_off()

    # http://developer.intra2net.com/git/?p=libftdi;a=blob;f=src/ftdi.c;h=811f801feab8a04a62526c68ff2a93aae11feb2b;hb=HEAD#l1512
    assert self._libusb is not None  # noqa: S101
    _ = self._libusb.ctrl_transfer(
        request_type=self._out_req_type,
        request=4,  # SIO_SET_DATA
        value=self._characteristics | (0x1 << 14) if state else self._characteristics,
        index=self._index,
    )
    return None

set_data_characteristics ¤

set_data_characteristics(
    *,
    data_bits: DataBits | str | int = EIGHT,
    parity: Parity | str = NONE,
    stop_bits: StopBits | str | float = ONE
) -> None

Set the RS-232 data characteristics for the FTDI chip.

Parameters:

Name Type Description Default
data_bits DataBits | str | int

The number of data bits (7 or 8). Can be an enum member name (case insensitive) or value.

EIGHT
parity Parity | str

The parity. Can be an enum member name (case insensitive) or value.

NONE
stop_bits StopBits | str | float

The number of stop bits. Can be an enum member name (case insensitive) or value.

ONE
Source code in src/msl/equipment/interfaces/ftdi.py
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
def set_data_characteristics(
    self,
    *,
    data_bits: DataBits | str | int = DataBits.EIGHT,
    parity: Parity | str = Parity.NONE,
    stop_bits: StopBits | str | float = StopBits.ONE,
) -> None:
    """Set the RS-232 data characteristics for the FTDI chip.

    Args:
        data_bits: The number of data bits (7 or 8). Can be an enum member name (case insensitive) or value.
        parity: The parity. Can be an enum member name (case insensitive) or value.
        stop_bits: The number of stop bits. Can be an enum member name (case insensitive) or value.
    """
    data_bits = to_enum(data_bits, DataBits, to_upper=True)
    parity = to_enum(parity, Parity, to_upper=True)
    stop_bits = to_enum(stop_bits, StopBits, to_upper=True)

    if data_bits not in {7, 8}:
        msg = f"Unsupported data_bits value {data_bits!r}, must be either 7 or 8"
        raise ValueError(msg)

    if self._d2xx is not None:
        return self._d2xx.set_data_characteristics(data_bits=data_bits, parity=parity, stop_bits=stop_bits)

    # http://developer.intra2net.com/git/?p=libftdi;a=blob;f=src/ftdi.c;h=811f801feab8a04a62526c68ff2a93aae11feb2b;hb=HEAD#l1512
    assert self._libusb is not None  # noqa: S101
    value = data_bits.value
    value |= {Parity.NONE: 0, Parity.ODD: 256, Parity.EVEN: 512, Parity.MARK: 768, Parity.SPACE: 1024}[parity]
    value |= {StopBits.ONE: 0, StopBits.ONE_POINT_FIVE: 2048, StopBits.TWO: 4096}[stop_bits]

    self._characteristics = value
    _ = self._libusb.ctrl_transfer(
        request_type=self._out_req_type,
        request=4,  # SIO_SET_DATA
        value=value,
        index=self._index,
    )
    return None

set_dtr ¤

set_dtr(state: bool) -> None

Set the Data Terminal Ready (DTR) control signal.

Parameters:

Name Type Description Default
state bool

New state of the DTR logical level: HIGH (True) or LOW (False).

required
Source code in src/msl/equipment/interfaces/ftdi.py
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
def set_dtr(self, state: bool) -> None:  # noqa: FBT001
    """Set the Data Terminal Ready (DTR) control signal.

    Args:
        state: New state of the DTR logical level: HIGH (`True`) or LOW (`False`).
    """
    if self._d2xx is not None:
        return self._d2xx.set_dtr() if state else self._d2xx.clr_dtr()

    # https://github.com/torvalds/linux/blob/5572ad8fddecd4a0db19801262072ff5916b7589/drivers/usb/serial/ftdi_sio.c#L1199
    assert self._libusb is not None  # noqa: S101
    # FTDI_SIO_SET_DTR_HIGH=((0x01 << 8) | 1), FTDI_SIO_SET_DTR_LOW=((0x01 << 8) | 0)
    _ = self._libusb.ctrl_transfer(
        request_type=self._out_req_type,
        request=1,  # FTDI_SIO_MODEM_CTRL
        value=257 if state else 256,
        index=self._index,
    )
    return None

set_flow_control ¤

set_flow_control(
    flow: Literal["RTS_CTS", "DTR_DSR", "XON_XOFF"] | None = None,
    *,
    xon: int = 0,
    xoff: int = 0
) -> None

Sets the flow control for the FTDI chip.

Parameters:

Name Type Description Default
flow Literal['RTS_CTS', 'DTR_DSR', 'XON_XOFF'] | None

The type of flow control to use, None disables flow control.

None
xon int

The character (between 0 and 255) to signal Xon. Only used if flow is XON_XOFF.

0
xoff int

The character (between 0 and 255) to signal Xoff. Only used if flow is XON_XOFF.

0
Source code in src/msl/equipment/interfaces/ftdi.py
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
def set_flow_control(
    self, flow: Literal["RTS_CTS", "DTR_DSR", "XON_XOFF"] | None = None, *, xon: int = 0, xoff: int = 0
) -> None:
    """Sets the flow control for the FTDI chip.

    Args:
        flow: The type of flow control to use, `None` disables flow control.
        xon: The character (between 0 and 255) to signal Xon. Only used if `flow` is `XON_XOFF`.
        xoff: The character (between 0 and 255) to signal Xoff. Only used if `flow` is `XON_XOFF`.
    """
    if self._d2xx is not None:
        return self._d2xx.set_flow_control(flow, xon=xon, xoff=xoff)

    # https://github.com/torvalds/linux/blob/5572ad8fddecd4a0db19801262072ff5916b7589/drivers/usb/serial/ftdi_sio.c#L2716
    assert self._libusb is not None  # noqa: S101
    value, index = 0, 0
    if flow == "RTS_CTS":
        index = 0x1 << 8  # FTDI_SIO_RTS_CTS_HS
    elif flow == "DTR_DSR":
        index = 0x2 << 8  # FTDI_SIO_DTR_DSR_HS
    elif flow == "XON_XOFF":
        value = (xoff << 8) | xon
        index = 0x4 << 8  # FTDI_SIO_XON_XOFF_HS

    _ = self._libusb.ctrl_transfer(
        request_type=self._out_req_type,
        request=2,  # FTDI_SIO_SET_FLOW_CTRL
        value=value,
        index=index | self._index,
    )
    return None

set_latency_timer ¤

set_latency_timer(value: int) -> None

Set the latency-timer value for the FTDI chip.

The latency timer is a form of timeout mechanism for the read buffer of FTDI chip.

The latency timer counts from the last time data was sent back to the computer. If the latency timer expires, the chip will send what data it has available to the computer regardless of how many bytes it is waiting on. The latency timer will then reset and begin counting again.

The timer allows the chip to be better optimized for protocols requiring faster response times from shorter data packets.

Parameters:

Name Type Description Default
value int

Required value, in milliseconds, of the latency timer. Valid range is [1, 255].

required
Source code in src/msl/equipment/interfaces/ftdi.py
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
def set_latency_timer(self, value: int) -> None:
    """Set the latency-timer value for the FTDI chip.

    The latency timer is a form of timeout mechanism for the read buffer of FTDI chip.

    The latency timer counts from the last time data was sent back to the computer. If
    the latency timer expires, the chip will send what data it has available to the
    computer regardless of how many bytes it is waiting on. The latency timer will then
    reset and begin counting again.

    The timer allows the chip to be better optimized for protocols requiring faster
    response times from shorter data packets.

    Args:
        value: Required value, in milliseconds, of the latency timer. Valid range is [1, 255].
    """
    v = int(value)
    if v < 1 or v > 255:  # noqa: PLR2004
        msg = f"Invalid latency timer value {value}, must be an integer in the range [1, 255]"
        raise ValueError(msg)

    if self._d2xx is not None:
        return self._d2xx.set_latency_timer(v)

    # https://github.com/torvalds/linux/blob/5572ad8fddecd4a0db19801262072ff5916b7589/drivers/usb/serial/ftdi_sio.c#L1365
    assert self._libusb is not None  # noqa: S101
    if self._libusb.device_version <= FT232A:
        # The linux code returns an error, but FT_SetLatencyTimer in the D2xx programming manual states:
        #  "In the FT8U232AM and FT8U245AM devices, the receive buffer timeout that is used
        #   to flush remaining data from the receive buffer was fixed at 16 ms."
        if v == 16:  # noqa: PLR2004
            return None
        msg = "Cannot set the latency timer for the FTDI chip. The value is fixed at 16 ms."
        raise MSLConnectionError(self, msg)

    _ = self._libusb.ctrl_transfer(
        request_type=self._out_req_type,
        request=9,  # FTDI_SIO_SET_LATENCY_TIMER
        value=v,
        index=self._index,
    )
    return None

set_rts ¤

set_rts(state: bool) -> None

Set the Request To Send (RTS) control signal.

Parameters:

Name Type Description Default
state bool

New state of the RTS logical level: HIGH (True) or LOW (False).

required
Source code in src/msl/equipment/interfaces/ftdi.py
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
def set_rts(self, state: bool) -> None:  # noqa: FBT001
    """Set the Request To Send (RTS) control signal.

    Args:
        state: New state of the RTS logical level: HIGH (`True`) or LOW (`False`).
    """
    if self._d2xx is not None:
        return self._d2xx.set_rts() if state else self._d2xx.clr_rts()

    # https://github.com/torvalds/linux/blob/5572ad8fddecd4a0db19801262072ff5916b7589/drivers/usb/serial/ftdi_sio.c#L1199
    assert self._libusb is not None  # noqa: S101
    # FTDI_SIO_SET_RTS_HIGH=((0x2 << 8) | 2), FTDI_SIO_SET_RTS_LOW=((0x2 << 8) | 0)
    _ = self._libusb.ctrl_transfer(
        request_type=self._out_req_type,
        request=1,  # FTDI_SIO_MODEM_CTRL
        value=514 if state else 512,
        index=self._index,
    )
    return None

write ¤

write(
    message: bytes | str,
    *,
    data: Sequence1D | None = None,
    dtype: MessageDataType = "<f",
    fmt: MessageFormat = "ieee"
) -> int

Write a message to the equipment.

Parameters:

Name Type Description Default
message bytes | str

The message to write to the equipment.

required
data Sequence1D | None

The data to append to message.

None
dtype MessageDataType

The data type to use to convert each element in data to bytes. Ignored if data is None. See MessageDataType for more details.

'<f'
fmt MessageFormat

The format to use to convert data to bytes. Ignored if data is None. See MessageFormat for more details.

'ieee'

Returns:

Type Description
int

The number of bytes written.

Source code in src/msl/equipment/interfaces/message_based.py
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
def write(
    self,
    message: bytes | str,
    *,
    data: Sequence1D | None = None,
    dtype: MessageDataType = "<f",
    fmt: MessageFormat = "ieee",
) -> int:
    """Write a message to the equipment.

    Args:
        message: The message to write to the equipment.
        data: The data to append to `message`.
        dtype: The data type to use to convert each element in `data` to bytes. Ignored
            if `data` is `None`. See [MessageDataType][msl.equipment._types.MessageDataType]
            for more details.
        fmt: The format to use to convert `data` to bytes. Ignored if `data` is `None`.
            See [MessageFormat][msl.equipment._types.MessageFormat] for more details.

    Returns:
        The number of bytes written.
    """
    if not isinstance(message, bytes):
        message = message.encode(encoding=self._encoding)

    if data is not None:
        message += to_bytes(data, fmt=fmt, dtype=dtype)

    if self._write_termination and not message.endswith(self._write_termination):
        message += self._write_termination

    logger.debug("%s.write(%r)", self, message)

    try:
        return self._write(message)
    except (serial.SerialTimeoutException, socket.timeout, TimeoutError, USBTimeoutError):
        raise MSLTimeoutError(self) from None
    except Exception as e:  # noqa: BLE001
        raise MSLConnectionError(self, str(e)) from None